@pk-design/react-tailwind 1.8.1 → 1.9.0

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.
@@ -151,59 +151,35 @@ TextContent.displayName = 'TextContent';
151
151
 
152
152
  /**
153
153
  * Tailwind CSS classes for theme + variant combinations.
154
+ *
155
+ * Only colours live here. Focus outline width/offset and disabled styling are
156
+ * applied uniformly in the component so they can be toggled in one place.
154
157
  */
155
158
  const THEMED_BTN_CLASS = {
156
159
  primary: {
157
- default: 'text-white bg-[var(--ui-primary)] hover:bg-[var(--ui-primary-hover)] focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-primary-ring)] disabled:bg-[var(--ui-primary-disabled)]',
158
- outlined: 'text-[var(--ui-primary-text)] hover:text-white border border-[var(--ui-primary)] hover:bg-[var(--ui-primary)] focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-primary-ring)] disabled:text-[var(--ui-primary-disabled)] disabled:border-[var(--ui-primary-disabled)]',
159
- plain: 'text-[var(--ui-primary-text)] hover:bg-blue-50/80 dark:hover:bg-blue-950/40 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-primary-ring)] disabled:text-[var(--ui-primary-disabled)]',
160
+ default: 'text-white bg-[var(--ui-primary)] hover:bg-[var(--ui-primary-hover)] focus-visible:outline-[var(--ui-primary-ring)]',
161
+ outlined: 'text-[var(--ui-primary-text)] hover:text-white border border-[var(--ui-primary)] hover:bg-[var(--ui-primary)] focus-visible:outline-[var(--ui-primary-ring)]',
162
+ plain: 'text-[var(--ui-primary-text)] hover:bg-current/10 active:bg-current/15 focus-visible:outline-[var(--ui-primary-ring)]',
160
163
  },
161
164
  danger: {
162
- default: 'text-white bg-[var(--ui-danger)] hover:bg-[var(--ui-danger-hover)] focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-danger-ring)] disabled:bg-[var(--ui-danger-disabled)]',
163
- outlined: 'text-[var(--ui-danger-text)] hover:text-white border border-[var(--ui-danger)] hover:bg-[var(--ui-danger)] focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-danger-ring)] disabled:text-[var(--ui-danger-disabled)] disabled:border-[var(--ui-danger-disabled)]',
164
- plain: 'text-[var(--ui-danger-text)] hover:bg-red-50/80 dark:hover:bg-red-950/40 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-danger-ring)] disabled:text-[var(--ui-danger-disabled)]',
165
+ default: 'text-white bg-[var(--ui-danger)] hover:bg-[var(--ui-danger-hover)] focus-visible:outline-[var(--ui-danger-ring)]',
166
+ outlined: 'text-[var(--ui-danger-text)] hover:text-white border border-[var(--ui-danger)] hover:bg-[var(--ui-danger)] focus-visible:outline-[var(--ui-danger-ring)]',
167
+ plain: 'text-[var(--ui-danger-text)] hover:bg-current/10 active:bg-current/15 focus-visible:outline-[var(--ui-danger-ring)]',
165
168
  },
166
169
  secondary: {
167
- default: 'text-gray-700 bg-white border border-[var(--ui-border)] hover:bg-gray-100 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-700 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-focus-ring)] disabled:bg-gray-200 dark:disabled:bg-gray-700',
168
- outlined: 'text-gray-700 hover:text-white border border-[var(--ui-border)] hover:bg-gray-700 dark:text-gray-200 dark:hover:text-white dark:hover:bg-gray-600 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-focus-ring)] disabled:text-gray-400 disabled:border-[var(--ui-border-muted)]',
169
- plain: 'text-gray-700 hover:bg-gray-100 dark:text-gray-200 dark:hover:bg-gray-800 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-focus-ring)] disabled:text-gray-400',
170
+ default: 'text-gray-700 bg-white border border-[var(--ui-border)] hover:bg-gray-100 dark:bg-gray-800 dark:text-white dark:hover:bg-gray-700 focus-visible:outline-[var(--ui-focus-ring)]',
171
+ outlined: 'text-gray-700 hover:text-white border border-[var(--ui-border)] hover:bg-gray-700 dark:text-gray-200 dark:hover:text-white dark:hover:bg-gray-600 focus-visible:outline-[var(--ui-focus-ring)]',
172
+ plain: 'text-gray-700 hover:bg-current/10 active:bg-current/15 dark:text-gray-200 focus-visible:outline-[var(--ui-focus-ring)]',
170
173
  },
171
174
  success: {
172
- default: 'text-white bg-emerald-600 hover:bg-emerald-700 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-emerald-500 disabled:bg-emerald-300 dark:disabled:bg-emerald-900',
173
- outlined: 'text-emerald-600 hover:text-white border border-emerald-600 hover:bg-emerald-600 dark:text-emerald-400 dark:border-emerald-500 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-emerald-500 disabled:opacity-50',
174
- plain: 'text-emerald-600 hover:bg-emerald-50 dark:text-emerald-400 dark:hover:bg-emerald-950/40 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-emerald-500 disabled:opacity-50',
175
+ default: 'text-white bg-emerald-600 hover:bg-emerald-700 focus-visible:outline-emerald-500',
176
+ outlined: 'text-emerald-700 hover:text-white border border-emerald-600 hover:bg-emerald-600 dark:text-emerald-400 dark:border-emerald-500 focus-visible:outline-emerald-500',
177
+ plain: 'text-emerald-700 hover:bg-current/10 active:bg-current/15 dark:text-emerald-400 focus-visible:outline-emerald-500',
175
178
  },
176
179
  warning: {
177
- default: 'text-white bg-amber-500 hover:bg-amber-600 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-amber-500 disabled:bg-amber-300 dark:disabled:bg-amber-900',
178
- outlined: 'text-amber-600 hover:text-white border border-amber-500 hover:bg-amber-500 dark:text-amber-400 dark:border-amber-500 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-amber-500 disabled:opacity-50',
179
- plain: 'text-amber-600 hover:bg-amber-50 dark:text-amber-400 dark:hover:bg-amber-950/40 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-amber-500 disabled:opacity-50',
180
- },
181
- };
182
- const LOADER_TEXT_CLASS = {
183
- primary: {
184
- default: 'text-white',
185
- outlined: 'text-[var(--ui-primary-text)]',
186
- plain: 'text-[var(--ui-primary-text)]',
187
- },
188
- danger: {
189
- default: 'text-white',
190
- outlined: 'text-[var(--ui-danger-text)]',
191
- plain: 'text-[var(--ui-danger-text)]',
192
- },
193
- secondary: {
194
- default: 'text-gray-700 dark:text-white',
195
- outlined: 'text-gray-700 dark:text-gray-200',
196
- plain: 'text-gray-700 dark:text-gray-200',
197
- },
198
- success: {
199
- default: 'text-white',
200
- outlined: 'text-emerald-600 dark:text-emerald-400',
201
- plain: 'text-emerald-600 dark:text-emerald-400',
202
- },
203
- warning: {
204
- default: 'text-white',
205
- outlined: 'text-amber-600 dark:text-amber-400',
206
- plain: 'text-amber-600 dark:text-amber-400',
180
+ default: 'text-white bg-amber-500 hover:bg-amber-600 focus-visible:outline-amber-500',
181
+ outlined: 'text-amber-700 hover:text-white border border-amber-500 hover:bg-amber-500 dark:text-amber-400 dark:border-amber-500 focus-visible:outline-amber-500',
182
+ plain: 'text-amber-700 hover:bg-current/10 active:bg-current/15 dark:text-amber-400 focus-visible:outline-amber-500',
207
183
  },
208
184
  };
