@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.
- package/eslint.config.js +4 -0
- package/package.json +66 -0
- package/postcss.config.mjs +1 -0
- package/src/components/Button.machine.tsx +50 -0
- package/src/components/Button.tsx +249 -0
- package/src/components/Button.variants.tsx +186 -0
- package/src/components/ButtonGroup.tsx +56 -0
- package/src/components/Calendar.tsx +275 -0
- package/src/components/Calendar.utils.tsx +22 -0
- package/src/components/Checkbox.tsx +199 -0
- package/src/components/ConfirmDialog.tsx +183 -0
- package/src/components/DashboardLayout/DashboardLayout.tsx +348 -0
- package/src/components/DashboardLayout/SidebarNav.tsx +509 -0
- package/src/components/DashboardLayout/index.ts +33 -0
- package/src/components/DataTable/DataTable.tsx +557 -0
- package/src/components/DataTable/DataTableColumnHeader.tsx +122 -0
- package/src/components/DataTable/DataTableDisplaySettings.tsx +265 -0
- package/src/components/DataTable/DataTableFloatingBar.tsx +44 -0
- package/src/components/DataTable/DataTablePagination.tsx +168 -0
- package/src/components/DataTable/DataTableStates.tsx +69 -0
- package/src/components/DataTable/DataTableToolbarContainer.tsx +47 -0
- package/src/components/DataTable/hooks/use-data-table-settings.ts +101 -0
- package/src/components/DataTable/index.ts +7 -0
- package/src/components/DataTable/types/data-table.ts +97 -0
- package/src/components/DatePicker.tsx +213 -0
- package/src/components/DatePicker.utils.tsx +38 -0
- package/src/components/Datefield.tsx +109 -0
- package/src/components/Datefield.utils.ts +10 -0
- package/src/components/Dialog.tsx +167 -0
- package/src/components/Field.tsx +49 -0
- package/src/components/Filter/Filter.store.tsx +122 -0
- package/src/components/Filter/Filter.tsx +11 -0
- package/src/components/Filter/Filter.types.ts +107 -0
- package/src/components/Filter/FilterBar.tsx +38 -0
- package/src/components/Filter/FilterBuilder.tsx +158 -0
- package/src/components/Filter/FilterField/DateModeRowValue.tsx +250 -0
- package/src/components/Filter/FilterField/FilterAsyncSelect.tsx +191 -0
- package/src/components/Filter/FilterField/FilterDateMode.tsx +241 -0
- package/src/components/Filter/FilterField/FilterDateRange.tsx +169 -0
- package/src/components/Filter/FilterField/FilterSelect.tsx +208 -0
- package/src/components/Filter/FilterField/FilterSingleDate.tsx +277 -0
- package/src/components/Filter/FilterField/OptionItem.tsx +112 -0
- package/src/components/Filter/FilterField/index.ts +6 -0
- package/src/components/Filter/FilterRow.tsx +527 -0
- package/src/components/Filter/index.ts +17 -0
- package/src/components/Form.tsx +195 -0
- package/src/components/Heading.tsx +41 -0
- package/src/components/Input.tsx +221 -0
- package/src/components/InputOTP.tsx +78 -0
- package/src/components/Label.tsx +65 -0
- package/src/components/Layout.tsx +129 -0
- package/src/components/ListBox.tsx +97 -0
- package/src/components/Menu.tsx +152 -0
- package/src/components/NativeSelect.tsx +77 -0
- package/src/components/NumberInput.tsx +114 -0
- package/src/components/Popover.tsx +44 -0
- package/src/components/Provider.tsx +22 -0
- package/src/components/RadioGroup.tsx +191 -0
- package/src/components/Resizable.tsx +71 -0
- package/src/components/ScrollArea.tsx +57 -0
- package/src/components/Select.tsx +626 -0
- package/src/components/Select.utils.tsx +64 -0
- package/src/components/Separator.tsx +25 -0
- package/src/components/Sheet.tsx +147 -0
- package/src/components/Sonner.tsx +96 -0
- package/src/components/Spinner.tsx +30 -0
- package/src/components/Switch.tsx +51 -0
- package/src/components/Text.tsx +35 -0
- package/src/components/Tooltip.tsx +58 -0
- package/src/consts/config.ts +2 -0
- package/src/hooks/.gitkeep +0 -0
- package/src/lib/utils.ts +10 -0
- package/tsconfig.json +11 -0
- package/tsconfig.lint.json +8 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { ButtonProps } from "@eggspot/ui/components/Button"
|
|
2
|
+
import {
|
|
3
|
+
ColumnDef,
|
|
4
|
+
ColumnPinningState,
|
|
5
|
+
ColumnSizingState,
|
|
6
|
+
InitialTableState,
|
|
7
|
+
Table,
|
|
8
|
+
TableState,
|
|
9
|
+
VisibilityState,
|
|
10
|
+
} from "@tanstack/react-table"
|
|
11
|
+
|
|
12
|
+
import { FilterValue } from "../../Filter"
|
|
13
|
+
|
|
14
|
+
export interface DataTableProps<TData> {
|
|
15
|
+
/** Columns to display */
|
|
16
|
+
columns: ColumnDef<TData, any>[]
|
|
17
|
+
/** Data to display */
|
|
18
|
+
data: TData[]
|
|
19
|
+
/** Whether the data is fetched from the server or client */
|
|
20
|
+
dataSource?: "server" | "client"
|
|
21
|
+
/** Whether the data is loading */
|
|
22
|
+
isLoading?: boolean
|
|
23
|
+
/** Whether the data is fetching */
|
|
24
|
+
isFetching?: boolean
|
|
25
|
+
/** Whether the data is error */
|
|
26
|
+
isError?: boolean
|
|
27
|
+
/** Error message */
|
|
28
|
+
errorMessage?: string
|
|
29
|
+
|
|
30
|
+
/** Render a toolbar */
|
|
31
|
+
filterBar?: (clientFilterProps: {
|
|
32
|
+
value?: FilterValue
|
|
33
|
+
onChange?: (value: FilterValue) => void
|
|
34
|
+
}) => React.ReactNode
|
|
35
|
+
/** Render an action bar */
|
|
36
|
+
actionBar?: ({
|
|
37
|
+
table,
|
|
38
|
+
}: {
|
|
39
|
+
table: Table<TData>
|
|
40
|
+
clearSelection: () => void
|
|
41
|
+
}) => React.ReactNode
|
|
42
|
+
|
|
43
|
+
/** Initial state for the table */
|
|
44
|
+
initialState?: InitialTableState
|
|
45
|
+
/** Callback when the state changes */
|
|
46
|
+
onStateChange?: (state: TableState) => void
|
|
47
|
+
/** Total number of rows for server-side pagination */
|
|
48
|
+
rowCount?: number
|
|
49
|
+
|
|
50
|
+
/** Whether to enable row selection */
|
|
51
|
+
enableRowSelection?: boolean
|
|
52
|
+
|
|
53
|
+
/** Key to store the display settings in localStorage */
|
|
54
|
+
storageKey?: string
|
|
55
|
+
defaultColumnVisibility?: VisibilityState
|
|
56
|
+
defaultColumnSizing?: ColumnSizingState
|
|
57
|
+
defaultColumnPinning?: ColumnPinningState
|
|
58
|
+
defaultColumnOrder?: string[]
|
|
59
|
+
|
|
60
|
+
/** Estimated row height for virtualization */
|
|
61
|
+
estimatedRowHeight?: number
|
|
62
|
+
|
|
63
|
+
/** Title for the empty state */
|
|
64
|
+
emptyStateTitle?: string
|
|
65
|
+
/** Description for the empty state */
|
|
66
|
+
emptyStateDescription?: string
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface SortingState {
|
|
70
|
+
id: string
|
|
71
|
+
desc: boolean
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface BulkAction {
|
|
75
|
+
label: string
|
|
76
|
+
leftIcon?: React.ReactNode
|
|
77
|
+
onClick: (selectedRows: Record<string, boolean>) => void
|
|
78
|
+
intent?: ButtonProps["intent"]
|
|
79
|
+
variant?: ButtonProps["variant"]
|
|
80
|
+
size?: ButtonProps["size"]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface DisplaySettings {
|
|
84
|
+
columnVisibility: VisibilityState
|
|
85
|
+
columnSizing: ColumnSizingState
|
|
86
|
+
columnPinning: ColumnPinningState
|
|
87
|
+
columnOrder: string[]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface DataTableState {
|
|
91
|
+
sorting: SortingState[]
|
|
92
|
+
columnVisibility: VisibilityState
|
|
93
|
+
columnSizing: ColumnSizingState
|
|
94
|
+
columnPinning: ColumnPinningState
|
|
95
|
+
columnOrder: string[]
|
|
96
|
+
rowSelection: Record<string, boolean>
|
|
97
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useContext } 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 { DateInput } from "@eggspot/ui/components/Datefield"
|
|
15
|
+
import { parse, toValidYear } from "@eggspot/ui/components/DatePicker.utils"
|
|
16
|
+
import { FieldGroup } from "@eggspot/ui/components/Field"
|
|
17
|
+
import { Label } from "@eggspot/ui/components/Label"
|
|
18
|
+
import { Popover } from "@eggspot/ui/components/Popover"
|
|
19
|
+
import { cn } from "@eggspot/ui/lib/utils"
|
|
20
|
+
import { getLocalTimeZone } from "@internationalized/date"
|
|
21
|
+
import { useControllableState } from "@radix-ui/react-use-controllable-state"
|
|
22
|
+
import dayjs from "dayjs"
|
|
23
|
+
import { CalendarIcon } from "lucide-react"
|
|
24
|
+
import {
|
|
25
|
+
DatePicker as AriaDatePicker,
|
|
26
|
+
Dialog as AriaDialog,
|
|
27
|
+
DialogProps as AriaDialogProps,
|
|
28
|
+
PopoverProps as AriaPopoverProps,
|
|
29
|
+
composeRenderProps,
|
|
30
|
+
DatePickerStateContext,
|
|
31
|
+
I18nProvider,
|
|
32
|
+
} from "react-aria-components"
|
|
33
|
+
|
|
34
|
+
const DatePicker = AriaDatePicker
|
|
35
|
+
|
|
36
|
+
const DatePickerContent = ({
|
|
37
|
+
className,
|
|
38
|
+
popoverClassName,
|
|
39
|
+
...props
|
|
40
|
+
}: AriaDialogProps & { popoverClassName?: AriaPopoverProps["className"] }) => (
|
|
41
|
+
<Popover
|
|
42
|
+
animateOut={false}
|
|
43
|
+
className={composeRenderProps(popoverClassName, (className) =>
|
|
44
|
+
cn("w-auto p-1", className)
|
|
45
|
+
)}
|
|
46
|
+
placement="bottom right"
|
|
47
|
+
>
|
|
48
|
+
<AriaDialog
|
|
49
|
+
className={cn(
|
|
50
|
+
"flex w-full flex-col space-y-4 outline-none sm:flex-row sm:space-y-0 sm:space-x-4",
|
|
51
|
+
className
|
|
52
|
+
)}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
</Popover>
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
interface EggDatePickerProps {
|
|
59
|
+
/* The current value for the DatePicker (controlled). */
|
|
60
|
+
value?: Date
|
|
61
|
+
|
|
62
|
+
/* Callback fired when the date value changes. */
|
|
63
|
+
onChange?: (value?: Date) => void
|
|
64
|
+
|
|
65
|
+
/* The initial value for the DatePicker (uncontrolled). */
|
|
66
|
+
defaultValue?: Date
|
|
67
|
+
|
|
68
|
+
/* The minimum selectable date. */
|
|
69
|
+
minValue?: Date
|
|
70
|
+
|
|
71
|
+
/* The maximum selectable date. */
|
|
72
|
+
maxValue?: Date
|
|
73
|
+
|
|
74
|
+
/* Whether the DatePicker should be disabled. */
|
|
75
|
+
isDisabled?: boolean
|
|
76
|
+
|
|
77
|
+
/* Whether the DatePicker is in an invalid state. */
|
|
78
|
+
isInvalid?: boolean
|
|
79
|
+
|
|
80
|
+
/* The label to display for the DatePicker. */
|
|
81
|
+
label?: string
|
|
82
|
+
|
|
83
|
+
/* If true, an asterisk will be shown next to the label to mark it as required. */
|
|
84
|
+
withAsterisk?: boolean
|
|
85
|
+
|
|
86
|
+
/* The tooltip for the DatePicker. */
|
|
87
|
+
tooltip?: React.ReactNode
|
|
88
|
+
|
|
89
|
+
/* The level of date/time granularity ('day' for date only, 'minute' for date and time). Default is 'day'. */
|
|
90
|
+
granularity?: "day" | "minute"
|
|
91
|
+
|
|
92
|
+
/* The time format for time picker: 12 or 24 hour cycle. Default is 24. */
|
|
93
|
+
hourCycle?: 12 | 24
|
|
94
|
+
|
|
95
|
+
/* The format of the date. Default is 'dd/mm/yyyy'. */
|
|
96
|
+
format?: "dd/mm/yyyy" | "mm/dd/yyyy" | "yyyy-mm-dd"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function EggDatePicker({
|
|
100
|
+
value: controlledValue,
|
|
101
|
+
onChange: controlledOnChange,
|
|
102
|
+
defaultValue,
|
|
103
|
+
minValue,
|
|
104
|
+
maxValue,
|
|
105
|
+
isDisabled,
|
|
106
|
+
isInvalid,
|
|
107
|
+
label,
|
|
108
|
+
withAsterisk,
|
|
109
|
+
tooltip,
|
|
110
|
+
granularity = "day",
|
|
111
|
+
hourCycle = 24,
|
|
112
|
+
format = "dd/mm/yyyy",
|
|
113
|
+
}: EggDatePickerProps) {
|
|
114
|
+
const [value, onChange] = useControllableState<Date | undefined>({
|
|
115
|
+
prop: controlledValue,
|
|
116
|
+
defaultProp: defaultValue,
|
|
117
|
+
onChange: controlledOnChange,
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
let locale = "en-AU"
|
|
121
|
+
|
|
122
|
+
if (format === "dd/mm/yyyy") {
|
|
123
|
+
locale = "en-AU"
|
|
124
|
+
} else if (format === "mm/dd/yyyy") {
|
|
125
|
+
locale = "en-US"
|
|
126
|
+
} else if (format === "yyyy-mm-dd") {
|
|
127
|
+
locale = "en-CA"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<I18nProvider locale={locale}>
|
|
132
|
+
<DatePicker
|
|
133
|
+
aria-label="Date Picker"
|
|
134
|
+
className={"flex w-full flex-col gap-1.5"}
|
|
135
|
+
value={parse(value, granularity)}
|
|
136
|
+
onChange={(value) => onChange(value?.toDate(getLocalTimeZone()))}
|
|
137
|
+
minValue={minValue ? parse(minValue) : null}
|
|
138
|
+
maxValue={maxValue ? parse(maxValue) : null}
|
|
139
|
+
isDisabled={isDisabled}
|
|
140
|
+
granularity={granularity}
|
|
141
|
+
hourCycle={hourCycle}
|
|
142
|
+
isInvalid={isInvalid}
|
|
143
|
+
hideTimeZone
|
|
144
|
+
onBlur={() => {
|
|
145
|
+
if (value) {
|
|
146
|
+
onChange(toValidYear(value))
|
|
147
|
+
}
|
|
148
|
+
}}
|
|
149
|
+
>
|
|
150
|
+
{label && (
|
|
151
|
+
<Label withAsterisk={withAsterisk} tooltip={tooltip}>
|
|
152
|
+
{label}
|
|
153
|
+
</Label>
|
|
154
|
+
)}
|
|
155
|
+
<FieldGroup className="has-[button[aria-expanded=true]]:ring-accent-9 pr-2.5 has-[button[aria-expanded=true]]:ring-2">
|
|
156
|
+
<DateInput className="flex-1" variant="ghost" />
|
|
157
|
+
<Button
|
|
158
|
+
mode="icon"
|
|
159
|
+
size="sm"
|
|
160
|
+
variant="ghost"
|
|
161
|
+
intent="secondary"
|
|
162
|
+
tooltip="Select Date"
|
|
163
|
+
tooltipDelay={2000}
|
|
164
|
+
className="-mr-1 size-6 data-[focus-visible]:ring-offset-0"
|
|
165
|
+
isDisabled={isDisabled}
|
|
166
|
+
>
|
|
167
|
+
<CalendarIcon aria-hidden className="text-gray-11 size-4" />
|
|
168
|
+
</Button>
|
|
169
|
+
</FieldGroup>
|
|
170
|
+
<span className="text-gray-11 text-xs leading-none">{format}</span>
|
|
171
|
+
<DatePickerContent>
|
|
172
|
+
<div>
|
|
173
|
+
<Calendar>
|
|
174
|
+
<CalendarHeading />
|
|
175
|
+
<CalendarGrid>
|
|
176
|
+
<CalendarGridHeader>
|
|
177
|
+
{(day) => <CalendarHeaderCell>{day}</CalendarHeaderCell>}
|
|
178
|
+
</CalendarGridHeader>
|
|
179
|
+
<CalendarGridBody>
|
|
180
|
+
{(date) => <CalendarCell date={date} />}
|
|
181
|
+
</CalendarGridBody>
|
|
182
|
+
</CalendarGrid>
|
|
183
|
+
</Calendar>
|
|
184
|
+
<div className="flex justify-center py-1">
|
|
185
|
+
<TodayButton />
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
</DatePickerContent>
|
|
189
|
+
</DatePicker>
|
|
190
|
+
</I18nProvider>
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function TodayButton() {
|
|
195
|
+
const state = useContext(DatePickerStateContext)
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<Button
|
|
199
|
+
size="sm"
|
|
200
|
+
variant="ghost"
|
|
201
|
+
intent="primary"
|
|
202
|
+
onClick={() => {
|
|
203
|
+
state?.setValue(parse(dayjs().startOf("day").toDate()))
|
|
204
|
+
state?.close()
|
|
205
|
+
}}
|
|
206
|
+
>
|
|
207
|
+
Today
|
|
208
|
+
</Button>
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export { EggDatePicker as DatePicker }
|
|
213
|
+
export type { EggDatePickerProps as DatePickerProps }
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { MAX_YEAR_FROM_NOW, MIN_YEAR } from "@eggspot/ui/consts/config"
|
|
2
|
+
import {
|
|
3
|
+
parseAbsoluteToLocal,
|
|
4
|
+
toCalendarDate,
|
|
5
|
+
toCalendarDateTime,
|
|
6
|
+
} from "@internationalized/date"
|
|
7
|
+
import dayjs from "dayjs"
|
|
8
|
+
import { clamp } from "lodash"
|
|
9
|
+
|
|
10
|
+
export const parse = (date?: Date, granularity?: "day" | "minute") => {
|
|
11
|
+
if (
|
|
12
|
+
!date ||
|
|
13
|
+
!(date instanceof Date) ||
|
|
14
|
+
typeof date.toISOString !== "function"
|
|
15
|
+
) {
|
|
16
|
+
return null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const value = parseAbsoluteToLocal(date.toISOString())
|
|
20
|
+
|
|
21
|
+
if (granularity === "day") {
|
|
22
|
+
return toCalendarDate(value)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return toCalendarDateTime(value)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const toValidYear = (date: Date): Date => {
|
|
29
|
+
let newDate = dayjs(date)
|
|
30
|
+
const validYear = clamp(
|
|
31
|
+
newDate.year(),
|
|
32
|
+
MIN_YEAR,
|
|
33
|
+
dayjs().year() + MAX_YEAR_FROM_NOW
|
|
34
|
+
)
|
|
35
|
+
newDate = newDate.year(validYear)
|
|
36
|
+
|
|
37
|
+
return newDate.toDate()
|
|
38
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React from "react"
|
|
4
|
+
import { fieldGroupVariants } from "@eggspot/ui/components/Field"
|
|
5
|
+
import { cn } from "@eggspot/ui/lib/utils"
|
|
6
|
+
import { parseDate } from "@internationalized/date"
|
|
7
|
+
import { VariantProps } from "class-variance-authority"
|
|
8
|
+
import {
|
|
9
|
+
DateField as AriaDateField,
|
|
10
|
+
DateInput as AriaDateInput,
|
|
11
|
+
DateInputProps as AriaDateInputProps,
|
|
12
|
+
DateSegment as AriaDateSegment,
|
|
13
|
+
DateSegmentProps as AriaDateSegmentProps,
|
|
14
|
+
TimeField as AriaTimeField,
|
|
15
|
+
composeRenderProps,
|
|
16
|
+
} from "react-aria-components"
|
|
17
|
+
|
|
18
|
+
const DateField = AriaDateField
|
|
19
|
+
|
|
20
|
+
const TimeField = AriaTimeField
|
|
21
|
+
|
|
22
|
+
function DateSegment({ className, ...props }: AriaDateSegmentProps) {
|
|
23
|
+
return (
|
|
24
|
+
<AriaDateSegment
|
|
25
|
+
className={composeRenderProps(className, (className) =>
|
|
26
|
+
cn(
|
|
27
|
+
"type-literal:px-0 inline rounded-sm px-[1px] caret-transparent outline-0",
|
|
28
|
+
/* Placeholder */
|
|
29
|
+
"data-[placeholder]:text-gray-11",
|
|
30
|
+
/* Disabled */
|
|
31
|
+
"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-70",
|
|
32
|
+
/* Focused */
|
|
33
|
+
"data-[focused]:bg-accent-9 data-[focused]:text-accent-contrast data-[focused]:data-[placeholder]:text-accent-contrast",
|
|
34
|
+
className
|
|
35
|
+
)
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface DateInputProps
|
|
43
|
+
extends AriaDateInputProps,
|
|
44
|
+
VariantProps<typeof fieldGroupVariants> {}
|
|
45
|
+
|
|
46
|
+
function DateInput({
|
|
47
|
+
className,
|
|
48
|
+
variant,
|
|
49
|
+
...props
|
|
50
|
+
}: Omit<DateInputProps, "children">) {
|
|
51
|
+
return (
|
|
52
|
+
<AriaDateInput
|
|
53
|
+
className={composeRenderProps(className, (className) =>
|
|
54
|
+
cn(fieldGroupVariants({ variant }), "md:text-sm", className)
|
|
55
|
+
)}
|
|
56
|
+
{...props}
|
|
57
|
+
>
|
|
58
|
+
{(segment) => <DateSegment segment={segment} />}
|
|
59
|
+
</AriaDateInput>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Accepts values in the format YYYY-MM-DD */
|
|
64
|
+
interface EGDateFieldProps {
|
|
65
|
+
value?: string
|
|
66
|
+
onChange?: (value: string) => void
|
|
67
|
+
defaultValue?: string
|
|
68
|
+
className?: string
|
|
69
|
+
variant?: "default" | "unstyled"
|
|
70
|
+
minValue?: string
|
|
71
|
+
maxValue?: string
|
|
72
|
+
isDisabled?: boolean
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function EGDateField({
|
|
76
|
+
value: controlledValue,
|
|
77
|
+
onChange: controlledOnChange,
|
|
78
|
+
defaultValue,
|
|
79
|
+
minValue,
|
|
80
|
+
maxValue,
|
|
81
|
+
className,
|
|
82
|
+
isDisabled,
|
|
83
|
+
...props
|
|
84
|
+
}: EGDateFieldProps) {
|
|
85
|
+
const [uncontrolledValue, setUncontrolledValue] = React.useState<
|
|
86
|
+
string | undefined
|
|
87
|
+
>(defaultValue)
|
|
88
|
+
|
|
89
|
+
const value = controlledValue ?? uncontrolledValue
|
|
90
|
+
const onChange = controlledOnChange ?? setUncontrolledValue
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<DateField
|
|
94
|
+
aria-label="Date Field"
|
|
95
|
+
className={cn("w-full", className)}
|
|
96
|
+
value={value ? parseDate(value) : null}
|
|
97
|
+
onChange={(value) => onChange(value?.toString() ?? "")}
|
|
98
|
+
minValue={minValue ? parseDate(minValue) : null}
|
|
99
|
+
maxValue={maxValue ? parseDate(maxValue) : null}
|
|
100
|
+
isDisabled={isDisabled}
|
|
101
|
+
{...props}
|
|
102
|
+
>
|
|
103
|
+
<DateInput />
|
|
104
|
+
</DateField>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export { DateField, DateInput, DateSegment, EGDateField, TimeField }
|
|
109
|
+
export type { DateInputProps, EGDateFieldProps }
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React from "react"
|
|
4
|
+
import { Button } from "@eggspot/ui/components/Button"
|
|
5
|
+
import { cn } from "@eggspot/ui/lib/utils"
|
|
6
|
+
import { X } from "lucide-react"
|
|
7
|
+
import {
|
|
8
|
+
Dialog as AriaDialog,
|
|
9
|
+
DialogProps as AriaDialogProps,
|
|
10
|
+
DialogTrigger as AriaDialogTrigger,
|
|
11
|
+
Heading as AriaHeading,
|
|
12
|
+
HeadingProps as AriaHeadingProps,
|
|
13
|
+
Modal as AriaModal,
|
|
14
|
+
ModalOverlay as AriaModalOverlay,
|
|
15
|
+
ModalOverlayProps as AriaModalOverlayProps,
|
|
16
|
+
composeRenderProps,
|
|
17
|
+
} from "react-aria-components"
|
|
18
|
+
|
|
19
|
+
const Dialog = AriaDialog
|
|
20
|
+
|
|
21
|
+
const DialogTrigger = AriaDialogTrigger
|
|
22
|
+
|
|
23
|
+
const DialogOverlay = ({
|
|
24
|
+
className,
|
|
25
|
+
isDismissable = true,
|
|
26
|
+
...props
|
|
27
|
+
}: AriaModalOverlayProps) => (
|
|
28
|
+
<AriaModalOverlay
|
|
29
|
+
isDismissable={isDismissable}
|
|
30
|
+
className={composeRenderProps(className, (className) =>
|
|
31
|
+
cn(
|
|
32
|
+
"fixed inset-0 z-50 h-[var(--page-height)] bg-black/50",
|
|
33
|
+
/* Exiting */
|
|
34
|
+
"data-[exiting]:animate-out data-[exiting]:fade-out-0 max-md:data-[exiting]:duration-300",
|
|
35
|
+
/* Entering */
|
|
36
|
+
"data-[entering]:animate-in data-[entering]:fade-in-0 max-md:data-[entering]:duration-300",
|
|
37
|
+
className
|
|
38
|
+
)
|
|
39
|
+
)}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
interface DialogContentProps
|
|
45
|
+
extends Omit<React.ComponentProps<typeof AriaModal>, "children"> {
|
|
46
|
+
children?: AriaDialogProps["children"]
|
|
47
|
+
role?: AriaDialogProps["role"]
|
|
48
|
+
/** Whether to show the close button */
|
|
49
|
+
closeButton?: boolean
|
|
50
|
+
|
|
51
|
+
/** Whether to dismiss the dialog when the user clicks outside of it */
|
|
52
|
+
isDismissable?: boolean
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const DialogContent = ({
|
|
56
|
+
className,
|
|
57
|
+
children,
|
|
58
|
+
role,
|
|
59
|
+
closeButton = true,
|
|
60
|
+
isDismissable = true,
|
|
61
|
+
...props
|
|
62
|
+
}: DialogContentProps) => (
|
|
63
|
+
<DialogOverlay isDismissable={isDismissable}>
|
|
64
|
+
<AriaModal
|
|
65
|
+
className={composeRenderProps(className, (className) =>
|
|
66
|
+
cn(
|
|
67
|
+
"bg-gray-1 fixed left-[50vw] z-50 max-h-[var(--visual-viewport-height)] w-full max-w-[calc(100vw-40px)] -translate-x-1/2 -translate-y-1/2 rounded-xl p-5 shadow-2xl md:max-w-lg dark:border",
|
|
68
|
+
"data-[entering]:animate-in data-[exiting]:animate-out data-[entering]:fade-in-0 data-[exiting]:fade-out-0",
|
|
69
|
+
"md:data-[entering]:zoom-in-97 md:data-[exiting]:zoom-out-97",
|
|
70
|
+
"max-md:data-[entering]:slide-in-from-bottom-5 max-md:data-[exiting]:slide-out-to-bottom-5 max-md:data-[exiting]:duration-300",
|
|
71
|
+
className
|
|
72
|
+
)
|
|
73
|
+
)}
|
|
74
|
+
style={{
|
|
75
|
+
// This is an exceptional case where we can't use Tailwind, so we have to apply the style directly
|
|
76
|
+
top: "calc(var(--visual-viewport-height) / 2)",
|
|
77
|
+
}}
|
|
78
|
+
{...props}
|
|
79
|
+
>
|
|
80
|
+
<AriaDialog
|
|
81
|
+
role={role}
|
|
82
|
+
className={cn("grid h-full gap-4", "h-full outline-none")}
|
|
83
|
+
>
|
|
84
|
+
{composeRenderProps(children, (children, renderProps) => (
|
|
85
|
+
<>
|
|
86
|
+
{children}
|
|
87
|
+
{closeButton && (
|
|
88
|
+
<Button
|
|
89
|
+
onClick={renderProps.close}
|
|
90
|
+
mode="icon"
|
|
91
|
+
variant="ghost"
|
|
92
|
+
tooltip="Close"
|
|
93
|
+
tooltipDelay={2000}
|
|
94
|
+
className="absolute top-2.5 right-2.5"
|
|
95
|
+
>
|
|
96
|
+
<X className="text-gray-11 size-4!" />
|
|
97
|
+
<span className="sr-only">Close</span>
|
|
98
|
+
</Button>
|
|
99
|
+
)}
|
|
100
|
+
</>
|
|
101
|
+
))}
|
|
102
|
+
</AriaDialog>
|
|
103
|
+
</AriaModal>
|
|
104
|
+
</DialogOverlay>
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
const DialogHeader = ({
|
|
108
|
+
className,
|
|
109
|
+
...props
|
|
110
|
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
111
|
+
<div
|
|
112
|
+
className={cn(
|
|
113
|
+
"flex flex-col space-y-2 text-center sm:text-left",
|
|
114
|
+
className
|
|
115
|
+
)}
|
|
116
|
+
{...props}
|
|
117
|
+
/>
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
const DialogFooter = ({
|
|
121
|
+
className,
|
|
122
|
+
...props
|
|
123
|
+
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
124
|
+
<div
|
|
125
|
+
className={cn(
|
|
126
|
+
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
|
127
|
+
className
|
|
128
|
+
)}
|
|
129
|
+
{...props}
|
|
130
|
+
/>
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
const DialogTitle = ({ className, ...props }: AriaHeadingProps) => (
|
|
134
|
+
<AriaHeading
|
|
135
|
+
slot="title"
|
|
136
|
+
className={cn(
|
|
137
|
+
"text-lg leading-none font-semibold tracking-tight",
|
|
138
|
+
className
|
|
139
|
+
)}
|
|
140
|
+
{...props}
|
|
141
|
+
/>
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
const DialogDescription = ({
|
|
145
|
+
className,
|
|
146
|
+
...props
|
|
147
|
+
}: React.HTMLAttributes<HTMLParagraphElement>) => (
|
|
148
|
+
<p
|
|
149
|
+
className={cn(
|
|
150
|
+
"text-gray-11 flex flex-col space-y-1.5 text-center text-sm sm:text-left",
|
|
151
|
+
className
|
|
152
|
+
)}
|
|
153
|
+
{...props}
|
|
154
|
+
/>
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
export {
|
|
158
|
+
Dialog,
|
|
159
|
+
DialogContent,
|
|
160
|
+
DialogDescription,
|
|
161
|
+
DialogFooter,
|
|
162
|
+
DialogHeader,
|
|
163
|
+
DialogOverlay,
|
|
164
|
+
DialogTitle,
|
|
165
|
+
DialogTrigger,
|
|
166
|
+
}
|
|
167
|
+
export type { DialogContentProps }
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@eggspot/ui/lib/utils"
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
5
|
+
import {
|
|
6
|
+
Group as AriaGroup,
|
|
7
|
+
GroupProps as AriaGroupProps,
|
|
8
|
+
composeRenderProps,
|
|
9
|
+
} from "react-aria-components"
|
|
10
|
+
|
|
11
|
+
const fieldGroupVariants = cva("", {
|
|
12
|
+
variants: {
|
|
13
|
+
variant: {
|
|
14
|
+
default: [
|
|
15
|
+
"bg-gray-2 relative flex h-8 w-full items-center overflow-hidden rounded-md px-3 py-2 transition-all md:text-sm",
|
|
16
|
+
"ring-gray-7 ring",
|
|
17
|
+
/* SVGs */
|
|
18
|
+
"[&_svg]:text-gray-11 [&_svg]:pointer-events-none [&_svg]:size-[14px] [&_svg]:shrink-0 [&_svg]:stroke-2",
|
|
19
|
+
/* Focus Within */
|
|
20
|
+
"data-[focus-within]:ring-accent-9 data-[focus-within]:ring-2",
|
|
21
|
+
/* Disabled */
|
|
22
|
+
"data-[disabled]:opacity-60",
|
|
23
|
+
/* Invalid */
|
|
24
|
+
"data-[invalid]:ring-error-9 aria-invalid:ring-error-9",
|
|
25
|
+
],
|
|
26
|
+
ghost: "",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
defaultVariants: {
|
|
30
|
+
variant: "default",
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
interface GroupProps
|
|
35
|
+
extends AriaGroupProps,
|
|
36
|
+
VariantProps<typeof fieldGroupVariants> {}
|
|
37
|
+
|
|
38
|
+
function FieldGroup({ className, variant, ...props }: GroupProps) {
|
|
39
|
+
return (
|
|
40
|
+
<AriaGroup
|
|
41
|
+
className={composeRenderProps(className, (className) =>
|
|
42
|
+
cn(fieldGroupVariants({ variant }), className)
|
|
43
|
+
)}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export { FieldGroup, fieldGroupVariants }
|