init
This commit is contained in:
216
src/components/search-filters.tsx
Normal file
216
src/components/search-filters.tsx
Normal 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',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user