@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.
- package/README.md +58 -25
- package/fragments.json +1 -1
- package/package.json +15 -5
- package/src/blocks/AppShell.block.ts +2 -2
- package/src/blocks/InsetDashboardLayout.block.ts +1 -1
- package/src/blocks/LoginForm.block.ts +14 -7
- package/src/components/Accordion/Accordion.fragment.tsx +10 -4
- package/src/components/Alert/Alert.fragment.tsx +2 -2
- package/src/components/Alert/Alert.module.scss +4 -4
- package/src/components/AppShell/AppShell.fragment.tsx +3 -3
- package/src/components/AppShell/index.tsx +2 -0
- package/src/components/Avatar/Avatar.fragment.tsx +7 -3
- package/src/components/Avatar/Avatar.module.scss +1 -1
- package/src/components/Avatar/index.tsx +37 -1
- package/src/components/Badge/Badge.fragment.tsx +5 -5
- package/src/components/Badge/Badge.module.scss +4 -4
- package/src/components/Badge/index.tsx +5 -1
- package/src/components/Box/Box.fragment.tsx +2 -2
- package/src/components/Box/index.tsx +5 -1
- package/src/components/Breadcrumbs/Breadcrumbs.fragment.tsx +2 -2
- package/src/components/Button/Button.fragment.tsx +19 -18
- package/src/components/Button/index.tsx +5 -1
- package/src/components/ButtonGroup/ButtonGroup.fragment.tsx +2 -2
- package/src/components/ButtonGroup/index.tsx +5 -1
- package/src/components/Card/Card.fragment.tsx +7 -7
- package/src/components/Chart/Chart.fragment.tsx +11 -3
- package/src/components/Chart/index.tsx +22 -4
- package/src/components/Checkbox/Checkbox.fragment.tsx +2 -2
- package/src/components/Checkbox/index.tsx +5 -1
- package/src/components/Chip/Chip.fragment.tsx +2 -7
- package/src/components/Chip/Chip.module.scss +2 -2
- package/src/components/CodeBlock/CodeBlock.fragment.tsx +11 -5
- package/src/components/CodeBlock/CodeBlock.module.scss +11 -53
- package/src/components/CodeBlock/index.tsx +13 -24
- package/src/components/Collapsible/Collapsible.fragment.tsx +2 -2
- package/src/components/ColorPicker/ColorPicker.fragment.tsx +2 -2
- package/src/components/ColorPicker/index.tsx +5 -1
- package/src/components/Combobox/Combobox.fragment.tsx +17 -9
- package/src/components/ConversationList/ConversationList.fragment.tsx +5 -5
- package/src/components/ConversationList/ConversationList.module.scss +1 -1
- package/src/components/DatePicker/DatePicker.fragment.tsx +245 -0
- package/src/components/DatePicker/DatePicker.module.scss +394 -0
- package/src/components/DatePicker/DatePicker.test.tsx +264 -0
- package/src/components/DatePicker/index.tsx +535 -0
- package/src/components/Dialog/Dialog.fragment.tsx +2 -2
- package/src/components/EmptyState/EmptyState.fragment.tsx +2 -2
- package/src/components/Field/Field.fragment.tsx +7 -6
- package/src/components/Fieldset/Fieldset.fragment.tsx +7 -6
- package/src/components/Form/Form.fragment.tsx +11 -5
- package/src/components/Form/index.tsx +5 -1
- package/src/components/Grid/Grid.fragment.tsx +6 -2
- package/src/components/Header/Header.fragment.tsx +38 -15
- package/src/components/Header/Header.module.scss +114 -1
- package/src/components/Header/Header.test.tsx +106 -1
- package/src/components/Header/index.tsx +100 -31
- package/src/components/Icon/Icon.fragment.tsx +8 -3
- package/src/components/Icon/index.tsx +5 -1
- package/src/components/Image/Image.fragment.tsx +4 -4
- package/src/components/Image/index.tsx +5 -1
- package/src/components/Input/Input.fragment.tsx +23 -5
- package/src/components/Input/Input.module.scss +1 -1
- package/src/components/Input/index.tsx +5 -1
- package/src/components/Link/Link.fragment.tsx +2 -6
- package/src/components/Link/index.tsx +5 -1
- package/src/components/List/List.fragment.tsx +2 -2
- package/src/components/Listbox/Listbox.fragment.tsx +2 -14
- package/src/components/Loading/Loading.fragment.tsx +2 -2
- package/src/components/Markdown/Markdown.fragment.tsx +2 -2
- package/src/components/Markdown/Markdown.module.scss +11 -3
- package/src/components/Markdown/index.tsx +5 -1
- package/src/components/Menu/Menu.fragment.tsx +2 -2
- package/src/components/Message/Message.fragment.tsx +10 -8
- package/src/components/Message/Message.module.scss +1 -1
- package/src/components/Popover/Popover.fragment.tsx +2 -2
- package/src/components/Progress/Progress.fragment.tsx +16 -2
- package/src/components/Progress/index.tsx +9 -2
- package/src/components/Prompt/Prompt.fragment.tsx +13 -2
- package/src/components/RadioGroup/RadioGroup.fragment.tsx +7 -2
- package/src/components/ScrollArea/ScrollArea.fragment.tsx +185 -0
- package/src/components/ScrollArea/ScrollArea.module.scss +136 -0
- package/src/components/ScrollArea/ScrollArea.test.tsx +38 -0
- package/src/components/ScrollArea/index.tsx +121 -0
- package/src/components/Select/Select.fragment.tsx +15 -7
- package/src/components/Separator/Separator.fragment.tsx +2 -2
- package/src/components/Separator/index.tsx +5 -1
- package/src/components/Sidebar/Sidebar.fragment.tsx +66 -13
- package/src/components/Sidebar/Sidebar.module.scss +69 -21
- package/src/components/Sidebar/Sidebar.test.tsx +31 -2
- package/src/components/Sidebar/index.tsx +69 -45
- package/src/components/Skeleton/Skeleton.fragment.tsx +7 -2
- package/src/components/Slider/Slider.fragment.tsx +2 -2
- package/src/components/Slider/index.tsx +5 -1
- package/src/components/Stack/Stack.fragment.tsx +4 -4
- package/src/components/Stack/index.tsx +5 -1
- package/src/components/Table/Table.fragment.tsx +31 -2
- package/src/components/Table/index.tsx +49 -6
- package/src/components/TableOfContents/TableOfContents.fragment.tsx +149 -0
- package/src/components/TableOfContents/TableOfContents.module.scss +66 -0
- package/src/components/TableOfContents/TableOfContents.test.tsx +126 -0
- package/src/components/TableOfContents/index.tsx +110 -0
- package/src/components/Tabs/Tabs.fragment.tsx +2 -2
- package/src/components/Text/Text.fragment.tsx +2 -2
- package/src/components/Text/Text.module.scss +6 -0
- package/src/components/Text/Text.test.tsx +5 -0
- package/src/components/Text/index.tsx +8 -1
- package/src/components/Textarea/Textarea.fragment.tsx +10 -2
- package/src/components/Textarea/index.tsx +5 -1
- package/src/components/Theme/Theme.fragment.tsx +2 -2
- package/src/components/Theme/ThemeToggle.module.scss +1 -1
- package/src/components/Theme/index.tsx +8 -1
- package/src/components/ThinkingIndicator/ThinkingIndicator.fragment.tsx +5 -4
- package/src/components/Toast/Toast.fragment.tsx +14 -2
- package/src/components/Toggle/Toggle.fragment.tsx +2 -2
- package/src/components/Toggle/index.tsx +5 -1
- package/src/components/ToggleGroup/ToggleGroup.fragment.tsx +5 -5
- package/src/components/Tooltip/Tooltip.fragment.tsx +20 -2
- package/src/components/Tooltip/index.tsx +6 -1
- package/src/components/VisuallyHidden/VisuallyHidden.fragment.tsx +2 -2
- package/src/components/VisuallyHidden/index.tsx +5 -1
- package/src/components/compound-pattern.test.ts +40 -0
- package/src/index.ts +29 -0
- package/src/recipes/AppShell.recipe.ts +2 -2
- package/src/recipes/LoginForm.recipe.ts +14 -7
- package/src/tokens/_computed.scss +12 -0
- package/src/tokens/_derive.scss +71 -0
- package/src/tokens/_mixins.scss +9 -0
- 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 {
|
|
2
|
+
import { defineFragment } from '@fragments/core';
|
|
3
3
|
import { Dialog } from '.';
|
|
4
4
|
import { Button } from '../Button';
|
|
5
5
|
|
|
6
|
-
export default
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
|
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
|
-
|
|
64
|
-
type: '
|
|
65
|
-
description: '
|
|
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
|
-
|
|
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
|
+
});
|