1
1
import clsx from "clsx"
2
2
import { useState } from "react"
3
- import { Combobox } from "@headlessui/react"
3
+ import {
4
+ Combobox ,
5
+ ComboboxInput ,
6
+ ComboboxOption ,
7
+ ComboboxOptions ,
8
+ ComboboxButton ,
9
+ Label ,
10
+ } from "@headlessui/react"
4
11
5
12
import { Tag } from "@/app/conf/_design-system/tag"
6
13
import { Button } from "@/app/conf/_design-system/button"
7
14
8
15
import CloseIcon from "@/app/conf/_design-system/pixelarticons/close.svg?svgr"
9
16
import CaretDownIcon from "@/app/conf/_design-system/pixelarticons/caret-down.svg?svgr"
10
17
import { eventsColors } from "../../utils"
18
+ import { ScheduleSession } from "@/app/conf/_api/sched-types"
19
+
20
+ export interface FilterCategoryConfig {
21
+ property : keyof ScheduleSession
22
+ label : string
23
+ options : string [ ]
24
+ }
25
+
26
+ export const FilterCategoryConfig = {
27
+ fromFields : (
28
+ fields : Partial < Record < keyof ScheduleSession , string /* label */ > > ,
29
+ scheduleData : ScheduleSession [ ] ,
30
+ ) => {
31
+ return Object . entries ( fields ) . map (
32
+ ( [ field , label ] ) : FilterCategoryConfig => ( {
33
+ property : field as keyof ScheduleSession ,
34
+ label,
35
+ options : Array . from (
36
+ new Set ( scheduleData . map ( session => session [ field ] ) ) ,
37
+ ) . filter ( ( x ) : x is string => ! ! x && typeof x === "string" ) ,
38
+ } ) ,
39
+ )
40
+ } ,
41
+ }
42
+
43
+ export type CategoryName = keyof ScheduleSession
44
+
45
+ export interface FilterStates extends Partial < Record < CategoryName , string [ ] > > { }
46
+ export const FilterStates = {
47
+ initial : ( fields : Array < keyof ScheduleSession > ) =>
48
+ Object . fromEntries ( fields . map ( field => [ field , [ ] ] ) ) ,
49
+ }
50
+
11
51
type FiltersProps = {
12
- categories : { name : string ; options : string [ ] } [ ]
52
+ categories : FilterCategoryConfig [ ]
13
53
filterState : Record < string , string [ ] >
14
54
onFilterChange : ( category : string , newSelectedOptions : string [ ] ) => void
15
55
}
@@ -23,14 +63,14 @@ export function Filters({
23
63
< div className = "flex flex-wrap justify-stretch gap-x-2 gap-y-4 pb-10" >
24
64
{ categories . map ( category => (
25
65
< FiltersCombobox
26
- key = { category . name }
27
- label = { category . name }
66
+ key = { category . property }
67
+ label = { category . label }
28
68
options = { category . options }
29
- value = { filterState [ category . name ] || [ ] }
69
+ value = { filterState [ category . property ] || [ ] }
30
70
onChange = { newSelectedOptions => {
31
- onFilterChange ( category . name , newSelectedOptions )
71
+ onFilterChange ( category . property , newSelectedOptions )
32
72
} }
33
- placeholder = { `Any ${ category . name . toLowerCase ( ) } ` }
73
+ placeholder = { `Any ${ category . label . toLowerCase ( ) } ` }
34
74
className = "flex-1"
35
75
/>
36
76
) ) }
@@ -95,12 +135,12 @@ function FiltersCombobox({
95
135
< Combobox immediate multiple value = { value } onChange = { onChange } >
96
136
< div className = { clsx ( "flex flex-col" , className ) } >
97
137
{ label && (
98
- < Combobox . Label className = "typography-menu mb-1 block font-mono font-medium uppercase text-neu-900" >
138
+ < Label className = "typography-menu mb-1 block font-mono font-medium uppercase text-neu-900" >
99
139
{ label }
100
- </ Combobox . Label >
140
+ </ Label >
101
141
) }
102
142
< label className = "relative w-full border border-neu-500 bg-neu-0 p-2 focus-within:outline-none focus-within:ring focus-within:ring-neu-300 dark:border-neu-200 dark:focus-within:ring-neu-200" >
103
- < Combobox . Input
143
+ < ComboboxInput
104
144
value = { query }
105
145
onChange = { e => setQuery ( e . target . value ) }
106
146
className = { clsx (
@@ -109,7 +149,7 @@ function FiltersCombobox({
109
149
placeholder = { placeholder }
110
150
autoComplete = "true"
111
151
/>
112
- < Combobox . Button
152
+ < ComboboxButton
113
153
className = { clsx (
114
154
"absolute inset-y-0 right-0 flex items-center px-2 focus:outline-none" ,
115
155
) }
@@ -118,7 +158,7 @@ function FiltersCombobox({
118
158
className = "ui-open:rotate-180 size-5 text-neu-400 transition-transform duration-150 group-hover:text-neu-500"
119
159
aria-hidden = "true"
120
160
/>
121
- </ Combobox . Button >
161
+ </ ComboboxButton >
122
162
{ value . length > 0 && (
123
163
< div className = "inset-y-0 left-0 z-[1] mt-1 flex items-center overflow-x-auto pr-8" >
124
164
< div className = "flex flex-wrap items-center gap-1" >
@@ -136,23 +176,23 @@ function FiltersCombobox({
136
176
</ label >
137
177
138
178
< div className = "relative" >
139
- < Combobox . Options
179
+ < ComboboxOptions
140
180
className = { clsx (
141
181
"absolute z-10 -mt-px max-h-60 w-full overflow-auto border border-neu-500 bg-neu-0 p-1 text-base" ,
142
182
) }
143
183
>
144
184
{ filteredOptions . map ( option => (
145
- < Combobox . Option key = { option } value = { option } >
185
+ < ComboboxOption key = { option } value = { option } >
146
186
{ ( { active, selected } ) => (
147
187
< FilterComboboxOption
148
188
active = { active }
149
189
selected = { selected }
150
190
option = { option }
151
191
/>
152
192
) }
153
- </ Combobox . Option >
193
+ </ ComboboxOption >
154
194
) ) }
155
- </ Combobox . Options >
195
+ </ ComboboxOptions >
156
196
</ div >
157
197
</ div >
158
198
</ Combobox >
0 commit comments