@pk-design/react-tailwind 1.8.0 → 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.
- package/dist/cjs/components/Banner/Banner.js +1 -1
- package/dist/cjs/components/Banner/Banner.js.map +1 -1
- package/dist/cjs/components/Button/Button.js +88 -60
- package/dist/cjs/components/Button/Button.js.map +1 -1
- package/dist/cjs/components/DataTable/DataTable.js +1 -1
- package/dist/cjs/components/DataTable/DataTable.js.map +1 -1
- package/dist/cjs/components/RadioGroup/RadioGroup.js +5 -3
- package/dist/cjs/components/RadioGroup/RadioGroup.js.map +1 -1
- package/dist/cjs/components/SelectBox/SelectBox.js +25 -4
- package/dist/cjs/components/SelectBox/SelectBox.js.map +1 -1
- package/dist/cjs/components/Toast/Toast.js +1 -1
- package/dist/cjs/components/Toast/Toast.js.map +1 -1
- package/dist/es/components/Banner/Banner.js +1 -1
- package/dist/es/components/Banner/Banner.js.map +1 -1
- package/dist/es/components/Button/Button.js +89 -61
- package/dist/es/components/Button/Button.js.map +1 -1
- package/dist/es/components/DataTable/DataTable.js +1 -1
- package/dist/es/components/DataTable/DataTable.js.map +1 -1
- package/dist/es/components/RadioGroup/RadioGroup.js +5 -3
- package/dist/es/components/RadioGroup/RadioGroup.js.map +1 -1
- package/dist/es/components/SelectBox/SelectBox.js +26 -5
- package/dist/es/components/SelectBox/SelectBox.js.map +1 -1
- package/dist/es/components/Toast/Toast.js +1 -1
- package/dist/es/components/Toast/Toast.js.map +1 -1
- package/dist/react-tailwind.cjs.js +121 -70
- package/dist/react-tailwind.cjs.js.map +1 -1
- package/dist/react-tailwind.es.js +122 -71
- package/dist/react-tailwind.es.js.map +1 -1
- package/dist/types/components/Button/Button.types.d.ts +12 -6
- package/dist/types/components/DataTable/DataTable.types.d.ts +2 -2
- package/dist/types/components/SelectBox/SelectBox.types.d.ts +18 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import React__default, { useId, useMemo, useState, forwardRef, Children, isValidElement, cloneElement, useRef, useImperativeHandle,
|
|
3
|
+
import React__default, { useId, useMemo, useState, forwardRef, useEffect, Children, isValidElement, cloneElement, useRef, useImperativeHandle, useCallback, Fragment as Fragment$1, createContext, useContext, useLayoutEffect } from 'react';
|
|
4
4
|
import { twMerge } from 'tailwind-merge';
|
|
5
5
|
import { Dialog, DialogBackdrop, DialogPanel, DialogTitle, Menu, MenuButton, MenuItems, MenuItem, Combobox, ComboboxInput, ComboboxButton, ComboboxOptions, ComboboxOption, TabGroup, TabList, Tab, TabPanels, TabPanel, Transition } from '@headlessui/react';
|
|
6
6
|
import { FixedSizeList } from 'react-window';
|
|
@@ -131,59 +131,35 @@ TextContent.displayName = 'TextContent';
|
|
|
131
131
|
|
|
132
132
|
/**
|
|
133
133
|
* Tailwind CSS classes for theme + variant combinations.
|
|
134
|
+
*
|
|
135
|
+
* Only colours live here. Focus outline width/offset and disabled styling are
|
|
136
|
+
* applied uniformly in the component so they can be toggled in one place.
|
|
134
137
|
*/
|
|
135
138
|
const THEMED_BTN_CLASS = {
|
|
136
139
|
primary: {
|
|
137
|
-
default: 'text-white bg-[var(--ui-primary)] hover:bg-[var(--ui-primary-hover)] focus-visible:
|
|
138
|
-
outlined: 'text-[var(--ui-primary-text)] hover:text-white border border-[var(--ui-primary)] hover:bg-[var(--ui-primary)] focus-visible:
|
|
139
|
-
plain: 'text-[var(--ui-primary-text)] hover:bg-
|
|
140
|
+
default: 'text-white bg-[var(--ui-primary)] hover:bg-[var(--ui-primary-hover)] focus-visible:outline-[var(--ui-primary-ring)]',
|
|
141
|
+
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)]',
|
|
142
|
+
plain: 'text-[var(--ui-primary-text)] hover:bg-current/10 active:bg-current/15 focus-visible:outline-[var(--ui-primary-ring)]',
|
|
140
143
|
},
|
|
141
144
|
danger: {
|
|
142
|
-
default: 'text-white bg-[var(--ui-danger)] hover:bg-[var(--ui-danger-hover)] focus-visible:
|
|
143
|
-
outlined: 'text-[var(--ui-danger-text)] hover:text-white border border-[var(--ui-danger)] hover:bg-[var(--ui-danger)] focus-visible:
|
|
144
|
-
plain: 'text-[var(--ui-danger-text)] hover:bg-
|
|
145
|
+
default: 'text-white bg-[var(--ui-danger)] hover:bg-[var(--ui-danger-hover)] focus-visible:outline-[var(--ui-danger-ring)]',
|
|
146
|
+
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)]',
|
|
147
|
+
plain: 'text-[var(--ui-danger-text)] hover:bg-current/10 active:bg-current/15 focus-visible:outline-[var(--ui-danger-ring)]',
|
|
145
148
|
},
|
|
146
149
|
secondary: {
|
|
147
|
-
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:
|
|
148
|
-
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:
|
|
149
|
-
plain: 'text-gray-700 hover:bg-
|
|
150
|
+
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)]',
|
|
151
|
+
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)]',
|
|
152
|
+
plain: 'text-gray-700 hover:bg-current/10 active:bg-current/15 dark:text-gray-200 focus-visible:outline-[var(--ui-focus-ring)]',
|
|
150
153
|
},
|
|
151
154
|
success: {
|
|
152
|
-
default: 'text-white bg-emerald-600 hover:bg-emerald-700 focus-visible:
|
|
153
|
-
outlined: 'text-emerald-
|
|
154
|
-
plain: 'text-emerald-
|
|
155
|
+
default: 'text-white bg-emerald-600 hover:bg-emerald-700 focus-visible:outline-emerald-500',
|
|
156
|
+
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',
|
|
157
|
+
plain: 'text-emerald-700 hover:bg-current/10 active:bg-current/15 dark:text-emerald-400 focus-visible:outline-emerald-500',
|
|
155
158
|
},
|
|
156
159
|
warning: {
|
|
157
|
-
default: 'text-white bg-amber-500 hover:bg-amber-600 focus-visible:
|
|
158
|
-
outlined: 'text-amber-
|
|
159
|
-
plain: 'text-amber-
|
|
160
|
-
},
|
|
161
|
-
};
|
|
162
|
-
const LOADER_TEXT_CLASS = {
|
|
163
|
-
primary: {
|
|
164
|
-
default: 'text-white',
|
|
165
|
-
outlined: 'text-[var(--ui-primary-text)]',
|
|
166
|
-
plain: 'text-[var(--ui-primary-text)]',
|
|
167
|
-
},
|
|
168
|
-
danger: {
|
|
169
|
-
default: 'text-white',
|
|
170
|
-
outlined: 'text-[var(--ui-danger-text)]',
|
|
171
|
-
plain: 'text-[var(--ui-danger-text)]',
|
|
172
|
-
},
|
|
173
|
-
secondary: {
|
|
174
|
-
default: 'text-gray-700 dark:text-white',
|
|
175
|
-
outlined: 'text-gray-700 dark:text-gray-200',
|
|
176
|
-
plain: 'text-gray-700 dark:text-gray-200',
|
|
177
|
-
},
|
|
178
|
-
success: {
|
|
179
|
-
default: 'text-white',
|
|
180
|
-
outlined: 'text-emerald-600 dark:text-emerald-400',
|
|
181
|
-
plain: 'text-emerald-600 dark:text-emerald-400',
|
|
182
|
-
},
|
|
183
|
-
warning: {
|
|
184
|
-
default: 'text-white',
|
|
185
|
-
outlined: 'text-amber-600 dark:text-amber-400',
|
|
186
|
-
plain: 'text-amber-600 dark:text-amber-400',
|
|
160
|
+
default: 'text-white bg-amber-500 hover:bg-amber-600 focus-visible:outline-amber-500',
|
|
161
|
+
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',
|
|
162
|
+
plain: 'text-amber-700 hover:bg-current/10 active:bg-current/15 dark:text-amber-400 focus-visible:outline-amber-500',
|
|
187
163
|
},
|
|
188
164
|
};
|
|
189
165
|
const SIZE_CLASSES = {
|
|
@@ -203,22 +179,32 @@ const LOADER_SIZE = {
|
|
|
203
179
|
};
|
|
204
180
|
/**
|
|
205
181
|
* Base shared tailwind classes.
|
|
182
|
+
*
|
|
183
|
+
* Disabled styling is keyed off both the native `disabled` attribute and
|
|
184
|
+
* `aria-disabled`, so non-button elements rendered via `as` look disabled too.
|
|
206
185
|
*/
|
|
207
186
|
const BASE_BTN_CLASS = 'font-medium inline-flex items-center justify-center shrink-0 cursor-pointer select-none ' +
|
|
208
|
-
'
|
|
209
|
-
'active:scale-[0.98]
|
|
187
|
+
'transition-[color,background-color,border-color,box-shadow,opacity,transform] duration-150 ease-out ' +
|
|
188
|
+
'active:scale-[0.98] focus:outline-none ' +
|
|
189
|
+
'disabled:pointer-events-none disabled:opacity-50 disabled:shadow-none ' +
|
|
190
|
+
'aria-disabled:pointer-events-none aria-disabled:opacity-50 aria-disabled:shadow-none';
|
|
210
191
|
const Button = forwardRef(({ as, type, ...props }, ref) => {
|
|
211
|
-
var _a, _b, _c, _d;
|
|
212
192
|
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;
|
|
213
193
|
const Component = as || 'button';
|
|
214
194
|
const isNativeButton = Component === 'button';
|
|
195
|
+
// Clean invalid props when not rendering a real button
|
|
196
|
+
const { type: _ignoreType, disabled: _ignoreDisabled, role, tabIndex, onKeyDown, onKeyUp, ...domProps } = restProps;
|
|
197
|
+
const ariaLabel = domProps['aria-label'];
|
|
198
|
+
const ariaLabelledBy = domProps['aria-labelledby'];
|
|
215
199
|
// Accessibility guard for icon-only buttons
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
if (iconOnly && !ariaLabel && !ariaLabelledBy) {
|
|
202
|
+
console.warn('[ui] <Button iconOnly> requires aria-label or aria-labelledby for accessibility.');
|
|
203
|
+
}
|
|
204
|
+
}, [iconOnly, ariaLabel, ariaLabelledBy]);
|
|
219
205
|
const resolvedDisabled = Boolean(disabled || loading);
|
|
220
|
-
//
|
|
221
|
-
const
|
|
206
|
+
// Links (`href`, or `to` for router Link components) keep their native semantics.
|
|
207
|
+
const isLink = !isNativeButton && (domProps.href != null || domProps.to != null);
|
|
222
208
|
const handleClick = (e) => {
|
|
223
209
|
if (resolvedDisabled) {
|
|
224
210
|
e.preventDefault();
|
|
@@ -227,31 +213,73 @@ const Button = forwardRef(({ as, type, ...props }, ref) => {
|
|
|
227
213
|
}
|
|
228
214
|
onClick === null || onClick === void 0 ? void 0 : onClick(e);
|
|
229
215
|
};
|
|
216
|
+
// Mirror native <button> keyboard activation for non-button elements:
|
|
217
|
+
// Enter activates on keydown, Space on keyup (and must not scroll the page).
|
|
218
|
+
const handleKeyDown = (e) => {
|
|
219
|
+
onKeyDown === null || onKeyDown === void 0 ? void 0 : onKeyDown(e);
|
|
220
|
+
if (e.defaultPrevented || e.target !== e.currentTarget)
|
|
221
|
+
return;
|
|
222
|
+
if (e.key === 'Enter') {
|
|
223
|
+
e.preventDefault();
|
|
224
|
+
e.currentTarget.click();
|
|
225
|
+
}
|
|
226
|
+
else if (e.key === ' ') {
|
|
227
|
+
e.preventDefault();
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
const handleKeyUp = (e) => {
|
|
231
|
+
onKeyUp === null || onKeyUp === void 0 ? void 0 : onKeyUp(e);
|
|
232
|
+
if (e.defaultPrevented || e.target !== e.currentTarget)
|
|
233
|
+
return;
|
|
234
|
+
if (e.key === ' ') {
|
|
235
|
+
e.preventDefault();
|
|
236
|
+
e.currentTarget.click();
|
|
237
|
+
}
|
|
238
|
+
};
|
|
230
239
|
const elementProps = isNativeButton
|
|
231
240
|
? {
|
|
232
241
|
type: type !== null && type !== void 0 ? type : 'button',
|
|
233
242
|
disabled: resolvedDisabled,
|
|
243
|
+
role,
|
|
244
|
+
tabIndex,
|
|
234
245
|
onClick: handleClick,
|
|
246
|
+
onKeyDown,
|
|
247
|
+
onKeyUp,
|
|
235
248
|
}
|
|
236
|
-
:
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
249
|
+
: isLink
|
|
250
|
+
? {
|
|
251
|
+
role,
|
|
252
|
+
tabIndex: resolvedDisabled ? -1 : tabIndex,
|
|
253
|
+
'aria-disabled': resolvedDisabled ? true : undefined,
|
|
254
|
+
onClick: handleClick,
|
|
255
|
+
onKeyDown,
|
|
256
|
+
onKeyUp,
|
|
257
|
+
}
|
|
258
|
+
: {
|
|
259
|
+
role: role !== null && role !== void 0 ? role : 'button',
|
|
260
|
+
tabIndex: resolvedDisabled ? -1 : (tabIndex !== null && tabIndex !== void 0 ? tabIndex : 0),
|
|
261
|
+
'aria-disabled': resolvedDisabled ? true : undefined,
|
|
262
|
+
onClick: handleClick,
|
|
263
|
+
onKeyDown: handleKeyDown,
|
|
264
|
+
onKeyUp: handleKeyUp,
|
|
265
|
+
};
|
|
242
266
|
const sizeObj = SIZE_CLASSES[size] || SIZE_CLASSES.md;
|
|
243
267
|
const classes = useMemo(() => {
|
|
244
268
|
var _a;
|
|
245
|
-
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,
|
|
269
|
+
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,
|
|
270
|
+
// An outline (not a ring) so the offset gap stays transparent on any surface/mode.
|
|
271
|
+
!noOutlineOnFocus &&
|
|
272
|
+
'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);
|
|
246
273
|
}, [theme, variant, sizeObj, iconOnly, noOutlineOnFocus, rounded, fullWidth, active, className]);
|
|
247
|
-
|
|
274
|
+
// The spinner and loading text inherit the button's text colour.
|
|
275
|
+
const loaderIcon = (jsx(Loader, { "data-testid": "btn-loader-icon", size: LOADER_SIZE[size] || 'xs', className: "text-current" }));
|
|
248
276
|
/**
|
|
249
277
|
* SPECIAL CASE: iconOnly + loading → show spinner only
|
|
250
278
|
*/
|
|
251
279
|
if (loading && iconOnly) {
|
|
252
|
-
return (jsx(Component, { ref: ref, className: classes, "aria-busy": "true", "aria-pressed": active !== undefined ? active : undefined, ...
|
|
280
|
+
return (jsx(Component, { ref: ref, className: classes, "aria-busy": "true", "aria-pressed": active !== undefined ? active : undefined, ...domProps, ...elementProps, children: loaderIcon }));
|
|
253
281
|
}
|
|
254
|
-
return (jsx(Component, { ref: ref, className: classes, "aria-busy": loading ? 'true' : undefined, "aria-pressed": active !== undefined ? active : undefined, ...
|
|
282
|
+
return (jsx(Component, { ref: ref, className: classes, "aria-busy": loading ? 'true' : undefined, "aria-pressed": active !== undefined ? active : undefined, ...domProps, ...elementProps, children: loading ? (jsxs("span", { className: "inline-flex items-center gap-2", "aria-live": "polite", children: [loaderIcon, loadingText ? (jsx(TextContent, { className: "text-current", children: loadingText })) : (children)] })) : (jsxs(Fragment, { children: [leftIcon && jsx("span", { className: "shrink-0 inline-flex items-center", children: leftIcon }), children, rightIcon && jsx("span", { className: "shrink-0 inline-flex items-center", children: rightIcon })] })) }));
|
|
255
283
|
});
|
|
256
284
|
Button.displayName = 'Button';
|
|
257
285
|
|
|
@@ -575,7 +603,7 @@ const Banner = forwardRef((props, ref) => {
|
|
|
575
603
|
const { type = 'info', iconSize = 'sm', children, icon, removable = false, onRemove, className, ...restProps } = props;
|
|
576
604
|
const DefaultIcon = IconMap[type];
|
|
577
605
|
const iconNode = icon !== undefined ? (icon) : (jsx(DefaultIcon, { "data-testid": `banner-${type}-icon`, className: buildClassName('shrink-0', sizeMap[iconSize]) }));
|
|
578
|
-
return (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, jsx("div", { className: "flex items-center w-full justify-between", children: children }), removable && (jsx(Button, { iconOnly: true, "aria-label": "Dismiss banner", size: "xs", variant: "plain",
|
|
606
|
+
return (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, jsx("div", { className: "flex items-center w-full justify-between", children: children }), removable && (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: jsx(SvgX, { className: "size-4.5", "data-testid": "banner-remove-icon" }) }))] }));
|
|
579
607
|
});
|
|
580
608
|
Banner.displayName = 'Banner';
|
|
581
609
|
|
|
@@ -1506,7 +1534,7 @@ function DataTable({ items, columns, sorting = {}, onSort, loading, pagination,
|
|
|
1506
1534
|
}), isInlineMode && (jsx(TableCell, { align: "right", className: buildClassName(densityCellClass, striped && idx % 2 === 1 && 'bg-gray-50 dark:bg-gray-800/40'), onClick: (e) => e.stopPropagation(), children: jsx("div", { className: buildClassName('opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity duration-150 flex items-center justify-end gap-1.5', rowHoverActionClass), "data-testid": `row-hover-actions-${key}`, children: renderActionsContent(item, idx) }) })), isOverlayMode && (jsx(TableCell, { align: "right", style: { padding: 0, width: 0, minWidth: 0, maxWidth: 0 }, className: "sticky right-0 z-20 !p-0 !pr-0 !pl-0 !py-0 w-0 max-w-0 min-w-0 border-0 overflow-visible text-right", onClick: (e) => e.stopPropagation(), children: jsx("div", { className: buildClassName('absolute right-0 top-0 bottom-0 h-full opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity duration-150 flex items-center justify-end gap-1 px-3 z-30 pointer-events-none group-hover:pointer-events-auto whitespace-nowrap', striped && idx % 2 === 1
|
|
1507
1535
|
? 'bg-gray-100 dark:bg-gray-800'
|
|
1508
1536
|
: 'bg-white group-hover:bg-gray-50 dark:bg-gray-900 dark:group-hover:bg-gray-800', rowHoverActionClass), "data-testid": `row-hover-actions-${key}`, children: renderActionsContent(item, idx) }) }))] }, key));
|
|
1509
|
-
})) }), footerRow && jsx(TableFoot, { children: footerRow })] }) }), pagination && (jsx("div", { className: buildClassName(stickyPagination && 'sticky bottom-0 z-20
|
|
1537
|
+
})) }), footerRow && jsx(TableFoot, { children: footerRow })] }) }), pagination && (jsx("div", { className: buildClassName(stickyPagination && 'sticky bottom-0 z-20', paginationContainerClass), children: renderPagination ? (renderPagination(pagination, setPagination)) : (jsx(Pagination, { ...pagination, ...paginationProps, className: buildClassName('rounded-t-none rounded-b-lg border border-[var(--ui-border)] bg-white dark:bg-gray-800', paginationClass, paginationProps === null || paginationProps === void 0 ? void 0 : paginationProps.className), onChange: setPagination })) }))] }));
|
|
1510
1538
|
}
|
|
1511
1539
|
DataTable.displayName = 'DataTable';
|
|
1512
1540
|
|
|
@@ -2426,6 +2454,7 @@ const COLUMN_GRID_MAP = {
|
|
|
2426
2454
|
const RadioGroup = (props) => {
|
|
2427
2455
|
const { name, variant = 'default', size = 'md', theme = 'primary', label, labelClass, labelWrapperClass, labelHint, containerClass, options, value, columns, disabled = false, row, error, showErrorMessage = true, onChange, required, 'aria-describedby': ariaDescribedBy, } = props;
|
|
2428
2456
|
const groupId = useId();
|
|
2457
|
+
const groupName = name || groupId;
|
|
2429
2458
|
const items = useMemo(() => {
|
|
2430
2459
|
var _a;
|
|
2431
2460
|
return (_a = options === null || options === void 0 ? void 0 : options.map((option) => typeof option === 'string'
|
|
@@ -2450,13 +2479,14 @@ const RadioGroup = (props) => {
|
|
|
2450
2479
|
: 'flex flex-col gap-3'), children: items.map((item) => {
|
|
2451
2480
|
const isSelected = value === item.value;
|
|
2452
2481
|
const isItemDisabled = disabled || Boolean(item.disabled);
|
|
2482
|
+
const radioItemId = `${groupName}-${item.value}`;
|
|
2453
2483
|
if (isCards) {
|
|
2454
|
-
return (jsxs("label", { htmlFor:
|
|
2484
|
+
return (jsxs("label", { htmlFor: radioItemId, className: buildClassName('relative flex items-start gap-3 p-4 rounded-xl border transition-all cursor-pointer select-none min-w-[200px] flex-1', 'focus-within:ring-2 focus-within:ring-[var(--ui-primary-ring)] focus-within:border-[var(--ui-primary)]', isSelected
|
|
2455
2485
|
? 'border-[var(--ui-primary)] bg-blue-50/40 dark:bg-blue-950/30 ring-2 ring-[var(--ui-primary-ring)]/30'
|
|
2456
2486
|
: 'border-[var(--ui-border)] bg-white dark:bg-gray-800 hover:border-gray-300 dark:hover:border-gray-600', isItemDisabled &&
|
|
2457
|
-
'opacity-50 cursor-not-allowed pointer-events-none bg-gray-50 dark:bg-gray-800/50'), children: [jsx(Radio, { name:
|
|
2487
|
+
'opacity-50 cursor-not-allowed pointer-events-none bg-gray-50 dark:bg-gray-800/50'), children: [jsx(Radio, { name: groupName, id: radioItemId, value: item.value, "aria-label": item.label, size: size, theme: theme, checked: isSelected, disabled: isItemDisabled, onChange: onChange, containerClass: "pt-0.5" }), jsxs("div", { className: "flex-1 min-w-0", children: [jsxs("div", { className: "flex items-center gap-2", children: [item.icon && (jsx("span", { className: "shrink-0 text-gray-500 dark:text-gray-400", children: item.icon })), jsx("span", { className: "text-sm font-semibold text-gray-900 dark:text-white truncate", children: item.label })] }), item.description && (jsx("p", { className: "mt-1 text-xs text-gray-500 dark:text-gray-400 font-normal leading-relaxed", children: item.description }))] })] }, item.value));
|
|
2458
2488
|
}
|
|
2459
|
-
return (jsx(Radio, { name:
|
|
2489
|
+
return (jsx(Radio, { name: groupName, id: radioItemId, label: item.label, description: item.description, value: item.value, size: size, theme: theme, checked: isSelected, disabled: isItemDisabled, onChange: onChange }, item.value));
|
|
2460
2490
|
}) }), showErrorMessage && error && (jsx("div", { className: "mt-1.5", children: jsx(TextContent, { error: true, small: true, id: errorId, children: error }) }))] }));
|
|
2461
2491
|
};
|
|
2462
2492
|
RadioGroup.displayName = 'RadioGroup';
|
|
@@ -3826,9 +3856,18 @@ const sizeClasses$1 = {
|
|
|
3826
3856
|
},
|
|
3827
3857
|
};
|
|
3828
3858
|
const SelectBox = forwardRef((props, forwardedRef) => {
|
|
3829
|
-
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;
|
|
3859
|
+
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;
|
|
3860
|
+
// Uncontrolled when `selected` is not passed: keep the selection internally.
|
|
3861
|
+
const isControlled = selectedProp !== undefined;
|
|
3862
|
+
const [internalSelected, setInternalSelected] = useState(defaultSelected !== null && defaultSelected !== void 0 ? defaultSelected : null);
|
|
3863
|
+
const selected = isControlled ? selectedProp : internalSelected;
|
|
3864
|
+
const onChange = useCallback((value) => {
|
|
3865
|
+
if (!isControlled)
|
|
3866
|
+
setInternalSelected(value);
|
|
3867
|
+
onChangeProp(value);
|
|
3868
|
+
}, [isControlled, onChangeProp]);
|
|
3830
3869
|
if (props.asBottomSheet || props.mobileMode === 'sheet') {
|
|
3831
|
-
return jsx(MobilePicker, { ...props });
|
|
3870
|
+
return jsx(MobilePicker, { ...props, selected: selected, onChange: onChange });
|
|
3832
3871
|
}
|
|
3833
3872
|
const hasError = Boolean(error);
|
|
3834
3873
|
const [query, setQuery] = useState('');
|
|
@@ -4026,6 +4065,16 @@ const SelectBox = forwardRef((props, forwardedRef) => {
|
|
|
4026
4065
|
}
|
|
4027
4066
|
}, [isAllSelected, onChange, allOptions]);
|
|
4028
4067
|
const hasLeftGroup = leftGroup != null && leftGroup !== undefined;
|
|
4068
|
+
// Reserve room for the chevron (and the clear button when shown) so long values
|
|
4069
|
+
// don't run underneath them.
|
|
4070
|
+
const showClearButton = allowClear && !multiple && !isEmpty(normalizedSelected);
|
|
4071
|
+
const rightPaddingClass = showClearButton
|
|
4072
|
+
? 'pe-16'
|
|
4073
|
+
: size === 'lg'
|
|
4074
|
+
? 'pe-11'
|
|
4075
|
+
: size === 'sm'
|
|
4076
|
+
? 'pe-9'
|
|
4077
|
+
: 'pe-10';
|
|
4029
4078
|
const hasFreeTextOption = Boolean(allowFreeText &&
|
|
4030
4079
|
query.trim() &&
|
|
4031
4080
|
!allOptions.some((opt) => { var _a; return ((_a = getDisplayValue(opt)) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === query.trim().toLowerCase(); }));
|
|
@@ -4038,7 +4087,7 @@ const SelectBox = forwardRef((props, forwardedRef) => {
|
|
|
4038
4087
|
const isOptSelected = selectedValueSet.has(optVal);
|
|
4039
4088
|
return (jsx(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))) : (jsxs("div", { className: "flex items-center justify-between gap-2 text-gray-900 dark:text-white group-data-[selected]:text-blue-600", children: [jsx("span", { className: "text-sm/6", children: getDisplayValue(option) }), jsx(SvgCheck, { className: "invisible size-5 dark:stroke-gray-300 group-data-[selected]:visible" })] })) }, optVal));
|
|
4040
4089
|
};
|
|
4041
|
-
return (jsxs("div", { className: buildClassName('group space-y-2', hasError && 'has-error', containerClass), ref: containerRef, children: [label && (jsxs("div", { className: buildClassName('flex items-center justify-between', labelWrapperClass), children: [jsx(Label, { className: labelClass, htmlFor: id, "aria-required": required, children: label }), labelHint && jsx(TextContent, { xsmall: true, children: labelHint })] })), jsxs(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 ? (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 && (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) &&
|
|
4090
|
+
return (jsxs("div", { className: buildClassName('group space-y-2', hasError && 'has-error', containerClass), ref: containerRef, children: [label && (jsxs("div", { className: buildClassName('flex items-center justify-between', labelWrapperClass), children: [jsx(Label, { className: labelClass, htmlFor: id, "aria-required": required, children: label }), labelHint && jsx(TextContent, { xsmall: true, children: labelHint })] })), jsxs(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 ? (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 && (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) &&
|
|
4042
4091
|
(normalizedSelected === null || normalizedSelected === void 0 ? void 0 : normalizedSelected.length) > 0 &&
|
|
4043
4092
|
normalizedSelected.map((option, idx) => (jsx(Badge, { theme: "secondary", size: currentSize.badgeSize, removable: !disabled, onRemove: () => removeSelection(idx), children: getDisplayValue(option) }, `${getValue(option)}-${idx}`))), jsx(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) => {
|
|
4044
4093
|
const val = e.target.value;
|
|
@@ -4078,7 +4127,9 @@ const SelectBox = forwardRef((props, forwardedRef) => {
|
|
|
4078
4127
|
}
|
|
4079
4128
|
}, onFocus: () => {
|
|
4080
4129
|
setQuery('');
|
|
4081
|
-
}, 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" }),
|
|
4130
|
+
}, 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 && (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: jsx(SvgX, { className: "size-4 stroke-gray-500 dark:stroke-gray-300" }) })), jsx(ComboboxButton, { className: "group absolute inset-y-0 right-0 px-2.5 flex items-center justify-center", "data-testid": "combobox-button", children: jsx(SvgChevronDown, { className: chevronIconClass }) })] })), jsxs(ComboboxOptions, { anchor: { to: 'bottom start', gap: 5, padding: 8 }, as: "div", modal: modalDropdown,
|
|
4131
|
+
// Matches the input width by default; min-width lets the panel grow wider than a narrow input.
|
|
4132
|
+
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 && (jsxs("div", { className: "pb-1 mb-1 border-b border-[var(--ui-border-muted)] px-3 pt-1 flex items-center justify-between", children: [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' }), jsxs("span", { className: "text-[11px] font-medium text-gray-400", children: [Array.isArray(normalizedSelected) ? normalizedSelected.length : 0, " of", ' ', allOptions.length] })] })), optionsLoading ? (jsx(BodyText, { "data-testid": "options-loading", className: "flex items-center justify-center py-2 px-3 select-none", children: jsx(Loader, { size: "sm" }) })) : shouldShowTypeToSearch ? (jsx(BodyText, { className: "flex items-center justify-between py-2 px-3 select-none", children: "Type to search..." })) : (jsxs("div", { children: [allowFreeText &&
|
|
4082
4133
|
query.trim() &&
|
|
4083
4134
|
!allOptions.some((opt) => { var _a; return ((_a = getDisplayValue(opt)) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === query.trim().toLowerCase(); }) && (jsx(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: jsxs("span", { children: ["Use \"", query.trim(), "\""] }) }, `freetext-${query.trim()}`)), filteredGroups
|
|
4084
4135
|
? filteredGroups.map((grp, gIdx) => (jsxs("div", { children: [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)))
|
|
@@ -4548,7 +4599,7 @@ const Toast = ({ id, title, message, type, variant = 'accent', action, icon, sho
|
|
|
4548
4599
|
const renderedIcon = icon !== null && icon !== void 0 ? icon : (variant === 'filled' ? filledIconMap[type] : iconMap[type]);
|
|
4549
4600
|
return (jsx(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: 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: [jsx("div", { className: "shrink-0 mt-0.5", children: variant === 'filled' ? (renderedIcon) : (jsx(TextContent, { className: buildClassName(textColorMap[type]), children: renderedIcon })) }), jsxs("div", { className: "flex-1 flex flex-col gap-0.5", children: [title && (jsx("div", { className: buildClassName('font-semibold text-sm leading-snug', variant === 'filled' ? 'text-white' : 'text-gray-900 dark:text-gray-100'), children: title })), 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 && (jsx("div", { className: "mt-1.5", children: jsx("button", { type: "button", onClick: action.onClick, className: buildClassName('text-xs font-semibold underline underline-offset-2 transition-opacity focus:outline-none', variant === 'filled'
|
|
4550
4601
|
? 'text-white hover:opacity-85'
|
|
4551
|
-
: 'text-[var(--ui-primary)] hover:opacity-80'), children: action.label }) }))] }), 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',
|
|
4602
|
+
: 'text-[var(--ui-primary)] hover:opacity-80'), children: action.label }) }))] }), 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: jsx(SvgX, { className: "size-4" }) }), showProgressBar && duration > 0 && (jsx("div", { className: "absolute bottom-0 left-0 right-0 h-1 bg-black/10 dark:bg-white/10 overflow-hidden", children: 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)}%` } }) }))] }) }));
|
|
4552
4603
|
};
|
|
4553
4604
|
Toast.displayName = 'Toast';
|
|
4554
4605
|
|