1- "use client"
2-
31import * as React from "react"
4- import { FancyBox , type FancyBoxItem } from "@/components/ui/fancy-box"
5- import { Button } from "@/components/ui/button"
6- import { Badge } from "@/components/ui/badge"
7- import { eventKinds , popularKinds } from "@/lib/event-kinds"
8- import { FilterIcon , XIcon } from "lucide-react"
2+ import { Input } from "@/components/ui/input"
93
104interface EventKindFilterProps {
115 selectedKinds : number [ ]
126 onKindsChange : ( kinds : number [ ] ) => void
137 className ?: string
148}
159
16- const PRESET_FILTERS = [
17- { label : "Core" , kinds : [ 0 , 1 , 3 , 4 , 6 , 7 ] } ,
18- { label : "Social" , kinds : [ 0 , 1 , 6 , 7 , 1111 ] } ,
19- { label : "Media" , kinds : [ 20 , 21 , 22 , 1063 , 1222 ] } ,
20- { label : "Commerce" , kinds : [ 1021 , 1022 , 30017 , 30018 , 30020 ] } ,
21- { label : "Bitcoin" , kinds : [ 9734 , 9735 , 7375 , 13194 ] } ,
22- { label : "Popular" , kinds : popularKinds } ,
23- ]
24-
2510export function EventKindFilter ( { selectedKinds, onKindsChange, className } : EventKindFilterProps ) {
26- // Convert event kinds to FancyBox items
27- const fancyBoxItems : FancyBoxItem [ ] = React . useMemo (
28- ( ) => eventKinds . map ( ( kind ) => ( {
29- value : kind . kind . toString ( ) ,
30- label : `${ kind . kind } : ${ kind . label } ` ,
31- description : kind . description ,
32- category : kind . category ,
33- deprecated : kind . deprecated ,
34- } ) ) ,
35- [ ]
36- )
37-
38- // Convert selected kinds to strings for FancyBox
39- const selectedValues = selectedKinds . map ( String )
40-
41- const handleSelectionChange = React . useCallback (
42- ( values : string [ ] ) => {
43- // Parse custom values as numbers and filter out invalid ones
44- const kinds = values
45- . map ( ( v ) => parseInt ( v , 10 ) )
46- . filter ( ( k ) => ! isNaN ( k ) )
47- . sort ( ( a , b ) => a - b )
48- onKindsChange ( kinds )
49- } ,
50- [ onKindsChange ]
51- )
52-
53- const applyPreset = React . useCallback (
54- ( preset : typeof PRESET_FILTERS [ 0 ] ) => {
55- onKindsChange ( preset . kinds )
56- } ,
57- [ onKindsChange ]
58- )
59-
60- const clearAll = React . useCallback ( ( ) => {
61- onKindsChange ( [ ] )
62- } , [ onKindsChange ] )
63-
64- const hasFilters = selectedKinds . length > 0
11+ const [ text , setText ] = React . useState ( ( ) => selectedKinds . join ( ", " ) )
12+
13+ React . useEffect ( ( ) => {
14+ const fromProps = selectedKinds . join ( ", " )
15+ const parsedFromText = text
16+ . split ( "," )
17+ . map ( s => parseInt ( s . trim ( ) , 10 ) )
18+ . filter ( n => Number . isFinite ( n ) && n >= 0 )
19+ if ( parsedFromText . join ( ", " ) !== fromProps ) {
20+ setText ( fromProps )
21+ }
22+ } , [ selectedKinds ] ) // eslint-disable-line react-hooks/exhaustive-deps
23+
24+ const handleChange = ( value : string ) => {
25+ setText ( value )
26+ const kinds = value
27+ . split ( "," )
28+ . map ( s => s . trim ( ) )
29+ . filter ( s => s . length > 0 )
30+ . map ( s => parseInt ( s , 10 ) )
31+ . filter ( n => Number . isFinite ( n ) && n >= 0 )
32+ onKindsChange ( kinds )
33+ }
6534
6635 return (
67- < div className = { className } >
68- < div className = "flex items-center gap-2 mb-3" >
69- < FilterIcon className = "h-4 w-4 text-muted-foreground" />
70- < span className = "text-sm font-medium" > Event Kinds</ span >
71- { hasFilters && (
72- < Badge variant = "secondary" className = "text-xs" >
73- { selectedKinds . length }
74- </ Badge >
75- ) }
76- { hasFilters && (
77- < Button
78- variant = "ghost"
79- size = "sm"
80- onClick = { clearAll }
81- className = "h-8 w-8 p-0 ml-auto"
82- >
83- < XIcon className = "h-4 w-4" />
84- </ Button >
85- ) }
86- </ div >
87-
88- < FancyBox
89- items = { fancyBoxItems }
90- selectedValues = { selectedValues }
91- onSelectionChange = { handleSelectionChange }
92- placeholder = "Filter by event kinds..."
93- searchPlaceholder = "Search event kinds..."
94- maxDisplay = { 2 }
95- allowCustomValues = { true }
96- customValuePlaceholder = "Add custom kind number..."
97- groupByCategory = { true }
98- className = "w-full"
99- />
100-
101- { /* Preset buttons */ }
102- < div className = "flex flex-wrap gap-1 mt-2" >
103- { PRESET_FILTERS . map ( ( preset ) => (
104- < Button
105- key = { preset . label }
106- variant = "outline"
107- size = "sm"
108- onClick = { ( ) => applyPreset ( preset ) }
109- className = "h-8 px-3 text-xs"
110- >
111- { preset . label }
112- </ Button >
113- ) ) }
114- </ div >
115-
116- { /* Active filters display */ }
117- { hasFilters && (
118- < div className = "mt-2 text-xs text-muted-foreground" >
119- Filtering { selectedKinds . length } event kind{ selectedKinds . length !== 1 ? 's' : '' } :
120- < div className = "flex flex-wrap gap-1 mt-1" >
121- { selectedKinds . slice ( 0 , 10 ) . map ( ( kind ) => {
122- const eventKind = eventKinds . find ( ( k ) => k . kind === kind )
123- return (
124- < Badge
125- key = { kind }
126- variant = "outline"
127- className = { `text-xs ${ eventKind ?. deprecated ? 'opacity-60' : '' } ` }
128- >
129- { kind }
130- </ Badge >
131- )
132- } ) }
133- { selectedKinds . length > 10 && (
134- < Badge variant = "outline" className = "text-xs" >
135- +{ selectedKinds . length - 10 } more
136- </ Badge >
137- ) }
138- </ div >
139- </ div >
140- ) }
141- </ div >
36+ < Input
37+ type = "text"
38+ value = { text }
39+ onChange = { ( e ) => handleChange ( e . target . value ) }
40+ placeholder = "Filter by kinds (comma-separated)"
41+ className = { className }
42+ />
14243 )
143- }
44+ }
0 commit comments