209
185
  const SIZE_CLASSES = {
@@ -223,22 +199,32 @@ const LOADER_SIZE = {
223
199
  };
224
200
  /**
225
201
  * Base shared tailwind classes.
202
+ *
203
+ * Disabled styling is keyed off both the native `disabled` attribute and
204
+ * `aria-disabled`, so non-button elements rendered via `as` look disabled too.
226
205
  */
227
206
  const BASE_BTN_CLASS = 'font-medium inline-flex items-center justify-center shrink-0 cursor-pointer select-none ' +
228
- 'disabled:cursor-not-allowed disabled:pointer-events-none disabled:opacity-75 ' +
229
- 'active:scale-[0.98] transition-all duration-150 ease-out focus:outline-none';
207
+ 'transition-[color,background-color,border-color,box-shadow,opacity,transform] duration-150 ease-out ' +
208
+ 'active:scale-[0.98] focus:outline-none ' +
209
+ 'disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none ' +
210
+ 'aria-disabled:pointer-events-none aria-disabled:opacity-50 aria-disabled:shadow-none';
230
211
  const Button = React.forwardRef(({ as, type, ...props }, ref) => {
231
- var _a, _b, _c, _d;
232
212
  const { className = '', children, theme = 'primary', variant = 'default', size = 'md', leftIcon, rightIcon, fullWidth = false, loading = false, loadingText = '', rounded = false, disabled, iconOnly = false, noOutlineOnFocus = false, active, onClick, ...restProps } = props;
233
213
  const Component = as || 'button';
234
214
  const isNativeButton = Component === 'button';
215
+ // Clean invalid props when not rendering a real button
216
+ const { type: _ignoreType, disabled: _ignoreDisabled, role, tabIndex, onKeyDown, onKeyUp, ...domProps } = restProps;
217
+ const ariaLabel = domProps['aria-label'];
218
+ const ariaLabelledBy = domProps['aria-labelledby'];
235
219
  // Accessibility guard for icon-only buttons
236
- if (iconOnly && !restProps['aria-label']) {
237
- console.warn('[ui] <Button iconOnly> requires aria-label for accessibility.');
238
- }
220
+ React.useEffect(() => {
221
+ if (iconOnly && !ariaLabel && !ariaLabelledBy) {
222
+ console.warn('[ui] <Button iconOnly> requires aria-label or aria-labelledby for accessibility.');
223
+ }
224
+ }, [iconOnly, ariaLabel, ariaLabelledBy]);
239
225
  const resolvedDisabled = Boolean(disabled || loading);
240
- // Clean invalid props when not rendering a real button
241
- const { type: _ignoreType, disabled: _ignoreDisabled, ...cleanRest } = restProps;
226
+ // Links (`href`, or `to` for router Link components) keep their native semantics.
227
+ const isLink = !isNativeButton && (domProps.href != null || domProps.to != null);
242
228
  const handleClick = (e) => {
243
229
  if (resolvedDisabled) {
244
230
  e.preventDefault();
@@ -247,31 +233,73 @@ const Button = React.forwardRef(({ as, type, ...props }, ref) => {
247
233
  }
248
234
  onClick === null || onClick === void 0 ? void 0 : onClick(e);
249
235
  };
236
+ // Mirror native <button> keyboard activation for non-button elements:
237
+ // Enter activates on keydown, Space on keyup (and must not scroll the page).
238
+ const handleKeyDown = (e) => {
239
+ onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(e);
240
+ if (e.defaultPrevented || e.target !== e.currentTarget)
241
+ return;
242
+ if (e.key === 'Enter') {
243
+ e.preventDefault();
244
+ e.currentTarget.click();
245
+ }
246
+ else if (e.key === ' ') {
247
+ e.preventDefault();
248
+ }
249
+ };
250
+ const handleKeyUp = (e) => {
251
+ onKeyUp === null || onKeyUp === void 0 ? void 0 : onKeyUp(e);
252
+ if (e.defaultPrevented || e.target !== e.currentTarget)
253
+ return;
254
+ if (e.key === ' ') {
255
+ e.preventDefault();
256
+ e.currentTarget.click();
257
+ }
258
+ };
250
259
  const elementProps = isNativeButton
251
260
  ? {
252
261
  type: type !== null && type !== void 0 ? type : 'button',
253
262
  disabled: resolvedDisabled,
263
+ role,
264
+ tabIndex,
254
265
  onClick: handleClick,
266
+ onKeyDown,
267
+ onKeyUp,
255
268
  }
256
- : {
257
- role: (_a = cleanRest.role) !== null && _a !== void 0 ? _a : 'button',
258
- tabIndex: resolvedDisabled ? -1 : ((_b = cleanRest.tabIndex) !== null && _b !== void 0 ? _b : 0),
259
- 'aria-disabled': resolvedDisabled ? true : undefined,
260
- onClick: handleClick,
261
- };
269
+ : isLink
270
+ ? {
271
+ role,
272
+ tabIndex: resolvedDisabled ? -1 : tabIndex,
273
+ 'aria-disabled': resolvedDisabled ? true : undefined,
274
+ onClick: handleClick,
275
+ onKeyDown,
276
+ onKeyUp,
277
+ }
278
+ : {
279
+ role: role !== null && role !== void 0 ? role : 'button',
280
+ tabIndex: resolvedDisabled ? -1 : (tabIndex !== null && tabIndex !== void 0 ? tabIndex : 0),
281
+ 'aria-disabled': resolvedDisabled ? true : undefined,
282
+ onClick: handleClick,
283
+ onKeyDown: handleKeyDown,
284
+ onKeyUp: handleKeyUp,
285
+ };
262
286
  const sizeObj = SIZE_CLASSES[size] || SIZE_CLASSES.md;
263
287
  const classes = React.useMemo(() => {
264
288
  var _a;
265
- return buildClassName(BASE_BTN_CLASS, ((_a = THEMED_BTN_CLASS[theme]) === null || _a === void 0 ? void 0 : _a[variant]) || THEMED_BTN_CLASS.primary.default, iconOnly ? sizeObj.iconOnly : sizeObj.normal, !noOutlineOnFocus && 'focus-visible:ring-2 focus-visible:ring-offset-2', rounded && 'rounded-full', fullWidth && 'w-full', variant === 'default' && 'shadow-xs hover:shadow-none disabled:shadow-none', active && 'ring-2 ring-offset-2 ring-current', className);
289
+ return buildClassName(BASE_BTN_CLASS, ((_a = THEMED_BTN_CLASS[theme]) === null || _a === void 0 ? void 0 : _a[variant]) || THEMED_BTN_CLASS.primary.default, iconOnly ? sizeObj.iconOnly : sizeObj.normal,
290
+ // An outline (not a ring) so the offset gap stays transparent on any surface/mode.
291
+ !noOutlineOnFocus &&
292
+ 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid', rounded && 'rounded-full', fullWidth && 'w-full', variant === 'default' && 'shadow-xs hover:shadow-none', active && 'ring-2 ring-current', className);
266
293
  }, [theme, variant, sizeObj, iconOnly, noOutlineOnFocus, rounded, fullWidth, active, className]);
267
- const loaderIcon = (jsxRuntime.jsx(Loader, { "data-testid": "btn-loader-icon", size: LOADER_SIZE[size] || 'xs', className: buildClassName((_c = LOADER_TEXT_CLASS[theme]) === null || _c === void 0 ? void 0 : _c[variant]) }));
294
+ // The spinner and loading text inherit the button's text colour.
295
+ const loaderIcon = (jsxRuntime.jsx(Loader, { "data-testid": "btn-loader-icon", size: LOADER_SIZE[size] || 'xs', className: "text-current" }));
268
296
  /**
269
297
  * SPECIAL CASE: iconOnly + loading → show spinner only
270
298
  */
271
299
  if (loading && iconOnly) {
272
- return (jsxRuntime.jsx(Component, { ref: ref, className: classes, "aria-busy": "true", "aria-pressed": active !== undefined ? active : undefined, ...elementProps, ...cleanRest, children: loaderIcon }));
300
+ return (jsxRuntime.jsx(Component, { ref: ref, className: classes, "aria-busy": "true", "aria-pressed": active !== undefined ? active : undefined, ...domProps, ...elementProps, children: loaderIcon }));
273
301
  }
274
- return (jsxRuntime.jsx(Component, { ref: ref, className: classes, "aria-busy": loading ? 'true' : undefined, "aria-pressed": active !== undefined ? active : undefined, ...elementProps, ...cleanRest, children: loading ? (jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-2", "aria-live": "polite", children: [loaderIcon, loadingText ? (jsxRuntime.jsx(TextContent, { className: buildClassName((_d = LOADER_TEXT_CLASS[theme]) === null || _d === void 0 ? void 0 : _d[variant]), children: loadingText })) : (children)] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [leftIcon && jsxRuntime.jsx("span", { className: "shrink-0 inline-flex items-center", children: leftIcon }), children, rightIcon && jsxRuntime.jsx("span", { className: "shrink-0 inline-flex items-center", children: rightIcon })] })) }));
302
+ return (jsxRuntime.jsx(Component, { ref: ref, className: classes, "aria-busy": loading ? 'true' : undefined, "aria-pressed": active !== undefined ? active : undefined, ...domProps, ...elementProps, children: loading ? (jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-2", "aria-live": "polite", children: [loaderIcon, loadingText ? (jsxRuntime.jsx(TextContent, { className: "text-current", children: loadingText })) : (children)] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [leftIcon && jsxRuntime.jsx("span", { className: "shrink-0 inline-flex items-center", children: leftIcon }), children, rightIcon && jsxRuntime.jsx("span", { className: "shrink-0 inline-flex items-center", children: rightIcon })] })) }));
275
303
  });
276
304
  Button.displayName = 'Button';
277
305
 
@@ -595,7 +623,7 @@ const Banner = React.forwardRef((props, ref) => {
595
623
  const { type = 'info', iconSize = 'sm', children, icon, removable = false, onRemove, className, ...restProps } = props;
596
624
  const DefaultIcon = IconMap[type];
597
625
  const iconNode = icon !== undefined ? (icon) : (jsxRuntime.jsx(DefaultIcon, { "data-testid": `banner-${type}-icon`, className: buildClassName('shrink-0', sizeMap[iconSize]) }));
598
- return (jsxRuntime.jsxs("div", { ref: ref, className: buildClassName('text-sm rounded-md flex w-full p-3 items-center gap-3 ring-1 ring-inset', TypeClassMap[type], className), "data-testid": `banner-${type}`, ...restProps, children: [iconNode, jsxRuntime.jsx("div", { className: "flex items-center w-full justify-between", children: children }), removable && (jsxRuntime.jsx(Button, { iconOnly: true, "aria-label": "Dismiss banner", size: "xs", variant: "plain", theme: "secondary", className: "hover:bg-black/10 dark:hover:bg-white/10 shrink-0", onClick: () => onRemove === null || onRemove === void 0 ? void 0 : onRemove(), "data-testid": "banner-remove-btn", children: jsxRuntime.jsx(SvgX, { className: "size-4.5", "data-testid": "banner-remove-icon" }) }))] }));
626
+ return (jsxRuntime.jsxs("div", { ref: ref, className: buildClassName('text-sm rounded-md flex w-full p-3 items-center gap-3 ring-1 ring-inset', TypeClassMap[type], className), "data-testid": `banner-${type}`, ...restProps, children: [iconNode, jsxRuntime.jsx("div", { className: "flex items-center w-full justify-between", children: children }), removable && (jsxRuntime.jsx(Button, { iconOnly: true, "aria-label": "Dismiss banner", size: "xs", variant: "plain", className: "text-current focus-visible:outline-current shrink-0", onClick: () => onRemove === null || onRemove === void 0 ? void 0 : onRemove(), "data-testid": "banner-remove-btn", children: jsxRuntime.jsx(SvgX, { className: "size-4.5", "data-testid": "banner-remove-icon" }) }))] }));
599
627
  });
600
628
  Banner.displayName = 'Banner';
601
629
 
@@ -3848,9 +3876,18 @@ const sizeClasses$1 = {
3848
3876
  },
3849
3877
  };
3850
3878
  const SelectBox = React.forwardRef((props, forwardedRef) => {
3851
- const { labelKey = 'label', valueKey = 'value', name, id = name, options = EMPTY_ARRAY, groups, selected, size = 'md', label = null, labelWrapperClass = '', labelClass = '', labelHint = null, disabled = false, containerClass = '', onChange = NOOP, placeholder, dropdownContainerClass = 'z-20', multiple = false, maxSelection, showSelectAll = false, immediate = true, showErrorMessage = true, error = null, helperText, leftGroup, renderOption, async = false, searchable = true, required = false, allowAdd = false, addNewText = 'Create', onAdd = NOOP, allowClear = false, allowFreeText = false, modalDropdown = false, noOptionsText = 'No options available', } = props;
3879
+ const { labelKey = 'label', valueKey = 'value', name, id = name, options = EMPTY_ARRAY, groups, selected: selectedProp, defaultSelected, size = 'md', label = null, labelWrapperClass = '', labelClass = '', labelHint = null, disabled = false, containerClass = '', onChange: onChangeProp = NOOP, placeholder, dropdownContainerClass = 'z-20', dropdownMinWidth, multiple = false, maxSelection, showSelectAll = false, immediate = true, showErrorMessage = true, error = null, helperText, leftGroup, renderOption, async = false, searchable = true, required = false, allowAdd = false, addNewText = 'Create', onAdd = NOOP, allowClear = false, allowFreeText = false, modalDropdown = false, noOptionsText = 'No options available', } = props;
3880
+ // Uncontrolled when `selected` is not passed: keep the selection internally.
3881
+ const isControlled = selectedProp !== undefined;
3882
+ const [internalSelected, setInternalSelected] = React.useState(defaultSelected !== null && defaultSelected !== void 0 ? defaultSelected : null);
3883
+ const selected = isControlled ? selectedProp : internalSelected;
3884
+ const onChange = React.useCallback((value) => {
3885
+ if (!isControlled)
3886
+ setInternalSelected(value);
3887
+ onChangeProp(value);
3888
+ }, [isControlled, onChangeProp]);
3852
3889
  if (props.asBottomSheet || props.mobileMode === 'sheet') {
3853
- return jsxRuntime.jsx(MobilePicker, { ...props });
3890
+ return jsxRuntime.jsx(MobilePicker, { ...props, selected: selected, onChange: onChange });
3854
3891
  }
3855
3892
  const hasError = Boolean(error);
3856
3893
  const [query, setQuery] = React.useState('');
@@ -4048,6 +4085,16 @@ const SelectBox = React.forwardRef((props, forwardedRef) => {
4048
4085
  }
4049
4086
  }, [isAllSelected, onChange, allOptions]);
4050
4087
  const hasLeftGroup = leftGroup != null && leftGroup !== undefined;
4088
+ // Reserve room for the chevron (and the clear button when shown) so long values
4089
+ // don't run underneath them.
4090
+ const showClearButton = allowClear && !multiple && !isEmpty(normalizedSelected);
4091
+ const rightPaddingClass = showClearButton
4092
+ ? 'pe-16'
4093
+ : size === 'lg'
4094
+ ? 'pe-11'
4095
+ : size === 'sm'
4096
+ ? 'pe-9'
4097
+ : 'pe-10';
4051
4098
  const hasFreeTextOption = Boolean(allowFreeText &&
4052
4099
  query.trim() &&
4053
4100
  !allOptions.some((opt) => { var _a; return ((_a = getDisplayValue(opt)) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === query.trim().toLowerCase(); }));
@@ -4060,7 +4107,7 @@ const SelectBox = React.forwardRef((props, forwardedRef) => {
4060
4107
  const isOptSelected = selectedValueSet.has(optVal);
4061
4108
  return (jsxRuntime.jsx(react.ComboboxOption, { disabled: option.disabled, value: option, className: "group rounded-lg py-1.5 px-3 select-none data-[focus]:bg-gray-100 dark:data-[focus]:bg-white/10 data-disabled:opacity-50 cursor-pointer data-disabled:pointer-events-none", children: renderOption ? (renderOption(option, Boolean(isOptSelected))) : (jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2 text-gray-900 dark:text-white group-data-[selected]:text-blue-600", children: [jsxRuntime.jsx("span", { className: "text-sm/6", children: getDisplayValue(option) }), jsxRuntime.jsx(SvgCheck, { className: "invisible size-5 dark:stroke-gray-300 group-data-[selected]:visible" })] })) }, optVal));
4062
4109
  };
4063
- return (jsxRuntime.jsxs("div", { className: buildClassName('group space-y-2', hasError && 'has-error', containerClass), ref: containerRef, children: [label && (jsxRuntime.jsxs("div", { className: buildClassName('flex items-center justify-between', labelWrapperClass), children: [jsxRuntime.jsx(Label, { className: labelClass, htmlFor: id, "aria-required": required, children: label }), labelHint && jsxRuntime.jsx(TextContent, { xsmall: true, children: labelHint })] })), jsxRuntime.jsxs(react.Combobox, { multiple: multiple, immediate: immediate, name: name, value: normalizedSelected, onChange: comboboxOnChangeWrapper, onClose: () => setQuery(''), disabled: disabled, by: (a, b) => (a && b ? getValue(a) === getValue(b) : a === b), children: [multiple ? (jsxRuntime.jsxs("div", { className: buildClassName(baseInputClass, 'flex flex-wrap gap-2 peer relative', hasLeftGroup ? (size === 'lg' ? 'ps-12' : size === 'sm' ? 'ps-8' : 'ps-10') : ''), ref: forwardedRef, children: [hasLeftGroup && (jsxRuntime.jsx("span", { className: buildClassName('absolute left-0 top-0 h-full flex items-center justify-center text-gray-400 dark:text-gray-500', currentSize.group), children: leftGroup })), isArray(normalizedSelected) &&
4110
+ return (jsxRuntime.jsxs("div", { className: buildClassName('group space-y-2', hasError && 'has-error', containerClass), ref: containerRef, children: [label && (jsxRuntime.jsxs("div", { className: buildClassName('flex items-center justify-between', labelWrapperClass), children: [jsxRuntime.jsx(Label, { className: labelClass, htmlFor: id, "aria-required": required, children: label }), labelHint && jsxRuntime.jsx(TextContent, { xsmall: true, children: labelHint })] })), jsxRuntime.jsxs(react.Combobox, { multiple: multiple, immediate: immediate, name: name, value: normalizedSelected, onChange: comboboxOnChangeWrapper, onClose: () => setQuery(''), disabled: disabled, by: (a, b) => (a && b ? getValue(a) === getValue(b) : a === b), children: [multiple ? (jsxRuntime.jsxs("div", { className: buildClassName(baseInputClass, 'flex flex-wrap gap-2 peer relative', rightPaddingClass, hasLeftGroup ? (size === 'lg' ? 'ps-12' : size === 'sm' ? 'ps-8' : 'ps-10') : ''), ref: forwardedRef, children: [hasLeftGroup && (jsxRuntime.jsx("span", { className: buildClassName('absolute left-0 top-0 h-full flex items-center justify-center text-gray-400 dark:text-gray-500', currentSize.group), children: leftGroup })), isArray(normalizedSelected) &&
4064
4111
  (normalizedSelected === null || normalizedSelected === void 0 ? void 0 : normalizedSelected.length) > 0 &&
4065
4112
  normalizedSelected.map((option, idx) => (jsxRuntime.jsx(Badge, { theme: "secondary", size: currentSize.badgeSize, removable: !disabled, onRemove: () => removeSelection(idx), children: getDisplayValue(option) }, `${getValue(option)}-${idx}`))), jsxRuntime.jsx(react.ComboboxInput, { className: "outline-none flex-1 dark:placeholder-gray-400 placeholder-gray-400 bg-transparent", "aria-label": name, value: query, autoComplete: "off", placeholder: (normalizedSelected === null || normalizedSelected === void 0 ? void 0 : normalizedSelected.length) ? '' : placeholder, onChange: (e) => {
4066
4113
  const val = e.target.value;
@@ -4100,7 +4147,9 @@ const SelectBox = React.forwardRef((props, forwardedRef) => {
4100
4147
  }
4101
4148
  }, onFocus: () => {
4102
4149
  setQuery('');
4103
- }, className: buildClassName(baseInputClass, hasLeftGroup ? (size === 'lg' ? 'ps-12' : size === 'sm' ? 'ps-8' : 'ps-10') : ''), disabled: disabled, id: id, readOnly: !searchable, ref: inputRef, "data-testid": "combobox-input" }), allowClear && !isEmpty(normalizedSelected) && (jsxRuntime.jsx(Button, { size: "xs", iconOnly: true, "aria-label": "Clear selection", theme: "secondary", variant: "plain", className: "group absolute inset-y-0 right-8 h-6 top-2", onClick: () => handleOnChangeInternal(multiple ? [] : null), disabled: disabled, "data-testid": "combobox-clear-button", noOutlineOnFocus: true, children: jsxRuntime.jsx(SvgX, { className: "size-4 stroke-gray-500 dark:stroke-gray-300" }) })), jsxRuntime.jsx(react.ComboboxButton, { className: "group absolute inset-y-0 right-0 px-2.5 flex items-center justify-center", "data-testid": "combobox-button", children: jsxRuntime.jsx(SvgChevronDown, { className: chevronIconClass }) })] })), jsxRuntime.jsxs(react.ComboboxOptions, { anchor: { to: 'bottom', gap: 5 }, as: "div", modal: modalDropdown, className: buildClassName('w-[var(--input-width)] rounded-lg border border-[var(--ui-border)] bg-white dark:bg-gray-800 p-1 shadow-lg max-h-80 overflow-y-auto z-50 [--anchor-gap:var(--spacing-1)]', '[--anchor-max-height:20rem]', 'origin-top border transition duration-200 ease-[cubic-bezier(0.4, 0.0, 0.2, 1)] data-[closed]:translate-y-[-15%] data-[closed]:opacity-0', dropdownContainerClass), transition: true, "data-testid": "combobox-options", children: [multiple && showSelectAll && allOptions.length > 0 && (jsxRuntime.jsxs("div", { className: "pb-1 mb-1 border-b border-[var(--ui-border-muted)] px-3 pt-1 flex items-center justify-between", children: [jsxRuntime.jsx("button", { type: "button", onClick: handleToggleSelectAll, className: "text-xs font-semibold text-blue-600 dark:text-blue-400 hover:underline cursor-pointer", children: isAllSelected ? 'Deselect All' : 'Select All' }), jsxRuntime.jsxs("span", { className: "text-[11px] font-medium text-gray-400", children: [Array.isArray(normalizedSelected) ? normalizedSelected.length : 0, " of", ' ', allOptions.length] })] })), optionsLoading ? (jsxRuntime.jsx(BodyText, { "data-testid": "options-loading", className: "flex items-center justify-center py-2 px-3 select-none", children: jsxRuntime.jsx(Loader, { size: "sm" }) })) : shouldShowTypeToSearch ? (jsxRuntime.jsx(BodyText, { className: "flex items-center justify-between py-2 px-3 select-none", children: "Type to search..." })) : (jsxRuntime.jsxs("div", { children: [allowFreeText &&
4150
+ }, className: buildClassName(baseInputClass, 'text-ellipsis', rightPaddingClass, hasLeftGroup ? (size === 'lg' ? 'ps-12' : size === 'sm' ? 'ps-8' : 'ps-10') : ''), disabled: disabled, id: id, readOnly: !searchable, ref: inputRef, "data-testid": "combobox-input" }), showClearButton && (jsxRuntime.jsx(Button, { size: "xs", iconOnly: true, "aria-label": "Clear selection", theme: "secondary", variant: "plain", className: "group absolute inset-y-0 right-8 h-6 top-2", onClick: () => handleOnChangeInternal(multiple ? [] : null), disabled: disabled, "data-testid": "combobox-clear-button", children: jsxRuntime.jsx(SvgX, { className: "size-4 stroke-gray-500 dark:stroke-gray-300" }) })), jsxRuntime.jsx(react.ComboboxButton, { className: "group absolute inset-y-0 right-0 px-2.5 flex items-center justify-center", "data-testid": "combobox-button", children: jsxRuntime.jsx(SvgChevronDown, { className: chevronIconClass }) })] })), jsxRuntime.jsxs(react.ComboboxOptions, { anchor: { to: 'bottom start', gap: 5, padding: 8 }, as: "div", modal: modalDropdown,
4151
+ // Matches the input width by default; min-width lets the panel grow wider than a narrow input.
4152
+ style: dropdownMinWidth != null ? { minWidth: dropdownMinWidth } : undefined, className: buildClassName('w-[var(--input-width)] rounded-lg border border-[var(--ui-border)] bg-white dark:bg-gray-800 p-1 shadow-lg max-h-80 overflow-y-auto z-50 [--anchor-gap:var(--spacing-1)]', '[--anchor-max-height:20rem]', 'origin-top border transition duration-200 ease-[cubic-bezier(0.4, 0.0, 0.2, 1)] data-[closed]:translate-y-[-15%] data-[closed]:opacity-0', dropdownContainerClass), transition: true, "data-testid": "combobox-options", children: [multiple && showSelectAll && allOptions.length > 0 && (jsxRuntime.jsxs("div", { className: "pb-1 mb-1 border-b border-[var(--ui-border-muted)] px-3 pt-1 flex items-center justify-between", children: [jsxRuntime.jsx("button", { type: "button", onClick: handleToggleSelectAll, className: "text-xs font-semibold text-blue-600 dark:text-blue-400 hover:underline cursor-pointer", children: isAllSelected ? 'Deselect All' : 'Select All' }), jsxRuntime.jsxs("span", { className: "text-[11px] font-medium text-gray-400", children: [Array.isArray(normalizedSelected) ? normalizedSelected.length : 0, " of", ' ', allOptions.length] })] })), optionsLoading ? (jsxRuntime.jsx(BodyText, { "data-testid": "options-loading", className: "flex items-center justify-center py-2 px-3 select-none", children: jsxRuntime.jsx(Loader, { size: "sm" }) })) : shouldShowTypeToSearch ? (jsxRuntime.jsx(BodyText, { className: "flex items-center justify-between py-2 px-3 select-none", children: "Type to search..." })) : (jsxRuntime.jsxs("div", { children: [allowFreeText &&
4104
4153
  query.trim() &&
4105
4154
  !allOptions.some((opt) => { var _a; return ((_a = getDisplayValue(opt)) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === query.trim().toLowerCase(); }) && (jsxRuntime.jsx(react.ComboboxOption, { value: createFreeTextOption(query.trim()), className: "group flex items-center gap-2 rounded-lg py-1.5 px-3 select-none text-sm data-[focus]:bg-blue-50 dark:data-[focus]:bg-blue-950/40 text-blue-600 dark:text-blue-400 font-medium cursor-pointer w-full", "data-testid": "freetext-option", children: jsxRuntime.jsxs("span", { children: ["Use \"", query.trim(), "\""] }) }, `freetext-${query.trim()}`)), filteredGroups
4106
4155
  ? filteredGroups.map((grp, gIdx) => (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "text-[11px] font-bold uppercase tracking-wider text-gray-400 dark:text-gray-500 px-3 py-1 select-none", children: grp.group }), grp.options.map(renderOptionItem)] }, gIdx)))
@@ -4570,7 +4619,7 @@ const Toast = ({ id, title, message, type, variant = 'accent', action, icon, sho
4570
4619
  const renderedIcon = icon !== null && icon !== void 0 ? icon : (variant === 'filled' ? filledIconMap[type] : iconMap[type]);
4571
4620
  return (jsxRuntime.jsx(react.Transition, { appear: true, show: true, enter: "transform transition duration-300 ease-out", enterFrom: enterFrom, enterTo: "opacity-100 translate-y-0 scale-100", leave: "transition duration-200 ease-in", leaveFrom: "opacity-100 translate-y-0 scale-100", leaveTo: leaveTo, children: jsxRuntime.jsxs("div", { role: "status", id: id.toString(), className: buildClassName('relative overflow-hidden flex items-start gap-3 rounded-xl px-4 py-3.5 min-w-[280px] max-w-md transition-all', variantStyleMap[variant][type], className), children: [jsxRuntime.jsx("div", { className: "shrink-0 mt-0.5", children: variant === 'filled' ? (renderedIcon) : (jsxRuntime.jsx(TextContent, { className: buildClassName(textColorMap[type]), children: renderedIcon })) }), jsxRuntime.jsxs("div", { className: "flex-1 flex flex-col gap-0.5", children: [title && (jsxRuntime.jsx("div", { className: buildClassName('font-semibold text-sm leading-snug', variant === 'filled' ? 'text-white' : 'text-gray-900 dark:text-gray-100'), children: title })), jsxRuntime.jsx("div", { className: buildClassName('text-sm font-medium leading-relaxed', variant === 'filled' ? 'text-white/95' : 'text-gray-700 dark:text-gray-300'), children: message }), action && (jsxRuntime.jsx("div", { className: "mt-1.5", children: jsxRuntime.jsx("button", { type: "button", onClick: action.onClick, className: buildClassName('text-xs font-semibold underline underline-offset-2 transition-opacity focus:outline-none', variant === 'filled'
4572
4621
  ? 'text-white hover:opacity-85'
4573
- : 'text-[var(--ui-primary)] hover:opacity-80'), children: action.label }) }))] }), jsxRuntime.jsx(Button, { onClick: onClose, "aria-label": "Close toast", className: buildClassName('shrink-0 mt-0.5 opacity-60 hover:opacity-100 transition', variant === 'filled' && 'text-white hover:text-white'), iconOnly: true, size: "xs", variant: "plain", theme: variant === 'filled' ? 'primary' : 'secondary', noOutlineOnFocus: true, children: jsxRuntime.jsx(SvgX, { className: "size-4" }) }), showProgressBar && duration > 0 && (jsxRuntime.jsx("div", { className: "absolute bottom-0 left-0 right-0 h-1 bg-black/10 dark:bg-white/10 overflow-hidden", children: jsxRuntime.jsx("div", { className: buildClassName('h-full transition-all duration-75 ease-linear', variant === 'filled' ? 'bg-white/70' : progressBarColorMap[type]), style: { width: `${Math.max(0, progress)}%` } }) }))] }) }));
4622
+ : 'text-[var(--ui-primary)] hover:opacity-80'), children: action.label }) }))] }), jsxRuntime.jsx(Button, { onClick: onClose, "aria-label": "Close toast", className: buildClassName('shrink-0 mt-0.5 opacity-60 hover:opacity-100 focus-visible:opacity-100 transition', variant === 'filled' && 'text-white hover:text-white focus-visible:outline-current'), iconOnly: true, size: "xs", variant: "plain", theme: variant === 'filled' ? 'primary' : 'secondary', children: jsxRuntime.jsx(SvgX, { className: "size-4" }) }), showProgressBar && duration > 0 && (jsxRuntime.jsx("div", { className: "absolute bottom-0 left-0 right-0 h-1 bg-black/10 dark:bg-white/10 overflow-hidden", children: jsxRuntime.jsx("div", { className: buildClassName('h-full transition-all duration-75 ease-linear', variant === 'filled' ? 'bg-white/70' : progressBarColorMap[type]), style: { width: `${Math.max(0, progress)}%` } }) }))] }) }));
4574
4623
  };
4575
4624
  Toast.displayName = 'Toast';
4576
4625