217 lines
5.5 KiB
TypeScript
217 lines
5.5 KiB
TypeScript
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',
|
||
},
|
||
});
|