@flikk/ui 1.0.0-beta.32 โ 1.0.0-beta.33
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/CHANGELOG.md +202 -0
- package/dist/components/ai/AgentRequest/AgentRequest.theme.js +1 -1
- package/dist/components/core/Button/Button.theme.js +3 -3
- package/dist/components/core/Pill/Pill.js +32 -17
- package/dist/components/core/Pill/Pill.types.d.ts +14 -1
- package/dist/components/core/SlidingNumber/SlidingNumber.d.ts +8 -0
- package/dist/components/core/SlidingNumber/SlidingNumber.js +12 -3
- package/dist/components/forms/ColorPicker/ColorPicker.js +45 -3
- package/dist/components/forms/ColorPicker/ColorPickerBody.js +8 -4
- package/dist/components/forms/Combobox/Combobox.js +8 -4
- package/dist/components/forms/Combobox/Combobox.theme.js +9 -0
- package/dist/components/forms/Combobox/Combobox.types.d.ts +13 -0
- package/dist/components/forms/DatePicker/DatePicker.js +19 -12
- package/dist/components/forms/DatePicker/DatePicker.theme.js +5 -0
- package/dist/components/forms/DatePicker/DatePicker.types.d.ts +8 -0
- package/dist/components/forms/DatePicker/DatePickerContext.d.ts +0 -1
- package/dist/components/forms/DatePicker/DatePickerTrigger.js +5 -3
- package/dist/components/forms/DateRangePicker/DateRangePicker.theme.js +5 -0
- package/dist/components/forms/DateRangePicker/DateRangePicker.types.d.ts +8 -0
- package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +5 -3
- package/dist/components/forms/Input/Input.js +17 -12
- package/dist/components/forms/Input/Input.theme.js +10 -0
- package/dist/components/forms/Input/Input.types.d.ts +14 -0
- package/dist/components/forms/InputAddress/InputAddress.js +5 -5
- package/dist/components/forms/InputAddress/InputAddress.types.d.ts +7 -0
- package/dist/components/forms/InputCounter/InputCounter.js +194 -17
- package/dist/components/forms/InputCounter/InputCounter.theme.d.ts +12 -0
- package/dist/components/forms/InputCounter/InputCounter.theme.js +92 -2
- package/dist/components/forms/InputCounter/InputCounter.types.d.ts +47 -4
- package/dist/components/forms/InputCreditCard/InputCreditCard.js +4 -4
- package/dist/components/forms/InputCreditCard/InputCreditCard.types.d.ts +7 -0
- package/dist/components/forms/Mention/Mention.js +13 -11
- package/dist/components/forms/RichTextEditor/RichTextEditor.js +12 -3
- package/dist/components/forms/Select/Select.js +15 -8
- package/dist/components/forms/Select/Select.theme.js +9 -1
- package/dist/components/forms/Select/Select.types.d.ts +16 -1
- package/dist/components/forms/Textarea/Textarea.js +14 -25
- package/dist/components/forms/TimePicker/TimePicker.theme.js +9 -0
- package/dist/components/forms/TimePicker/TimePicker.types.d.ts +11 -0
- package/dist/components/forms/TimePicker/TimePickerTrigger.js +7 -5
- package/dist/components/forms/forms.theme.d.ts +57 -0
- package/dist/components/forms/forms.theme.js +83 -8
- package/dist/components/generative/registry.js +1 -0
- package/dist/components/generative/schema.generated.js +21 -1
- package/dist/generative.schema.json +21 -1
- package/dist/registry.json +108 -17
- package/dist/styles.css +1 -1
- package/dist/tools.json +21 -1
- package/dist/utils/composeEventHandlers.d.ts +19 -0
- package/dist/utils/composeEventHandlers.js +26 -0
- package/dist/utils/composeEventHandlers.test.d.ts +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/package.json +1 -1
- package/src/styles/theme.css +33 -9
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
|
-
import React__default, { useState, useRef,
|
|
3
|
+
import React__default, { useState, useRef, useCallback, useMemo } from 'react';
|
|
4
4
|
import { datePickerTheme } from './DatePicker.theme.js';
|
|
5
5
|
import { FormLabel } from '../FormLabel/FormLabel.js';
|
|
6
6
|
import { DatePickerTrigger } from './DatePickerTrigger.js';
|
|
@@ -50,30 +50,38 @@ const DatePickerComponent = React__default.forwardRef(({ id, name, mode = "singl
|
|
|
50
50
|
const theme = { ...datePickerTheme };
|
|
51
51
|
// State
|
|
52
52
|
const [isOpen, setIsOpen] = useState(false);
|
|
53
|
-
|
|
53
|
+
// ๐ด ยง9: mode is DERIVED per render, never stored. `value` used to be mirrored
|
|
54
|
+
// into state and re-synced from an unconditional `useEffect(โฆ, [value])` โ
|
|
55
|
+
// which on mount fired with `value === undefined` and wiped the `defaultValue`
|
|
56
|
+
// seed, and in controlled mode let an ignored change stick because an
|
|
57
|
+
// unchanged prop never re-ran the effect.
|
|
58
|
+
const isControlled = value !== undefined;
|
|
59
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
|
|
60
|
+
const selectedValue = isControlled ? value : uncontrolledValue;
|
|
54
61
|
// Refs
|
|
55
62
|
const triggerRef = useRef(null);
|
|
56
63
|
const contentRef = useRef(null);
|
|
57
|
-
// Update internal state when prop value changes
|
|
58
|
-
useEffect(() => {
|
|
59
|
-
setSelectedValue(value);
|
|
60
|
-
}, [value]);
|
|
61
64
|
// Date selection handler (single mode)
|
|
62
65
|
const handleDateSelect = useCallback((newDate) => {
|
|
63
66
|
if (mode === "single") {
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
// Compared by time, not identity: every calendar click builds a NEW Date,
|
|
68
|
+
// so an identity check was always true and the guard never fired.
|
|
69
|
+
const isSameSelection = selectedValue instanceof Date && selectedValue.getTime() === newDate.getTime();
|
|
70
|
+
if (!isSameSelection) {
|
|
71
|
+
if (!isControlled)
|
|
72
|
+
setUncontrolledValue(newDate);
|
|
66
73
|
onChange === null || onChange === void 0 ? void 0 : onChange(newDate);
|
|
67
74
|
}
|
|
68
75
|
setIsOpen(false);
|
|
69
76
|
}
|
|
70
|
-
}, [mode, selectedValue, onChange]);
|
|
77
|
+
}, [mode, selectedValue, isControlled, onChange]);
|
|
71
78
|
// Range selection handler (range mode)
|
|
72
79
|
const handleRangeSelect = useCallback((newRange) => {
|
|
73
|
-
|
|
80
|
+
if (!isControlled)
|
|
81
|
+
setUncontrolledValue(newRange);
|
|
74
82
|
onChange === null || onChange === void 0 ? void 0 : onChange(newRange);
|
|
75
83
|
// Don't auto-close anymore - footer Apply button handles this
|
|
76
|
-
}, [onChange]);
|
|
84
|
+
}, [isControlled, onChange]);
|
|
77
85
|
// Button click handler
|
|
78
86
|
const handleButtonClick = useCallback(() => {
|
|
79
87
|
if (!disabled) {
|
|
@@ -105,7 +113,6 @@ const DatePickerComponent = React__default.forwardRef(({ id, name, mode = "singl
|
|
|
105
113
|
isOpen,
|
|
106
114
|
setIsOpen,
|
|
107
115
|
selectedValue,
|
|
108
|
-
setSelectedValue,
|
|
109
116
|
minDate,
|
|
110
117
|
maxDate,
|
|
111
118
|
isDateDisabled,
|
|
@@ -26,6 +26,11 @@ const datePickerTheme = {
|
|
|
26
26
|
triggerFocusStates: formsBaseTheme.focusStates,
|
|
27
27
|
iconEndStyle: formsBaseTheme.iconStyles.right,
|
|
28
28
|
iconEndPadding: formsBaseTheme.iconStyles.padding.right,
|
|
29
|
+
// Size-aware icon geometry: slot inset from --form-px-*, glyph from
|
|
30
|
+
// --form-icon-size-*. Flat keys above stay as the fallback.
|
|
31
|
+
iconEndStyles: formsBaseTheme.iconStyles.rightSizes,
|
|
32
|
+
iconEndPaddings: formsBaseTheme.iconStyles.paddingRightSizes,
|
|
33
|
+
iconSizes: formsBaseTheme.iconStyles.sizes,
|
|
29
34
|
// Dropdown body - surface painted by the relative-elevation engine (surfaceClasses)
|
|
30
35
|
bodyStyle: "min-w-fit max-w-none backdrop-blur-md border border-[var(--color-border)] rounded-[var(--popover-radius)] p-0 overflow-hidden z-[1000] fixed",
|
|
31
36
|
bodyWithPresetsStyle: "flex flex-col sm:flex-row w-fit max-w-[calc(100vw-2rem)] sm:max-w-none backdrop-blur-md border border-[var(--color-border)] rounded-[var(--popover-radius)] shadow-lg p-0 overflow-hidden z-[1000] fixed",
|
|
@@ -155,8 +155,16 @@ export interface DatePickerTheme {
|
|
|
155
155
|
triggerStates: Record<DatePickerState, string>;
|
|
156
156
|
triggerHoverStates: Record<DatePickerState, string>;
|
|
157
157
|
triggerFocusStates: Record<DatePickerState, string>;
|
|
158
|
+
/** @deprecated size-blind; superseded by `iconEndStyles` */
|
|
158
159
|
iconEndStyle: string;
|
|
160
|
+
/** @deprecated size-blind; superseded by `iconEndPaddings` */
|
|
159
161
|
iconEndPadding: string;
|
|
162
|
+
/** Per-size trailing icon slot (position + inset) */
|
|
163
|
+
iconEndStyles?: Partial<Record<DatePickerSize, string>>;
|
|
164
|
+
/** Per-size text runway reserved for the trailing icon */
|
|
165
|
+
iconEndPaddings?: Partial<Record<DatePickerSize, string>>;
|
|
166
|
+
/** Per-size sizing for the built-in calendar icon */
|
|
167
|
+
iconSizes?: Record<DatePickerSize, string>;
|
|
160
168
|
bodyStyle: string;
|
|
161
169
|
bodyWithPresetsStyle: string;
|
|
162
170
|
presetsContainerStyle: string;
|
|
@@ -5,7 +5,6 @@ export interface DatePickerContextType {
|
|
|
5
5
|
isOpen: boolean;
|
|
6
6
|
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
7
7
|
selectedValue: Date | DateRange | undefined;
|
|
8
|
-
setSelectedValue: React.Dispatch<React.SetStateAction<Date | DateRange | undefined>>;
|
|
9
8
|
minDate?: Date;
|
|
10
9
|
maxDate?: Date;
|
|
11
10
|
isDateDisabled?: (date: Date) => boolean;
|
|
@@ -10,7 +10,7 @@ import { DatePickerContext } from './DatePickerContext.js';
|
|
|
10
10
|
* Displays selected date or placeholder text
|
|
11
11
|
*/
|
|
12
12
|
const DatePickerTrigger = ({ id, disabled, isOpen, state = "default", size = "md", className, onClick, onFocus, onBlur, children, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedby, }) => {
|
|
13
|
-
var _a, _b, _c, _d;
|
|
13
|
+
var _a, _b, _c, _d, _e, _f;
|
|
14
14
|
const context = useContext(DatePickerContext);
|
|
15
15
|
if (!context) {
|
|
16
16
|
throw new Error("DatePickerTrigger must be used within a DatePicker");
|
|
@@ -21,7 +21,9 @@ const DatePickerTrigger = ({ id, disabled, isOpen, state = "default", size = "md
|
|
|
21
21
|
const stateStyle = ((_b = theme.triggerStates) === null || _b === void 0 ? void 0 : _b[state]) || "";
|
|
22
22
|
const hoverStateStyle = ((_c = theme.triggerHoverStates) === null || _c === void 0 ? void 0 : _c[state]) || "";
|
|
23
23
|
const focusStateStyle = ((_d = theme.triggerFocusStates) === null || _d === void 0 ? void 0 : _d[state]) || "";
|
|
24
|
-
const iconEndStyle = theme.iconEndStyle || "";
|
|
24
|
+
const iconEndStyle = ((_e = theme.iconEndStyles) === null || _e === void 0 ? void 0 : _e[size]) || theme.iconEndStyle || "";
|
|
25
|
+
// Glyph ramps with the control (--form-icon-size-*), not a flat h-4 w-4.
|
|
26
|
+
const iconSizeStyle = ((_f = theme.iconSizes) === null || _f === void 0 ? void 0 : _f[size]) || "size-4";
|
|
25
27
|
const iconEndPadding = theme.iconEndPadding || ""; // Always apply right padding for built-in calendar icon
|
|
26
28
|
const triggerButtonStyle = theme.triggerButtonStyle || "";
|
|
27
29
|
// Apply focused styles when open - same pattern as SelectButton
|
|
@@ -29,7 +31,7 @@ const DatePickerTrigger = ({ id, disabled, isOpen, state = "default", size = "md
|
|
|
29
31
|
? focusStateStyle ||
|
|
30
32
|
"shadow-[inset_0_0_0_1px_var(--color-primary-600)] ring-4 ring-[var(--color-primary)]/10"
|
|
31
33
|
: "";
|
|
32
|
-
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(Calendar, { className:
|
|
34
|
+
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(Calendar, { className: iconSizeStyle }) })] }) }));
|
|
33
35
|
};
|
|
34
36
|
|
|
35
37
|
export { DatePickerTrigger };
|
|
@@ -19,6 +19,11 @@ const dateRangePickerTheme = {
|
|
|
19
19
|
triggerFocusStates: formsBaseTheme.focusStates,
|
|
20
20
|
iconEndStyle: formsBaseTheme.iconStyles.right,
|
|
21
21
|
iconEndPadding: formsBaseTheme.iconStyles.padding.right,
|
|
22
|
+
// Size-aware icon geometry: slot inset from --form-px-*, glyph from
|
|
23
|
+
// --form-icon-size-*. Flat keys above stay as the fallback.
|
|
24
|
+
iconEndStyles: formsBaseTheme.iconStyles.rightSizes,
|
|
25
|
+
iconEndPaddings: formsBaseTheme.iconStyles.paddingRightSizes,
|
|
26
|
+
iconSizes: formsBaseTheme.iconStyles.sizes,
|
|
22
27
|
bodyStyle: "flex flex-col sm:flex-row w-fit max-w-[calc(100vw-2rem)] sm:max-w-none backdrop-blur-md border border-[var(--color-border)] rounded-[var(--popover-radius)] shadow-lg p-0 overflow-hidden z-[1000] fixed",
|
|
23
28
|
presetsContainerStyle: "hidden sm:flex flex-col gap-1 p-3 sm:border-r border-[var(--color-border)] bg-[var(--color-surface-sunken)] sm:min-w-[180px]",
|
|
24
29
|
presetButtonStyle: "w-full text-left px-3 py-2 text-sm font-medium rounded-[calc(var(--radius-base)*0.75)] transition-colors cursor-pointer " +
|
|
@@ -163,8 +163,16 @@ export interface DateRangePickerTheme {
|
|
|
163
163
|
triggerStates: Record<DateRangePickerState, string>;
|
|
164
164
|
triggerHoverStates: Record<DateRangePickerState, string>;
|
|
165
165
|
triggerFocusStates: Record<DateRangePickerState, string>;
|
|
166
|
+
/** @deprecated size-blind; superseded by `iconEndStyles` */
|
|
166
167
|
iconEndStyle: string;
|
|
168
|
+
/** @deprecated size-blind; superseded by `iconEndPaddings` */
|
|
167
169
|
iconEndPadding: string;
|
|
170
|
+
/** Per-size trailing icon slot (position + inset) */
|
|
171
|
+
iconEndStyles?: Partial<Record<DateRangePickerSize, string>>;
|
|
172
|
+
/** Per-size text runway reserved for the trailing icon */
|
|
173
|
+
iconEndPaddings?: Partial<Record<DateRangePickerSize, string>>;
|
|
174
|
+
/** Per-size sizing for the built-in calendar icon */
|
|
175
|
+
iconSizes?: Record<DateRangePickerSize, string>;
|
|
168
176
|
bodyStyle: string;
|
|
169
177
|
presetsContainerStyle: string;
|
|
170
178
|
presetButtonStyle: string;
|
|
@@ -9,7 +9,7 @@ import { DateRangePickerContext } from './DateRangePickerContext.js';
|
|
|
9
9
|
* DateRangePicker Trigger Component โ styled like form input
|
|
10
10
|
*/
|
|
11
11
|
const DateRangePickerTrigger = ({ id, disabled, isOpen, state = "default", size = "md", className, onClick, onFocus, onBlur, children, 'aria-invalid': ariaInvalid, 'aria-describedby': ariaDescribedby, }) => {
|
|
12
|
-
var _a, _b, _c, _d;
|
|
12
|
+
var _a, _b, _c, _d, _e, _f;
|
|
13
13
|
const context = useContext(DateRangePickerContext);
|
|
14
14
|
if (!context) {
|
|
15
15
|
throw new Error("DateRangePickerTrigger must be used within a DateRangePicker");
|
|
@@ -20,14 +20,16 @@ const DateRangePickerTrigger = ({ id, disabled, isOpen, state = "default", size
|
|
|
20
20
|
const stateStyle = ((_b = theme.triggerStates) === null || _b === void 0 ? void 0 : _b[state]) || "";
|
|
21
21
|
const hoverStateStyle = ((_c = theme.triggerHoverStates) === null || _c === void 0 ? void 0 : _c[state]) || "";
|
|
22
22
|
const focusStateStyle = ((_d = theme.triggerFocusStates) === null || _d === void 0 ? void 0 : _d[state]) || "";
|
|
23
|
-
const iconEndStyle = theme.iconEndStyle || "";
|
|
23
|
+
const iconEndStyle = ((_e = theme.iconEndStyles) === null || _e === void 0 ? void 0 : _e[size]) || theme.iconEndStyle || "";
|
|
24
|
+
// Glyph ramps with the control (--form-icon-size-*), not a flat h-4 w-4.
|
|
25
|
+
const iconSizeStyle = ((_f = theme.iconSizes) === null || _f === void 0 ? void 0 : _f[size]) || "size-4";
|
|
24
26
|
const iconEndPadding = theme.iconEndPadding || "";
|
|
25
27
|
const triggerButtonStyle = theme.triggerButtonStyle || "";
|
|
26
28
|
const activeFocusState = isOpen
|
|
27
29
|
? focusStateStyle ||
|
|
28
30
|
"shadow-[inset_0_0_0_1px_var(--color-primary-600)] ring-4 ring-[var(--color-primary)]/10"
|
|
29
31
|
: "";
|
|
30
|
-
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(Calendar, { className:
|
|
32
|
+
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(Calendar, { className: iconSizeStyle }) })] }) }));
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
export { DateRangePickerTrigger };
|
|
@@ -20,16 +20,16 @@ const PasswordToggleButton = ({ showPassword, onToggle, passwordToggleClasses, }
|
|
|
20
20
|
}
|
|
21
21
|
}, children: showPassword ? (jsx(EyeSlash, { className: toggleIconActive, "aria-hidden": "true" })) : (jsx(Eye, { className: toggleIconInactive, "aria-hidden": "true" })) }));
|
|
22
22
|
};
|
|
23
|
-
const NumberButtons = ({ isDisabled, isAtMax, isAtMin, onIncrement, onDecrement, numberButtonsContainerClasses, numberButtonClasses, }) => {
|
|
23
|
+
const NumberButtons = ({ isDisabled, isAtMax, isAtMin, onIncrement, onDecrement, numberButtonsContainerClasses, numberButtonClasses, iconSizeClass, }) => {
|
|
24
24
|
const incrementIconStyle = inputTheme.typeStyles.number.incrementIconStyle;
|
|
25
25
|
const decrementIconStyle = inputTheme.typeStyles.number.decrementIconStyle;
|
|
26
26
|
return (jsxs("div", { className: numberButtonsContainerClasses, children: [jsx("button", { type: "button", className: `${numberButtonClasses}`, onClick: (e) => {
|
|
27
27
|
e.stopPropagation();
|
|
28
28
|
onDecrement();
|
|
29
|
-
}, disabled: isDisabled || isAtMin, "aria-label": "Decrease value", tabIndex: -1, children: jsx(Minus, { className: cn(
|
|
29
|
+
}, disabled: isDisabled || isAtMin, "aria-label": "Decrease value", tabIndex: -1, children: jsx(Minus, { className: cn(iconSizeClass, decrementIconStyle), "aria-hidden": "true" }) }), jsx("button", { type: "button", className: `${numberButtonClasses}`, onClick: (e) => {
|
|
30
30
|
e.stopPropagation();
|
|
31
31
|
onIncrement();
|
|
32
|
-
}, disabled: isDisabled || isAtMax, "aria-label": "Increase value", tabIndex: -1, children: jsx(Plus, { className: cn(
|
|
32
|
+
}, disabled: isDisabled || isAtMax, "aria-label": "Increase value", tabIndex: -1, children: jsx(Plus, { className: cn(iconSizeClass, incrementIconStyle), "aria-hidden": "true" }) })] }));
|
|
33
33
|
};
|
|
34
34
|
const getActiveDots = (strength) => {
|
|
35
35
|
switch (strength) {
|
|
@@ -68,6 +68,7 @@ const PasswordStrengthIndicator = ({ inline = false, passwordStrengthIndicator,
|
|
|
68
68
|
// Main Input Component
|
|
69
69
|
// ============================================================================
|
|
70
70
|
const Input = React__default.forwardRef(({ size = "md", state = "default", className = "", label, labelClassName = "", helperText, errorMessage, 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
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
71
72
|
const [isFocused, setIsFocused] = useState(false);
|
|
72
73
|
const [showPassword, setShowPassword] = useState(false);
|
|
73
74
|
const generatedId = React__default.useId();
|
|
@@ -81,9 +82,12 @@ const Input = React__default.forwardRef(({ size = "md", state = "default", class
|
|
|
81
82
|
: undefined);
|
|
82
83
|
const inputRef = useRef(null);
|
|
83
84
|
const cursorPositionRef = useRef(null);
|
|
84
|
-
// Default icons based on input type
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
// Default icons based on input type. Sized from the shared control-icon
|
|
86
|
+
// ramp (--form-icon-size-*) rather than a flat size-5, so they scale with
|
|
87
|
+
// the field and with a theme that retunes it.
|
|
88
|
+
const iconSizeClass = ((_a = inputTheme.iconSizes) === null || _a === void 0 ? void 0 : _a[size]) || "size-4";
|
|
89
|
+
const defaultPasswordIcon = showTypeIcon && type === "password" && !iconStart ? (jsx(Lock, { className: cn(iconSizeClass, "text-[var(--color-text-muted)]") })) : null;
|
|
90
|
+
const defaultSearchIcon = showTypeIcon && type === "search" && !iconStart ? (jsx(MagnifyingGlass, { className: cn(iconSizeClass, inputTheme.typeStyles.search.iconStyle) })) : null;
|
|
87
91
|
// Use the provided iconStart or the default icon based on input type
|
|
88
92
|
const finalLeftIcon = iconStart || defaultPasswordIcon || defaultSearchIcon;
|
|
89
93
|
// Derived state flags for internal use
|
|
@@ -253,11 +257,12 @@ const Input = React__default.forwardRef(({ size = "md", state = "default", class
|
|
|
253
257
|
const sizeClasses = inputTheme.sizes[size];
|
|
254
258
|
const stateClasses = inputTheme.states[state];
|
|
255
259
|
const helperTextClasses = inputTheme.helperText;
|
|
256
|
-
// Icon classes using design tokens
|
|
257
|
-
|
|
258
|
-
const
|
|
259
|
-
const
|
|
260
|
-
const
|
|
260
|
+
// Icon classes using design tokens โ size-aware slot inset and text runway,
|
|
261
|
+
// falling back to the flat originals if a theme only overrode those.
|
|
262
|
+
const iconStartClasses = ((_b = inputTheme.iconStartStyles) === null || _b === void 0 ? void 0 : _b[size]) || inputTheme.iconStartStyle;
|
|
263
|
+
const iconEndClasses = ((_c = inputTheme.iconEndStyles) === null || _c === void 0 ? void 0 : _c[size]) || inputTheme.iconEndStyle;
|
|
264
|
+
const iconStartPadding = ((_e = (_d = inputTheme.iconPaddings) === null || _d === void 0 ? void 0 : _d.left) === null || _e === void 0 ? void 0 : _e[size]) || inputTheme.iconPadding.left;
|
|
265
|
+
const iconEndPadding = ((_g = (_f = inputTheme.iconPaddings) === null || _f === void 0 ? void 0 : _f.right) === null || _g === void 0 ? void 0 : _g[size]) || inputTheme.iconPadding.right;
|
|
261
266
|
// Content classes - size-aware
|
|
262
267
|
const contentStyleClasses = inputTheme.contentStyle[size];
|
|
263
268
|
const contentStartStyle = inputTheme.contentStartStyle;
|
|
@@ -282,7 +287,7 @@ const Input = React__default.forwardRef(({ size = "md", state = "default", class
|
|
|
282
287
|
}, onBlur: (e) => {
|
|
283
288
|
setIsFocused(false);
|
|
284
289
|
onBlur === null || onBlur === void 0 ? void 0 : onBlur(e);
|
|
285
|
-
}, 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 }), displayedMessage && (typeof displayedMessage === 'string' ? (jsx("div", { id: `${inputId}-helper`, className: cn(helperTextClasses, helperTextClassName), role: isInvalid ? "alert" : undefined, children: displayedMessage })) : (displayedMessage))] }));
|
|
290
|
+
}, 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, iconSizeClass: iconSizeClass })), 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 }), displayedMessage && (typeof displayedMessage === 'string' ? (jsx("div", { id: `${inputId}-helper`, className: cn(helperTextClasses, helperTextClassName), role: isInvalid ? "alert" : undefined, children: displayedMessage })) : (displayedMessage))] }));
|
|
286
291
|
});
|
|
287
292
|
Input.displayName = "Input";
|
|
288
293
|
|
|
@@ -45,6 +45,16 @@ const inputTheme = {
|
|
|
45
45
|
// Icon positioning styles
|
|
46
46
|
iconStartStyle: formsBaseTheme.iconStyles.left,
|
|
47
47
|
iconEndStyle: formsBaseTheme.iconStyles.right,
|
|
48
|
+
// Size-aware icon geometry: slot inset from --form-px-*, glyph from
|
|
49
|
+
// --form-icon-size-*. The flat keys above stay as the fallback for a theme
|
|
50
|
+
// that only overrode those.
|
|
51
|
+
iconStartStyles: formsBaseTheme.iconStyles.leftSizes,
|
|
52
|
+
iconEndStyles: formsBaseTheme.iconStyles.rightSizes,
|
|
53
|
+
iconSizes: formsBaseTheme.iconStyles.sizes,
|
|
54
|
+
iconPaddings: {
|
|
55
|
+
left: formsBaseTheme.iconStyles.paddingLeftSizes,
|
|
56
|
+
right: formsBaseTheme.iconStyles.paddingRightSizes,
|
|
57
|
+
},
|
|
48
58
|
// Icon padding configuration
|
|
49
59
|
iconPadding: formsBaseTheme.iconStyles.padding,
|
|
50
60
|
// Content styles - prefix/suffix content with size-aware font sizes
|
|
@@ -115,12 +115,26 @@ export interface InputTheme {
|
|
|
115
115
|
sizes: Record<InputSize, string>;
|
|
116
116
|
states: Record<InputState, string>;
|
|
117
117
|
helperText: string;
|
|
118
|
+
/** @deprecated size-blind; superseded by `iconStartStyles` */
|
|
118
119
|
iconStartStyle: string;
|
|
120
|
+
/** @deprecated size-blind; superseded by `iconEndStyles` */
|
|
119
121
|
iconEndStyle: string;
|
|
122
|
+
/** @deprecated size-blind; superseded by `iconPaddings` */
|
|
120
123
|
iconPadding: {
|
|
121
124
|
left: string;
|
|
122
125
|
right: string;
|
|
123
126
|
};
|
|
127
|
+
/** Per-size leading icon slot (position + inset) */
|
|
128
|
+
iconStartStyles?: Partial<Record<InputSize, string>>;
|
|
129
|
+
/** Per-size trailing icon slot (position + inset) */
|
|
130
|
+
iconEndStyles?: Partial<Record<InputSize, string>>;
|
|
131
|
+
/** Per-size text runway reserved for each icon slot */
|
|
132
|
+
iconPaddings?: {
|
|
133
|
+
left?: Partial<Record<InputSize, string>>;
|
|
134
|
+
right?: Partial<Record<InputSize, string>>;
|
|
135
|
+
};
|
|
136
|
+
/** Per-size sizing for the built-in type icons (lock, search) */
|
|
137
|
+
iconSizes?: Record<InputSize, string>;
|
|
124
138
|
contentStyle: Record<InputSize, string>;
|
|
125
139
|
contentStartStyle: string;
|
|
126
140
|
contentEndStyle: string;
|
|
@@ -75,7 +75,7 @@ const DEFAULT_COUNTRIES = [
|
|
|
75
75
|
* />
|
|
76
76
|
* ```
|
|
77
77
|
*/
|
|
78
|
-
const InputAddress = React__default.forwardRef(({ state = 'default', errorMessage, showCountrySelector = true, showPostalCode = true, availableCountries = [], defaultCountry = "United States", onCountryChange, onPostalCodeChange, countryLabel = "Country", postalCodeLabel = "ZIP / Postal code", legendText = "Billing address", streetInputProps = {}, stateInputProps = {}, countryInputProps = {}, postalCodeInputProps = {}, className = "", theme = {}, }, ref) => {
|
|
78
|
+
const InputAddress = React__default.forwardRef(({ state = 'default', size = 'md', errorMessage, showCountrySelector = true, showPostalCode = true, availableCountries = [], defaultCountry = "United States", onCountryChange, onPostalCodeChange, countryLabel = "Country", postalCodeLabel = "ZIP / Postal code", legendText = "Billing address", streetInputProps = {}, stateInputProps = {}, countryInputProps = {}, postalCodeInputProps = {}, className = "", theme = {}, }, ref) => {
|
|
79
79
|
var _a;
|
|
80
80
|
const isDisabled = state === 'disabled';
|
|
81
81
|
// Clear the error visually as soon as the user starts re-entering after an error.
|
|
@@ -140,10 +140,10 @@ const InputAddress = React__default.forwardRef(({ state = 'default', errorMessag
|
|
|
140
140
|
const isBottomRowSplit = showCountrySelector && showPostalCode;
|
|
141
141
|
// Merge group state into each field's props; strip per-field helperText so only the
|
|
142
142
|
// consolidated message shows at the bottom.
|
|
143
|
-
const { helperText: _st, onChange: _stOnChange, ...streetProps } = { state: fieldState, ...streetInputProps };
|
|
144
|
-
const { helperText: _sa, onChange: _saOnChange, ...stateProps } = { state: fieldState, ...stateInputProps };
|
|
145
|
-
const { helperText: _co, className: _coClass, ...countryProps } = { state: fieldState, ...countryInputProps };
|
|
146
|
-
const { helperText: _pc, ...postalProps } = { state: fieldState, ...postalCodeInputProps };
|
|
143
|
+
const { helperText: _st, onChange: _stOnChange, ...streetProps } = { state: fieldState, size, ...streetInputProps };
|
|
144
|
+
const { helperText: _sa, onChange: _saOnChange, ...stateProps } = { state: fieldState, size, ...stateInputProps };
|
|
145
|
+
const { helperText: _co, className: _coClass, ...countryProps } = { state: fieldState, size, ...countryInputProps };
|
|
146
|
+
const { helperText: _pc, ...postalProps } = { state: fieldState, size, ...postalCodeInputProps };
|
|
147
147
|
return (jsxs("fieldset", { ref: ref, className: `w-full border-0 m-0 p-0 ${isInvalid ? 'state-invalid' : ''} ${className}`, "data-testid": "input-address-fieldset", "aria-invalid": isInvalid || undefined, "aria-errormessage": isInvalid && errorMessage ? errorId : undefined, 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: `relative grid gap-0 ${mergedTheme.contentStyles}`, "data-testid": "input-address-content", children: [jsx("div", { className: "w-full relative focus-within:z-10 hover: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", ...streetProps, onChange: markDirty(_stOnChange) }) }), jsx("div", { className: "w-full -mt-[0.5px] relative focus-within:z-10 hover:z-10", "data-testid": "input-address-state", children: jsx(Input, { id: stateId, type: "text", placeholder: "State", "aria-label": "state", inputGroupClassName: showBottomRow ? "rounded-none" : "rounded-t-none", ...stateProps, onChange: markDirty(_saOnChange) }) }), showBottomRow && (jsxs("div", { className: cn("grid grid-cols-1 w-full -mt-px relative focus-within:z-10 hover:z-10", isBottomRowSplit && "sm:grid-cols-2"), children: [showCountrySelector && (jsx("div", { className: `${mergedTheme.countryWrapperStyles} w-full sm:pr-0 relative focus-within:z-10 hover:z-10`, "data-testid": "input-address-country", children: jsx(Select, { id: countryId, options: countryOptions, value: country, onChange: handleCountryChange, placeholder: countryLabel, searchable: true, searchPlaceholder: `Search ${countryLabel.toLowerCase()}`, disabled: isDisabled,
|
|
148
148
|
// className lands on the Select trigger button (which carries the same
|
|
149
149
|
// inputGroup border/radius as Input's inputGroupClassName target), so the
|
|
@@ -17,6 +17,13 @@ import { SelectSingleProps } from '../Select/Select.types';
|
|
|
17
17
|
export interface InputAddressProps {
|
|
18
18
|
/** Visual/validation state of the entire address group */
|
|
19
19
|
state?: 'default' | 'disabled' | 'invalid';
|
|
20
|
+
/**
|
|
21
|
+
* Size of every field in the group, from the shared `--form-*` scale, so the
|
|
22
|
+
* address block lines up with the Inputs and Selects around it. A per-field
|
|
23
|
+
* `*InputProps.size` still wins.
|
|
24
|
+
* @default 'md'
|
|
25
|
+
*/
|
|
26
|
+
size?: InputProps['size'];
|
|
20
27
|
/** Consolidated error message shown below all fields when `state="invalid"` */
|
|
21
28
|
errorMessage?: React.ReactNode;
|
|
22
29
|
/** Whether to show the country selector */
|