@glyphui/ui 0.1.1 → 0.3.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/README.md +1 -1
- package/dist/chart.d.ts +202 -0
- package/dist/chart.js +457 -0
- package/dist/data-table.js +3 -2
- package/dist/{field-DXZD0l8w.js → field-CWZ_FdkD.js} +2 -1
- package/dist/{field-context-B8cYheXz.js → field-context-anGPYXbW.js} +89 -143
- package/dist/form.js +3 -2
- package/dist/index.d.ts +4960 -266
- package/dist/index.js +5336 -146
- package/dist/{states-BxPjCUxz.js → states-DR-OoDT-.js} +2 -1
- package/dist/tv-DBAGlgXJ.js +58 -0
- package/package.json +19 -2
- package/src/components/accordion/accordion.styles.ts +41 -0
- package/src/components/accordion/accordion.tsx +86 -0
- package/src/components/accordion/index.ts +8 -0
- package/src/components/alert/alert-icons.tsx +66 -0
- package/src/components/alert/alert.styles.ts +57 -0
- package/src/components/alert/alert.tsx +86 -0
- package/src/components/alert/index.ts +2 -0
- package/src/components/alert-dialog/alert-dialog.styles.ts +24 -0
- package/src/components/alert-dialog/alert-dialog.tsx +118 -0
- package/src/components/alert-dialog/confirm.tsx +111 -0
- package/src/components/alert-dialog/index.ts +15 -0
- package/src/components/aspect-ratio/aspect-ratio.tsx +40 -0
- package/src/components/aspect-ratio/index.ts +1 -0
- package/src/components/badge/badge.styles.ts +1 -1
- package/src/components/banner/banner.styles.ts +40 -0
- package/src/components/banner/banner.tsx +101 -0
- package/src/components/banner/index.ts +2 -0
- package/src/components/blockquote/blockquote.tsx +59 -0
- package/src/components/blockquote/index.ts +1 -0
- package/src/components/breadcrumb/breadcrumb.styles.ts +20 -0
- package/src/components/breadcrumb/breadcrumb.tsx +135 -0
- package/src/components/breadcrumb/index.ts +12 -0
- package/src/components/button/button.tsx +11 -8
- package/src/components/calendar/calendar.styles.ts +50 -0
- package/src/components/calendar/calendar.tsx +84 -0
- package/src/components/calendar/index.ts +3 -0
- package/src/components/carousel/carousel.styles.ts +43 -0
- package/src/components/carousel/carousel.tsx +278 -0
- package/src/components/carousel/index.ts +13 -0
- package/src/components/code/code.styles.ts +33 -0
- package/src/components/code/code.tsx +96 -0
- package/src/components/code/index.ts +2 -0
- package/src/components/collapsible/collapsible.tsx +66 -0
- package/src/components/collapsible/index.ts +6 -0
- package/src/components/combobox/combobox.styles.ts +50 -0
- package/src/components/combobox/combobox.tsx +203 -0
- package/src/components/combobox/index.ts +2 -0
- package/src/components/command-menu/command-menu.styles.ts +41 -0
- package/src/components/command-menu/command-menu.tsx +217 -0
- package/src/components/command-menu/index.ts +14 -0
- package/src/components/context-menu/context-menu.styles.ts +19 -0
- package/src/components/context-menu/context-menu.tsx +226 -0
- package/src/components/context-menu/index.ts +19 -0
- package/src/components/copy-button/copy-button.tsx +117 -0
- package/src/components/copy-button/index.ts +1 -0
- package/src/components/date-picker/date-picker.styles.ts +42 -0
- package/src/components/date-picker/date-picker.tsx +322 -0
- package/src/components/date-picker/index.ts +8 -0
- package/src/components/description-list/description-list.styles.ts +31 -0
- package/src/components/description-list/description-list.tsx +56 -0
- package/src/components/description-list/index.ts +7 -0
- package/src/components/dialog/close-icon.tsx +22 -0
- package/src/components/dialog/dialog.styles.ts +41 -0
- package/src/components/dialog/dialog.tsx +111 -0
- package/src/components/dialog/index.ts +13 -0
- package/src/components/dropdown-menu/dropdown-menu.styles.ts +45 -0
- package/src/components/dropdown-menu/dropdown-menu.tsx +230 -0
- package/src/components/dropdown-menu/index.ts +19 -0
- package/src/components/file-upload/file-upload.styles.ts +43 -0
- package/src/components/file-upload/file-upload.tsx +343 -0
- package/src/components/file-upload/index.ts +10 -0
- package/src/components/grid/grid.tsx +144 -0
- package/src/components/grid/index.ts +1 -0
- package/src/components/hover-card/hover-card.tsx +41 -0
- package/src/components/hover-card/index.ts +1 -0
- package/src/components/input-otp/index.ts +10 -0
- package/src/components/input-otp/input-otp.styles.ts +38 -0
- package/src/components/input-otp/input-otp.tsx +116 -0
- package/src/components/link/index.ts +2 -0
- package/src/components/link/link.styles.ts +39 -0
- package/src/components/link/link.tsx +62 -0
- package/src/components/modal/index.ts +2 -0
- package/src/components/modal/modal.styles.ts +18 -0
- package/src/components/modal/modal.tsx +86 -0
- package/src/components/navigation-menu/index.ts +14 -0
- package/src/components/navigation-menu/navigation-menu.styles.ts +48 -0
- package/src/components/navigation-menu/navigation-menu.tsx +198 -0
- package/src/components/number-input/index.ts +2 -0
- package/src/components/number-input/number-input.styles.ts +41 -0
- package/src/components/number-input/number-input.tsx +211 -0
- package/src/components/pagination/index.ts +3 -0
- package/src/components/pagination/pagination-range.ts +35 -0
- package/src/components/pagination/pagination.styles.ts +41 -0
- package/src/components/pagination/pagination.tsx +130 -0
- package/src/components/password-input/index.ts +7 -0
- package/src/components/password-input/password-input.styles.ts +30 -0
- package/src/components/password-input/password-input.tsx +127 -0
- package/src/components/popover/index.ts +9 -0
- package/src/components/popover/popover.tsx +66 -0
- package/src/components/progress/index.ts +7 -0
- package/src/components/progress/progress.styles.ts +68 -0
- package/src/components/progress/progress.tsx +158 -0
- package/src/components/prose/index.ts +1 -0
- package/src/components/prose/prose.tsx +82 -0
- package/src/components/radio-group/index.ts +7 -0
- package/src/components/radio-group/radio-group.styles.ts +64 -0
- package/src/components/radio-group/radio-group.tsx +128 -0
- package/src/components/resizable/index.ts +9 -0
- package/src/components/resizable/resizable.styles.ts +28 -0
- package/src/components/resizable/resizable.tsx +69 -0
- package/src/components/scroll-area/index.ts +1 -0
- package/src/components/scroll-area/scroll-area.tsx +68 -0
- package/src/components/sheet/index.ts +13 -0
- package/src/components/sheet/sheet.styles.ts +60 -0
- package/src/components/sheet/sheet.tsx +108 -0
- package/src/components/slider/index.ts +2 -0
- package/src/components/slider/slider.styles.ts +32 -0
- package/src/components/slider/slider.tsx +131 -0
- package/src/components/stat-card/index.ts +2 -0
- package/src/components/stat-card/stat-card.styles.ts +24 -0
- package/src/components/stat-card/stat-card.tsx +108 -0
- package/src/components/stepper/index.ts +2 -0
- package/src/components/stepper/stepper.styles.ts +74 -0
- package/src/components/stepper/stepper.tsx +138 -0
- package/src/components/tag-input/index.ts +2 -0
- package/src/components/tag-input/tag-input.styles.ts +39 -0
- package/src/components/tag-input/tag-input.tsx +198 -0
- package/src/components/timeline/index.ts +8 -0
- package/src/components/timeline/timeline.styles.ts +56 -0
- package/src/components/timeline/timeline.tsx +91 -0
- package/src/components/toast/index.ts +1 -0
- package/src/components/toast/toast.tsx +91 -0
- package/src/components/toggle/index.ts +9 -0
- package/src/components/toggle/toggle.styles.ts +39 -0
- package/src/components/toggle/toggle.tsx +61 -0
- package/src/components/tree-view/index.ts +2 -0
- package/src/components/tree-view/tree-view.styles.ts +42 -0
- package/src/components/tree-view/tree-view.tsx +274 -0
- package/src/components/visually-hidden/index.ts +1 -0
- package/src/components/visually-hidden/visually-hidden.tsx +10 -0
- package/src/index.ts +45 -0
- package/src/patterns/chart/chart-parts.tsx +171 -0
- package/src/patterns/chart/chart.styles.ts +35 -0
- package/src/patterns/chart/chart.tsx +369 -0
- package/src/patterns/chart/index.ts +13 -0
- package/src/provider/glyph-provider.tsx +16 -2
- package/styles.css +16 -0
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { format as formatDate, isSameDay } from 'date-fns'
|
|
2
|
+
import { type ReactNode, useState } from 'react'
|
|
3
|
+
import type { DateRange, Matcher } from 'react-day-picker'
|
|
4
|
+
import { useComponentConfig } from '../../hooks/use-component-config'
|
|
5
|
+
import { useControllableState } from '../../hooks/use-controllable-state'
|
|
6
|
+
import { Calendar } from '../calendar/calendar'
|
|
7
|
+
import { useFieldControl } from '../field/field-context'
|
|
8
|
+
import { Popover, PopoverContent, PopoverTrigger } from '../popover'
|
|
9
|
+
import { type DatePickerVariants, datePickerStyles } from './date-picker.styles'
|
|
10
|
+
|
|
11
|
+
function CalendarIcon({ className }: { className?: string }) {
|
|
12
|
+
return (
|
|
13
|
+
<svg
|
|
14
|
+
viewBox="0 0 24 24"
|
|
15
|
+
fill="none"
|
|
16
|
+
stroke="currentColor"
|
|
17
|
+
strokeWidth="2"
|
|
18
|
+
strokeLinecap="round"
|
|
19
|
+
strokeLinejoin="round"
|
|
20
|
+
aria-hidden="true"
|
|
21
|
+
className={className}
|
|
22
|
+
>
|
|
23
|
+
<rect x="3" y="4" width="18" height="18" rx="2" />
|
|
24
|
+
<path d="M16 2v4M8 2v4M3 10h18" />
|
|
25
|
+
</svg>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function ClearIcon() {
|
|
30
|
+
return (
|
|
31
|
+
<svg
|
|
32
|
+
viewBox="0 0 24 24"
|
|
33
|
+
fill="none"
|
|
34
|
+
stroke="currentColor"
|
|
35
|
+
strokeWidth="2"
|
|
36
|
+
strokeLinecap="round"
|
|
37
|
+
aria-hidden="true"
|
|
38
|
+
>
|
|
39
|
+
<path d="M18 6 6 18M6 6l12 12" />
|
|
40
|
+
</svg>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface SharedProps extends DatePickerVariants {
|
|
45
|
+
placeholder?: ReactNode
|
|
46
|
+
/** date-fns format string for the trigger. Default `"MMM d, yyyy"`. */
|
|
47
|
+
format?: string
|
|
48
|
+
/** Earliest selectable date. */
|
|
49
|
+
minDate?: Date
|
|
50
|
+
/** Latest selectable date. */
|
|
51
|
+
maxDate?: Date
|
|
52
|
+
/** Extra disabled dates, in react-day-picker matcher form. */
|
|
53
|
+
disabledDates?: Matcher | Matcher[]
|
|
54
|
+
/** Show a button that clears the value. */
|
|
55
|
+
clearable?: boolean
|
|
56
|
+
disabled?: boolean
|
|
57
|
+
required?: boolean
|
|
58
|
+
/** Submits the ISO date (yyyy-MM-dd) with a native form. */
|
|
59
|
+
name?: string
|
|
60
|
+
id?: string
|
|
61
|
+
className?: string
|
|
62
|
+
contentClassName?: string
|
|
63
|
+
'aria-label'?: string
|
|
64
|
+
'aria-describedby'?: string
|
|
65
|
+
'aria-invalid'?: boolean
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function disabledMatchers({ minDate, maxDate, disabledDates }: SharedProps): Matcher[] {
|
|
69
|
+
const list: Matcher[] = []
|
|
70
|
+
if (minDate) list.push({ before: minDate })
|
|
71
|
+
if (maxDate) list.push({ after: maxDate })
|
|
72
|
+
if (disabledDates) list.push(...(Array.isArray(disabledDates) ? disabledDates : [disabledDates]))
|
|
73
|
+
return list
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const iso = (date: Date) => formatDate(date, 'yyyy-MM-dd')
|
|
77
|
+
|
|
78
|
+
export interface DatePickerProps extends SharedProps {
|
|
79
|
+
value?: Date | undefined
|
|
80
|
+
defaultValue?: Date | undefined
|
|
81
|
+
onValueChange?: (date: Date | undefined) => void
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** A date field that opens a calendar. Works with Field and with `Form` (`value` + `onValueChange`). */
|
|
85
|
+
export function DatePicker(inProps: DatePickerProps) {
|
|
86
|
+
const [props, slots] = useComponentConfig('DatePicker', useFieldControl(inProps))
|
|
87
|
+
const {
|
|
88
|
+
value: valueProp,
|
|
89
|
+
defaultValue,
|
|
90
|
+
onValueChange,
|
|
91
|
+
placeholder = 'Pick a date',
|
|
92
|
+
format = 'MMM d, yyyy',
|
|
93
|
+
clearable = false,
|
|
94
|
+
size,
|
|
95
|
+
disabled,
|
|
96
|
+
name,
|
|
97
|
+
id,
|
|
98
|
+
className,
|
|
99
|
+
contentClassName,
|
|
100
|
+
'aria-label': ariaLabel,
|
|
101
|
+
'aria-describedby': ariaDescribedBy,
|
|
102
|
+
'aria-invalid': ariaInvalid,
|
|
103
|
+
} = props
|
|
104
|
+
const [open, setOpen] = useState(false)
|
|
105
|
+
const [value, setValue] = useControllableState<Date | undefined>({
|
|
106
|
+
value: valueProp,
|
|
107
|
+
defaultValue,
|
|
108
|
+
onChange: onValueChange,
|
|
109
|
+
})
|
|
110
|
+
// Controlled `undefined` means "no date", not "uncontrolled".
|
|
111
|
+
const current = 'value' in inProps ? valueProp : value
|
|
112
|
+
const s = datePickerStyles({ size })
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
116
|
+
<PopoverTrigger asChild>
|
|
117
|
+
<button
|
|
118
|
+
type="button"
|
|
119
|
+
id={id}
|
|
120
|
+
data-slot="date-picker"
|
|
121
|
+
disabled={disabled}
|
|
122
|
+
aria-label={ariaLabel}
|
|
123
|
+
aria-describedby={ariaDescribedBy}
|
|
124
|
+
aria-invalid={ariaInvalid}
|
|
125
|
+
className={s.trigger({ class: [slots.trigger, className] })}
|
|
126
|
+
>
|
|
127
|
+
<CalendarIcon className={s.icon({ class: slots.icon })} />
|
|
128
|
+
{current ? (
|
|
129
|
+
<span className={s.value({ class: slots.value })}>{formatDate(current, format)}</span>
|
|
130
|
+
) : (
|
|
131
|
+
<span className={s.placeholder({ class: slots.placeholder })}>{placeholder}</span>
|
|
132
|
+
)}
|
|
133
|
+
{clearable && current && !disabled && (
|
|
134
|
+
// biome-ignore lint/a11y/useSemanticElements: nested inside the trigger button, so it can't be a <button>
|
|
135
|
+
<span
|
|
136
|
+
role="button"
|
|
137
|
+
tabIndex={0}
|
|
138
|
+
aria-label="Clear date"
|
|
139
|
+
className={s.clear({ class: slots.clear })}
|
|
140
|
+
onPointerDown={(event) => event.stopPropagation()}
|
|
141
|
+
onClick={(event) => {
|
|
142
|
+
event.stopPropagation()
|
|
143
|
+
setValue(undefined)
|
|
144
|
+
}}
|
|
145
|
+
onKeyDown={(event) => {
|
|
146
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
147
|
+
event.preventDefault()
|
|
148
|
+
event.stopPropagation()
|
|
149
|
+
setValue(undefined)
|
|
150
|
+
}
|
|
151
|
+
}}
|
|
152
|
+
>
|
|
153
|
+
<ClearIcon />
|
|
154
|
+
</span>
|
|
155
|
+
)}
|
|
156
|
+
</button>
|
|
157
|
+
</PopoverTrigger>
|
|
158
|
+
<PopoverContent
|
|
159
|
+
align="start"
|
|
160
|
+
className={s.content({ class: [slots.content, contentClassName] })}
|
|
161
|
+
>
|
|
162
|
+
<Calendar
|
|
163
|
+
mode="single"
|
|
164
|
+
selected={current}
|
|
165
|
+
defaultMonth={current}
|
|
166
|
+
disabled={disabledMatchers(props)}
|
|
167
|
+
onSelect={(date) => {
|
|
168
|
+
setValue(date)
|
|
169
|
+
if (date) setOpen(false)
|
|
170
|
+
}}
|
|
171
|
+
autoFocus
|
|
172
|
+
/>
|
|
173
|
+
</PopoverContent>
|
|
174
|
+
{name && <input type="hidden" name={name} value={current ? iso(current) : ''} />}
|
|
175
|
+
</Popover>
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface DateRangePreset {
|
|
180
|
+
label: string
|
|
181
|
+
/** Called when chosen, so relative ranges ("Last 7 days") are computed at that moment. */
|
|
182
|
+
range: () => DateRange
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export interface DateRangePickerProps extends SharedProps {
|
|
186
|
+
value?: DateRange | undefined
|
|
187
|
+
defaultValue?: DateRange | undefined
|
|
188
|
+
onValueChange?: (range: DateRange | undefined) => void
|
|
189
|
+
/** Quick picks shown beside the calendar. */
|
|
190
|
+
presets?: readonly DateRangePreset[]
|
|
191
|
+
/** Months shown side by side. Default 2. */
|
|
192
|
+
numberOfMonths?: number
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function formatRange(range: DateRange, format: string): string {
|
|
196
|
+
if (!range.from) return ''
|
|
197
|
+
if (!range.to || isSameDay(range.from, range.to)) return formatDate(range.from, format)
|
|
198
|
+
return `${formatDate(range.from, format)} – ${formatDate(range.to, format)}`
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** A date-range field with a two-month calendar and optional presets. */
|
|
202
|
+
export function DateRangePicker(inProps: DateRangePickerProps) {
|
|
203
|
+
const [props, slots] = useComponentConfig('DatePicker', useFieldControl(inProps))
|
|
204
|
+
const {
|
|
205
|
+
value: valueProp,
|
|
206
|
+
defaultValue,
|
|
207
|
+
onValueChange,
|
|
208
|
+
presets,
|
|
209
|
+
numberOfMonths = 2,
|
|
210
|
+
placeholder = 'Pick a date range',
|
|
211
|
+
format = 'MMM d, yyyy',
|
|
212
|
+
clearable = false,
|
|
213
|
+
size,
|
|
214
|
+
disabled,
|
|
215
|
+
name,
|
|
216
|
+
id,
|
|
217
|
+
className,
|
|
218
|
+
contentClassName,
|
|
219
|
+
'aria-label': ariaLabel,
|
|
220
|
+
'aria-describedby': ariaDescribedBy,
|
|
221
|
+
'aria-invalid': ariaInvalid,
|
|
222
|
+
} = props
|
|
223
|
+
const [open, setOpen] = useState(false)
|
|
224
|
+
const [value, setValue] = useControllableState<DateRange | undefined>({
|
|
225
|
+
value: valueProp,
|
|
226
|
+
defaultValue,
|
|
227
|
+
onChange: onValueChange,
|
|
228
|
+
})
|
|
229
|
+
const current = 'value' in inProps ? valueProp : value
|
|
230
|
+
const s = datePickerStyles({ size })
|
|
231
|
+
const label = current ? formatRange(current, format) : ''
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
235
|
+
<PopoverTrigger asChild>
|
|
236
|
+
<button
|
|
237
|
+
type="button"
|
|
238
|
+
id={id}
|
|
239
|
+
data-slot="date-range-picker"
|
|
240
|
+
disabled={disabled}
|
|
241
|
+
aria-label={ariaLabel}
|
|
242
|
+
aria-describedby={ariaDescribedBy}
|
|
243
|
+
aria-invalid={ariaInvalid}
|
|
244
|
+
className={s.trigger({ class: [slots.trigger, className] })}
|
|
245
|
+
>
|
|
246
|
+
<CalendarIcon className={s.icon({ class: slots.icon })} />
|
|
247
|
+
{label ? (
|
|
248
|
+
<span className={s.value({ class: slots.value })}>{label}</span>
|
|
249
|
+
) : (
|
|
250
|
+
<span className={s.placeholder({ class: slots.placeholder })}>{placeholder}</span>
|
|
251
|
+
)}
|
|
252
|
+
{clearable && current?.from && !disabled && (
|
|
253
|
+
// biome-ignore lint/a11y/useSemanticElements: nested inside the trigger button, so it can't be a <button>
|
|
254
|
+
<span
|
|
255
|
+
role="button"
|
|
256
|
+
tabIndex={0}
|
|
257
|
+
aria-label="Clear dates"
|
|
258
|
+
className={s.clear({ class: slots.clear })}
|
|
259
|
+
onPointerDown={(event) => event.stopPropagation()}
|
|
260
|
+
onClick={(event) => {
|
|
261
|
+
event.stopPropagation()
|
|
262
|
+
setValue(undefined)
|
|
263
|
+
}}
|
|
264
|
+
onKeyDown={(event) => {
|
|
265
|
+
if (event.key === 'Enter' || event.key === ' ') {
|
|
266
|
+
event.preventDefault()
|
|
267
|
+
event.stopPropagation()
|
|
268
|
+
setValue(undefined)
|
|
269
|
+
}
|
|
270
|
+
}}
|
|
271
|
+
>
|
|
272
|
+
<ClearIcon />
|
|
273
|
+
</span>
|
|
274
|
+
)}
|
|
275
|
+
</button>
|
|
276
|
+
</PopoverTrigger>
|
|
277
|
+
<PopoverContent
|
|
278
|
+
align="start"
|
|
279
|
+
className={s.content({ class: [slots.content, contentClassName] })}
|
|
280
|
+
>
|
|
281
|
+
<div className={s.layout({ class: slots.layout })}>
|
|
282
|
+
{presets && presets.length > 0 && (
|
|
283
|
+
<div className={s.presets({ class: slots.presets })}>
|
|
284
|
+
{presets.map((preset) => (
|
|
285
|
+
<button
|
|
286
|
+
key={preset.label}
|
|
287
|
+
type="button"
|
|
288
|
+
className={s.preset({ class: slots.preset })}
|
|
289
|
+
onClick={() => {
|
|
290
|
+
setValue(preset.range())
|
|
291
|
+
setOpen(false)
|
|
292
|
+
}}
|
|
293
|
+
>
|
|
294
|
+
{preset.label}
|
|
295
|
+
</button>
|
|
296
|
+
))}
|
|
297
|
+
</div>
|
|
298
|
+
)}
|
|
299
|
+
<Calendar
|
|
300
|
+
mode="range"
|
|
301
|
+
selected={current}
|
|
302
|
+
defaultMonth={current?.from}
|
|
303
|
+
numberOfMonths={numberOfMonths}
|
|
304
|
+
disabled={disabledMatchers(props)}
|
|
305
|
+
onSelect={(range) => setValue(range)}
|
|
306
|
+
autoFocus
|
|
307
|
+
/>
|
|
308
|
+
</div>
|
|
309
|
+
</PopoverContent>
|
|
310
|
+
{name && (
|
|
311
|
+
<>
|
|
312
|
+
<input
|
|
313
|
+
type="hidden"
|
|
314
|
+
name={`${name}.from`}
|
|
315
|
+
value={current?.from ? iso(current.from) : ''}
|
|
316
|
+
/>
|
|
317
|
+
<input type="hidden" name={`${name}.to`} value={current?.to ? iso(current.to) : ''} />
|
|
318
|
+
</>
|
|
319
|
+
)}
|
|
320
|
+
</Popover>
|
|
321
|
+
)
|
|
322
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { StylesEntry } from '../../utils/registry'
|
|
2
|
+
import { tv, type VariantProps } from '../../utils/tv'
|
|
3
|
+
|
|
4
|
+
export const descriptionListStyles = tv({
|
|
5
|
+
slots: {
|
|
6
|
+
root: 'grid w-full',
|
|
7
|
+
item: 'grid min-w-0 gap-1',
|
|
8
|
+
term: 'text-small text-muted-foreground',
|
|
9
|
+
details: 'min-w-0 break-words text-small text-foreground',
|
|
10
|
+
},
|
|
11
|
+
variants: {
|
|
12
|
+
layout: {
|
|
13
|
+
stacked: { root: 'gap-4' },
|
|
14
|
+
inline: { item: 'sm:grid-cols-[minmax(0,12rem)_1fr] sm:gap-4' },
|
|
15
|
+
},
|
|
16
|
+
dividers: {
|
|
17
|
+
true: { root: 'gap-0 divide-y divide-border', item: 'py-3 first:pt-0 last:pb-0' },
|
|
18
|
+
false: {},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
compoundVariants: [{ layout: 'inline', dividers: false, class: { root: 'gap-3' } }],
|
|
22
|
+
defaultVariants: { layout: 'stacked', dividers: false },
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
export type DescriptionListVariants = VariantProps<typeof descriptionListStyles>
|
|
26
|
+
|
|
27
|
+
declare module '@glyphui/tokens' {
|
|
28
|
+
interface GlyphComponentRegistry {
|
|
29
|
+
DescriptionList: StylesEntry<typeof descriptionListStyles>
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type ComponentProps, createContext, type ReactNode, useContext } from 'react'
|
|
2
|
+
import { type SlotClasses, useComponentConfig } from '../../hooks/use-component-config'
|
|
3
|
+
import { type DescriptionListVariants, descriptionListStyles } from './description-list.styles'
|
|
4
|
+
|
|
5
|
+
type Styles = ReturnType<typeof descriptionListStyles>
|
|
6
|
+
|
|
7
|
+
const DescriptionListContext = createContext<{
|
|
8
|
+
s: Styles
|
|
9
|
+
slots: SlotClasses<'DescriptionList'>
|
|
10
|
+
} | null>(null)
|
|
11
|
+
|
|
12
|
+
export interface DescriptionListProps extends ComponentProps<'dl'>, DescriptionListVariants {}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Label–value pairs, such as account details or order metadata.
|
|
16
|
+
* `layout="inline"` puts terms and details side by side from the `sm` breakpoint.
|
|
17
|
+
*/
|
|
18
|
+
export function DescriptionList(inProps: DescriptionListProps) {
|
|
19
|
+
const [props, slots] = useComponentConfig('DescriptionList', inProps)
|
|
20
|
+
const { layout, dividers, className, ...rest } = props
|
|
21
|
+
const s = descriptionListStyles({ layout, dividers })
|
|
22
|
+
return (
|
|
23
|
+
<DescriptionListContext.Provider value={{ s, slots }}>
|
|
24
|
+
<dl
|
|
25
|
+
data-slot="description-list"
|
|
26
|
+
className={s.root({ class: [slots.root, className] })}
|
|
27
|
+
{...rest}
|
|
28
|
+
/>
|
|
29
|
+
</DescriptionListContext.Provider>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface DescriptionItemProps extends Omit<ComponentProps<'div'>, 'children'> {
|
|
34
|
+
term: ReactNode
|
|
35
|
+
children: ReactNode
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function DescriptionItem({ term, children, className, ...props }: DescriptionItemProps) {
|
|
39
|
+
const ctx = useContext(DescriptionListContext)
|
|
40
|
+
const s = ctx?.s ?? descriptionListStyles()
|
|
41
|
+
const slots = ctx?.slots ?? {}
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
data-slot="description-item"
|
|
45
|
+
className={s.item({ class: [slots.item, className] })}
|
|
46
|
+
{...props}
|
|
47
|
+
>
|
|
48
|
+
<dt data-slot="description-term" className={s.term({ class: slots.term })}>
|
|
49
|
+
{term}
|
|
50
|
+
</dt>
|
|
51
|
+
<dd data-slot="description-details" className={s.details({ class: slots.details })}>
|
|
52
|
+
{children}
|
|
53
|
+
</dd>
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { SVGProps } from 'react'
|
|
2
|
+
|
|
3
|
+
/** Local close icon so overlay components stay self-contained. */
|
|
4
|
+
export function CloseIcon(props: SVGProps<SVGSVGElement>) {
|
|
5
|
+
return (
|
|
6
|
+
<svg
|
|
7
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
8
|
+
width="16"
|
|
9
|
+
height="16"
|
|
10
|
+
viewBox="0 0 24 24"
|
|
11
|
+
fill="none"
|
|
12
|
+
stroke="currentColor"
|
|
13
|
+
strokeWidth="2"
|
|
14
|
+
strokeLinecap="round"
|
|
15
|
+
strokeLinejoin="round"
|
|
16
|
+
aria-hidden="true"
|
|
17
|
+
{...props}
|
|
18
|
+
>
|
|
19
|
+
<path d="M18 6 6 18M6 6l12 12" />
|
|
20
|
+
</svg>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { StylesEntry } from '../../utils/registry'
|
|
2
|
+
import { focusRing, themedBorder, transition } from '../../utils/styles'
|
|
3
|
+
import { tv, type VariantProps } from '../../utils/tv'
|
|
4
|
+
|
|
5
|
+
export const dialogStyles = tv({
|
|
6
|
+
slots: {
|
|
7
|
+
overlay: 'fixed inset-0 z-50 animate-fade-in bg-overlay',
|
|
8
|
+
content: [
|
|
9
|
+
'fixed top-1/2 left-1/2 z-50 grid max-h-[calc(100dvh-2rem)] w-[calc(100vw-2rem)] -translate-x-1/2 -translate-y-1/2 gap-5 overflow-y-auto',
|
|
10
|
+
'rounded-xl border-border bg-surface p-6 text-foreground shadow-lg outline-none animate-pop-in',
|
|
11
|
+
themedBorder,
|
|
12
|
+
],
|
|
13
|
+
close: [
|
|
14
|
+
'absolute top-4 right-4 inline-flex size-7 cursor-pointer items-center justify-center rounded-sm text-muted-foreground hover:bg-muted hover:text-foreground [&>svg]:size-4',
|
|
15
|
+
transition,
|
|
16
|
+
focusRing,
|
|
17
|
+
],
|
|
18
|
+
header: 'flex flex-col gap-1.5 pr-8',
|
|
19
|
+
title: 'font-display text-h4 text-foreground',
|
|
20
|
+
description: 'text-small text-muted-foreground',
|
|
21
|
+
body: 'min-w-0 text-small text-foreground',
|
|
22
|
+
footer: 'flex flex-col-reverse gap-2 sm:flex-row sm:justify-end',
|
|
23
|
+
},
|
|
24
|
+
variants: {
|
|
25
|
+
size: {
|
|
26
|
+
sm: { content: 'max-w-sm' },
|
|
27
|
+
md: { content: 'max-w-lg' },
|
|
28
|
+
lg: { content: 'max-w-2xl' },
|
|
29
|
+
xl: { content: 'max-w-4xl' },
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
defaultVariants: { size: 'md' },
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
export type DialogVariants = VariantProps<typeof dialogStyles>
|
|
36
|
+
|
|
37
|
+
declare module '@glyphui/tokens' {
|
|
38
|
+
interface GlyphComponentRegistry {
|
|
39
|
+
Dialog: StylesEntry<typeof dialogStyles>
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Dialog as DialogPrimitive } from 'radix-ui'
|
|
2
|
+
import type { ComponentProps } from 'react'
|
|
3
|
+
import { useComponentConfig } from '../../hooks/use-component-config'
|
|
4
|
+
import { CloseIcon } from './close-icon'
|
|
5
|
+
import { type DialogVariants, dialogStyles } from './dialog.styles'
|
|
6
|
+
|
|
7
|
+
function useParts() {
|
|
8
|
+
const [, slots] = useComponentConfig('Dialog', {})
|
|
9
|
+
return { s: dialogStyles(), slots }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** A modal window. Focus is trapped inside and returns to the trigger on close. */
|
|
13
|
+
export const Dialog = DialogPrimitive.Root
|
|
14
|
+
export const DialogTrigger = DialogPrimitive.Trigger
|
|
15
|
+
export const DialogClose = DialogPrimitive.Close
|
|
16
|
+
|
|
17
|
+
export interface DialogContentProps
|
|
18
|
+
extends ComponentProps<typeof DialogPrimitive.Content>,
|
|
19
|
+
DialogVariants {
|
|
20
|
+
/** Show the close button in the corner. Default true. */
|
|
21
|
+
showClose?: boolean
|
|
22
|
+
overlayClassName?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function DialogContent(inProps: DialogContentProps) {
|
|
26
|
+
const [props, slots] = useComponentConfig('Dialog', inProps)
|
|
27
|
+
const { size, showClose = true, overlayClassName, className, children, ...rest } = props
|
|
28
|
+
const s = dialogStyles({ size })
|
|
29
|
+
return (
|
|
30
|
+
<DialogPrimitive.Portal>
|
|
31
|
+
<DialogPrimitive.Overlay
|
|
32
|
+
data-slot="dialog-overlay"
|
|
33
|
+
className={s.overlay({ class: [slots.overlay, overlayClassName] })}
|
|
34
|
+
/>
|
|
35
|
+
<DialogPrimitive.Content
|
|
36
|
+
data-slot="dialog-content"
|
|
37
|
+
className={s.content({ class: [slots.content, className] })}
|
|
38
|
+
{...rest}
|
|
39
|
+
>
|
|
40
|
+
{children}
|
|
41
|
+
{showClose && (
|
|
42
|
+
<DialogPrimitive.Close
|
|
43
|
+
data-slot="dialog-close"
|
|
44
|
+
aria-label="Close"
|
|
45
|
+
className={s.close({ class: slots.close })}
|
|
46
|
+
>
|
|
47
|
+
<CloseIcon />
|
|
48
|
+
</DialogPrimitive.Close>
|
|
49
|
+
)}
|
|
50
|
+
</DialogPrimitive.Content>
|
|
51
|
+
</DialogPrimitive.Portal>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function DialogHeader({ className, ...props }: ComponentProps<'div'>) {
|
|
56
|
+
const { s, slots } = useParts()
|
|
57
|
+
return (
|
|
58
|
+
<div
|
|
59
|
+
data-slot="dialog-header"
|
|
60
|
+
className={s.header({ class: [slots.header, className] })}
|
|
61
|
+
{...props}
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function DialogTitle({ className, ...props }: ComponentProps<typeof DialogPrimitive.Title>) {
|
|
67
|
+
const { s, slots } = useParts()
|
|
68
|
+
return (
|
|
69
|
+
<DialogPrimitive.Title
|
|
70
|
+
data-slot="dialog-title"
|
|
71
|
+
className={s.title({ class: [slots.title, className] })}
|
|
72
|
+
{...props}
|
|
73
|
+
/>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function DialogDescription({
|
|
78
|
+
className,
|
|
79
|
+
...props
|
|
80
|
+
}: ComponentProps<typeof DialogPrimitive.Description>) {
|
|
81
|
+
const { s, slots } = useParts()
|
|
82
|
+
return (
|
|
83
|
+
<DialogPrimitive.Description
|
|
84
|
+
data-slot="dialog-description"
|
|
85
|
+
className={s.description({ class: [slots.description, className] })}
|
|
86
|
+
{...props}
|
|
87
|
+
/>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function DialogBody({ className, ...props }: ComponentProps<'div'>) {
|
|
92
|
+
const { s, slots } = useParts()
|
|
93
|
+
return (
|
|
94
|
+
<div
|
|
95
|
+
data-slot="dialog-body"
|
|
96
|
+
className={s.body({ class: [slots.body, className] })}
|
|
97
|
+
{...props}
|
|
98
|
+
/>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function DialogFooter({ className, ...props }: ComponentProps<'div'>) {
|
|
103
|
+
const { s, slots } = useParts()
|
|
104
|
+
return (
|
|
105
|
+
<div
|
|
106
|
+
data-slot="dialog-footer"
|
|
107
|
+
className={s.footer({ class: [slots.footer, className] })}
|
|
108
|
+
{...props}
|
|
109
|
+
/>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export {
|
|
2
|
+
Dialog,
|
|
3
|
+
DialogBody,
|
|
4
|
+
DialogClose,
|
|
5
|
+
DialogContent,
|
|
6
|
+
type DialogContentProps,
|
|
7
|
+
DialogDescription,
|
|
8
|
+
DialogFooter,
|
|
9
|
+
DialogHeader,
|
|
10
|
+
DialogTitle,
|
|
11
|
+
DialogTrigger,
|
|
12
|
+
} from './dialog'
|
|
13
|
+
export { type DialogVariants, dialogStyles } from './dialog.styles'
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { StylesEntry } from '../../utils/registry'
|
|
2
|
+
import { popover } from '../../utils/styles'
|
|
3
|
+
import { tv, type VariantProps } from '../../utils/tv'
|
|
4
|
+
|
|
5
|
+
const item = [
|
|
6
|
+
'relative flex h-8 cursor-pointer select-none items-center gap-2 rounded-sm px-2 text-small text-foreground outline-none',
|
|
7
|
+
'data-highlighted:bg-muted data-disabled:pointer-events-none data-disabled:opacity-50',
|
|
8
|
+
'[&_svg]:size-4 [&_svg]:shrink-0',
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
export const dropdownMenuStyles = tv({
|
|
12
|
+
slots: {
|
|
13
|
+
content: [
|
|
14
|
+
popover,
|
|
15
|
+
'min-w-48 max-h-(--radix-dropdown-menu-content-available-height) overflow-y-auto p-1',
|
|
16
|
+
],
|
|
17
|
+
item,
|
|
18
|
+
icon: 'flex size-4 shrink-0 items-center justify-center text-muted-foreground',
|
|
19
|
+
indicator: 'absolute left-2 flex size-4 items-center justify-center text-primary',
|
|
20
|
+
label: 'px-2 pt-1.5 pb-1 text-caption font-medium text-faint-foreground',
|
|
21
|
+
separator: '-mx-1 my-1 h-px bg-border',
|
|
22
|
+
shortcut: 'ml-auto pl-4 font-mono text-caption tracking-wider text-faint-foreground',
|
|
23
|
+
subTrigger: [...item, 'data-[state=open]:bg-muted'],
|
|
24
|
+
subContent: [popover, 'min-w-40 p-1'],
|
|
25
|
+
},
|
|
26
|
+
variants: {
|
|
27
|
+
tone: {
|
|
28
|
+
default: {},
|
|
29
|
+
danger: {
|
|
30
|
+
item: 'text-danger data-highlighted:bg-danger/10',
|
|
31
|
+
icon: 'text-danger',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
inset: { true: { item: 'pl-8', label: 'pl-8' } },
|
|
35
|
+
},
|
|
36
|
+
defaultVariants: { tone: 'default' },
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export type DropdownMenuVariants = VariantProps<typeof dropdownMenuStyles>
|
|
40
|
+
|
|
41
|
+
declare module '@glyphui/tokens' {
|
|
42
|
+
interface GlyphComponentRegistry {
|
|
43
|
+
DropdownMenu: StylesEntry<typeof dropdownMenuStyles>
|
|
44
|
+
}
|
|
45
|
+
}
|