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
+ enabledOptions ?: Set < string > | null
25
+ }
26
+
27
+ export const FilterCategoryConfig = {
28
+ fromFields : (
29
+ fields : Partial < Record < keyof ScheduleSession , string /* label */ > > ,
30
+ scheduleData : ScheduleSession [ ] ,
31
+ ) => {
32
+ return Object . entries ( fields ) . map (
33
+ ( [ field , label ] ) : FilterCategoryConfig => ( {
34
+ property : field as keyof ScheduleSession ,
35
+ label,
36
+ options : Array . from (
37
+ new Set (
38
+ scheduleData . map (
39
+ session => session [ field as keyof ScheduleSession ] ,
40
+ ) ,
41
+ ) ,
42
+ ) . filter ( ( x ) : x is string => ! ! x && typeof x === "string" ) ,
43
+ } ) ,
44
+ )
45
+ } ,
46
+ }
47
+
48
+ export type CategoryName = keyof ScheduleSession
49
+
50
+ export interface FilterStates extends Partial < Record < CategoryName , string [ ] > > { }
51
+ export const FilterStates = {
52
+ initial : ( fields : Array < keyof ScheduleSession > ) =>
53
+ Object . fromEntries ( fields . map ( field => [ field , [ ] ] ) ) ,
54
+ }
55
+
11
56
type FiltersProps = {
12
- categories : { name : string ; options : string [ ] } [ ]
57
+ categories : FilterCategoryConfig [ ]
13
58
filterState : Record < string , string [ ] >
59
+ enabledOptions : Record < string , Set < string > | null >
14
60
onFilterChange : ( category : string , newSelectedOptions : string [ ] ) => void
15
61
}
16
62
17
63
export function Filters ( {
18
64
categories,
19
65
filterState,
66
+ enabledOptions,
20
67
onFilterChange,
21
68
} : FiltersProps ) {
22
69
return (
23
70
< div className = "flex flex-wrap justify-stretch gap-x-2 gap-y-4 pb-10" >
24
71
{ categories . map ( category => (
25
72
< FiltersCombobox
26
- key = { category . name }
27
- label = { category . name }
73
+ key = { category . property }
74
+ label = { category . label }
28
75
options = { category . options }
29
- value = { filterState [ category . name ] || [ ] }
76
+ enabledOptions = { enabledOptions [ category . property ] }
77
+ value = { filterState [ category . property ] || [ ] }
30
78
onChange = { newSelectedOptions => {
31
- onFilterChange ( category . name , newSelectedOptions )
79
+ onFilterChange ( category . property , newSelectedOptions )
32
80
} }
33
- placeholder = { `Any ${ category . name . toLowerCase ( ) } ` }
81
+ placeholder = { `Any ${ category . label . toLowerCase ( ) } ` }
34
82
className = "flex-1"
35
83
/>
36
84
) ) }
@@ -69,6 +117,7 @@ export function ResetFiltersButton({
69
117
interface FiltersComboboxProps {
70
118
label : string
71
119
options : string [ ]
120
+ enabledOptions : Set < string > | null
72
121
value : string [ ]
73
122
onChange : ( newSelectedOptions : string [ ] ) => void
74
123
placeholder : string
@@ -77,6 +126,7 @@ interface FiltersComboboxProps {
77
126
function FiltersCombobox ( {
78
127
label,
79
128
options,
129
+ enabledOptions,
80
130
value,
81
131
onChange,
82
132
placeholder,
@@ -95,12 +145,12 @@ function FiltersCombobox({
95
145
< Combobox immediate multiple value = { value } onChange = { onChange } >
96
146
< div className = { clsx ( "flex flex-col" , className ) } >
97
147
{ label && (
98
- < Combobox . Label className = "typography-menu mb-1 block font-mono font-medium uppercase text-neu-900" >
148
+ < Label className = "typography-menu mb-1 block font-mono font-medium uppercase text-neu-900" >
99
149
{ label }
100
- </ Combobox . Label >
150
+ </ Label >
101
151
) }
102
152
< 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
153
+ < ComboboxInput
104
154
value = { query }
105
155
onChange = { e => setQuery ( e . target . value ) }
106
156
className = { clsx (
@@ -109,7 +159,7 @@ function FiltersCombobox({
109
159
placeholder = { placeholder }
110
160
autoComplete = "true"
111
161
/>
112
- < Combobox . Button
162
+ < ComboboxButton
113
163
className = { clsx (
114
164
"absolute inset-y-0 right-0 flex items-center px-2 focus:outline-none" ,
115
165
) }
@@ -118,7 +168,7 @@ function FiltersCombobox({
118
168
className = "ui-open:rotate-180 size-5 text-neu-400 transition-transform duration-150 group-hover:text-neu-500"
119
169
aria-hidden = "true"
120
170
/>
121
- </ Combobox . Button >
171
+ </ ComboboxButton >
122
172
{ value . length > 0 && (
123
173
< div className = "inset-y-0 left-0 z-[1] mt-1 flex items-center overflow-x-auto pr-8" >
124
174
< div className = "flex flex-wrap items-center gap-1" >
@@ -136,23 +186,27 @@ function FiltersCombobox({
136
186
</ label >
137
187
138
188
< div className = "relative" >
139
- < Combobox . Options
189
+ < ComboboxOptions
140
190
className = { clsx (
141
191
"absolute z-10 -mt-px max-h-60 w-full overflow-auto border border-neu-500 bg-neu-0 p-1 text-base" ,
142
192
) }
143
193
>
144
194
{ filteredOptions . map ( option => (
145
- < Combobox . Option key = { option } value = { option } >
195
+ < ComboboxOption
196
+ key = { option }
197
+ value = { option }
198
+ disabled = { enabledOptions ? ! enabledOptions . has ( option ) : false }
199
+ >
146
200
{ ( { active, selected } ) => (
147
201
< FilterComboboxOption
148
202
active = { active }
149
203
selected = { selected }
150
204
option = { option }
151
205
/>
152
206
) }
153
- </ Combobox . Option >
207
+ </ ComboboxOption >
154
208
) ) }
155
- </ Combobox . Options >
209
+ </ ComboboxOptions >
156
210
</ div >
157
211
</ div >
158
212
</ Combobox >
@@ -208,6 +262,7 @@ function FilterComboboxOption({
208
262
className = { clsx (
209
263
"typography-body-sm relative flex cursor-default select-none items-center p-1 font-sans" ,
210
264
active && "bg-neu-100 dark:bg-neu-50" ,
265
+ "[[data-disabled]_&]:line-through [[data-disabled]_&]:opacity-40" ,
211
266
) }
212
267
>
213
268
< CheckboxIcon
0 commit comments