@flikk/ui 1.0.0-beta.23 → 1.0.0-beta.25
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/dist/components/charts/AreaChart/AreaChart.types.d.ts +2 -2
- package/dist/components/charts/BarChart/BarChart.types.d.ts +2 -2
- package/dist/components/charts/ComboChart/ComboChart.types.d.ts +2 -2
- package/dist/components/charts/LineChart/LineChart.types.d.ts +2 -2
- package/dist/components/charts/ScatterPlot/ScatterPlot.types.d.ts +2 -2
- package/dist/components/charts/StackedBarChart/StackedBarChart.types.d.ts +2 -2
- package/dist/components/core/Accordion/Accordion.theme.js +2 -2
- package/dist/components/core/Accordion/AccordionTrigger.js +28 -1
- package/dist/components/core/Alert/Alert.js +2 -1
- package/dist/components/core/AlertDialog/AlertDialog.js +1 -1
- package/dist/components/core/Badge/Badge.js +6 -17
- package/dist/components/core/Breadcrumbs/Breadcrumbs.js +17 -28
- package/dist/components/core/Breadcrumbs/Breadcrumbs.theme.js +2 -2
- package/dist/components/core/Calendar/Calendar.js +1 -1
- package/dist/components/core/Calendar/CalendarMini/CalendarMini.js +4 -2
- package/dist/components/core/Calendar/CalendarMini/CalendarMini.theme.js +1 -1
- package/dist/components/core/Carousel/CarouselBody.js +6 -1
- package/dist/components/core/ContextMenu/ContextMenuBody.js +6 -3
- package/dist/components/core/ContextMenu/ContextMenuSubBody.js +6 -3
- package/dist/components/core/DragDrop/DragDrop.js +170 -17
- package/dist/components/core/DragDrop/DragDrop.types.d.ts +6 -0
- package/dist/components/core/Drawer/Drawer.d.ts +1 -1
- package/dist/components/core/Drawer/Drawer.js +2 -0
- package/dist/components/core/Drawer/Drawer.types.d.ts +2 -0
- package/dist/components/core/Drawer/DrawerTitle.d.ts +1 -1
- package/dist/components/core/Drawer/DrawerTitle.js +2 -2
- package/dist/components/core/Dropdown/DropdownMenu.js +2 -30
- package/dist/components/core/Empty/Empty.theme.js +1 -1
- package/dist/components/core/Link/Link.js +1 -1
- package/dist/components/core/Masonry/Masonry.js +7 -2
- package/dist/components/core/Modal/Modal.d.ts +1 -1
- package/dist/components/core/Modal/Modal.js +3 -3
- package/dist/components/core/Modal/Modal.types.d.ts +2 -0
- package/dist/components/core/Modal/ModalTitle.d.ts +1 -1
- package/dist/components/core/Modal/ModalTitle.js +2 -2
- package/dist/components/core/NavItem/NavItem.js +13 -8
- package/dist/components/core/Pagination/Pagination.js +1 -1
- package/dist/components/core/Popover/PopoverBody.js +3 -27
- package/dist/components/core/Popover/PopoverTrigger.js +1 -1
- package/dist/components/core/Progress/Progress.js +4 -4
- package/dist/components/core/Rating/Rating.js +1 -1
- package/dist/components/core/ScrollArea/ScrollArea.js +3 -2
- package/dist/components/core/Segmented/Segmented.js +6 -6
- package/dist/components/core/Segmented/SegmentedItem.js +28 -2
- package/dist/components/core/Skeleton/Skeleton.js +2 -2
- package/dist/components/core/Sortable/Sortable.d.ts +1 -1
- package/dist/components/core/Sortable/Sortable.js +20 -4
- package/dist/components/core/Sortable/Sortable.types.d.ts +2 -0
- package/dist/components/core/Tabs/Tabs.js +6 -4
- package/dist/components/core/Tabs/Tabs.types.d.ts +2 -0
- package/dist/components/core/Tabs/TabsBody.js +3 -3
- package/dist/components/core/Tabs/TabsList.js +19 -21
- package/dist/components/core/Tabs/TabsTrigger.js +47 -28
- package/dist/components/core/Toast/Toast.js +1 -1
- package/dist/components/core/Toast/ToastProvider.js +3 -2
- package/dist/components/core/Tooltip/Tooltip.js +25 -34
- package/dist/components/core/Tree/Tree.js +178 -8
- package/dist/components/data-display/GanttChart/GanttSplitter.js +15 -1
- package/dist/components/data-display/Table/Table.js +2 -2
- package/dist/components/data-display/Table/Table.types.d.ts +2 -0
- package/dist/components/data-display/Table/TableFilter.js +21 -18
- package/dist/components/data-display/Timeline/Timeline.animations.d.ts +0 -7
- package/dist/components/data-display/Timeline/Timeline.animations.js +1 -27
- package/dist/components/data-display/Timeline/Timeline.js +3 -4
- package/dist/components/data-display/Timeline/Timeline.types.d.ts +0 -13
- package/dist/components/data-display/Timeline/TimelineContent.js +3 -7
- package/dist/components/data-display/Timeline/TimelineItem.js +4 -17
- package/dist/components/data-display/Timeline/index.d.ts +2 -2
- package/dist/components/effects/CustomCursor/CustomCursor.js +3 -7
- package/dist/components/effects/Overlay/Overlay.js +4 -9
- package/dist/components/effects/PageTransition/PageTransition.js +17 -6
- package/dist/components/effects/SpotlightBorder/SpotlightBorder.js +3 -4
- package/dist/components/effects/index.d.ts +7 -0
- package/dist/components/effects/index.js +3 -0
- package/dist/components/forms/Checkbox/Checkbox.js +11 -1
- package/dist/components/forms/Checkbox/Checkbox.theme.js +4 -1
- package/dist/components/forms/Checkbox/Checkbox.types.d.ts +7 -5
- package/dist/components/forms/Checkbox/CheckboxGroup.js +9 -4
- package/dist/components/forms/ColorPicker/ColorPicker.js +7 -4
- package/dist/components/forms/ColorPicker/ColorPicker.types.d.ts +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerBody.js +5 -3
- package/dist/components/forms/ColorPicker/ColorPickerEyeDropper.js +3 -3
- package/dist/components/forms/ColorPicker/ColorPickerGradient.js +8 -8
- package/dist/components/forms/ColorPicker/ColorPickerInput.js +1 -1
- package/dist/components/forms/ColorPicker/index.d.ts +1 -1
- package/dist/components/forms/Combobox/Combobox.js +20 -45
- package/dist/components/forms/Combobox/Combobox.types.d.ts +4 -2
- package/dist/components/forms/CronInput/CronInput.js +10 -4
- package/dist/components/forms/CronInput/CronInput.types.d.ts +12 -1
- package/dist/components/forms/DatePicker/DatePicker.js +2 -2
- package/dist/components/forms/DatePicker/DatePicker.types.d.ts +4 -2
- package/dist/components/forms/DatePicker/DatePickerBody.js +1 -29
- package/dist/components/forms/DatePicker/DatePickerTrigger.js +2 -2
- package/dist/components/forms/DateRangePicker/DateRangePicker.js +3 -3
- package/dist/components/forms/DateRangePicker/DateRangePicker.types.d.ts +4 -2
- package/dist/components/forms/DateRangePicker/DateRangePickerBody.js +1 -28
- package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +2 -2
- package/dist/components/forms/FileUpload/FileUpload.js +14 -10
- package/dist/components/forms/FileUpload/FileUpload.types.d.ts +15 -0
- package/dist/components/forms/FormLabel/FormLabel.js +2 -2
- package/dist/components/forms/Input/Input.js +5 -3
- package/dist/components/forms/Input/Input.types.d.ts +2 -0
- package/dist/components/forms/InputAddress/InputAddress.d.ts +1 -1
- package/dist/components/forms/InputAddress/InputAddress.js +8 -9
- package/dist/components/forms/InputCounter/InputCounter.js +25 -18
- package/dist/components/forms/InputCounter/InputCounter.types.d.ts +8 -1
- package/dist/components/forms/InputCreditCard/InputCreditCard.d.ts +1 -1
- package/dist/components/forms/InputCreditCard/InputCreditCard.js +27 -9
- package/dist/components/forms/InputCreditCard/InputCreditCard.types.d.ts +5 -0
- package/dist/components/forms/InputOTP/InputOTP.js +26 -17
- package/dist/components/forms/InputOTP/InputOTP.types.d.ts +21 -4
- package/dist/components/forms/InputTag/InputTag.js +12 -39
- package/dist/components/forms/InputTag/InputTag.types.d.ts +2 -2
- package/dist/components/forms/Mention/Mention.js +13 -6
- package/dist/components/forms/Mention/Mention.types.d.ts +9 -0
- package/dist/components/forms/Radio/Radio.js +12 -2
- package/dist/components/forms/Radio/Radio.theme.js +5 -1
- package/dist/components/forms/Radio/Radio.types.d.ts +7 -5
- package/dist/components/forms/Radio/RadioGroup.js +8 -3
- package/dist/components/forms/RichTextEditor/RichTextEditor.js +21 -21
- package/dist/components/forms/RichTextEditor/RichTextEditor.types.d.ts +2 -2
- package/dist/components/forms/Select/Select.d.ts +2 -0
- package/dist/components/forms/Select/Select.js +27 -57
- package/dist/components/forms/Select/Select.types.d.ts +14 -0
- package/dist/components/forms/Signature/Signature.js +1 -1
- package/dist/components/forms/Signature/Signature.types.d.ts +3 -3
- package/dist/components/forms/Slider/Slider.js +18 -4
- package/dist/components/forms/Slider/Slider.types.d.ts +2 -2
- package/dist/components/forms/Switch/Switch.js +12 -19
- package/dist/components/forms/Switch/Switch.theme.js +12 -2
- package/dist/components/forms/Switch/Switch.types.d.ts +20 -2
- package/dist/components/forms/Textarea/Textarea.js +8 -3
- package/dist/components/forms/Textarea/Textarea.types.d.ts +2 -0
- package/dist/components/forms/TimePicker/TimePicker.js +1 -1
- package/dist/components/forms/TimePicker/TimePicker.types.d.ts +6 -2
- package/dist/components/forms/TimePicker/TimePickerContent.js +2 -29
- package/dist/components/forms/TimePicker/TimePickerTrigger.js +2 -2
- package/dist/components/forms/index.js +1 -1
- package/dist/components/layout/index.js +4 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useClickOutside.d.ts +10 -4
- package/dist/hooks/useClickOutside.js +44 -24
- package/dist/hooks/useDarkMode.d.ts +1 -0
- package/dist/hooks/useDarkMode.js +42 -0
- package/dist/hooks/useFormStateMachine.d.ts +1 -1
- package/dist/hooks/useNetworkStatus.js +1 -1
- package/dist/hooks/useOverlay.js +4 -0
- package/dist/hooks/useScaleBackground.js +5 -2
- package/dist/hooks/useSelectPortal.js +14 -5
- package/dist/index.d.ts +0 -2
- package/dist/index.js +1 -57
- package/dist/shadcn-compat.css +80 -0
- package/dist/styles.css +1 -1
- package/dist/utils/stateMachine.d.ts +4 -4
- package/package.json +11 -10
- package/tailwind.preset.cjs +32 -3
- /package/{src/theme-plugin.css → dist/theme.css} +0 -0
|
@@ -13,9 +13,12 @@ const FieldSelect = ({ label, value, options, onChange, disabled, size = "md", c
|
|
|
13
13
|
// ============================================================================
|
|
14
14
|
// Main CronInput Component
|
|
15
15
|
// ============================================================================
|
|
16
|
-
const CronInput = React__default.forwardRef(({ value: controlledValue, defaultValue = "* * * * *", onChange, showPresets = true, showDescription = true, showNextRuns = true, nextRunCount = 3, size = "md", mode = "both", className, theme: themeOverrides, disabled = false, label, ...props }, ref) => {
|
|
16
|
+
const CronInput = React__default.forwardRef(({ value: controlledValue, defaultValue = "* * * * *", onChange, showPresets = true, showDescription = true, showNextRuns = true, nextRunCount = 3, size = "md", mode = "both", className, theme: themeOverrides, state = "default", disabled = false, label, helperText, ...props }, ref) => {
|
|
17
17
|
// Unique ID for accessibility
|
|
18
18
|
const generatedId = useId();
|
|
19
|
+
// Derive disabled/invalid from `state` prop (with deprecated `disabled` as fallback)
|
|
20
|
+
const isDisabled = state === "disabled" || disabled;
|
|
21
|
+
const isInvalid = state === "invalid";
|
|
19
22
|
// Controlled vs uncontrolled state
|
|
20
23
|
const isControlled = controlledValue !== undefined;
|
|
21
24
|
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
@@ -88,10 +91,10 @@ const CronInput = React__default.forwardRef(({ value: controlledValue, defaultVa
|
|
|
88
91
|
}, [rawInput, cronValue, updateValue]);
|
|
89
92
|
// Handle preset click
|
|
90
93
|
const handlePresetClick = useCallback((presetValue) => {
|
|
91
|
-
if (
|
|
94
|
+
if (isDisabled)
|
|
92
95
|
return;
|
|
93
96
|
updateValue(presetValue);
|
|
94
|
-
}, [
|
|
97
|
+
}, [isDisabled, updateValue]);
|
|
95
98
|
// Handle Enter key in raw input
|
|
96
99
|
const handleRawKeyDown = useCallback((e) => {
|
|
97
100
|
if (e.key === "Enter") {
|
|
@@ -100,7 +103,10 @@ const CronInput = React__default.forwardRef(({ value: controlledValue, defaultVa
|
|
|
100
103
|
}, [handleRawInputBlur]);
|
|
101
104
|
const showVisual = mode === "visual" || (mode === "both" && activeMode === "visual");
|
|
102
105
|
const showRaw = mode === "raw" || (mode === "both" && activeMode === "raw");
|
|
103
|
-
|
|
106
|
+
const helperId = `${generatedId}-helper`;
|
|
107
|
+
return (jsxs("div", { ref: ref, className: cn(theme.baseStyle, className), role: "group", "aria-label": label || "Cron expression builder", ...props, children: [label && (jsx(FormLabel, { htmlFor: `${generatedId}-cron`, state: isDisabled ? "disabled" : isInvalid ? "invalid" : "default", children: label })), mode === "both" && (jsxs("div", { className: "flex items-center gap-1.5", children: [jsxs("button", { type: "button", onClick: () => setActiveMode("visual"), disabled: isDisabled, className: cn(theme.modeToggleStyle, activeMode === "visual" && theme.modeToggleActiveStyle), "aria-pressed": activeMode === "visual", children: [jsx(CalendarIcon, { className: "w-3.5 h-3.5 mr-1", "aria-hidden": "true" }), "Visual"] }), jsxs("button", { type: "button", onClick: () => setActiveMode("raw"), disabled: isDisabled, className: cn(theme.modeToggleStyle, activeMode === "raw" && theme.modeToggleActiveStyle), "aria-pressed": activeMode === "raw", children: [jsx(ClockIcon, { className: "w-3.5 h-3.5 mr-1", "aria-hidden": "true" }), "Cron"] })] })), showPresets && (jsxs("div", { children: [jsx("div", { className: cn(theme.sectionHeadingStyle, "mb-1.5"), children: "Presets" }), jsx("div", { className: "flex flex-wrap gap-1.5", children: CRON_PRESETS.map((preset) => (jsx("button", { type: "button", onClick: () => handlePresetClick(preset.value), disabled: isDisabled, className: cn(theme.presetButtonStyle, activePreset === preset.value && theme.presetButtonActiveStyle), title: preset.description, "aria-pressed": activePreset === preset.value, children: preset.label }, preset.value))) })] })), showVisual && (jsxs("div", { className: cn(theme.builderStyle, isInvalid && "ring-1 ring-[var(--color-danger)] rounded-[var(--form-radius)]"), children: [jsx(FieldSelect, { label: "Minute", value: fields.minute, options: minuteOptions, onChange: (v) => handleFieldChange("minute", v), disabled: isDisabled, size: size }), jsx(FieldSelect, { label: "Hour", value: fields.hour, options: hourOptions, onChange: (v) => handleFieldChange("hour", v), disabled: isDisabled, size: size }), jsx(FieldSelect, { label: "Day of Month", value: fields.dayOfMonth, options: dayOfMonthOptions, onChange: (v) => handleFieldChange("dayOfMonth", v), disabled: isDisabled, size: size }), jsx(FieldSelect, { label: "Month", value: fields.month, options: monthOptions, onChange: (v) => handleFieldChange("month", v), disabled: isDisabled, size: size }), jsx(FieldSelect, { label: "Day of Week", value: fields.dayOfWeek, options: dayOfWeekOptions, onChange: (v) => handleFieldChange("dayOfWeek", v), disabled: isDisabled, size: size })] })), showRaw && (jsxs("div", { children: [jsx("div", { className: cn(theme.sectionHeadingStyle, "mb-1.5"), children: "Cron Expression" }), jsx("input", { type: "text", value: rawInput, onChange: handleRawInputChange, onBlur: handleRawInputBlur, onKeyDown: handleRawKeyDown, disabled: isDisabled, placeholder: "* * * * *", className: cn(theme.rawInputStyle, theme.rawInputSizes[size], isInvalid && "border-[var(--color-danger)] focus:border-[var(--color-danger)] focus:ring-[var(--color-danger)]"), id: `${generatedId}-cron`, "aria-label": "Cron expression", "aria-invalid": isInvalid || !validateCron(rawInput), "aria-describedby": helperText ? helperId : undefined, spellCheck: false, autoComplete: "off" }), !validateCron(rawInput) && rawInput !== cronValue && (jsx("div", { className: cn(theme.errorStyle, "mt-1"), children: "Invalid cron expression. Format: minute hour day month weekday" }))] })), showVisual && !showRaw && (jsxs("div", { className: "flex items-center gap-2", children: [jsx("span", { className: cn(theme.fieldLabelStyle, "shrink-0"), children: "Expression:" }), jsx("code", { className: cn("font-mono text-sm px-2 py-1 rounded-[var(--form-radius)]", "bg-[var(--color-background-secondary)] text-[var(--color-text-primary)]", "dark:bg-[var(--color-neutral-800)] dark:text-[var(--color-neutral-200)]"), children: cronValue })] })), showDescription && isValid && (jsxs("div", { className: theme.descriptionStyle, children: [jsx(ClockIcon, { className: "w-4 h-4 shrink-0", "aria-hidden": "true" }), jsx("span", { children: description })] })), showNextRuns && isValid && nextRuns.length > 0 && (jsxs("div", { className: theme.nextRunsStyle, children: [jsx("div", { className: cn(theme.sectionHeadingStyle, "mb-1"), children: "Next runs" }), nextRuns.map((date, index) => (jsxs("div", { className: theme.nextRunItemStyle, children: [jsx(CalendarIcon, { className: "w-3.5 h-3.5 shrink-0", "aria-hidden": "true" }), jsx("span", { children: formatNextRun(date) })] }, index)))] })), helperText && (jsx("div", { id: helperId, className: cn("text-sm mt-1", isInvalid
|
|
108
|
+
? "text-[var(--color-danger)]"
|
|
109
|
+
: "text-[var(--color-text-muted)]"), children: helperText }))] }));
|
|
104
110
|
});
|
|
105
111
|
CronInput.displayName = "CronInput";
|
|
106
112
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
export type CronInputState = "default" | "disabled" | "invalid";
|
|
2
3
|
/**
|
|
3
4
|
* CronInput component props
|
|
4
5
|
* A visual cron expression builder that outputs standard cron syntax
|
|
@@ -26,10 +27,20 @@ export interface CronInputProps extends Omit<React.HTMLAttributes<HTMLDivElement
|
|
|
26
27
|
className?: string;
|
|
27
28
|
/** Theme overrides for sub-component styling */
|
|
28
29
|
theme?: CronInputThemeOverrides;
|
|
29
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Validation/interaction state
|
|
32
|
+
* @default "default"
|
|
33
|
+
*/
|
|
34
|
+
state?: CronInputState;
|
|
35
|
+
/**
|
|
36
|
+
* Whether the input is disabled
|
|
37
|
+
* @deprecated Use `state="disabled"` instead
|
|
38
|
+
*/
|
|
30
39
|
disabled?: boolean;
|
|
31
40
|
/** Label text for the component */
|
|
32
41
|
label?: string;
|
|
42
|
+
/** Helper text displayed below the component */
|
|
43
|
+
helperText?: React.ReactNode;
|
|
33
44
|
}
|
|
34
45
|
export type CronInputSize = "sm" | "md" | "lg";
|
|
35
46
|
export type CronInputMode = "visual" | "raw" | "both";
|
|
@@ -23,7 +23,7 @@ const formatDate = (date, formatString, options = {
|
|
|
23
23
|
return formatDateCustom(date, formatString);
|
|
24
24
|
}
|
|
25
25
|
// Otherwise use the Intl API
|
|
26
|
-
return date.toLocaleDateString(
|
|
26
|
+
return date.toLocaleDateString('en-US', options);
|
|
27
27
|
};
|
|
28
28
|
/**
|
|
29
29
|
* Format date range for display
|
|
@@ -120,7 +120,7 @@ const DatePickerComponent = React__default.forwardRef(({ id, name, mode = "singl
|
|
|
120
120
|
theme,
|
|
121
121
|
triggerRef,
|
|
122
122
|
contentRef,
|
|
123
|
-
}), [mode, isOpen, selectedValue, minDate, maxDate, isDateDisabled, showToday, selectTodayByDefault, weekdays, months, yearRange, handleDateSelect, handleRangeSelect, size, state, theme]), children: jsxs("div", { ref: ref, className: cn(containerStyle, wrapperClassName), children: [label && (jsx(FormLabel, { htmlFor: datePickerId, required: required, children: label })), jsx(DatePickerTrigger, { id: datePickerId, disabled: disabled, isOpen: isOpen, state: state, size: size, onClick: handleButtonClick, onFocus: onFocus, onBlur: onBlur, className: className, children: jsx("span", { className: cn("block truncate", selectedValue ? theme.valueTextStyle : theme.placeholderTextStyle), children: displayValue }) }), jsx(DatePickerBody, { isOpen: isOpen, onClose: () => setIsOpen(false), portal: portal, placement: placement, offset: offset, presets: presets, children: children }), helperText && (jsx("p", { className: cn(helperTextStyle, helperTextStateStyle), children: helperText })), name && mode === "single" && selectedValue instanceof Date && (jsx("input", { type: "hidden", name: name, value: selectedValue.toISOString() })), name &&
|
|
123
|
+
}), [mode, isOpen, selectedValue, minDate, maxDate, isDateDisabled, showToday, selectTodayByDefault, weekdays, months, yearRange, handleDateSelect, handleRangeSelect, size, state, theme]), children: jsxs("div", { ref: ref, className: cn(containerStyle, wrapperClassName), children: [label && (typeof label === 'string' ? (jsx(FormLabel, { htmlFor: datePickerId, state: state, required: required, children: label })) : (label)), jsx(DatePickerTrigger, { id: datePickerId, disabled: disabled, isOpen: isOpen, state: state, size: size, onClick: handleButtonClick, onFocus: onFocus, onBlur: onBlur, className: className, "aria-invalid": state === 'invalid' || undefined, "aria-describedby": helperText ? `${datePickerId}-helper` : undefined, children: jsx("span", { className: cn("block truncate", selectedValue ? theme.valueTextStyle : theme.placeholderTextStyle), children: displayValue }) }), jsx(DatePickerBody, { isOpen: isOpen, onClose: () => setIsOpen(false), portal: portal, placement: placement, offset: offset, presets: presets, children: children }), helperText && (typeof helperText === 'string' ? (jsx("p", { id: `${datePickerId}-helper`, className: cn(helperTextStyle, helperTextStateStyle), children: helperText })) : (helperText)), name && mode === "single" && selectedValue instanceof Date && (jsx("input", { type: "hidden", name: name, value: selectedValue.toISOString() })), name &&
|
|
124
124
|
mode === "range" &&
|
|
125
125
|
selectedValue &&
|
|
126
126
|
typeof selectedValue === "object" &&
|
|
@@ -49,9 +49,9 @@ export interface DatePickerProps extends Omit<React.HTMLAttributes<HTMLDivElemen
|
|
|
49
49
|
/** Whether the field is required */
|
|
50
50
|
required?: boolean;
|
|
51
51
|
/** Label for the field */
|
|
52
|
-
label?:
|
|
52
|
+
label?: React.ReactNode;
|
|
53
53
|
/** Helper text below the field */
|
|
54
|
-
helperText?:
|
|
54
|
+
helperText?: React.ReactNode;
|
|
55
55
|
/** Callback fired when a date is selected - type depends on mode */
|
|
56
56
|
onChange?: (value: Date | DateRange | null) => void;
|
|
57
57
|
/** Array of preset date ranges (only for range mode) */
|
|
@@ -110,6 +110,8 @@ export interface DatePickerTriggerProps {
|
|
|
110
110
|
onFocus?: (event: React.FocusEvent<HTMLButtonElement>) => void;
|
|
111
111
|
onBlur?: (event: React.FocusEvent<HTMLButtonElement>) => void;
|
|
112
112
|
children?: React.ReactNode;
|
|
113
|
+
"aria-invalid"?: boolean;
|
|
114
|
+
"aria-describedby"?: string;
|
|
113
115
|
}
|
|
114
116
|
/**
|
|
115
117
|
* DatePicker body props (calendar dropdown content)
|
|
@@ -12,36 +12,8 @@ import { useSelectPortal } from '../../../hooks/useSelectPortal.js';
|
|
|
12
12
|
import { formatDateCustom } from '../../../utils/dateUtils.js';
|
|
13
13
|
import { createDropdownAnimations } from './DatePicker.animations.js';
|
|
14
14
|
import { useIsClient } from '../../../hooks/useIsClient.js';
|
|
15
|
+
import { useDarkMode } from '../../../hooks/useDarkMode.js';
|
|
15
16
|
|
|
16
|
-
/**
|
|
17
|
-
* Hook to detect dark mode state from document
|
|
18
|
-
*/
|
|
19
|
-
const useDarkMode = () => {
|
|
20
|
-
const [isDark, setIsDark] = useState(false);
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
if (typeof document === "undefined")
|
|
23
|
-
return;
|
|
24
|
-
const checkDarkMode = () => {
|
|
25
|
-
const hasDarkClass = document.documentElement.classList.contains("dark") ||
|
|
26
|
-
document.body.classList.contains("dark") ||
|
|
27
|
-
document.querySelector(".dark") !== null;
|
|
28
|
-
setIsDark(hasDarkClass);
|
|
29
|
-
};
|
|
30
|
-
checkDarkMode();
|
|
31
|
-
// Observe class changes on html and body
|
|
32
|
-
const observer = new MutationObserver(checkDarkMode);
|
|
33
|
-
observer.observe(document.documentElement, {
|
|
34
|
-
attributes: true,
|
|
35
|
-
attributeFilter: ["class"],
|
|
36
|
-
});
|
|
37
|
-
observer.observe(document.body, {
|
|
38
|
-
attributes: true,
|
|
39
|
-
attributeFilter: ["class"],
|
|
40
|
-
});
|
|
41
|
-
return () => observer.disconnect();
|
|
42
|
-
}, []);
|
|
43
|
-
return isDark;
|
|
44
|
-
};
|
|
45
17
|
/**
|
|
46
18
|
* DatePicker Body Component - contains the Calendar and optional Presets
|
|
47
19
|
* Uses portal positioning like Select component
|
|
@@ -8,7 +8,7 @@ import { DatePickerContext } from './DatePicker.js';
|
|
|
8
8
|
* DatePicker Trigger Component - styled like form input
|
|
9
9
|
* Displays selected date or placeholder text
|
|
10
10
|
*/
|
|
11
|
-
const DatePickerTrigger = ({ id, disabled, isOpen, state = "default", size = "md", className, onClick, onFocus, onBlur, children, }) => {
|
|
11
|
+
const DatePickerTrigger = ({ id, disabled, isOpen, state = "default", size = "md", className, onClick, onFocus, onBlur, children, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedby, }) => {
|
|
12
12
|
var _a, _b, _c, _d;
|
|
13
13
|
const context = useContext(DatePickerContext);
|
|
14
14
|
if (!context) {
|
|
@@ -28,7 +28,7 @@ const DatePickerTrigger = ({ id, disabled, isOpen, state = "default", size = "md
|
|
|
28
28
|
? focusStateStyle ||
|
|
29
29
|
"outline-2 outline-primary-600 -outline-offset-1 ring-4 ring-[var(--color-primary)]/10"
|
|
30
30
|
: "";
|
|
31
|
-
return (jsx("div", { className: "relative w-full", children: jsxs("button", { ref: triggerRef, id: id, type: "button", className: cn(baseStyle, sizeStyle, stateStyle, hoverStateStyle, activeFocusState, iconEndPadding, triggerButtonStyle, "text-left", className), onClick: onClick, onFocus: onFocus, onBlur: onBlur, disabled: disabled, "aria-expanded": isOpen, "aria-haspopup": "dialog", "aria-label": "Open calendar", children: [children, jsx("span", { className: iconEndStyle, children: jsx(CalendarIcon, { className: "h-4 w-4" }) })] }) }));
|
|
31
|
+
return (jsx("div", { className: "relative w-full", children: jsxs("button", { ref: triggerRef, id: id, type: "button", className: cn(baseStyle, sizeStyle, stateStyle, hoverStateStyle, activeFocusState, iconEndPadding, triggerButtonStyle, "text-left", className), onClick: onClick, onFocus: onFocus, onBlur: onBlur, disabled: disabled, "aria-expanded": isOpen, "aria-haspopup": "dialog", "aria-label": "Open calendar", "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedby, children: [children, jsx("span", { className: iconEndStyle, children: jsx(CalendarIcon, { className: "h-4 w-4" }) })] }) }));
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
export { DatePickerTrigger };
|
|
@@ -21,7 +21,7 @@ const formatDate = (date, formatString, options = {
|
|
|
21
21
|
if (formatString) {
|
|
22
22
|
return formatDateCustom(date, formatString);
|
|
23
23
|
}
|
|
24
|
-
return date.toLocaleDateString(
|
|
24
|
+
return date.toLocaleDateString('en-US', options);
|
|
25
25
|
};
|
|
26
26
|
/**
|
|
27
27
|
* Format date range for display
|
|
@@ -96,9 +96,9 @@ const DateRangePickerComponent = React__default.forwardRef(({ id, name, size = "
|
|
|
96
96
|
size,
|
|
97
97
|
state,
|
|
98
98
|
theme,
|
|
99
|
-
]), children: jsxs("div", { ref: ref, className: cn(theme.containerStyle, wrapperClassName), ...rest, children: [label && (jsx(FormLabel, { htmlFor: pickerId, required: required, children: label })), jsx(DateRangePickerTrigger, { id: pickerId, disabled: disabled, isOpen: isOpen, state: state, size: size, onClick: handleButtonClick, onFocus: onFocus, onBlur: onBlur, className: className, children: jsx("span", { className: cn("block truncate", selectedRange && (selectedRange.start || selectedRange.end)
|
|
99
|
+
]), children: jsxs("div", { ref: ref, className: cn(theme.containerStyle, wrapperClassName), ...rest, children: [label && (typeof label === 'string' ? (jsx(FormLabel, { htmlFor: pickerId, state: state, required: required, children: label })) : (label)), jsx(DateRangePickerTrigger, { id: pickerId, disabled: disabled, isOpen: isOpen, state: state, size: size, onClick: handleButtonClick, onFocus: onFocus, onBlur: onBlur, className: className, "aria-invalid": state === 'invalid' || undefined, "aria-describedby": helperText ? `${pickerId}-helper` : undefined, children: jsx("span", { className: cn("block truncate", selectedRange && (selectedRange.start || selectedRange.end)
|
|
100
100
|
? theme.valueTextStyle
|
|
101
|
-
: theme.placeholderTextStyle), children: displayValue }) }), jsx(DateRangePickerBody, { isOpen: isOpen, onClose: () => setIsOpen(false), portal: portal, placement: placement, offset: offset, presets: presets, showPresets: showPresets }), helperText && (jsx("p", { className: cn(theme.helperTextStyle, theme.helperTextStates[state]), children: helperText })), name && selectedRange && (jsxs(Fragment, { children: [jsx("input", { type: "hidden", name: `${name}[start]`, value: ((_a = selectedRange.start) === null || _a === void 0 ? void 0 : _a.toISOString()) || "" }), jsx("input", { type: "hidden", name: `${name}[end]`, value: ((_b = selectedRange.end) === null || _b === void 0 ? void 0 : _b.toISOString()) || "" })] }))] }) }));
|
|
101
|
+
: theme.placeholderTextStyle), children: displayValue }) }), jsx(DateRangePickerBody, { isOpen: isOpen, onClose: () => setIsOpen(false), portal: portal, placement: placement, offset: offset, presets: presets, showPresets: showPresets }), helperText && (typeof helperText === 'string' ? (jsx("p", { id: `${pickerId}-helper`, className: cn(theme.helperTextStyle, theme.helperTextStates[state]), children: helperText })) : (helperText)), name && selectedRange && (jsxs(Fragment, { children: [jsx("input", { type: "hidden", name: `${name}[start]`, value: ((_a = selectedRange.start) === null || _a === void 0 ? void 0 : _a.toISOString()) || "" }), jsx("input", { type: "hidden", name: `${name}[end]`, value: ((_b = selectedRange.end) === null || _b === void 0 ? void 0 : _b.toISOString()) || "" })] }))] }) }));
|
|
102
102
|
});
|
|
103
103
|
DateRangePickerComponent.displayName = "DateRangePicker";
|
|
104
104
|
// Compound component
|
|
@@ -44,9 +44,9 @@ export interface DateRangePickerProps extends Omit<React.HTMLAttributes<HTMLDivE
|
|
|
44
44
|
/** Whether the field is required */
|
|
45
45
|
required?: boolean;
|
|
46
46
|
/** Label for the field */
|
|
47
|
-
label?:
|
|
47
|
+
label?: React.ReactNode;
|
|
48
48
|
/** Helper text below the field */
|
|
49
|
-
helperText?:
|
|
49
|
+
helperText?: React.ReactNode;
|
|
50
50
|
/** Callback fired when a range is applied */
|
|
51
51
|
onChange?: (range: DateRange | null) => void;
|
|
52
52
|
/** Array of preset date ranges */
|
|
@@ -103,6 +103,8 @@ export interface DateRangePickerTriggerProps {
|
|
|
103
103
|
onFocus?: (event: React.FocusEvent<HTMLButtonElement>) => void;
|
|
104
104
|
onBlur?: (event: React.FocusEvent<HTMLButtonElement>) => void;
|
|
105
105
|
children?: React.ReactNode;
|
|
106
|
+
"aria-invalid"?: boolean;
|
|
107
|
+
"aria-describedby"?: string;
|
|
106
108
|
}
|
|
107
109
|
/**
|
|
108
110
|
* DateRangePicker body props
|
|
@@ -13,35 +13,8 @@ import { formatDateCustom } from '../../../utils/dateUtils.js';
|
|
|
13
13
|
import { createDropdownAnimations } from '../DatePicker/DatePicker.animations.js';
|
|
14
14
|
import { useIsClient } from '../../../hooks/useIsClient.js';
|
|
15
15
|
import { useMediaQuery } from '../../../hooks/useMediaQuery.js';
|
|
16
|
+
import { useDarkMode } from '../../../hooks/useDarkMode.js';
|
|
16
17
|
|
|
17
|
-
/**
|
|
18
|
-
* Hook to detect dark mode state from document
|
|
19
|
-
*/
|
|
20
|
-
const useDarkMode = () => {
|
|
21
|
-
const [isDark, setIsDark] = useState(false);
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
if (typeof document === "undefined")
|
|
24
|
-
return;
|
|
25
|
-
const checkDarkMode = () => {
|
|
26
|
-
const hasDarkClass = document.documentElement.classList.contains("dark") ||
|
|
27
|
-
document.body.classList.contains("dark") ||
|
|
28
|
-
document.querySelector(".dark") !== null;
|
|
29
|
-
setIsDark(hasDarkClass);
|
|
30
|
-
};
|
|
31
|
-
checkDarkMode();
|
|
32
|
-
const observer = new MutationObserver(checkDarkMode);
|
|
33
|
-
observer.observe(document.documentElement, {
|
|
34
|
-
attributes: true,
|
|
35
|
-
attributeFilter: ["class"],
|
|
36
|
-
});
|
|
37
|
-
observer.observe(document.body, {
|
|
38
|
-
attributes: true,
|
|
39
|
-
attributeFilter: ["class"],
|
|
40
|
-
});
|
|
41
|
-
return () => observer.disconnect();
|
|
42
|
-
}, []);
|
|
43
|
-
return isDark;
|
|
44
|
-
};
|
|
45
18
|
/**
|
|
46
19
|
* DateRangePicker Body Component — dual calendars + presets sidebar + footer
|
|
47
20
|
*/
|
|
@@ -7,7 +7,7 @@ import { DateRangePickerContext } from './DateRangePicker.js';
|
|
|
7
7
|
/**
|
|
8
8
|
* DateRangePicker Trigger Component — styled like form input
|
|
9
9
|
*/
|
|
10
|
-
const DateRangePickerTrigger = ({ id, disabled, isOpen, state = "default", size = "md", className, onClick, onFocus, onBlur, children, }) => {
|
|
10
|
+
const DateRangePickerTrigger = ({ id, disabled, isOpen, state = "default", size = "md", className, onClick, onFocus, onBlur, children, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedby, }) => {
|
|
11
11
|
var _a, _b, _c, _d;
|
|
12
12
|
const context = useContext(DateRangePickerContext);
|
|
13
13
|
if (!context) {
|
|
@@ -26,7 +26,7 @@ const DateRangePickerTrigger = ({ id, disabled, isOpen, state = "default", size
|
|
|
26
26
|
? focusStateStyle ||
|
|
27
27
|
"outline-2 outline-primary-600 -outline-offset-1 ring-4 ring-[var(--color-primary)]/10"
|
|
28
28
|
: "";
|
|
29
|
-
return (jsx("div", { className: "relative w-full", children: jsxs("button", { ref: triggerRef, id: id, type: "button", className: cn(baseStyle, sizeStyle, stateStyle, hoverStateStyle, activeFocusState, iconEndPadding, triggerButtonStyle, "text-left", className), onClick: onClick, onFocus: onFocus, onBlur: onBlur, disabled: disabled, "aria-expanded": isOpen, "aria-haspopup": "dialog", "aria-label": "Open date range picker", children: [children, jsx("span", { className: iconEndStyle, children: jsx(CalendarIcon, { className: "h-4 w-4" }) })] }) }));
|
|
29
|
+
return (jsx("div", { className: "relative w-full", children: jsxs("button", { ref: triggerRef, id: id, type: "button", className: cn(baseStyle, sizeStyle, stateStyle, hoverStateStyle, activeFocusState, iconEndPadding, triggerButtonStyle, "text-left", className), onClick: onClick, onFocus: onFocus, onBlur: onBlur, disabled: disabled, "aria-expanded": isOpen, "aria-haspopup": "dialog", "aria-label": "Open date range picker", "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedby, children: [children, jsx("span", { className: iconEndStyle, children: jsx(CalendarIcon, { className: "h-4 w-4" }) })] }) }));
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
export { DateRangePickerTrigger };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
-
import React__default, { useMemo, useRef, useState, useCallback } from 'react';
|
|
2
|
+
import React__default, { useMemo, useId, useRef, useState, useCallback } from 'react';
|
|
3
3
|
import { fileUploadTheme } from './FileUpload.theme.js';
|
|
4
4
|
import { cn } from '../../../utils/cn.js';
|
|
5
5
|
import { CloudArrowUpIcon, DocumentIcon, XMarkIcon } from '@heroicons/react/24/outline';
|
|
@@ -89,6 +89,7 @@ import '../../core/ContextMenu/ContextMenu.js';
|
|
|
89
89
|
import '../../core/ContextMenu/ContextMenu.theme.js';
|
|
90
90
|
import '../../core/Pill/Pill.js';
|
|
91
91
|
import '../../core/Icon/Icon.js';
|
|
92
|
+
import { FormLabel } from '../FormLabel/FormLabel.js';
|
|
92
93
|
|
|
93
94
|
/**
|
|
94
95
|
* FileUpload component - Drag-and-drop file uploader with previews
|
|
@@ -110,11 +111,14 @@ import '../../core/Icon/Icon.js';
|
|
|
110
111
|
* onUpload={(files) => console.log(files)}
|
|
111
112
|
* />
|
|
112
113
|
*/
|
|
113
|
-
const FileUpload = React__default.forwardRef(({ accept, multiple = false, maxSize, maxFiles, disabled = false, onUpload, onRemove, onError, showPreview = true, buttonText = "Choose files", helperText = "or drag and drop", description, className, theme: customTheme, ...props }, ref) => {
|
|
114
|
+
const FileUpload = React__default.forwardRef(({ accept, multiple = false, maxSize, maxFiles, state = "default", disabled = false, label, errorMessage, onUpload, onRemove, onError, showPreview = true, buttonText = "Choose files", helperText = "or drag and drop", description, className, theme: customTheme, ...props }, ref) => {
|
|
114
115
|
// Merge custom theme with default theme
|
|
115
116
|
const theme = useMemo(() => customTheme
|
|
116
117
|
? { ...fileUploadTheme, ...customTheme }
|
|
117
118
|
: fileUploadTheme, [customTheme]);
|
|
119
|
+
const generatedId = useId();
|
|
120
|
+
const isDisabled = state === "disabled" || disabled;
|
|
121
|
+
const isInvalid = state === "invalid";
|
|
118
122
|
const inputRef = useRef(null);
|
|
119
123
|
const [isDragActive, setIsDragActive] = useState(false);
|
|
120
124
|
const [selectedFiles, setSelectedFiles] = useState([]);
|
|
@@ -229,9 +233,9 @@ const FileUpload = React__default.forwardRef(({ accept, multiple = false, maxSiz
|
|
|
229
233
|
const handleDragEnter = useCallback((e) => {
|
|
230
234
|
e.preventDefault();
|
|
231
235
|
e.stopPropagation();
|
|
232
|
-
if (!
|
|
236
|
+
if (!isDisabled)
|
|
233
237
|
setIsDragActive(true);
|
|
234
|
-
}, [
|
|
238
|
+
}, [isDisabled]);
|
|
235
239
|
const handleDragLeave = useCallback((e) => {
|
|
236
240
|
e.preventDefault();
|
|
237
241
|
e.stopPropagation();
|
|
@@ -245,18 +249,18 @@ const FileUpload = React__default.forwardRef(({ accept, multiple = false, maxSiz
|
|
|
245
249
|
e.preventDefault();
|
|
246
250
|
e.stopPropagation();
|
|
247
251
|
setIsDragActive(false);
|
|
248
|
-
if (
|
|
252
|
+
if (isDisabled)
|
|
249
253
|
return;
|
|
250
254
|
const files = e.dataTransfer.files;
|
|
251
255
|
handleFiles(files);
|
|
252
|
-
}, [
|
|
256
|
+
}, [isDisabled, handleFiles]);
|
|
253
257
|
// Handle click to select files
|
|
254
258
|
const handleClick = useCallback(() => {
|
|
255
259
|
var _a;
|
|
256
|
-
if (!
|
|
260
|
+
if (!isDisabled) {
|
|
257
261
|
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.click();
|
|
258
262
|
}
|
|
259
|
-
}, [
|
|
263
|
+
}, [isDisabled]);
|
|
260
264
|
// Handle input change
|
|
261
265
|
const handleInputChange = useCallback((e) => {
|
|
262
266
|
handleFiles(e.target.files);
|
|
@@ -278,12 +282,12 @@ const FileUpload = React__default.forwardRef(({ accept, multiple = false, maxSiz
|
|
|
278
282
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
279
283
|
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
|
|
280
284
|
};
|
|
281
|
-
return (jsxs("div", { ref: ref, className: cn(theme.containerStyle, className), ...props, children: [jsx("input", { ref: inputRef, type: "file", accept: acceptString, multiple: multiple, onChange: handleInputChange, disabled:
|
|
285
|
+
return (jsxs("div", { ref: ref, className: cn(theme.containerStyle, className), ...props, children: [label && (typeof label === "string" ? (jsx(FormLabel, { htmlFor: `${generatedId}-input`, state: isDisabled ? "disabled" : isInvalid ? "invalid" : "default", children: label })) : (label)), jsx("input", { ref: inputRef, id: `${generatedId}-input`, type: "file", accept: acceptString, multiple: multiple, onChange: handleInputChange, disabled: isDisabled, className: "sr-only", "aria-label": "File upload" }), jsx("div", { className: cn(theme.dropzoneStyle, isDragActive && theme.dropzoneActiveStyle, isDisabled && theme.dropzoneDisabledStyle, isInvalid && "border-[var(--color-danger)]"), onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, onClick: handleClick, role: "button", tabIndex: isDisabled ? -1 : 0, "aria-disabled": isDisabled, "aria-invalid": isInvalid || undefined, onKeyDown: (e) => {
|
|
282
286
|
if (e.key === "Enter" || e.key === " ") {
|
|
283
287
|
e.preventDefault();
|
|
284
288
|
handleClick();
|
|
285
289
|
}
|
|
286
|
-
}, children: jsxs("div", { className: theme.contentStyle, children: [jsx("div", { className: "size-11 border border-border bg-white rounded-full flex items-center justify-center mb-4", children: jsx(CloudArrowUpIcon, { className: `size-6 text-[var(--color-text-secondary)]` }) }), jsxs("p", { className: "text-base font-medium text-[var(--color-text-primary)]", children: [jsx("span", { className: "text-[var(--color-primary)] font-semibold", children: buttonText }), " ", helperText] }), description && (jsx("p", { className: "text-sm text-[var(--color-text-muted)] mt-1", children: description }))] }) }), showPreview && selectedFiles.length > 0 && (jsx("div", { className: theme.previewContainerStyle, children: selectedFiles.map((file, index) => {
|
|
290
|
+
}, children: jsxs("div", { className: theme.contentStyle, children: [jsx("div", { className: "size-11 border border-border bg-white rounded-full flex items-center justify-center mb-4", children: jsx(CloudArrowUpIcon, { className: `size-6 text-[var(--color-text-secondary)]` }) }), jsxs("p", { className: "text-base font-medium text-[var(--color-text-primary)]", children: [jsx("span", { className: "text-[var(--color-primary)] font-semibold", children: buttonText }), " ", helperText] }), description && (jsx("p", { className: "text-sm text-[var(--color-text-muted)] mt-1", children: description }))] }) }), errorMessage && (jsx("div", { className: "text-sm mt-1 text-[var(--color-danger)]", children: errorMessage })), showPreview && selectedFiles.length > 0 && (jsx("div", { className: theme.previewContainerStyle, children: selectedFiles.map((file, index) => {
|
|
287
291
|
const previewUrl = previewUrls.get(file);
|
|
288
292
|
const isImage = file.type.startsWith("image/");
|
|
289
293
|
return (jsxs("div", { className: theme.previewItemStyle, children: [isImage && previewUrl ? (jsx("img", { src: previewUrl, alt: file.name, className: theme.previewImageStyle })) : (jsxs("div", { className: theme.previewFileStyle, children: [jsx(DocumentIcon, { className: "w-8 h-8 mx-auto mb-2 text-[var(--color-text-muted)]" }), jsx("span", { className: "text-sm", children: file.name }), jsx("span", { className: "text-sm text-[var(--color-text-placeholder)] mt-1", children: formatFileSize(file.size) })] })), jsx(Button, { variant: "filled", color: "danger", size: "sm", iconOnly: true, onClick: (e) => {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
export type FileUploadState = "default" | "disabled" | "invalid";
|
|
1
3
|
/**
|
|
2
4
|
* FileUpload component props
|
|
3
5
|
* Supports both click-to-upload and drag-and-drop
|
|
@@ -22,11 +24,24 @@ export interface FileUploadProps extends Omit<React.HTMLAttributes<HTMLDivElemen
|
|
|
22
24
|
* Maximum number of files (when multiple is true)
|
|
23
25
|
*/
|
|
24
26
|
maxFiles?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Validation/interaction state
|
|
29
|
+
* @default "default"
|
|
30
|
+
*/
|
|
31
|
+
state?: FileUploadState;
|
|
25
32
|
/**
|
|
26
33
|
* Whether the upload area is disabled
|
|
27
34
|
* @default false
|
|
28
35
|
*/
|
|
29
36
|
disabled?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Field label rendered above the dropzone
|
|
39
|
+
*/
|
|
40
|
+
label?: React.ReactNode;
|
|
41
|
+
/**
|
|
42
|
+
* Validation error message rendered below the dropzone (styled in danger color)
|
|
43
|
+
*/
|
|
44
|
+
errorMessage?: React.ReactNode;
|
|
30
45
|
/**
|
|
31
46
|
* Callback when files are selected/dropped
|
|
32
47
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
1
|
+
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
2
2
|
import React__default from 'react';
|
|
3
3
|
import { formLabelTheme } from './FormLabel.theme.js';
|
|
4
4
|
import { cn } from '../../../utils/cn.js';
|
|
@@ -7,7 +7,7 @@ import { cn } from '../../../utils/cn.js';
|
|
|
7
7
|
* FormLabel component for providing consistent labeling for form elements
|
|
8
8
|
*/
|
|
9
9
|
const FormLabel = React__default.forwardRef(({ htmlFor, children, className, required = false, state = 'default', ...props }, ref) => {
|
|
10
|
-
return (jsxs("label", { ref: ref, htmlFor: htmlFor, className: cn(formLabelTheme.baseStyle, formLabelTheme.stateStyles[state], className), ...props, children: [children, required && (jsx("span", { className: formLabelTheme.requiredIndicatorStyle, children: "*" }))] }));
|
|
10
|
+
return (jsxs("label", { ref: ref, htmlFor: htmlFor, className: cn(formLabelTheme.baseStyle, formLabelTheme.stateStyles[state], className), ...props, children: [children, required && (jsxs(Fragment, { children: [jsx("span", { "aria-hidden": "true", className: formLabelTheme.requiredIndicatorStyle, children: "*" }), jsx("span", { className: "sr-only", children: "(required)" })] }))] }));
|
|
11
11
|
});
|
|
12
12
|
FormLabel.displayName = "FormLabel";
|
|
13
13
|
|
|
@@ -62,12 +62,12 @@ const PasswordStrengthIndicator = ({ inline = false, passwordStrengthIndicator,
|
|
|
62
62
|
const colorClass = strengthConfig.color || "text-neutral-500";
|
|
63
63
|
const label = customLabel || strengthConfig.label || "Password";
|
|
64
64
|
const activeDots = getActiveDots(strength);
|
|
65
|
-
return (jsxs("div", { className: `${containerStyle} ${indicatorClassName} ${colorClass}`, children: [jsx("div", { className: labelStyle, children: label }), [...Array(5)].map((_, index) => (jsx("div", { className: `${dotStyle} ${index < activeDots ? dotActiveStyle : dotInactiveStyle}` }, index)))] }));
|
|
65
|
+
return (jsxs("div", { className: `${containerStyle} ${indicatorClassName} ${colorClass}`, children: [jsx("div", { className: labelStyle, children: label }), [...Array(5)].map((_, index) => (jsx("div", { className: `${dotStyle} ${index < activeDots ? dotActiveStyle : dotInactiveStyle}` }, `dot-${index}`)))] }));
|
|
66
66
|
};
|
|
67
67
|
// ============================================================================
|
|
68
68
|
// Main Input Component
|
|
69
69
|
// ============================================================================
|
|
70
|
-
const Input = React__default.forwardRef(({ size = "md", state = "default", className = "", label, labelClassName = "", helperText, helperTextClassName = "", wrapperClassName = "", inputGroupClassName = "", iconStart, iconEnd, contentStart, contentEnd, keyboardShortcut, id, required, type = "text", passwordToggle = type === "password", passwordStrengthIndicator, showNumberControls = false, min, max, step = 1, onChange, onFocus, onBlur, value, currencyDecimalPlaces, darkMode = false, showTypeIcon = true, ...props }, ref) => {
|
|
70
|
+
const Input = React__default.forwardRef(({ size = "md", state = "default", className = "", label, labelClassName = "", helperText, helperTextClassName = "", wrapperClassName = "", inputGroupClassName = "", iconStart, iconEnd, contentStart, contentEnd, keyboardShortcut, id, required, type = "text", passwordToggle = type === "password", passwordStrengthIndicator, showNumberControls = false, min, max, step = 1, onChange, onValueChange, onFocus, onBlur, value, currencyDecimalPlaces, darkMode = false, showTypeIcon = true, ...props }, ref) => {
|
|
71
71
|
const [isFocused, setIsFocused] = useState(false);
|
|
72
72
|
const [showPassword, setShowPassword] = useState(false);
|
|
73
73
|
const generatedId = React__default.useId();
|
|
@@ -201,6 +201,7 @@ const Input = React__default.forwardRef(({ size = "md", state = "default", class
|
|
|
201
201
|
if (onChange) {
|
|
202
202
|
onChange(event);
|
|
203
203
|
}
|
|
204
|
+
onValueChange === null || onValueChange === void 0 ? void 0 : onValueChange(newValue.toString());
|
|
204
205
|
}
|
|
205
206
|
};
|
|
206
207
|
// Handle input change
|
|
@@ -240,6 +241,7 @@ const Input = React__default.forwardRef(({ size = "md", state = "default", class
|
|
|
240
241
|
if (onChange) {
|
|
241
242
|
onChange(e);
|
|
242
243
|
}
|
|
244
|
+
onValueChange === null || onValueChange === void 0 ? void 0 : onValueChange(e.target.value);
|
|
243
245
|
};
|
|
244
246
|
// Theme classes - using simplified approach with design tokens
|
|
245
247
|
const wrapperClasses = inputTheme.wrapperStyle;
|
|
@@ -277,7 +279,7 @@ const Input = React__default.forwardRef(({ size = "md", state = "default", class
|
|
|
277
279
|
}, onBlur: (e) => {
|
|
278
280
|
setIsFocused(false);
|
|
279
281
|
onBlur === null || onBlur === void 0 ? void 0 : onBlur(e);
|
|
280
|
-
}, placeholder: props.placeholder, ...props }), hasLeftIcon && (jsx("div", { className: iconStartClasses, children: finalLeftIcon })), hasRightIcon && !isPasswordField && !displayNumberControls && (jsx("div", { className: iconEndClasses, children: iconEnd })), isPasswordField && (jsx(PasswordToggleButton, { showPassword: showPassword, onToggle: handleTogglePassword, passwordToggleClasses: passwordToggleClasses })), displayNumberControls && (jsx(NumberButtons, { isDisabled: isDisabled, isAtMax: isAtMax, isAtMin: isAtMin, onIncrement: handleIncrement, onDecrement: handleDecrement, numberButtonsContainerClasses: numberButtonsContainerClasses, numberButtonClasses: numberButtonClasses })), hasKeyboardShortcut && (jsx("div", { className: inputTheme.keyboardShortcutStyle, children: keyboardShortcut }))] }), hasContentEnd && (jsx("div", { "data-slot": "content-end", className: cn(contentStyleClasses, contentEndStyle), children: contentEnd }))] }) }), showStrengthBelow && jsx(PasswordStrengthIndicator, { inline: false, passwordStrengthIndicator: passwordStrengthIndicator }), helperText && (typeof helperText === 'string' ? (jsx("div", { id: `${inputId}-helper`, className: cn(helperTextClasses, helperTextClassName), children: helperText })) : (helperText))] }));
|
|
282
|
+
}, placeholder: props.placeholder, ...props }), hasLeftIcon && (jsx("div", { className: iconStartClasses, children: finalLeftIcon })), hasRightIcon && !isPasswordField && !displayNumberControls && (jsx("div", { className: iconEndClasses, children: iconEnd })), isPasswordField && (jsx(PasswordToggleButton, { showPassword: showPassword, onToggle: handleTogglePassword, passwordToggleClasses: passwordToggleClasses })), displayNumberControls && (jsx(NumberButtons, { isDisabled: isDisabled, isAtMax: isAtMax, isAtMin: isAtMin, onIncrement: handleIncrement, onDecrement: handleDecrement, numberButtonsContainerClasses: numberButtonsContainerClasses, numberButtonClasses: numberButtonClasses })), hasKeyboardShortcut && (jsx("div", { className: inputTheme.keyboardShortcutStyle, children: keyboardShortcut }))] }), hasContentEnd && (jsx("div", { "data-slot": "content-end", className: cn(contentStyleClasses, contentEndStyle), children: contentEnd }))] }) }), showStrengthBelow && jsx(PasswordStrengthIndicator, { inline: false, passwordStrengthIndicator: passwordStrengthIndicator }), helperText && (typeof helperText === 'string' ? (jsx("div", { id: `${inputId}-helper`, className: cn(helperTextClasses, helperTextClassName), role: isInvalid ? "alert" : undefined, children: helperText })) : (helperText))] }));
|
|
281
283
|
});
|
|
282
284
|
Input.displayName = "Input";
|
|
283
285
|
|
|
@@ -69,6 +69,8 @@ export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElem
|
|
|
69
69
|
* @default false
|
|
70
70
|
*/
|
|
71
71
|
darkMode?: boolean;
|
|
72
|
+
/** Callback fired with the raw string value on every change — convenience alternative to reading e.target.value from onChange */
|
|
73
|
+
onValueChange?: (value: string) => void;
|
|
72
74
|
}
|
|
73
75
|
export type InputSize = 'sm' | 'md' | 'lg';
|
|
74
76
|
export type InputState = 'default' | 'disabled' | 'invalid';
|
|
@@ -16,4 +16,4 @@ import { InputAddressProps } from "./InputAddress.types";
|
|
|
16
16
|
* />
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
|
-
export declare const InputAddress: React.ForwardRefExoticComponent<InputAddressProps & React.RefAttributes<
|
|
19
|
+
export declare const InputAddress: React.ForwardRefExoticComponent<InputAddressProps & React.RefAttributes<HTMLFieldSetElement>>;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
-
import React__default, { useMemo, useState, useCallback } from 'react';
|
|
2
|
+
import React__default, { useMemo, useState, useCallback, useId } from 'react';
|
|
3
3
|
import { Input } from '../Input/Input.js';
|
|
4
|
-
import { FormLabel } from '../FormLabel/FormLabel.js';
|
|
5
4
|
import { inputAddressTheme } from './InputAddress.theme.js';
|
|
6
5
|
|
|
7
6
|
/**
|
|
@@ -21,6 +20,7 @@ import { inputAddressTheme } from './InputAddress.theme.js';
|
|
|
21
20
|
* ```
|
|
22
21
|
*/
|
|
23
22
|
const InputAddress = React__default.forwardRef(({ availableCountries = [], defaultCountry = "United States", onCountryChange, onPostalCodeChange, countryLabel = "Country", postalCodeLabel = "ZIP / Postal code", legendText = "Billing address", streetInputProps = {}, stateInputProps = {}, countryInputProps = {}, postalCodeInputProps = {}, className = "", theme = {}, }, ref) => {
|
|
23
|
+
var _a;
|
|
24
24
|
// Merge default theme with provided theme using useMemo for performance
|
|
25
25
|
const mergedTheme = useMemo(() => ({
|
|
26
26
|
...inputAddressTheme,
|
|
@@ -56,13 +56,12 @@ const InputAddress = React__default.forwardRef(({ availableCountries = [], defau
|
|
|
56
56
|
return availableCountries;
|
|
57
57
|
}, [availableCountries]);
|
|
58
58
|
// Create unique IDs for accessibility
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
return (jsxs("div", { ref: ref, className: `w-full ${className}`, "data-testid": "input-address-fieldset", children: [jsx(FormLabel, { htmlFor: streetId, className: mergedTheme.legendStyles, children: legendText }), jsxs("div", { className: `grid gap-0 ${mergedTheme.contentStyles}`, "data-testid": "input-address-content", children: [jsx("div", { className: "w-full relative focus-within:z-10", "data-testid": "input-address-street", children: jsx(Input, { id: streetId, type: "text", placeholder: "Street address", "aria-label": "street address", inputGroupClassName: "rounded-b-none", ...streetInputProps }) }), jsx("div", { className: "w-full -mt-[0.5px] relative focus-within:z-10", "data-testid": "input-address-state", children: jsx(Input, { id: stateId, type: "text", placeholder: "State", "aria-label": "state", inputGroupClassName: "rounded-none", ...stateInputProps }) }), jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 w-full -mt-px relative focus-within:z-10", children: [jsx("div", { className: `${mergedTheme.countryWrapperStyles} w-full sm:pr-0 relative focus-within:z-10`, "data-testid": "input-address-country", children: jsx(Input, { id: countryId, type: "text", value: country, onChange: handleCountryChange, placeholder: countryLabel, "aria-label": countryLabel, inputGroupClassName: "sm:rounded-bl-md rounded-t-none sm:rounded-tr-none sm:rounded-br-none", ...countryInputProps }) }), jsx("div", { className: `${mergedTheme.postalCodeWrapperStyles} w-full relative focus-within:z-10`, "data-testid": "input-address-postal", children: jsx(Input, { id: postalCodeId, type: "text", value: postalCode, onChange: handlePostalCodeChange, placeholder: postalCodeLabel, "aria-label": postalCodeLabel, inputGroupClassName: "sm:rounded-br-md rounded-t-none rounded-tl-none rounded-bl-md sm:rounded-bl-none", ...postalCodeInputProps }) })] })] })] }));
|
|
59
|
+
const baseId = useId();
|
|
60
|
+
const streetId = `${baseId}-street`;
|
|
61
|
+
const stateId = `${baseId}-state`;
|
|
62
|
+
const countryId = `${baseId}-country`;
|
|
63
|
+
const postalCodeId = `${baseId}-postal-code`;
|
|
64
|
+
return (jsxs("fieldset", { ref: ref, className: `w-full border-0 m-0 p-0 ${className}`, "data-testid": "input-address-fieldset", children: [jsx("legend", { className: `block text-sm/6 font-medium mb-2 text-[var(--color-text-primary)] ${(_a = mergedTheme.legendStyles) !== null && _a !== void 0 ? _a : ""}`, children: legendText }), jsxs("div", { className: `grid gap-0 ${mergedTheme.contentStyles}`, "data-testid": "input-address-content", children: [jsx("div", { className: "w-full relative focus-within:z-10", "data-testid": "input-address-street", children: jsx(Input, { id: streetId, type: "text", placeholder: "Street address", "aria-label": "street address", inputGroupClassName: "rounded-b-none", ...streetInputProps }) }), jsx("div", { className: "w-full -mt-[0.5px] relative focus-within:z-10", "data-testid": "input-address-state", children: jsx(Input, { id: stateId, type: "text", placeholder: "State", "aria-label": "state", inputGroupClassName: "rounded-none", ...stateInputProps }) }), jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 w-full -mt-px relative focus-within:z-10", children: [jsx("div", { className: `${mergedTheme.countryWrapperStyles} w-full sm:pr-0 relative focus-within:z-10`, "data-testid": "input-address-country", children: jsx(Input, { id: countryId, type: "text", value: country, onChange: handleCountryChange, placeholder: countryLabel, "aria-label": countryLabel, inputGroupClassName: "sm:rounded-bl-md rounded-t-none sm:rounded-tr-none sm:rounded-br-none", ...countryInputProps }) }), jsx("div", { className: `${mergedTheme.postalCodeWrapperStyles} w-full relative focus-within:z-10`, "data-testid": "input-address-postal", children: jsx(Input, { id: postalCodeId, type: "text", value: postalCode, onChange: handlePostalCodeChange, placeholder: postalCodeLabel, "aria-label": postalCodeLabel, inputGroupClassName: "sm:rounded-br-md rounded-t-none rounded-tl-none rounded-bl-md sm:rounded-bl-none", ...postalCodeInputProps }) })] })] })] }));
|
|
66
65
|
});
|
|
67
66
|
InputAddress.displayName = "InputAddress";
|
|
68
67
|
|