@fragments-sdk/ui 0.7.5 → 0.8.1

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 (127) hide show
  1. package/README.md +58 -25
  2. package/fragments.json +1 -1
  3. package/package.json +15 -5
  4. package/src/blocks/AppShell.block.ts +2 -2
  5. package/src/blocks/InsetDashboardLayout.block.ts +1 -1
  6. package/src/blocks/LoginForm.block.ts +14 -7
  7. package/src/components/Accordion/Accordion.fragment.tsx +10 -4
  8. package/src/components/Alert/Alert.fragment.tsx +2 -2
  9. package/src/components/Alert/Alert.module.scss +4 -4
  10. package/src/components/AppShell/AppShell.fragment.tsx +3 -3
  11. package/src/components/AppShell/index.tsx +2 -0
  12. package/src/components/Avatar/Avatar.fragment.tsx +7 -3
  13. package/src/components/Avatar/Avatar.module.scss +1 -1
  14. package/src/components/Avatar/index.tsx +37 -1
  15. package/src/components/Badge/Badge.fragment.tsx +5 -5
  16. package/src/components/Badge/Badge.module.scss +4 -4
  17. package/src/components/Badge/index.tsx +5 -1
  18. package/src/components/Box/Box.fragment.tsx +2 -2
  19. package/src/components/Box/index.tsx +5 -1
  20. package/src/components/Breadcrumbs/Breadcrumbs.fragment.tsx +2 -2
  21. package/src/components/Button/Button.fragment.tsx +19 -18
  22. package/src/components/Button/index.tsx +5 -1
  23. package/src/components/ButtonGroup/ButtonGroup.fragment.tsx +2 -2
  24. package/src/components/ButtonGroup/index.tsx +5 -1
  25. package/src/components/Card/Card.fragment.tsx +7 -7
  26. package/src/components/Chart/Chart.fragment.tsx +11 -3
  27. package/src/components/Chart/index.tsx +22 -4
  28. package/src/components/Checkbox/Checkbox.fragment.tsx +2 -2
  29. package/src/components/Checkbox/index.tsx +5 -1
  30. package/src/components/Chip/Chip.fragment.tsx +2 -7
  31. package/src/components/Chip/Chip.module.scss +2 -2
  32. package/src/components/CodeBlock/CodeBlock.fragment.tsx +11 -5
  33. package/src/components/CodeBlock/CodeBlock.module.scss +11 -53
  34. package/src/components/CodeBlock/index.tsx +13 -24
  35. package/src/components/Collapsible/Collapsible.fragment.tsx +2 -2
  36. package/src/components/ColorPicker/ColorPicker.fragment.tsx +2 -2
  37. package/src/components/ColorPicker/index.tsx +5 -1
  38. package/src/components/Combobox/Combobox.fragment.tsx +17 -9
  39. package/src/components/ConversationList/ConversationList.fragment.tsx +5 -5
  40. package/src/components/ConversationList/ConversationList.module.scss +1 -1
  41. package/src/components/DatePicker/DatePicker.fragment.tsx +245 -0
  42. package/src/components/DatePicker/DatePicker.module.scss +394 -0
  43. package/src/components/DatePicker/DatePicker.test.tsx +264 -0
  44. package/src/components/DatePicker/index.tsx +535 -0
  45. package/src/components/Dialog/Dialog.fragment.tsx +2 -2
  46. package/src/components/EmptyState/EmptyState.fragment.tsx +2 -2
  47. package/src/components/Field/Field.fragment.tsx +7 -6
  48. package/src/components/Fieldset/Fieldset.fragment.tsx +7 -6
  49. package/src/components/Form/Form.fragment.tsx +11 -5
  50. package/src/components/Form/index.tsx +5 -1
  51. package/src/components/Grid/Grid.fragment.tsx +6 -2
  52. package/src/components/Header/Header.fragment.tsx +38 -15
  53. package/src/components/Header/Header.module.scss +114 -1
  54. package/src/components/Header/Header.test.tsx +106 -1
  55. package/src/components/Header/index.tsx +100 -31
  56. package/src/components/Icon/Icon.fragment.tsx +8 -3
  57. package/src/components/Icon/index.tsx +5 -1
  58. package/src/components/Image/Image.fragment.tsx +4 -4
  59. package/src/components/Image/index.tsx +5 -1
  60. package/src/components/Input/Input.fragment.tsx +23 -5
  61. package/src/components/Input/Input.module.scss +1 -1
  62. package/src/components/Input/index.tsx +5 -1
  63. package/src/components/Link/Link.fragment.tsx +2 -6
  64. package/src/components/Link/index.tsx +5 -1
  65. package/src/components/List/List.fragment.tsx +2 -2
  66. package/src/components/Listbox/Listbox.fragment.tsx +2 -14
  67. package/src/components/Loading/Loading.fragment.tsx +2 -2
  68. package/src/components/Markdown/Markdown.fragment.tsx +2 -2
  69. package/src/components/Markdown/Markdown.module.scss +11 -3
  70. package/src/components/Markdown/index.tsx +5 -1
  71. package/src/components/Menu/Menu.fragment.tsx +2 -2
  72. package/src/components/Message/Message.fragment.tsx +10 -8
  73. package/src/components/Message/Message.module.scss +1 -1
  74. package/src/components/Popover/Popover.fragment.tsx +2 -2
  75. package/src/components/Progress/Progress.fragment.tsx +16 -2
  76. package/src/components/Progress/index.tsx +9 -2
  77. package/src/components/Prompt/Prompt.fragment.tsx +13 -2
  78. package/src/components/RadioGroup/RadioGroup.fragment.tsx +7 -2
  79. package/src/components/ScrollArea/ScrollArea.fragment.tsx +185 -0
  80. package/src/components/ScrollArea/ScrollArea.module.scss +136 -0
  81. package/src/components/ScrollArea/ScrollArea.test.tsx +38 -0
  82. package/src/components/ScrollArea/index.tsx +121 -0
  83. package/src/components/Select/Select.fragment.tsx +15 -7
  84. package/src/components/Separator/Separator.fragment.tsx +2 -2
  85. package/src/components/Separator/index.tsx +5 -1
  86. package/src/components/Sidebar/Sidebar.fragment.tsx +66 -13
  87. package/src/components/Sidebar/Sidebar.module.scss +69 -21
  88. package/src/components/Sidebar/Sidebar.test.tsx +31 -2
  89. package/src/components/Sidebar/index.tsx +69 -45
  90. package/src/components/Skeleton/Skeleton.fragment.tsx +7 -2
  91. package/src/components/Slider/Slider.fragment.tsx +2 -2
  92. package/src/components/Slider/index.tsx +5 -1
  93. package/src/components/Stack/Stack.fragment.tsx +4 -4
  94. package/src/components/Stack/index.tsx +5 -1
  95. package/src/components/Table/Table.fragment.tsx +31 -2
  96. package/src/components/Table/index.tsx +49 -6
  97. package/src/components/TableOfContents/TableOfContents.fragment.tsx +149 -0
  98. package/src/components/TableOfContents/TableOfContents.module.scss +66 -0
  99. package/src/components/TableOfContents/TableOfContents.test.tsx +126 -0
  100. package/src/components/TableOfContents/index.tsx +110 -0
  101. package/src/components/Tabs/Tabs.fragment.tsx +2 -2
  102. package/src/components/Text/Text.fragment.tsx +2 -2
  103. package/src/components/Text/Text.module.scss +6 -0
  104. package/src/components/Text/Text.test.tsx +5 -0
  105. package/src/components/Text/index.tsx +8 -1
  106. package/src/components/Textarea/Textarea.fragment.tsx +10 -2
  107. package/src/components/Textarea/index.tsx +5 -1
  108. package/src/components/Theme/Theme.fragment.tsx +2 -2
  109. package/src/components/Theme/ThemeToggle.module.scss +1 -1
  110. package/src/components/Theme/index.tsx +8 -1
  111. package/src/components/ThinkingIndicator/ThinkingIndicator.fragment.tsx +5 -4
  112. package/src/components/Toast/Toast.fragment.tsx +14 -2
  113. package/src/components/Toggle/Toggle.fragment.tsx +2 -2
  114. package/src/components/Toggle/index.tsx +5 -1
  115. package/src/components/ToggleGroup/ToggleGroup.fragment.tsx +5 -5
  116. package/src/components/Tooltip/Tooltip.fragment.tsx +20 -2
  117. package/src/components/Tooltip/index.tsx +6 -1
  118. package/src/components/VisuallyHidden/VisuallyHidden.fragment.tsx +2 -2
  119. package/src/components/VisuallyHidden/index.tsx +5 -1
  120. package/src/components/compound-pattern.test.ts +40 -0
  121. package/src/index.ts +29 -0
  122. package/src/recipes/AppShell.recipe.ts +2 -2
  123. package/src/recipes/LoginForm.recipe.ts +14 -7
  124. package/src/tokens/_computed.scss +12 -0
  125. package/src/tokens/_derive.scss +71 -0
  126. package/src/tokens/_mixins.scss +9 -0
  127. package/src/tokens/_variables.scss +26 -4
