@eggspot/ui 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/eslint.config.js +4 -0
  2. package/package.json +66 -0
  3. package/postcss.config.mjs +1 -0
  4. package/src/components/Button.machine.tsx +50 -0
  5. package/src/components/Button.tsx +249 -0
  6. package/src/components/Button.variants.tsx +186 -0
  7. package/src/components/ButtonGroup.tsx +56 -0
  8. package/src/components/Calendar.tsx +275 -0
  9. package/src/components/Calendar.utils.tsx +22 -0
  10. package/src/components/Checkbox.tsx +199 -0
  11. package/src/components/ConfirmDialog.tsx +183 -0
  12. package/src/components/DashboardLayout/DashboardLayout.tsx +348 -0
  13. package/src/components/DashboardLayout/SidebarNav.tsx +509 -0
  14. package/src/components/DashboardLayout/index.ts +33 -0
  15. package/src/components/DataTable/DataTable.tsx +557 -0
  16. package/src/components/DataTable/DataTableColumnHeader.tsx +122 -0
  17. package/src/components/DataTable/DataTableDisplaySettings.tsx +265 -0
  18. package/src/components/DataTable/DataTableFloatingBar.tsx +44 -0
  19. package/src/components/DataTable/DataTablePagination.tsx +168 -0
  20. package/src/components/DataTable/DataTableStates.tsx +69 -0
  21. package/src/components/DataTable/DataTableToolbarContainer.tsx +47 -0
  22. package/src/components/DataTable/hooks/use-data-table-settings.ts +101 -0
  23. package/src/components/DataTable/index.ts +7 -0
  24. package/src/components/DataTable/types/data-table.ts +97 -0
  25. package/src/components/DatePicker.tsx +213 -0
  26. package/src/components/DatePicker.utils.tsx +38 -0
  27. package/src/components/Datefield.tsx +109 -0
  28. package/src/components/Datefield.utils.ts +10 -0
  29. package/src/components/Dialog.tsx +167 -0
  30. package/src/components/Field.tsx +49 -0
  31. package/src/components/Filter/Filter.store.tsx +122 -0
  32. package/src/components/Filter/Filter.tsx +11 -0
  33. package/src/components/Filter/Filter.types.ts +107 -0
  34. package/src/components/Filter/FilterBar.tsx +38 -0
  35. package/src/components/Filter/FilterBuilder.tsx +158 -0
  36. package/src/components/Filter/FilterField/DateModeRowValue.tsx +250 -0
  37. package/src/components/Filter/FilterField/FilterAsyncSelect.tsx +191 -0
  38. package/src/components/Filter/FilterField/FilterDateMode.tsx +241 -0
  39. package/src/components/Filter/FilterField/FilterDateRange.tsx +169 -0
  40. package/src/components/Filter/FilterField/FilterSelect.tsx +208 -0
  41. package/src/components/Filter/FilterField/FilterSingleDate.tsx +277 -0
  42. package/src/components/Filter/FilterField/OptionItem.tsx +112 -0
  43. package/src/components/Filter/FilterField/index.ts +6 -0
  44. package/src/components/Filter/FilterRow.tsx +527 -0
  45. package/src/components/Filter/index.ts +17 -0
  46. package/src/components/Form.tsx +195 -0
  47. package/src/components/Heading.tsx +41 -0
  48. package/src/components/Input.tsx +221 -0
  49. package/src/components/InputOTP.tsx +78 -0
  50. package/src/components/Label.tsx +65 -0
  51. package/src/components/Layout.tsx +129 -0
  52. package/src/components/ListBox.tsx +97 -0
  53. package/src/components/Menu.tsx +152 -0
  54. package/src/components/NativeSelect.tsx +77 -0
  55. package/src/components/NumberInput.tsx +114 -0
  56. package/src/components/Popover.tsx +44 -0
  57. package/src/components/Provider.tsx +22 -0
  58. package/src/components/RadioGroup.tsx +191 -0
  59. package/src/components/Resizable.tsx +71 -0
  60. package/src/components/ScrollArea.tsx +57 -0
  61. package/src/components/Select.tsx +626 -0
  62. package/src/components/Select.utils.tsx +64 -0
  63. package/src/components/Separator.tsx +25 -0
  64. package/src/components/Sheet.tsx +147 -0
  65. package/src/components/Sonner.tsx +96 -0
  66. package/src/components/Spinner.tsx +30 -0
  67. package/src/components/Switch.tsx +51 -0
  68. package/src/components/Text.tsx +35 -0
  69. package/src/components/Tooltip.tsx +58 -0
  70. package/src/consts/config.ts +2 -0
  71. package/src/hooks/.gitkeep +0 -0
  72. package/src/lib/utils.ts +10 -0
  73. package/tsconfig.json +11 -0
  74. package/tsconfig.lint.json +8 -0
