@glyphui/ui 0.2.0 → 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.
Files changed (75) hide show
  1. package/dist/chart.d.ts +202 -0
  2. package/dist/chart.js +457 -0
  3. package/dist/data-table.js +3 -2
  4. package/dist/{field-CCecIzdq.js → field-CWZ_FdkD.js} +2 -1
  5. package/dist/{field-context-B6WOcK5m.js → field-context-anGPYXbW.js} +2 -56
  6. package/dist/form.js +3 -2
  7. package/dist/index.d.ts +2285 -241
  8. package/dist/index.js +2819 -266
  9. package/dist/{states-Rp8wZ_jH.js → states-DR-OoDT-.js} +2 -1
  10. package/dist/tv-DBAGlgXJ.js +58 -0
  11. package/package.json +17 -2
  12. package/src/components/accordion/accordion.styles.ts +4 -4
  13. package/src/components/calendar/calendar.styles.ts +50 -0
  14. package/src/components/calendar/calendar.tsx +84 -0
  15. package/src/components/calendar/index.ts +3 -0
  16. package/src/components/carousel/carousel.styles.ts +43 -0
  17. package/src/components/carousel/carousel.tsx +278 -0
  18. package/src/components/carousel/index.ts +13 -0
  19. package/src/components/collapsible/collapsible.tsx +66 -0
  20. package/src/components/collapsible/index.ts +6 -0
  21. package/src/components/context-menu/context-menu.styles.ts +19 -0
  22. package/src/components/context-menu/context-menu.tsx +226 -0
  23. package/src/components/context-menu/index.ts +19 -0
  24. package/src/components/copy-button/copy-button.tsx +117 -0
  25. package/src/components/copy-button/index.ts +1 -0
  26. package/src/components/date-picker/date-picker.styles.ts +42 -0
  27. package/src/components/date-picker/date-picker.tsx +322 -0
  28. package/src/components/date-picker/index.ts +8 -0
  29. package/src/components/file-upload/file-upload.styles.ts +43 -0
  30. package/src/components/file-upload/file-upload.tsx +343 -0
  31. package/src/components/file-upload/index.ts +10 -0
  32. package/src/components/hover-card/hover-card.tsx +41 -0
  33. package/src/components/hover-card/index.ts +1 -0
  34. package/src/components/input-otp/index.ts +10 -0
  35. package/src/components/input-otp/input-otp.styles.ts +38 -0
  36. package/src/components/input-otp/input-otp.tsx +116 -0
  37. package/src/components/modal/index.ts +2 -0
  38. package/src/components/modal/modal.styles.ts +18 -0
  39. package/src/components/modal/modal.tsx +86 -0
  40. package/src/components/navigation-menu/index.ts +14 -0
  41. package/src/components/navigation-menu/navigation-menu.styles.ts +48 -0
  42. package/src/components/navigation-menu/navigation-menu.tsx +198 -0
  43. package/src/components/number-input/index.ts +2 -0
  44. package/src/components/number-input/number-input.styles.ts +41 -0
  45. package/src/components/number-input/number-input.tsx +211 -0
  46. package/src/components/password-input/index.ts +7 -0
  47. package/src/components/password-input/password-input.styles.ts +30 -0
  48. package/src/components/password-input/password-input.tsx +127 -0
  49. package/src/components/resizable/index.ts +9 -0
  50. package/src/components/resizable/resizable.styles.ts +28 -0
  51. package/src/components/resizable/resizable.tsx +69 -0
  52. package/src/components/sheet/sheet.styles.ts +7 -7
  53. package/src/components/stepper/index.ts +2 -0
  54. package/src/components/stepper/stepper.styles.ts +74 -0
  55. package/src/components/stepper/stepper.tsx +138 -0
  56. package/src/components/tag-input/index.ts +2 -0
  57. package/src/components/tag-input/tag-input.styles.ts +39 -0
  58. package/src/components/tag-input/tag-input.tsx +198 -0
  59. package/src/components/timeline/index.ts +8 -0
  60. package/src/components/timeline/timeline.styles.ts +56 -0
  61. package/src/components/timeline/timeline.tsx +91 -0
  62. package/src/components/toggle/index.ts +9 -0
  63. package/src/components/toggle/toggle.styles.ts +39 -0
  64. package/src/components/toggle/toggle.tsx +61 -0
  65. package/src/components/tree-view/index.ts +2 -0
  66. package/src/components/tree-view/tree-view.styles.ts +42 -0
  67. package/src/components/tree-view/tree-view.tsx +274 -0
  68. package/src/components/visually-hidden/index.ts +1 -0
  69. package/src/components/visually-hidden/visually-hidden.tsx +10 -0
  70. package/src/index.ts +20 -0
  71. package/src/patterns/chart/chart-parts.tsx +171 -0
  72. package/src/patterns/chart/chart.styles.ts +35 -0
  73. package/src/patterns/chart/chart.tsx +369 -0
  74. package/src/patterns/chart/index.ts +13 -0
  75. 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,8 @@
1
+ export {
2
+ DatePicker,
3
+ type DatePickerProps,
4
+ DateRangePicker,
5
+ type DateRangePickerProps,
6
+ type DateRangePreset,
7
+ } from './date-picker'
8
+ export { type DatePickerVariants, datePickerStyles } from './date-picker.styles'
@@ -0,0 +1,43 @@
1
+ import type { StylesEntry } from '../../utils/registry'
2
+ import { focusRing, themedBorder, transition } from '../../utils/styles'
3
+ import { tv } from '../../utils/tv'
4
+
5
+ export const fileUploadStyles = tv({
6
+ slots: {
7
+ root: 'grid gap-3',
8
+ dropzone: [
9
+ 'flex w-full cursor-pointer flex-col items-center justify-center gap-2 rounded-lg border-dashed border-border-strong bg-subtle px-6 py-8 text-center',
10
+ themedBorder,
11
+ transition,
12
+ focusRing,
13
+ 'hover:border-primary/60 hover:bg-primary/4',
14
+ 'data-[dragging=true]:border-primary data-[dragging=true]:bg-primary/8',
15
+ 'disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-danger',
16
+ ],
17
+ icon: 'flex size-10 items-center justify-center rounded-md border border-border bg-surface text-muted-foreground shadow-xs [&>svg]:size-5',
18
+ title: 'text-small font-medium text-foreground',
19
+ highlight: 'text-primary',
20
+ hint: 'text-caption text-faint-foreground',
21
+ rejections: 'grid gap-1 text-small font-medium text-danger',
22
+ list: 'grid gap-2',
23
+ item: [
24
+ 'grid grid-cols-[auto_1fr_auto] items-center gap-3 rounded-md border-border bg-surface px-3 py-2.5',
25
+ themedBorder,
26
+ ],
27
+ fileIcon:
28
+ 'flex size-8 items-center justify-center rounded-sm bg-muted text-muted-foreground [&>svg]:size-4',
29
+ fileText: 'grid min-w-0 gap-1',
30
+ fileName: 'truncate text-small font-medium text-foreground',
31
+ fileMeta: 'text-caption tabular-nums text-faint-foreground',
32
+ fileError: 'text-caption font-medium text-danger',
33
+ progress: 'h-1 overflow-hidden rounded-pill bg-muted',
34
+ progressBar:
35
+ 'h-full rounded-pill bg-primary transition-[width] duration-(--glyph-duration-base)',
36
+ },
37
+ })
38
+
39
+ declare module '@glyphui/tokens' {
40
+ interface GlyphComponentRegistry {
41
+ FileUpload: StylesEntry<typeof fileUploadStyles>
42
+ }
43
+ }