This commit is contained in:
2026-03-28 23:51:50 -04:00
parent 0a477300f4
commit e56e3ba531
47 changed files with 13489 additions and 201 deletions

View File

@@ -0,0 +1,216 @@
import React from 'react';
import { StyleSheet, ScrollView, TouchableOpacity, View } from 'react-native';
import { SearchFilters } from '@/types/feed';
import { ThemedText } from './themed-text';
import { ThemedView } from './themed-view';
import { Colors, Spacing } from '@/constants/theme';
import { useColorScheme } from 'react-native';
interface FilterChipProps {
label: string;
value?: string;
onRemove: () => void;
icon?: string;
}
function FilterChip({ label, value, onRemove, icon }: FilterChipProps) {
const scheme = useColorScheme();
const colors = Colors[scheme === 'unspecified' ? 'light' : scheme];
return (
<TouchableOpacity
style={styles.chip}
onPress={onRemove}
activeOpacity={0.7}
>
{icon && <ThemedText style={styles.chipIcon}>{icon}</ThemedText>}
<ThemedText style={styles.chipText}>
{label}:
</ThemedText>
<ThemedText style={styles.chipValue}>
{value}
</ThemedText>
<ThemedText style={styles.chipClose}></ThemedText>
</TouchableOpacity>
);
}
interface SearchFiltersProps {
filters: SearchFilters;
onFilterChange: (filters: Partial<SearchFilters>) => void;
onClearAll: () => void;
availableFeeds?: Array<{ id: string; title: string }>;
}
export function SearchFilters({ filters, onFilterChange, onClearAll, availableFeeds }: SearchFiltersProps) {
const scheme = useColorScheme();
const colors = Colors[scheme === 'unspecified' ? 'light' : scheme];
const getFeedTitle = (feedId: string) => {
const feed = availableFeeds?.find(f => f.id === feedId);
return feed?.title || feedId;
};
const formatDate = (date: Date) => {
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
};
const renderFilterChips = () => {
const chips: any[] = [];
// Date range filter
if (filters.dateFrom || filters.dateTo) {
const fromLabel = filters.dateFrom ? formatDate(filters.dateFrom) : 'Any date';
const toLabel = filters.dateTo ? formatDate(filters.dateTo) : 'Now';
chips.push(
<FilterChip
key="date"
label="Date"
value={`${fromLabel} ${toLabel}`}
icon="📅"
onRemove={() => onFilterChange({ dateFrom: undefined, dateTo: undefined })}
/>
);
}
// Feed filters
if (filters.feedIds && filters.feedIds.length > 0) {
const feedCount = filters.feedIds.length;
const feedTitle = feedCount === 1
? getFeedTitle(filters.feedIds[0])
: `${feedCount} feeds`;
chips.push(
<FilterChip
key="feeds"
label="Feed"
value={feedTitle}
icon="📰"
onRemove={() => onFilterChange({ feedIds: undefined })}
/>
);
}
// Author filters
if (filters.authors && filters.authors.length > 0) {
const authorCount = filters.authors.length;
const authorLabel = authorCount === 1
? filters.authors[0]
: `${authorCount} authors`;
chips.push(
<FilterChip
key="authors"
label="Author"
value={authorLabel}
icon="✍️"
onRemove={() => onFilterChange({ authors: undefined })}
/>
);
}
// Content type filter
if (filters.contentType) {
const contentTypeLabels: Record<string, string> = {
article: 'Articles',
audio: 'Audio',
video: 'Video',
};
const contentTypeIcons: Record<string, string> = {
article: '📄',
audio: '🎵',
video: '🎬',
};
chips.push(
<FilterChip
key="contentType"
label="Type"
value={contentTypeLabels[filters.contentType]}
icon={contentTypeIcons[filters.contentType]}
onRemove={() => onFilterChange({ contentType: undefined })}
/>
);
}
return chips;
};
const hasActiveFilters =
filters.dateFrom ||
filters.dateTo ||
(filters.feedIds?.length ?? 0) > 0 ||
(filters.authors?.length ?? 0) > 0 ||
filters.contentType !== undefined;
if (!hasActiveFilters) {
return null;
}
return (
<ThemedView style={styles.container}>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.chipsContainer}
>
{renderFilterChips()}
</ScrollView>
{hasActiveFilters && (
<TouchableOpacity
style={styles.clearAllButton}
onPress={onClearAll}
activeOpacity={0.7}
>
<ThemedText style={styles.clearAllText}>Clear all</ThemedText>
</TouchableOpacity>
)}
</ThemedView>
);
}
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: Spacing.three,
paddingVertical: Spacing.two,
gap: Spacing.two,
},
chipsContainer: {
flex: 1,
gap: Spacing.two,
},
chip: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: Spacing.three,
paddingVertical: Spacing.one,
borderRadius: Spacing.two,
backgroundColor: '#e5e5ea',
gap: Spacing.one,
},
chipIcon: {
fontSize: 12,
},
chipText: {
fontSize: 12,
color: '#8e8e93',
fontWeight: '600',
},
chipValue: {
fontSize: 12,
color: '#000000',
},
chipClose: {
fontSize: 14,
color: '#8e8e93',
marginLeft: Spacing.one,
},
clearAllButton: {
paddingHorizontal: Spacing.two,
paddingVertical: Spacing.one,
},
clearAllText: {
fontSize: 12,
color: '#007AFF',
fontWeight: '500',
},
});