@@ -0,0 +1,535 @@
1
+ import * as React from 'react';
2
+ import { Popover as BasePopover } from '@base-ui/react/popover';
3
+ import { DayPicker, UI, SelectionState, DayFlag } from 'react-day-picker';
4
+ import { format } from 'date-fns';
5
+ import styles from './DatePicker.module.scss';
6
+ // Import globals to ensure CSS variables are defined
7
+ import '../../styles/globals.scss';
8
+
9
+ // ============================================
10
+ // Types
11
+ // ============================================
12
+
13
+ export type { DateRange, Matcher } from 'react-day-picker';
14
+ import type { DateRange, Matcher, Locale } from 'react-day-picker';
15
+
16
+ export interface DatePickerProps {
17
+ children: React.ReactNode;
18
+ /** Selection mode */
19
+ mode?: 'single' | 'range';
20
+ /** Controlled date (single mode) */
21
+ selected?: Date | null;
22
+ /** Controlled range (range mode) */
23
+ selectedRange?: DateRange | null;
24
+ /** Single selection callback */
25
+ onSelect?: (date: Date | null) => void;
26
+ /** Range selection callback */
27
+ onRangeSelect?: (range: DateRange | null) => void;
28
+ /** Number of months displayed side-by-side */
29
+ numberOfMonths?: number;
30
+ /** Disable the picker */
31
+ disabled?: boolean;
32
+ /** react-day-picker Matcher for disabled dates */
33
+ disabledDates?: Matcher | Matcher[];
34
+ /** Trigger placeholder text */
35
+ placeholder?: string;
36
+ /** date-fns locale for i18n */
37
+ locale?: Locale;
38
+ /** Always show 6 rows */
39
+ fixedWeeks?: boolean;
40
+ /** Custom trigger date formatter */
41
+ formatDate?: (date: Date) => string;
42
+ /** Custom trigger range formatter */
43
+ formatRange?: (range: DateRange) => string;
44
+ /** Controlled popover open state */
45
+ open?: boolean;
46
+ /** Popover open state change callback */
47
+ onOpenChange?: (open: boolean) => void;
48
+ /** Hidden input name for forms */
49
+ name?: string;
50
+ }
51
+
52
+ export interface DatePickerTriggerProps extends React.HTMLAttributes<HTMLButtonElement> {
53
+ children?: React.ReactNode;
54
+ placeholder?: string;
55
+ }
56
+
57
+ export interface DatePickerContentProps extends React.HTMLAttributes<HTMLDivElement> {
58
+ children: React.ReactNode;
59
+ sideOffset?: number;
60
+ align?: 'start' | 'center' | 'end';
61
+ }
62
+
63
+ export interface DatePickerCalendarProps {
64
+ /** Override number of months from root */
65
+ numberOfMonths?: number;
66
+ className?: string;
67
+ }
68
+
69
+ export interface DatePickerPresetProps {
70
+ children: React.ReactNode;
71
+ /** Date to select (single mode) */
72
+ date?: Date;
73
+ /** Range to select (range mode) */
74
+ range?: DateRange;
75
+ className?: string;
76
+ }
77
+
78
+ // ============================================
79
+ // Icons
80
+ // ============================================
81
+
82
+ function CalendarIcon() {
83
+ return (
84
+ <svg
85
+ xmlns="http://www.w3.org/2000/svg"
86
+ width="16"
87
+ height="16"
88
+ viewBox="0 0 24 24"
89
+ fill="none"
90
+ stroke="currentColor"
91
+ strokeWidth="2"
92
+ strokeLinecap="round"
93
+ strokeLinejoin="round"
94
+ aria-hidden="true"
95
+ >
96
+ <rect x="3" y="4" width="18" height="18" rx="2" ry="2" />
97
+ <line x1="16" y1="2" x2="16" y2="6" />
98
+ <line x1="8" y1="2" x2="8" y2="6" />
99
+ <line x1="3" y1="10" x2="21" y2="10" />
100
+ </svg>
101
+ );
102
+ }
103
+
104
+ function ChevronLeftIcon() {
105
+ return (
106
+ <svg
107
+ xmlns="http://www.w3.org/2000/svg"
108
+ width="16"
109
+ height="16"
110
+ viewBox="0 0 24 24"
111
+ fill="none"
112
+ stroke="currentColor"
113
+ strokeWidth="2"
114
+ strokeLinecap="round"
115
+ strokeLinejoin="round"
116
+ aria-hidden="true"
117
+ >
118
+ <polyline points="15 18 9 12 15 6" />
119
+ </svg>
120
+ );
121
+ }
122
+
123
+ function ChevronRightIcon() {
124
+ return (
125
+ <svg
126
+ xmlns="http://www.w3.org/2000/svg"
127
+ width="16"
128
+ height="16"
129
+ viewBox="0 0 24 24"
130
+ fill="none"
131
+ stroke="currentColor"
132
+ strokeWidth="2"
133
+ strokeLinecap="round"
134
+ strokeLinejoin="round"
135
+ aria-hidden="true"
136
+ >
137
+ <polyline points="9 18 15 12 9 6" />
138
+ </svg>
139
+ );
140
+ }
141
+
142
+ // ============================================
143
+ // Context
144
+ // ============================================
145
+
146
+ interface DatePickerContextValue {
147
+ mode: 'single' | 'range';
148
+ selected: Date | null;
149
+ selectedRange: DateRange | null;
150
+ setSelected: (date: Date | null) => void;
151
+ setSelectedRange: (range: DateRange | null) => void;
152
+ numberOfMonths: number;
153
+ disabled: boolean;
154
+ disabledDates?: Matcher | Matcher[];
155
+ placeholder: string;
156
+ locale?: Locale;
157
+ fixedWeeks: boolean;
158
+ formatDate: (date: Date) => string;
159
+ formatRange: (range: DateRange) => string;
160
+ isOpen: boolean;
161
+ setIsOpen: (open: boolean) => void;
162
+ isControlledOpen: boolean;
163
+ name?: string;
164
+ }
165
+
166
+ const DatePickerContext = React.createContext<DatePickerContextValue | null>(null);
167
+
168
+ function useDatePickerContext() {
169
+ const context = React.useContext(DatePickerContext);
170
+ if (!context) {
171
+ throw new Error('DatePicker compound components must be used within <DatePicker>');
172
+ }
173
+ return context;
174
+ }
175
+
176
+ // ============================================
177
+ // Default formatters
178
+ // ============================================
179
+
180
+ function defaultFormatDate(date: Date): string {
181
+ return format(date, 'PPP');
182
+ }
183
+
184
+ function defaultFormatRange(range: DateRange): string {
185
+ if (!range.from) return '';
186
+ if (!range.to) return format(range.from, 'LLL dd, y');
187
+ return `${format(range.from, 'LLL dd, y')} - ${format(range.to, 'LLL dd, y')}`;
188
+ }
189
+
190
+ // ============================================
191
+ // ClassNames mapping
192
+ // ============================================
193
+
194
+ const calendarClassNames = {
195
+ [UI.Root]: styles.calendar,
196
+ [UI.Months]: styles.months,
197
+ [UI.Month]: styles.month,
198
+ [UI.MonthCaption]: styles.monthCaption,
199
+ [UI.CaptionLabel]: styles.captionLabel,
200
+ [UI.Nav]: styles.nav,
201
+ [UI.PreviousMonthButton]: styles.navButton,
202
+ [UI.NextMonthButton]: styles.navButton,
203
+ [UI.MonthGrid]: styles.monthGrid,
204
+ [UI.Weekdays]: styles.weekdays,
205
+ [UI.Weekday]: styles.weekday,
206
+ [UI.Weeks]: styles.weeks,
207
+ [UI.Week]: styles.week,
208
+ [UI.Day]: styles.day,
209
+ [UI.DayButton]: styles.dayButton,
210
+ [UI.Chevron]: styles.chevron,
211
+ [SelectionState.selected]: styles.selected,
212
+ [SelectionState.range_start]: styles.rangeStart,
213
+ [SelectionState.range_middle]: styles.rangeMiddle,
214
+ [SelectionState.range_end]: styles.rangeEnd,
215
+ [DayFlag.today]: styles.today,
216
+ [DayFlag.outside]: styles.outside,
217
+ [DayFlag.disabled]: styles.disabled,
218
+ [DayFlag.focused]: styles.focused,
219
+ };
220
+
221
+ // ============================================
222
+ // Components
223
+ // ============================================
224
+
225
+ function DatePickerRoot({
226
+ children,
227
+ mode = 'single',
228
+ selected: selectedProp,
229
+ selectedRange: selectedRangeProp,
230
+ onSelect,
231
+ onRangeSelect,
232
+ numberOfMonths = 1,
233
+ disabled = false,
234
+ disabledDates,
235
+ placeholder,
236
+ locale,
237
+ fixedWeeks = false,
238
+ formatDate: formatDateProp,
239
+ formatRange: formatRangeProp,
240
+ open: openProp,
241
+ onOpenChange,
242
+ name,
243
+ }: DatePickerProps) {
244
+ const [internalSelected, setInternalSelected] = React.useState<Date | null>(
245
+ selectedProp ?? null
246
+ );
247
+ const [internalRange, setInternalRange] = React.useState<DateRange | null>(
248
+ selectedRangeProp ?? null
249
+ );
250
+ const [internalOpen, setInternalOpen] = React.useState(false);
251
+
252
+ const isControlledOpen = openProp !== undefined;
253
+ const isOpen = isControlledOpen ? openProp : internalOpen;
254
+
255
+ // Sync controlled selected
256
+ React.useEffect(() => {
257
+ if (selectedProp !== undefined) {
258
+ setInternalSelected(selectedProp);
259
+ }
260
+ }, [selectedProp]);
261
+
262
+ // Sync controlled range
263
+ React.useEffect(() => {
264
+ if (selectedRangeProp !== undefined) {
265
+ setInternalRange(selectedRangeProp);
266
+ }
267
+ }, [selectedRangeProp]);
268
+
269
+ const handleOpenChange = React.useCallback(
270
+ (newOpen: boolean) => {
271
+ if (!isControlledOpen) {
272
+ setInternalOpen(newOpen);
273
+ }
274
+ onOpenChange?.(newOpen);
275
+ },
276
+ [isControlledOpen, onOpenChange]
277
+ );
278
+
279
+ const setSelected = React.useCallback(
280
+ (date: Date | null) => {
281
+ if (selectedProp === undefined) {
282
+ setInternalSelected(date);
283
+ }
284
+ onSelect?.(date);
285
+
286
+ // Auto-close after single selection (uncontrolled)
287
+ if (!isControlledOpen && date) {
288
+ setTimeout(() => {
289
+ setInternalOpen(false);
290
+ onOpenChange?.(false);
291
+ }, 150);
292
+ }
293
+ },
294
+ [selectedProp, onSelect, isControlledOpen, onOpenChange]
295
+ );
296
+
297
+ const setSelectedRange = React.useCallback(
298
+ (range: DateRange | null) => {
299
+ if (selectedRangeProp === undefined) {
300
+ setInternalRange(range);
301
+ }
302
+ onRangeSelect?.(range);
303
+
304
+ // Range mode never auto-closes. The user closes manually via
305
+ // click-outside, Escape, or clicking the trigger again. This
306
+ // matches shadcn behavior and avoids premature close on first
307
+ // click or preset selection.
308
+ },
309
+ [selectedRangeProp, onRangeSelect, isControlledOpen, onOpenChange]
310
+ );
311
+
312
+ const defaultPlaceholder = mode === 'range' ? 'Select date range' : 'Pick a date';
313
+
314
+ const contextValue = React.useMemo<DatePickerContextValue>(
315
+ () => ({
316
+ mode,
317
+ selected: selectedProp !== undefined ? selectedProp : internalSelected,
318
+ selectedRange: selectedRangeProp !== undefined ? selectedRangeProp : internalRange,
319
+ setSelected,
320
+ setSelectedRange,
321
+ numberOfMonths,
322
+ disabled,
323
+ disabledDates,
324
+ placeholder: placeholder ?? defaultPlaceholder,
325
+ locale,
326
+ fixedWeeks,
327
+ formatDate: formatDateProp ?? defaultFormatDate,
328
+ formatRange: formatRangeProp ?? defaultFormatRange,
329
+ isOpen,
330
+ setIsOpen: handleOpenChange,
331
+ isControlledOpen,
332
+ name,
333
+ }),
334
+ [
335
+ mode,
336
+ selectedProp,
337
+ internalSelected,
338
+ selectedRangeProp,
339
+ internalRange,
340
+ setSelected,
341
+ setSelectedRange,
342
+ numberOfMonths,
343
+ disabled,
344
+ disabledDates,
345
+ placeholder,
346
+ defaultPlaceholder,
347
+ locale,
348
+ fixedWeeks,
349
+ formatDateProp,
350
+ formatRangeProp,
351
+ isOpen,
352
+ handleOpenChange,
353
+ isControlledOpen,
354
+ name,
355
+ ]
356
+ );
357
+
358
+ return (
359
+ <DatePickerContext.Provider value={contextValue}>
360
+ <BasePopover.Root
361
+ open={isOpen}
362
+ onOpenChange={handleOpenChange}
363
+ >
364
+ {children}
365
+ </BasePopover.Root>
366
+ {name && (
367
+ <input
368
+ type="hidden"
369
+ name={name}
370
+ value={
371
+ mode === 'single'
372
+ ? (contextValue.selected?.toISOString() ?? '')
373
+ : contextValue.selectedRange
374
+ ? `${contextValue.selectedRange.from?.toISOString() ?? ''},${contextValue.selectedRange.to?.toISOString() ?? ''}`
375
+ : ''
376
+ }
377
+ />
378
+ )}
379
+ </DatePickerContext.Provider>
380
+ );
381
+ }
382
+
383
+ function DatePickerTrigger({ children, placeholder, className, ...htmlProps }: DatePickerTriggerProps) {
384
+ const ctx = useDatePickerContext();
385
+ const placeholderText = placeholder ?? ctx.placeholder;
386
+
387
+ const classes = [styles.trigger, className].filter(Boolean).join(' ');
388
+
389
+ let displayText: string | null = null;
390
+ if (ctx.mode === 'single' && ctx.selected) {
391
+ displayText = ctx.formatDate(ctx.selected);
392
+ } else if (ctx.mode === 'range' && ctx.selectedRange?.from) {
393
+ displayText = ctx.formatRange(ctx.selectedRange);
394
+ }
395
+
396
+ return (
397
+ <BasePopover.Trigger
398
+ {...htmlProps}
399
+ className={classes}
400
+ disabled={ctx.disabled}
401
+ >
402
+ {children ?? (
403
+ <>
404
+ <span className={styles.triggerIcon}>
405
+ <CalendarIcon />
406
+ </span>
407
+ <span className={displayText ? styles.triggerValue : styles.triggerPlaceholder}>
408
+ {displayText ?? placeholderText}
409
+ </span>
410
+ </>
411
+ )}
412
+ </BasePopover.Trigger>
413
+ );
414
+ }
415
+
416
+ function DatePickerContent({
417
+ children,
418
+ className,
419
+ sideOffset = 4,
420
+ align = 'start',
421
+ ...htmlProps
422
+ }: DatePickerContentProps) {
423
+ const popupClasses = [styles.popup, className].filter(Boolean).join(' ');
424
+
425
+ return (
426
+ <BasePopover.Portal>
427
+ <BasePopover.Positioner
428
+ side="bottom"
429
+ align={align}
430
+ sideOffset={sideOffset}
431
+ className={styles.positioner}
432
+ >
433
+ <BasePopover.Popup {...htmlProps} className={popupClasses}>
434
+ {children}
435
+ </BasePopover.Popup>
436
+ </BasePopover.Positioner>
437
+ </BasePopover.Portal>
438
+ );
439
+ }
440
+
441
+ function DatePickerCalendar({ numberOfMonths: numberOfMonthsProp, className }: DatePickerCalendarProps) {
442
+ const ctx = useDatePickerContext();
443
+ const monthCount = numberOfMonthsProp ?? ctx.numberOfMonths;
444
+
445
+ const calendarClasses = className
446
+ ? { ...calendarClassNames, [UI.Root]: [styles.calendar, className].join(' ') }
447
+ : calendarClassNames;
448
+
449
+ const components = React.useMemo(
450
+ () => ({
451
+ Chevron: (props: { orientation?: string }) =>
452
+ props.orientation === 'left' ? <ChevronLeftIcon /> : <ChevronRightIcon />,
453
+ }),
454
+ []
455
+ );
456
+
457
+ if (ctx.mode === 'range') {
458
+ const rangeSelected = ctx.selectedRange
459
+ ? { from: ctx.selectedRange.from ?? undefined, to: ctx.selectedRange.to ?? undefined }
460
+ : undefined;
461
+
462
+ return (
463
+ <DayPicker
464
+ mode="range"
465
+ selected={rangeSelected}
466
+ onSelect={(range) => {
467
+ ctx.setSelectedRange(range ? { from: range.from ?? undefined, to: range.to ?? undefined } : null);
468
+ }}
469
+ numberOfMonths={monthCount}
470
+ disabled={ctx.disabledDates}
471
+ locale={ctx.locale}
472
+ fixedWeeks={ctx.fixedWeeks}
473
+ classNames={calendarClasses}
474
+ components={components}
475
+ showOutsideDays
476
+ />
477
+ );
478
+ }
479
+
480
+ return (
481
+ <DayPicker
482
+ mode="single"
483
+ selected={ctx.selected ?? undefined}
484
+ onSelect={(date) => {
485
+ ctx.setSelected(date ?? null);
486
+ }}
487
+ numberOfMonths={monthCount}
488
+ disabled={ctx.disabledDates}
489
+ locale={ctx.locale}
490
+ fixedWeeks={ctx.fixedWeeks}
491
+ classNames={calendarClasses}
492
+ components={components}
493
+ showOutsideDays
494
+ />
495
+ );
496
+ }
497
+
498
+ function DatePickerPreset({ children, date, range, className }: DatePickerPresetProps) {
499
+ const ctx = useDatePickerContext();
500
+ const classes = [styles.preset, className].filter(Boolean).join(' ');
501
+
502
+ const handleClick = React.useCallback(() => {
503
+ if (ctx.mode === 'single' && date) {
504
+ ctx.setSelected(date);
505
+ } else if (ctx.mode === 'range' && range) {
506
+ ctx.setSelectedRange(range);
507
+ }
508
+ }, [ctx, date, range]);
509
+
510
+ return (
511
+ <button type="button" className={classes} onClick={handleClick}>
512
+ {children}
513
+ </button>
514
+ );
515
+ }
516
+
517
+ // ============================================
518
+ // Export compound component
519
+ // ============================================
520
+
521
+ export const DatePicker = Object.assign(DatePickerRoot, {
522
+ Trigger: DatePickerTrigger,
523
+ Content: DatePickerContent,
524
+ Calendar: DatePickerCalendar,
525
+ Preset: DatePickerPreset,
526
+ });
527
+
528
+ // Re-export individual components
529
+ export {
530
+ DatePickerRoot,
531
+ DatePickerTrigger,
532
+ DatePickerContent,
533
+ DatePickerCalendar,
534
+ DatePickerPreset,
535
+ };
@@ -1,9 +1,9 @@
1
1
  import React from 'react';
