@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,191 @@
1
+ "use client"
2
+
3
+ import { useCallback, useContext, useMemo } from "react"
4
+ import { SearchInput } from "@eggspot/ui/components/Input"
5
+ import {
6
+ Autocomplete,
7
+ Collection,
8
+ ListBox,
9
+ ListBoxLoadMoreItem,
10
+ ListLayout,
11
+ RootMenuTriggerStateContext,
12
+ Virtualizer,
13
+ } from "react-aria-components"
14
+
15
+ import {
16
+ useFilterContext,
17
+ useFilterItem,
18
+ useFilterItemClose,
19
+ } from "../Filter.store"
20
+ import type {
21
+ OptionsLoader,
22
+ SelectOption,
23
+ SerializableSelectOption,
24
+ } from "../Filter.types"
25
+ import { isSelectOptionArray } from "../Filter.types"
26
+ import { ListBoxOptionItem } from "./OptionItem"
27
+
28
+ interface FilterAsyncSelectProps {
29
+ field?: string
30
+ options: SelectOption[]
31
+ multi?: boolean
32
+ renderIcon?: (option: SelectOption, isSelected: boolean) => React.ReactNode
33
+ placeholder?: string
34
+ emptyMessage?: string
35
+ optionsLoader: OptionsLoader
36
+ }
37
+
38
+ export function FilterAsyncSelect({
39
+ field: fieldProp,
40
+ options,
41
+ multi = true,
42
+ renderIcon,
43
+ placeholder = "Search...",
44
+ emptyMessage = "No matching options",
45
+ optionsLoader,
46
+ }: FilterAsyncSelectProps) {
47
+ const { value: filters, setFieldValue } = useFilterContext()
48
+ const filterItem = useFilterItem()
49
+ const onClose = useFilterItemClose()
50
+ const menuState = useContext(RootMenuTriggerStateContext)
51
+
52
+ const field = fieldProp ?? filterItem?.field
53
+
54
+ if (!field) {
55
+ throw new Error(
56
+ "FilterAsyncSelect requires a field prop or must be used within a FilterItemProvider"
57
+ )
58
+ }
59
+
60
+ const raw = filters[field]
61
+
62
+ const storedOptions = useMemo(() => {
63
+ if (isSelectOptionArray(raw)) return raw
64
+ return []
65
+ }, [raw])
66
+
67
+ const selectedValues = useMemo(() => {
68
+ if (!raw) return []
69
+ if (isSelectOptionArray(raw)) {
70
+ return raw.map((opt) => opt.value)
71
+ }
72
+ if (Array.isArray(raw)) {
73
+ return raw.map(String)
74
+ }
75
+ return [String(raw)]
76
+ }, [raw])
77
+
78
+ const selectedKeys = useMemo(() => new Set(selectedValues), [selectedValues])
79
+
80
+ const optionsWithId = useMemo(
81
+ () => options.map((opt) => ({ ...opt, id: String(opt.value) })),
82
+ [options]
83
+ )
84
+
85
+ const handleSelectionChange = useCallback(
86
+ (keys: Set<React.Key>) => {
87
+ const selectedIds = Array.from(keys).map(String)
88
+
89
+ const allAvailableOptions = new Map<string, SerializableSelectOption>()
90
+
91
+ storedOptions.forEach((opt) => {
92
+ allAvailableOptions.set(opt.value, opt)
93
+ })
94
+
95
+ options.forEach((opt) => {
96
+ const { icon, ...rest } = opt
97
+ allAvailableOptions.set(String(opt.value), rest)
98
+ })
99
+
100
+ const selectedOptions = selectedIds
101
+ .map((id) => allAvailableOptions.get(id))
102
+ .filter((opt): opt is SerializableSelectOption => opt !== undefined)
103
+
104
+ setFieldValue(
105
+ field,
106
+ selectedOptions.length > 0 ? selectedOptions : undefined
107
+ )
108
+
109
+ if (!multi && selectedOptions.length > 0) {
110
+ menuState?.close()
111
+ onClose?.()
112
+ }
113
+ },
114
+ [field, options, storedOptions, setFieldValue, multi, onClose]
115
+ )
116
+
117
+ const isInitialLoading = optionsLoader.isFetching && options.length === 0
118
+
119
+ return (
120
+ <div className="flex max-h-[300px] min-w-[200px] flex-col">
121
+ <Autocomplete disableVirtualFocus>
122
+ <div className="border-gray-6 border-b px-3 py-2">
123
+ <SearchInput
124
+ autoFocus
125
+ placeholder={placeholder}
126
+ className="placeholder:text-gray-11 h-7 rounded border-0 bg-transparent pl-0 text-xs ring-0!"
127
+ onChange={optionsLoader.onSearch}
128
+ />
129
+ </div>
130
+
131
+ {isInitialLoading ? (
132
+ <div className="py-1">
133
+ {Array.from({ length: 5 }).map((_, i) => (
134
+ <SkeletonOption key={i} />
135
+ ))}
136
+ </div>
137
+ ) : (
138
+ <Virtualizer
139
+ layout={ListLayout}
140
+ layoutOptions={{ estimatedRowHeight: 32 }}
141
+ >
142
+ <ListBox
143
+ aria-label="Filter options"
144
+ selectionMode={multi ? "multiple" : "single"}
145
+ selectedKeys={selectedKeys}
146
+ onSelectionChange={(keys) =>
147
+ handleSelectionChange(keys as Set<string>)
148
+ }
149
+ className="max-h-[250px] flex-1 overflow-auto py-1 outline-none"
150
+ renderEmptyState={() => (
151
+ <div className="text-gray-11 px-3 py-6 text-center text-xs">
152
+ {emptyMessage}
153
+ </div>
154
+ )}
155
+ >
156
+ <Collection items={optionsWithId}>
157
+ {(option) => (
158
+ <ListBoxOptionItem
159
+ option={option}
160
+ multi={multi}
161
+ renderIcon={renderIcon}
162
+ />
163
+ )}
164
+ </Collection>
165
+
166
+ {optionsLoader.hasNextPage && (
167
+ <ListBoxLoadMoreItem
168
+ onLoadMore={optionsLoader.onFetchNextPage}
169
+ isLoading={optionsLoader.isFetching}
170
+ className="py-1"
171
+ >
172
+ <SkeletonOption />
173
+ <SkeletonOption />
174
+ </ListBoxLoadMoreItem>
175
+ )}
176
+ </ListBox>
177
+ </Virtualizer>
178
+ )}
179
+ </Autocomplete>
180
+ </div>
181
+ )
182
+ }
183
+
184
+ function SkeletonOption() {
185
+ return (
186
+ <div className="flex h-8 animate-pulse items-center gap-2.5 px-3 py-1.5">
187
+ <div className="border-gray-6 bg-gray-4 size-4 shrink-0 rounded border" />
188
+ <div className="bg-gray-4 h-4 max-w-[120px] flex-1 rounded" />
189
+ </div>
190
+ )
191
+ }
@@ -0,0 +1,241 @@
1
+ "use client"
2
+
3
+ import { useContext, useEffect, useRef } from "react"
4
+ import {
5
+ Menu,
6
+ MenuItem,
7
+ MenuPopover,
8
+ MenuSeparator,
9
+ MenuSubTrigger,
10
+ } from "@eggspot/ui/components/Menu"
11
+ import dayjs from "dayjs"
12
+ import { RootMenuTriggerStateContext } from "react-aria-components"
13
+
14
+ import {
15
+ useFilterContext,
16
+ useFilterItem,
17
+ useFilterItemClose,
18
+ } from "../Filter.store"
19
+ import type { DateModeFilterValue } from "../Filter.types"
20
+ import { FilterDateRange } from "./FilterDateRange"
21
+ import { FilterSingleDate } from "./FilterSingleDate"
22
+
23
+ function normalizeValue(value: unknown): DateModeFilterValue | null {
24
+ if (value == null) return null
25
+
26
+ if (typeof value === "object" && !Array.isArray(value) && "mode" in value) {
27
+ const v = value as DateModeFilterValue
28
+ return {
29
+ ...v,
30
+ from: v.from ? new Date(v.from) : undefined,
31
+ to: v.to ? new Date(v.to) : undefined,
32
+ }
33
+ }
34
+
35
+ if (Array.isArray(value) && value.length === 2) {
36
+ const [fromRaw, toRaw] = value
37
+ return {
38
+ mode: "range",
39
+ from: fromRaw ? new Date(fromRaw as any) : undefined,
40
+ to: toRaw ? new Date(toRaw as any) : undefined,
41
+ }
42
+ }
43
+
44
+ return null
45
+ }
46
+
47
+ export function FilterDateMode() {
48
+ const { value, setFieldValue } = useFilterContext()
49
+ const item = useFilterItem()
50
+ const field = item?.field
51
+
52
+ if (!field) {
53
+ throw new Error("FilterDateMode must be used within a FilterItemProvider")
54
+ }
55
+
56
+ const current = normalizeValue(value[field])
57
+ const today = dayjs()
58
+ const todayEnd = today.endOf("day").toDate()
59
+ const todayStart = today.startOf("day").toDate()
60
+
61
+ const updateValue = (newValue: DateModeFilterValue | undefined) => {
62
+ setFieldValue(field, newValue)
63
+ }
64
+
65
+ const getDateForSingleDate = (
66
+ targetMode: "from" | "to"
67
+ ): Date | undefined => {
68
+ if (!current) return undefined
69
+ if (current.mode === targetMode) {
70
+ return targetMode === "from" ? current.from : current.to
71
+ }
72
+ return undefined
73
+ }
74
+
75
+ const getRangeForDateRange = (): [Date, Date] | undefined => {
76
+ if (!current) return undefined
77
+ if (current.mode === "range" && current.from && current.to) {
78
+ return [current.from, current.to]
79
+ }
80
+ return undefined
81
+ }
82
+
83
+ const tempFieldFrom = `${field}__temp_from`
84
+ const tempFieldTo = `${field}__temp_to`
85
+ const tempFieldRange = `${field}__temp_range`
86
+
87
+ const prevCurrentRef = useRef<DateModeFilterValue | null>(null)
88
+
89
+ const menuState = useContext(RootMenuTriggerStateContext)
90
+ const onClose = useFilterItemClose()
91
+
92
+ useEffect(() => {
93
+ const currentChanged =
94
+ (!current && prevCurrentRef.current) ||
95
+ (current && !prevCurrentRef.current) ||
96
+ (current &&
97
+ prevCurrentRef.current &&
98
+ (current.mode !== prevCurrentRef.current.mode ||
99
+ current.from?.getTime() !== prevCurrentRef.current.from?.getTime() ||
100
+ current.to?.getTime() !== prevCurrentRef.current.to?.getTime()))
101
+
102
+ if (currentChanged) {
103
+ prevCurrentRef.current = current
104
+ }
105
+
106
+ const dateForFrom = getDateForSingleDate("from")
107
+ const dateForTo = getDateForSingleDate("to")
108
+ const rangeForRange = getRangeForDateRange()
109
+ const currentFrom = value[tempFieldFrom] as Date | undefined
110
+ const currentTo = value[tempFieldTo] as Date | undefined
111
+ const currentRange = value[tempFieldRange] as [Date, Date] | undefined
112
+
113
+ if (dateForFrom?.getTime() !== currentFrom?.getTime()) {
114
+ setFieldValue(tempFieldFrom, dateForFrom)
115
+ }
116
+ if (dateForTo?.getTime() !== currentTo?.getTime()) {
117
+ setFieldValue(tempFieldTo, dateForTo)
118
+ }
119
+ const rangeChanged =
120
+ (!rangeForRange && currentRange) ||
121
+ (rangeForRange && !currentRange) ||
122
+ (rangeForRange &&
123
+ currentRange &&
124
+ (rangeForRange[0]?.getTime() !== currentRange[0]?.getTime() ||
125
+ rangeForRange[1]?.getTime() !== currentRange[1]?.getTime()))
126
+ if (rangeChanged) {
127
+ setFieldValue(tempFieldRange, rangeForRange)
128
+ }
129
+ }, [
130
+ current,
131
+ tempFieldFrom,
132
+ tempFieldTo,
133
+ tempFieldRange,
134
+ setFieldValue,
135
+ value,
136
+ ])
137
+
138
+ const applyPreset = (preset: "lastWeek" | "thisWeek" | "thisMonth") => {
139
+ let start: dayjs.Dayjs
140
+ let end: dayjs.Dayjs
141
+
142
+ switch (preset) {
143
+ case "lastWeek":
144
+ start = today.startOf("week").subtract(1, "week")
145
+ end = start.endOf("week")
146
+ break
147
+ case "thisWeek":
148
+ start = today.startOf("week")
149
+ end = today.endOf("week")
150
+ break
151
+ case "thisMonth":
152
+ start = today.startOf("month")
153
+ end = today.endOf("month")
154
+ break
155
+ }
156
+
157
+ updateValue({
158
+ mode: "range",
159
+ from: start.startOf("day").toDate(),
160
+ to: end.endOf("day").toDate(),
161
+ preset,
162
+ })
163
+ menuState?.close()
164
+ onClose?.()
165
+ }
166
+
167
+ return (
168
+ <Menu aria-label="Date filter options" className="min-w-[220px]">
169
+ <MenuSubTrigger>
170
+ <MenuItem>From date</MenuItem>
171
+ <MenuPopover className="p-0">
172
+ <FilterSingleDate
173
+ field={tempFieldFrom}
174
+ onSelect={(date) => {
175
+ if (!date) {
176
+ updateValue(undefined)
177
+ return
178
+ }
179
+ const fromDate = dayjs(date).startOf("day").toDate()
180
+ updateValue({
181
+ mode: "from",
182
+ from: fromDate,
183
+ to: undefined,
184
+ })
185
+ }}
186
+ />
187
+ </MenuPopover>
188
+ </MenuSubTrigger>
189
+
190
+ <MenuSubTrigger>
191
+ <MenuItem>To date</MenuItem>
192
+ <MenuPopover className="p-0">
193
+ <FilterSingleDate
194
+ field={tempFieldTo}
195
+ onSelect={(date) => {
196
+ if (!date) {
197
+ updateValue(undefined)
198
+ return
199
+ }
200
+ const toDate = dayjs(date).endOf("day").toDate()
201
+ updateValue({
202
+ mode: "to",
203
+ from: undefined,
204
+ to: toDate,
205
+ })
206
+ }}
207
+ />
208
+ </MenuPopover>
209
+ </MenuSubTrigger>
210
+
211
+ <MenuSubTrigger>
212
+ <MenuItem>Custom date</MenuItem>
213
+ <MenuPopover className="p-0">
214
+ <FilterDateRange
215
+ field={tempFieldRange}
216
+ onSelectRange={(range) => {
217
+ if (!range) {
218
+ updateValue(undefined)
219
+ return
220
+ }
221
+ updateValue({
222
+ mode: "range",
223
+ from: range[0],
224
+ to: range[1],
225
+ })
226
+ }}
227
+ />
228
+ </MenuPopover>
229
+ </MenuSubTrigger>
230
+
231
+ <MenuSeparator />
232
+
233
+ {/* PRESETS */}
234
+ <MenuItem onAction={() => applyPreset("lastWeek")}>Last week</MenuItem>
235
+
236
+ <MenuItem onAction={() => applyPreset("thisWeek")}>This week</MenuItem>
237
+
238
+ <MenuItem onAction={() => applyPreset("thisMonth")}>This month</MenuItem>
239
+ </Menu>
240
+ )
241
+ }
@@ -0,0 +1,169 @@
1
+ "use client"
2
+
3
+ import { useContext, useState } from "react"
4
+ import { Button } from "@eggspot/ui/components/Button"
5
+ import {
6
+ CalendarCell,
7
+ CalendarGrid,
8
+ CalendarGridBody,
9
+ CalendarGridHeader,
10
+ CalendarHeaderCell,
11
+ CalendarHeading,
12
+ RangeCalendar,
13
+ } from "@eggspot/ui/components/Calendar"
14
+ import { cn } from "@eggspot/ui/lib/utils"
15
+ import {
16
+ fromDate,
17
+ getLocalTimeZone,
18
+ today,
19
+ type CalendarDate,
20
+ } from "@internationalized/date"
21
+ import dayjs from "dayjs"
22
+ import {
23
+ RootMenuTriggerStateContext,
24
+ type DateRange,
25
+ } from "react-aria-components"
26
+
27
+ import {
28
+ useFilterContext,
29
+ useFilterItem,
30
+ useFilterItemClose,
31
+ } from "../Filter.store"
32
+
33
+ interface FilterDateRangeProps {
34
+ field?: string
35
+ onSelectRange?: (range: [Date, Date] | undefined) => void
36
+ }
37
+
38
+ export function FilterDateRange({
39
+ field: fieldProp,
40
+ onSelectRange,
41
+ }: FilterDateRangeProps) {
42
+ const { value, setFieldValue } = useFilterContext()
43
+ const filterItem = useFilterItem()
44
+ const onClose = useFilterItemClose()
45
+ const menuState = useContext(RootMenuTriggerStateContext)
46
+ const field = fieldProp ?? filterItem?.field
47
+ if (!field) throw new Error("...")
48
+
49
+ const raw = value[field] as [Date, Date] | null | undefined
50
+ const [startDate, endDate] = raw ?? []
51
+
52
+ const tz = getLocalTimeZone()
53
+ const todayDate = today(tz)
54
+
55
+ const initialStart = startDate ? fromDate(startDate, tz) : undefined
56
+ const initialEnd = endDate ? fromDate(endDate, tz) : undefined
57
+
58
+ const [draftRange, setDraftRange] = useState<DateRange | null>(
59
+ initialStart && initialEnd ? { start: initialStart, end: initialEnd } : null
60
+ )
61
+
62
+ const calendarValue = draftRange ?? null
63
+
64
+ const applyRange = (range: [Date, Date] | undefined) => {
65
+ if (onSelectRange) {
66
+ onSelectRange(range)
67
+ } else {
68
+ setFieldValue(field, range)
69
+ }
70
+ }
71
+
72
+ const commitDraft = () => {
73
+ if (!draftRange?.start || !draftRange?.end) {
74
+ applyRange(undefined)
75
+ onClose?.()
76
+ return
77
+ }
78
+ const start = draftRange.start.toDate(tz)
79
+ const end = draftRange.end.toDate(tz)
80
+ applyRange([
81
+ dayjs(start).startOf("day").toDate(),
82
+ dayjs(end).endOf("day").toDate(),
83
+ ])
84
+ menuState?.close()
85
+ onClose?.()
86
+ }
87
+
88
+ const handleClear = () => {
89
+ setDraftRange(null)
90
+ applyRange(undefined)
91
+ }
92
+
93
+ return (
94
+ <div className="flex h-[352px] min-w-[280px] flex-col p-2">
95
+ <RangeCalendar
96
+ aria-label="Select date range"
97
+ className="w-full flex-1"
98
+ value={calendarValue}
99
+ onChange={setDraftRange}
100
+ defaultFocusedValue={calendarValue?.start ?? todayDate}
101
+ >
102
+ <CalendarHeading />
103
+ <CalendarGrid className="border-spacing-x-0 border-spacing-y-0.5">
104
+ <CalendarGridHeader>
105
+ {(day) => (
106
+ <CalendarHeaderCell className="text-center">
107
+ {day}
108
+ </CalendarHeaderCell>
109
+ )}
110
+ </CalendarGridHeader>
111
+ <CalendarGridBody>
112
+ {(date) => (
113
+ <RangeCalendarCell
114
+ date={date as CalendarDate}
115
+ range={draftRange}
116
+ />
117
+ )}
118
+ </CalendarGridBody>
119
+ </CalendarGrid>
120
+ </RangeCalendar>
121
+
122
+ <div className="border-gray-6 flex justify-center gap-2 border-t pt-2">
123
+ <Button
124
+ size="sm"
125
+ variant="ghost"
126
+ intent="primary"
127
+ onClick={commitDraft}
128
+ isDisabled={!draftRange}
129
+ >
130
+ Apply
131
+ </Button>
132
+ <Button
133
+ size="sm"
134
+ variant="ghost"
135
+ intent="secondary"
136
+ onClick={handleClear}
137
+ isDisabled={!raw}
138
+ className="ml-2"
139
+ >
140
+ Clear
141
+ </Button>
142
+ </div>
143
+ </div>
144
+ )
145
+ }
146
+
147
+ function RangeCalendarCell({
148
+ date,
149
+ range,
150
+ }: {
151
+ date: CalendarDate
152
+ range: DateRange | null
153
+ }) {
154
+ const hasRange =
155
+ !!range?.start && !!range?.end && range.start.compare(range.end) !== 0
156
+ return (
157
+ <CalendarCell
158
+ date={date}
159
+ className={cn(
160
+ "data-selected:bg-accent-4 data-selected:text-accent-12 data-selected:rounded-none",
161
+ "data-selection-start:rounded-l-md",
162
+ "data-selection-end:rounded-r-md",
163
+ "data-selection-start:data-selection-end:rounded-md",
164
+ "data-selected:data-selection-start:bg-accent-9 data-selected:data-selection-start:text-accent-contrast",
165
+ "data-selected:data-selection-end:bg-accent-9 data-selected:data-selection-end:text-accent-contrast"
166
+ )}
167
+ />
168
+ )
169
+ }