@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.
Files changed (54) hide show
  1. package/CHANGELOG.md +202 -0
  2. package/dist/components/ai/AgentRequest/AgentRequest.theme.js +1 -1
  3. package/dist/components/core/Button/Button.theme.js +3 -3
  4. package/dist/components/core/Pill/Pill.js +32 -17
  5. package/dist/components/core/Pill/Pill.types.d.ts +14 -1
  6. package/dist/components/core/SlidingNumber/SlidingNumber.d.ts +8 -0
  7. package/dist/components/core/SlidingNumber/SlidingNumber.js +12 -3
  8. package/dist/components/forms/ColorPicker/ColorPicker.js +45 -3
  9. package/dist/components/forms/ColorPicker/ColorPickerBody.js +8 -4
  10. package/dist/components/forms/Combobox/Combobox.js +8 -4
  11. package/dist/components/forms/Combobox/Combobox.theme.js +9 -0
  12. package/dist/components/forms/Combobox/Combobox.types.d.ts +13 -0
  13. package/dist/components/forms/DatePicker/DatePicker.js +19 -12
  14. package/dist/components/forms/DatePicker/DatePicker.theme.js +5 -0
  15. package/dist/components/forms/DatePicker/DatePicker.types.d.ts +8 -0
  16. package/dist/components/forms/DatePicker/DatePickerContext.d.ts +0 -1
  17. package/dist/components/forms/DatePicker/DatePickerTrigger.js +5 -3
  18. package/dist/components/forms/DateRangePicker/DateRangePicker.theme.js +5 -0
  19. package/dist/components/forms/DateRangePicker/DateRangePicker.types.d.ts +8 -0
  20. package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +5 -3
  21. package/dist/components/forms/Input/Input.js +17 -12
  22. package/dist/components/forms/Input/Input.theme.js +10 -0
  23. package/dist/components/forms/Input/Input.types.d.ts +14 -0
  24. package/dist/components/forms/InputAddress/InputAddress.js +5 -5
  25. package/dist/components/forms/InputAddress/InputAddress.types.d.ts +7 -0
  26. package/dist/components/forms/InputCounter/InputCounter.js +194 -17
  27. package/dist/components/forms/InputCounter/InputCounter.theme.d.ts +12 -0
  28. package/dist/components/forms/InputCounter/InputCounter.theme.js +92 -2
  29. package/dist/components/forms/InputCounter/InputCounter.types.d.ts +47 -4
  30. package/dist/components/forms/InputCreditCard/InputCreditCard.js +4 -4
  31. package/dist/components/forms/InputCreditCard/InputCreditCard.types.d.ts +7 -0
  32. package/dist/components/forms/Mention/Mention.js +13 -11
  33. package/dist/components/forms/RichTextEditor/RichTextEditor.js +12 -3
  34. package/dist/components/forms/Select/Select.js +15 -8
  35. package/dist/components/forms/Select/Select.theme.js +9 -1
  36. package/dist/components/forms/Select/Select.types.d.ts +16 -1
  37. package/dist/components/forms/Textarea/Textarea.js +14 -25
  38. package/dist/components/forms/TimePicker/TimePicker.theme.js +9 -0
  39. package/dist/components/forms/TimePicker/TimePicker.types.d.ts +11 -0
  40. package/dist/components/forms/TimePicker/TimePickerTrigger.js +7 -5
  41. package/dist/components/forms/forms.theme.d.ts +57 -0
  42. package/dist/components/forms/forms.theme.js +83 -8
  43. package/dist/components/generative/registry.js +1 -0
  44. package/dist/components/generative/schema.generated.js +21 -1
  45. package/dist/generative.schema.json +21 -1
  46. package/dist/registry.json +108 -17
  47. package/dist/styles.css +1 -1
  48. package/dist/tools.json +21 -1
  49. package/dist/utils/composeEventHandlers.d.ts +19 -0
  50. package/dist/utils/composeEventHandlers.js +26 -0
  51. package/dist/utils/composeEventHandlers.test.d.ts +1 -0
  52. package/dist/utils/index.d.ts +1 -0
  53. package/package.json +1 -1
  54. 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, useEffect, useCallback, useMemo } from 'react';
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
- const [selectedValue, setSelectedValue] = useState(value || defaultValue);
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
- if (newDate !== selectedValue) {
65
- setSelectedValue(newDate);
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
- setSelectedValue(newRange);
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: "h-4 w-4" }) })] }) }));
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: "h-4 w-4" }) })] }) }));
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("size-4", decrementIconStyle), "aria-hidden": "true" }) }), jsx("button", { type: "button", className: `${numberButtonClasses}`, onClick: (e) => {
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("size-4", incrementIconStyle), "aria-hidden": "true" }) })] }));
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
- const defaultPasswordIcon = showTypeIcon && type === "password" && !iconStart ? (jsx(Lock, { className: "size-5 text-[var(--color-text-muted)]" })) : null;
86
- const defaultSearchIcon = showTypeIcon && type === "search" && !iconStart ? (jsx(MagnifyingGlass, { className: cn("size-5", inputTheme.typeStyles.search.iconStyle) })) : null;
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
- const iconStartClasses = inputTheme.iconStartStyle;
258
- const iconEndClasses = inputTheme.iconEndStyle;
259
- const iconStartPadding = inputTheme.iconPadding.left;
260
- const iconEndPadding = inputTheme.iconPadding.right;
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 */