@@ -0,0 +1,208 @@
1
+ "use client"
2
+
3
+ import { useCallback, useContext, useMemo, useState } from "react"
4
+ import { SearchInput } from "@eggspot/ui/components/Input"
5
+ import { cn } from "@eggspot/ui/lib/utils"
6
+ import { Check, Minus } from "lucide-react"
7
+ import {
8
+ Menu as AriaMenu,
9
+ MenuItem as AriaMenuItem,
10
+ Collection,
11
+ ListLayout,
12
+ RootMenuTriggerStateContext,
13
+ Virtualizer,
14
+ } from "react-aria-components"
15
+
16
+ import {
17
+ useFilterContext,
18
+ useFilterItem,
19
+ useFilterItemClose,
20
+ } from "../Filter.store"
21
+ import type { SelectOption } from "../Filter.types"
22
+ import { isSelectOptionArray } from "../Filter.types"
23
+ import { MenuOptionItem } from "./OptionItem"
24
+
25
+ interface FilterSelectProps {
26
+ field?: string
27
+ options?: SelectOption[]
28
+ multi?: boolean
29
+ renderIcon?: (option: SelectOption, isSelected: boolean) => React.ReactNode
30
+ placeholder?: string
31
+ emptyMessage?: string
32
+ }
33
+
34
+ export function FilterSelect({
35
+ field: fieldProp,
36
+ options: optionsProp,
37
+ multi = true,
38
+ renderIcon,
39
+ placeholder = "Filter...",
40
+ emptyMessage = "No matching options",
41
+ }: FilterSelectProps) {
42
+ const { value: filters, setFieldValue } = useFilterContext()
43
+ const filterItem = useFilterItem()
44
+ const onClose = useFilterItemClose()
45
+ const menuState = useContext(RootMenuTriggerStateContext)
46
+
47
+ const field = fieldProp ?? filterItem?.field
48
+ const options = optionsProp ?? filterItem?.options ?? []
49
+
50
+ if (!field) {
51
+ throw new Error(
52
+ "FilterSelect requires a field prop or must be used within a FilterItemProvider"
53
+ )
54
+ }
55
+
56
+ const [search, setSearch] = useState("")
57
+
58
+ const raw = filters[field]
59
+
60
+ const selectedValues = useMemo(() => {
61
+ if (!raw) return []
62
+ if (isSelectOptionArray(raw)) {
63
+ return raw.map((opt) => opt.value)
64
+ }
65
+ if (Array.isArray(raw)) {
66
+ return raw.map(String)
67
+ }
68
+ return [String(raw)]
69
+ }, [raw])
70
+
71
+ const selectedKeys = useMemo(() => new Set(selectedValues), [selectedValues])
72
+
73
+ const filteredOptions = useMemo(() => {
74
+ if (!search) return options
75
+ const lowerSearch = search.toLowerCase()
76
+ return options.filter((opt) =>
77
+ opt.label.toLowerCase().includes(lowerSearch)
78
+ )
79
+ }, [options, search])
80
+
81
+ const optionsWithId = useMemo(
82
+ () => filteredOptions.map((opt) => ({ ...opt, id: String(opt.value) })),
83
+ [filteredOptions]
84
+ )
85
+
86
+ const handleSelectionChange = useCallback(
87
+ (keys: Set<React.Key>) => {
88
+ const selectedIds = Array.from(keys).map(String)
89
+ const selectedOptions = options
90
+ .filter((opt) => selectedIds.includes(String(opt.value)))
91
+ .map(({ icon, ...rest }) => rest)
92
+
93
+ setFieldValue(
94
+ field,
95
+ selectedOptions.length > 0 ? selectedOptions : undefined
96
+ )
97
+
98
+ if (!multi && selectedOptions.length > 0) {
99
+ menuState?.close()
100
+ onClose?.()
101
+ }
102
+ },
103
+ [field, options, setFieldValue, multi, onClose]
104
+ )
105
+
106
+ const handleSelectAll = useCallback(() => {
107
+ const isAllSelected = selectedValues.length === optionsWithId.length
108
+
109
+ if (isAllSelected) {
110
+ setFieldValue(field, undefined)
111
+ } else {
112
+ const allOptions = optionsWithId.map(({ icon, id, ...rest }) => rest)
113
+ setFieldValue(field, allOptions)
114
+ }
115
+ }, [field, optionsWithId, selectedValues.length, setFieldValue])
116
+
117
+ const isAllSelected =
118
+ selectedValues.length === optionsWithId.length && optionsWithId.length > 0
119
+ const isIndeterminate =
120
+ selectedValues.length > 0 && selectedValues.length < optionsWithId.length
121
+
122
+ return (
123
+ <div className="flex max-h-[300px] min-w-[200px] flex-col">
124
+ <div className="border-gray-6 border-b px-3 py-2">
125
+ <SearchInput
126
+ placeholder={placeholder}
127
+ className="placeholder:text-gray-11 h-7 rounded border-0 bg-transparent pl-0 text-xs ring-0!"
128
+ onChange={setSearch}
129
+ value={search}
130
+ />
131
+ </div>
132
+
133
+ <Virtualizer
134
+ layout={ListLayout}
135
+ layoutOptions={{ estimatedRowHeight: 32 }}
136
+ >
137
+ <AriaMenu
138
+ aria-label="Filter options"
139
+ selectionMode={multi ? "multiple" : "single"}
140
+ selectedKeys={selectedKeys}
141
+ onSelectionChange={(keys) =>
142
+ handleSelectionChange(keys as Set<string>)
143
+ }
144
+ className="max-h-[250px] flex-1 overflow-auto py-1 outline-none"
145
+ renderEmptyState={() => (
146
+ <div className="text-gray-11 px-3 py-6 text-center text-xs">
147
+ {emptyMessage}
148
+ </div>
149
+ )}
150
+ >
151
+ {multi && optionsWithId.length > 0 && (
152
+ <SelectAllItem
153
+ isAllSelected={isAllSelected}
154
+ isIndeterminate={isIndeterminate}
155
+ onSelectAll={handleSelectAll}
156
+ />
157
+ )}
158
+
159
+ <Collection items={optionsWithId}>
160
+ {(option) => (
161
+ <MenuOptionItem
162
+ option={option}
163
+ multi={multi}
164
+ renderIcon={renderIcon}
165
+ />
166
+ )}
167
+ </Collection>
168
+ </AriaMenu>
169
+ </Virtualizer>
170
+ </div>
171
+ )
172
+ }
173
+
174
+ function SelectAllItem({
175
+ isAllSelected,
176
+ isIndeterminate,
177
+ onSelectAll,
178
+ }: {
179
+ isAllSelected: boolean
180
+ isIndeterminate: boolean
181
+ onSelectAll: () => void
182
+ }) {
183
+ return (
184
+ <AriaMenuItem
185
+ id="__select_all__"
186
+ textValue="Select All"
187
+ onAction={onSelectAll}
188
+ className={cn(
189
+ "flex w-full cursor-pointer items-center gap-2.5 px-3 py-1.5 text-left text-sm outline-none",
190
+ "hover:bg-gray-3 data-focused:bg-gray-3 transition-colors",
191
+ "text-gray-11 font-medium"
192
+ )}
193
+ >
194
+ <span
195
+ className={cn(
196
+ "flex size-4 shrink-0 items-center justify-center rounded border",
197
+ isAllSelected || isIndeterminate
198
+ ? "bg-accent-9 border-accent-9 text-white"
199
+ : "border-gray-7 bg-transparent"
200
+ )}
201
+ >
202
+ {isAllSelected && <Check className="size-3" strokeWidth={2.5} />}
203
+ {isIndeterminate && <Minus className="size-3" strokeWidth={2.5} />}
204
+ </span>
205
+ <span className="flex-1">Select All</span>
206
+ </AriaMenuItem>
207
+ )
208
+ }
@@ -0,0 +1,277 @@
1
+ "use client"
2
+
3
+ import { useContext, useState } from "react"
4
+ import { Button } from "@eggspot/ui/components/Button"
5
+ import {
6
+ Calendar,
7
+ CalendarCell,
8
+ CalendarGrid,
9
+ CalendarGridBody,
10
+ CalendarGridHeader,
11
+ CalendarHeaderCell,
12
+ CalendarHeading,
13
+ } from "@eggspot/ui/components/Calendar"
14
+ import { parse } from "@eggspot/ui/components/DatePicker.utils"
15
+ import { cn } from "@eggspot/ui/lib/utils"
16
+ import type { CalendarDate, CalendarDateTime } from "@internationalized/date"
17
+ import { getLocalTimeZone, today } from "@internationalized/date"
18
+ import dayjs from "dayjs"
19
+ import { ChevronLeft, ChevronRight } from "lucide-react"
20
+ import { RootMenuTriggerStateContext } from "react-aria-components"
21
+
22
+ import {
23
+ useFilterContext,
24
+ useFilterItem,
25
+ useFilterItemClose,
26
+ } from "../Filter.store"
27
+
28
+ interface FilterSingleDateProps {
29
+ field?: string
30
+ granularity?: "day" | "month"
31
+ onSelect?: (date: Date | undefined) => void
32
+ }
33
+
34
+ export function FilterSingleDate({
35
+ field: fieldProp,
36
+ granularity = "day",
37
+ onSelect,
38
+ }: FilterSingleDateProps) {
39
+ const { value, setFieldValue } = useFilterContext()
40
+ const filterItem = useFilterItem()
41
+
42
+ const field = fieldProp ?? filterItem?.field
43
+
44
+ if (!field) {
45
+ throw new Error(
46
+ "FilterSingleDate requires a field prop or must be used within a FilterItemProvider"
47
+ )
48
+ }
49
+
50
+ const dateValue = value[field] as Date | null | undefined
51
+
52
+ const handleChange = (date: Date) => {
53
+ if (onSelect) {
54
+ onSelect(date)
55
+ } else {
56
+ setFieldValue(field, date)
57
+ }
58
+ }
59
+
60
+ const handleClear = () => {
61
+ if (onSelect) {
62
+ onSelect(undefined)
63
+ } else {
64
+ setFieldValue(field, undefined)
65
+ }
66
+ }
67
+
68
+ if (granularity === "month") {
69
+ return (
70
+ <MonthPicker
71
+ value={dateValue ?? undefined}
72
+ onChange={handleChange}
73
+ onClear={handleClear}
74
+ />
75
+ )
76
+ }
77
+
78
+ return (
79
+ <DayPicker
80
+ value={dateValue ?? undefined}
81
+ onChange={handleChange}
82
+ onClear={handleClear}
83
+ />
84
+ )
85
+ }
86
+
87
+ interface DayPickerProps {
88
+ value?: Date
89
+ onChange: (date: Date) => void
90
+ onClear: () => void
91
+ }
92
+
93
+ function DayPicker({ value, onChange, onClear }: DayPickerProps) {
94
+ const calendarValue = parse(value, "day")
95
+ const todayDate = today(getLocalTimeZone())
96
+ const menuState = useContext(RootMenuTriggerStateContext)
97
+ const onClose = useFilterItemClose()
98
+
99
+ const handleDateChange = (date: CalendarDate | CalendarDateTime) => {
100
+ onChange(date.toDate(getLocalTimeZone()))
101
+ // Close popover after selection
102
+ menuState?.close()
103
+ onClose?.()
104
+ }
105
+
106
+ const handleToday = () => {
107
+ onChange(dayjs().startOf("day").toDate())
108
+ }
109
+
110
+ return (
111
+ <div className="flex h-[352px] min-w-[280px] flex-col p-2">
112
+ <Calendar
113
+ aria-label="Select date"
114
+ className="w-full flex-1"
115
+ value={calendarValue}
116
+ onChange={handleDateChange}
117
+ defaultFocusedValue={calendarValue ?? todayDate}
118
+ >
119
+ <CalendarHeading />
120
+ <CalendarGrid className="w-full">
121
+ <CalendarGridHeader>
122
+ {(day) => (
123
+ <CalendarHeaderCell className="flex-1">{day}</CalendarHeaderCell>
124
+ )}
125
+ </CalendarGridHeader>
126
+ <CalendarGridBody>
127
+ {(date) => <CalendarCell date={date} />}
128
+ </CalendarGridBody>
129
+ </CalendarGrid>
130
+ </Calendar>
131
+
132
+ <div className="border-gray-6 flex justify-center gap-2 border-t pt-2">
133
+ <Button
134
+ size="sm"
135
+ variant="ghost"
136
+ intent="primary"
137
+ onClick={handleToday}
138
+ >
139
+ Today
140
+ </Button>
141
+ <Button
142
+ size="sm"
143
+ variant="ghost"
144
+ intent="secondary"
145
+ onClick={onClear}
146
+ isDisabled={!value}
147
+ >
148
+ Clear
149
+ </Button>
150
+ </div>
151
+ </div>
152
+ )
153
+ }
154
+
155
+ interface MonthPickerProps {
156
+ value?: Date
157
+ onChange: (date: Date) => void
158
+ onClear: () => void
159
+ }
160
+
161
+ const MONTHS = [
162
+ "Jan",
163
+ "Feb",
164
+ "Mar",
165
+ "Apr",
166
+ "May",
167
+ "Jun",
168
+ "Jul",
169
+ "Aug",
170
+ "Sep",
171
+ "Oct",
172
+ "Nov",
173
+ "Dec",
174
+ ]
175
+
176
+ function MonthPicker({ value, onChange, onClear }: MonthPickerProps) {
177
+ const currentYear = dayjs().year()
178
+ const [viewYear, setViewYear] = useState(
179
+ value ? dayjs(value).year() : currentYear
180
+ )
181
+ const onClose = useFilterItemClose()
182
+ const menuState = useContext(RootMenuTriggerStateContext)
183
+
184
+ const selectedMonth = value ? dayjs(value).month() : null
185
+ const selectedYear = value ? dayjs(value).year() : null
186
+
187
+ const handleMonthSelect = (monthIndex: number) => {
188
+ const date = dayjs()
189
+ .year(viewYear)
190
+ .month(monthIndex)
191
+ .startOf("month")
192
+ .toDate()
193
+ onChange(date)
194
+ onClose?.()
195
+ menuState?.close()
196
+ }
197
+
198
+ const handleThisMonth = () => {
199
+ onChange(dayjs().startOf("month").toDate())
200
+ }
201
+
202
+ return (
203
+ <div className="flex min-w-[280px] flex-col p-2">
204
+ <div className="mb-2 flex items-center justify-between">
205
+ <Button
206
+ mode="icon"
207
+ size="sm"
208
+ variant="ghost"
209
+ intent="secondary"
210
+ onClick={() => setViewYear((y) => y - 1)}
211
+ aria-label="Previous year"
212
+ >
213
+ <ChevronLeft className="size-4" />
214
+ </Button>
215
+ <span className="text-gray-12 text-sm font-medium">{viewYear}</span>
216
+ <Button
217
+ mode="icon"
218
+ size="sm"
219
+ variant="ghost"
220
+ intent="secondary"
221
+ onClick={() => setViewYear((y) => y + 1)}
222
+ aria-label="Next year"
223
+ >
224
+ <ChevronRight className="size-4" />
225
+ </Button>
226
+ </div>
227
+
228
+ <div className="grid grid-cols-3 gap-1">
229
+ {MONTHS.map((month, index) => {
230
+ const isSelected =
231
+ selectedMonth === index && selectedYear === viewYear
232
+ const isCurrentMonth =
233
+ dayjs().month() === index && dayjs().year() === viewYear
234
+
235
+ return (
236
+ <button
237
+ key={month}
238
+ type="button"
239
+ onClick={() => handleMonthSelect(index)}
240
+ className={cn(
241
+ "rounded-md px-3 py-2 text-sm transition-colors",
242
+ "hover:bg-gray-4 focus-visible:ring-accent-9 focus-visible:ring-2 focus-visible:outline-none",
243
+ isSelected
244
+ ? "bg-accent-9 hover:bg-accent-10 text-white"
245
+ : isCurrentMonth
246
+ ? "text-accent-11"
247
+ : "text-gray-11 hover:text-gray-12"
248
+ )}
249
+ >
250
+ {month}
251
+ </button>
252
+ )
253
+ })}
254
+ </div>
255
+
256
+ <div className="border-gray-6 mt-2 flex justify-center gap-2 border-t pt-2">
257
+ <Button
258
+ size="sm"
259
+ variant="ghost"
260
+ intent="primary"
261
+ onClick={handleThisMonth}
262
+ >
263
+ This month
264
+ </Button>
265
+ <Button
266
+ size="sm"
267
+ variant="ghost"
268
+ intent="secondary"
269
+ onClick={onClear}
270
+ isDisabled={!value}
271
+ >
272
+ Clear
273
+ </Button>
274
+ </div>
275
+ </div>
276
+ )
277
+ }
@@ -0,0 +1,112 @@
1
+ "use client"
2
+
3
+ import { cn } from "@eggspot/ui/lib/utils"
4
+ import { Check } from "lucide-react"
5
+ import { MenuItem as AriaMenuItem, ListBoxItem } from "react-aria-components"
6
+
7
+ import type { SelectOption } from "../Filter.types"
8
+
9
+ interface OptionItemProps {
10
+ option: SelectOption
11
+ multi?: boolean
12
+ renderIcon?: (option: SelectOption, isSelected: boolean) => React.ReactNode
13
+ }
14
+
15
+ /* Shared option item for Menu-based selects (FilterSelect) */
16
+ export function MenuOptionItem({
17
+ option,
18
+ multi = true,
19
+ renderIcon,
20
+ }: OptionItemProps) {
21
+ return (
22
+ <AriaMenuItem
23
+ id={String(option.value)}
24
+ textValue={option.label}
25
+ className={cn(
26
+ "flex w-full cursor-pointer items-center gap-2.5 px-3 py-1.5 text-left text-sm outline-none",
27
+ "hover:bg-gray-3 data-focused:bg-gray-3 transition-colors",
28
+ "data-selected:text-gray-12 text-gray-11"
29
+ )}
30
+ >
31
+ {({ isSelected }) => (
32
+ <OptionItemContent
33
+ option={option}
34
+ isSelected={isSelected}
35
+ multi={multi}
36
+ renderIcon={renderIcon}
37
+ />
38
+ )}
39
+ </AriaMenuItem>
40
+ )
41
+ }
42
+
43
+ /* Shared option item for ListBox-based selects (FilterAsyncSelect) */
44
+ export function ListBoxOptionItem({
45
+ option,
46
+ multi = true,
47
+ renderIcon,
48
+ }: OptionItemProps) {
49
+ return (
50
+ <ListBoxItem
51
+ id={String(option.value)}
52
+ textValue={option.label}
53
+ className={cn(
54
+ "flex w-full cursor-pointer items-center gap-2.5 px-3 py-1.5 text-left text-sm outline-none",
55
+ "hover:bg-gray-3 data-focused:bg-gray-3 transition-colors",
56
+ "data-selected:text-gray-12 text-gray-11"
57
+ )}
58
+ >
59
+ {({ isSelected }) => (
60
+ <OptionItemContent
61
+ option={option}
62
+ isSelected={isSelected}
63
+ multi={multi}
64
+ renderIcon={renderIcon}
65
+ />
66
+ )}
67
+ </ListBoxItem>
68
+ )
69
+ }
70
+
71
+ /* Shared content for option items */
72
+ function OptionItemContent({
73
+ option,
74
+ isSelected,
75
+ multi,
76
+ renderIcon,
77
+ }: {
78
+ option: SelectOption
79
+ isSelected: boolean
80
+ multi?: boolean
81
+ renderIcon?: (option: SelectOption, isSelected: boolean) => React.ReactNode
82
+ }) {
83
+ return (
84
+ <>
85
+ <span
86
+ className={cn(
87
+ "flex size-4 shrink-0 items-center justify-center rounded border",
88
+ multi ? "rounded" : "rounded-full",
89
+ isSelected
90
+ ? "bg-accent-9 border-accent-9 text-white"
91
+ : "border-gray-7 bg-transparent"
92
+ )}
93
+ >
94
+ {isSelected && <Check className="size-3" strokeWidth={2.5} />}
95
+ </span>
96
+
97
+ {renderIcon ? (
98
+ <span className="shrink-0">{renderIcon(option, isSelected)}</span>
99
+ ) : option.avatar ? (
100
+ <img
101
+ src={option.avatar}
102
+ alt={option.label}
103
+ className="size-5 shrink-0 rounded-full object-cover"
104
+ />
105
+ ) : option.icon ? (
106
+ <span className="text-gray-11 shrink-0">{option.icon}</span>
107
+ ) : null}
108
+
109
+ <span className="flex-1 truncate">{option.label}</span>
110
+ </>
111
+ )
112
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./DateModeRowValue"
2
+ export * from "./FilterAsyncSelect"
3
+ export * from "./FilterDateMode"
4
+ export * from "./FilterDateRange"
5
+ export * from "./FilterSelect"
6
+ export * from "./FilterSingleDate"