2
- import { defineSegment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments/core';
3
3
  import { Dialog } from '.';
4
4
  import { Button } from '../Button';
5
5
 
6
- export default defineSegment({
6
+ export default defineFragment({
7
7
  component: Dialog,
8
8
 
9
9
  meta: {
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { defineSegment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments/core';
3
3
  import { EmptyState } from '.';
4
4
  import { Button } from '../Button';
5
5
 
@@ -54,7 +54,7 @@ const InboxIcon = () => (
54
54
  </svg>
55
55
  );
56
56
 
57
- export default defineSegment({
57
+ export default defineFragment({
58
58
  component: EmptyState,
59
59
 
60
60
  meta: {
@@ -1,10 +1,10 @@
1
1
  import React from 'react';
2
- import { defineSegment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments/core';
3
3
  import { Field } from '.';
4
4
  import { Input } from '../Input';
5
5
  import { Grid } from '../Grid';
6
6
 
7
- export default defineSegment({
7
+ export default defineFragment({
8
8
  component: Field,
9
9
 
10
10
  meta: {
@@ -42,6 +42,11 @@ export default defineSegment({
42
42
  },
43
43
 
44
44
  props: {
45
+ children: {
46
+ type: 'node',
47
+ description: 'Field content (Label, Control, Description, Error)',
48
+ required: true,
49
+ },
45
50
  name: {
46
51
  type: 'string',
47
52
  description: 'Field name, used for error distribution from Form',
@@ -67,10 +72,6 @@ export default defineSegment({
67
72
  type: 'number',
68
73
  description: 'Debounce time in ms for onChange validation',
69
74
  },
70
- className: {
71
- type: 'string',
72
- description: 'Additional CSS class',
73
- },
74
75
  },
75
76
 
76
77
  relations: [
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { defineSegment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments/core';
3
3
  import { Fieldset } from '.';
4
4
  import { Field } from '../Field';
5
5
  import { Input } from '../Input';
@@ -8,7 +8,7 @@ import { Select } from '../Select';
8
8
  import { Checkbox } from '../Checkbox';
9
9
  import { Grid } from '../Grid';
10
10
 
11
- export default defineSegment({
11
+ export default defineFragment({
12
12
  component: Fieldset,
13
13
 
14
14
  meta: {
@@ -43,14 +43,15 @@ export default defineSegment({
43
43
  },
44
44
 
45
45
  props: {
46
+ children: {
47
+ type: 'node',
48
+ description: 'Fieldset content including Fieldset.Legend and form fields',
49
+ required: true,
50
+ },
46
51
  disabled: {
47
52
  type: 'boolean',
48
53
  description: 'Disables all fields within the fieldset',
49
54
  },
50
- className: {
51
- type: 'string',
52
- description: 'Additional CSS class',
53
- },
54
55
  },
55
56
 
56
57
  relations: [
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { defineSegment } from '@fragments/core';
2
+ import { defineFragment } from '@fragments/core';
3
3
  import { Form } from '.';
4
4
  import { Field } from '../Field';
5
5
  import { Fieldset } from '../Fieldset';
@@ -12,7 +12,7 @@ import { Toggle } from '../Toggle';
12
12
  import { Button } from '../Button';
13
13
  import { Grid } from '../Grid';
14
14
 
15
- export default defineSegment({
15
+ export default defineFragment({
16
16
  component: Form,
17
17
 
18
18
  meta: {
@@ -48,6 +48,11 @@ export default defineSegment({
48
48
  },
49
49
 
50
50
  props: {
51
+ children: {
52
+ type: 'node',
53
+ description: 'Form content',
54
+ required: true,
55
+ },
51
56
  errors: {
52
57
  type: 'object',
53
58
  description: 'Server-side errors keyed by field name',
@@ -60,9 +65,10 @@ export default defineSegment({
60
65
  type: 'function',
61
66
  description: 'Called with field name when errors should be cleared',
62
67
  },
63
- className: {
64
- type: 'string',
65
- description: 'Additional CSS class',
68
+ validationMode: {
69
+ type: 'enum',
70
+ description: 'When field validation should run',
71
+ values: ['onSubmit', 'onBlur', 'onChange'],
66
72
  },
67
73
  },
68
74
 
@@ -19,7 +19,7 @@ export interface FormProps extends Omit<React.HTMLAttributes<HTMLFormElement>, '
19
19
  // Component
20
20
  // ============================================
21
21
 
22
- export function Form({
22
+ function FormRoot({
23
23
  children,
24
24
  errors,
25
25
  onFormSubmit,
@@ -51,3 +51,7 @@ export function Form({
51
51
  </BaseForm>
52
52
  );
53
53
  }
54
+
55
+ export const Form = Object.assign(FormRoot, {
56
+ Root: FormRoot,
57
+ });