@pk-design/react-tailwind 1.5.4 → 1.6.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/react-tailwind.cjs.js +1429 -289
- package/dist/react-tailwind.cjs.js.map +1 -1
- package/dist/react-tailwind.es.js +1420 -292
- package/dist/react-tailwind.es.js.map +1 -1
- package/dist/types/components/BodyText/BodyText.d.ts +1 -1
- package/dist/types/components/BodyText/BodyText.types.d.ts +47 -8
- package/dist/types/components/Breadcrumb/Breadcrumb.d.ts +2 -3
- package/dist/types/components/Breadcrumb/Breadcrumb.types.d.ts +43 -10
- package/dist/types/components/Button/Button.types.d.ts +17 -63
- package/dist/types/components/ButtonGroup/ButtonGroup.types.d.ts +16 -0
- package/dist/types/components/Card/Card.d.ts +7 -1
- package/dist/types/components/Card/Card.types.d.ts +48 -42
- package/dist/types/components/Checkbox/Checkbox.types.d.ts +28 -1
- package/dist/types/components/CheckboxGroup/CheckboxGroup.types.d.ts +53 -1
- package/dist/types/components/DetailedInformation/DetailedInformation.d.ts +1 -1
- package/dist/types/components/DetailedInformation/DetailedInformation.types.d.ts +47 -3
- package/dist/types/components/Drawer/Drawer.types.d.ts +37 -13
- package/dist/types/components/Dropdown/Dropdown.types.d.ts +17 -1
- package/dist/types/components/HeadingText/HeadingText.d.ts +7 -7
- package/dist/types/components/HeadingText/HeadingText.test.d.ts +1 -0
- package/dist/types/components/HeadingText/HeadingText.types.d.ts +56 -3
- package/dist/types/components/Input/Input.d.ts +4 -2
- package/dist/types/components/Input/Input.types.d.ts +36 -3
- package/dist/types/components/Loader/index.d.ts +1 -1
- package/dist/types/components/PasswordInput/PasswordInput.d.ts +8 -4
- package/dist/types/components/PasswordInput/PasswordInput.types.d.ts +8 -0
- package/dist/types/components/QuantityStepper/QuantityStepper.d.ts +3 -0
- package/dist/types/components/QuantityStepper/QuantityStepper.test.d.ts +1 -0
- package/dist/types/components/QuantityStepper/QuantityStepper.types.d.ts +49 -0
- package/dist/types/components/QuantityStepper/index.d.ts +2 -0
- package/dist/types/components/Radio/Radio.d.ts +1 -1
- package/dist/types/components/Radio/Radio.types.d.ts +12 -2
- package/dist/types/components/RadioGroup/RadioGroup.d.ts +1 -1
- package/dist/types/components/RadioGroup/RadioGroup.types.d.ts +41 -13
- package/dist/types/components/RadioSwitch/RadioSwitch.d.ts +0 -4
- package/dist/types/components/RadioSwitch/RadioSwitch.types.d.ts +6 -31
- package/dist/types/components/RangeInput/RangeInput.d.ts +1 -1
- package/dist/types/components/RangeInput/RangeInput.types.d.ts +14 -1
- package/dist/types/components/RangeSlider/RangeSlider.d.ts +1 -1
- package/dist/types/components/RangeSlider/RangeSlider.types.d.ts +28 -13
- package/dist/types/components/SegmentedControl/SegmentedControl.d.ts +3 -0
- package/dist/types/components/SegmentedControl/SegmentedControl.test.d.ts +1 -0
- package/dist/types/components/SegmentedControl/SegmentedControl.types.d.ts +53 -0
- package/dist/types/components/SegmentedControl/index.d.ts +2 -0
- package/dist/types/components/SelectBox/SelectBox.types.d.ts +23 -14
- package/dist/types/components/Skeleton/Skeleton.d.ts +2 -0
- package/dist/types/components/Skeleton/Skeleton.test.d.ts +1 -0
- package/dist/types/components/Skeleton/Skeleton.types.d.ts +38 -0
- package/dist/types/components/Skeleton/index.d.ts +2 -0
- package/dist/types/components/StatusPill/StatusPill.d.ts +2 -0
- package/dist/types/components/StatusPill/StatusPill.test.d.ts +1 -0
- package/dist/types/components/StatusPill/StatusPill.types.d.ts +29 -0
- package/dist/types/components/StatusPill/index.d.ts +2 -0
- package/dist/types/components/StickyActionBar/StickyActionBar.d.ts +2 -0
- package/dist/types/components/StickyActionBar/StickyActionBar.test.d.ts +1 -0
- package/dist/types/components/StickyActionBar/StickyActionBar.types.d.ts +30 -0
- package/dist/types/components/StickyActionBar/index.d.ts +2 -0
- package/dist/types/components/TextContent/TextContent.d.ts +2 -2
- package/dist/types/components/TextContent/TextContent.types.d.ts +47 -7
- package/dist/types/components/Textarea/Textarea.types.d.ts +37 -3
- package/dist/types/components/Toast/Toast.d.ts +1 -1
- package/dist/types/components/Toast/Toast.types.d.ts +117 -43
- package/dist/types/components/Toast/ToastProvider.d.ts +3 -6
- package/dist/types/index.d.ts +5 -0
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
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,
|
|
3
|
+
import React__default, { useId, useMemo, useState, forwardRef, Children, isValidElement, cloneElement, useRef, useImperativeHandle, useEffect, useCallback, Fragment as Fragment$1, useLayoutEffect, createContext, useContext } from 'react';
|
|
4
4
|
import { twMerge } from 'tailwind-merge';
|
|
5
|
-
import { Dialog, DialogBackdrop, DialogPanel, DialogTitle, Menu, MenuButton, MenuItems, MenuItem,
|
|
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';
|
|
7
7
|
|
|
8
8
|
const buildClassName = (...classes) => twMerge(classes.filter(Boolean));
|
|
@@ -194,15 +194,46 @@ const loaderSizeMap = {
|
|
|
194
194
|
lg: 'size-10',
|
|
195
195
|
};
|
|
196
196
|
const Loader = ({ className = '', size = 'md', ...props }) => {
|
|
197
|
-
|
|
197
|
+
const hasColorClass = /\btext-/.test(className);
|
|
198
|
+
return (jsx("svg", { className: buildClassName(!hasColorClass && 'text-blue-600 dark:text-white', loaderSizeMap[size], className, 'spinner'), viewBox: "0 0 50 50", ...props, children: jsx("circle", { className: "path", cx: "25", cy: "25", r: "20", fill: "none", stroke: "currentColor", strokeLinecap: "round", strokeWidth: "5" }) }));
|
|
198
199
|
};
|
|
199
200
|
|
|
201
|
+
const sizeClassMap$2 = {
|
|
202
|
+
xs: 'text-xs/5',
|
|
203
|
+
sm: 'text-sm/6',
|
|
204
|
+
md: 'text-base/7',
|
|
205
|
+
lg: 'text-lg/8',
|
|
206
|
+
xl: 'text-xl/9',
|
|
207
|
+
};
|
|
208
|
+
const weightClassMap$2 = {
|
|
209
|
+
light: 'font-light',
|
|
210
|
+
normal: 'font-normal',
|
|
211
|
+
medium: 'font-medium',
|
|
212
|
+
semibold: 'font-semibold',
|
|
213
|
+
bold: 'font-bold',
|
|
214
|
+
};
|
|
200
215
|
const TextContent = forwardRef((props, ref) => {
|
|
201
|
-
const { xsmall = false, small = false, strong = false, muted = false, error = false, className, ...rest } = props;
|
|
202
|
-
|
|
203
|
-
|
|
216
|
+
const { as = 'span', size, weight, xsmall = false, small = false, strong = false, muted = false, error = false, success = false, warning = false, info = false, invert = false, monospace = false, truncate = false, className, children, ...rest } = props;
|
|
217
|
+
const textColorClass = error
|
|
218
|
+
? 'text-[var(--ui-text-danger)]'
|
|
219
|
+
: success
|
|
220
|
+
? 'text-[var(--ui-success)]'
|
|
221
|
+
: warning
|
|
222
|
+
? 'text-[var(--ui-warning)]'
|
|
223
|
+
: info
|
|
224
|
+
? 'text-[var(--ui-info)]'
|
|
225
|
+
: muted
|
|
226
|
+
? invert
|
|
227
|
+
? 'text-gray-300 dark:text-gray-600'
|
|
228
|
+
: 'text-[var(--ui-text-muted)]'
|
|
229
|
+
: invert
|
|
230
|
+
? 'text-white dark:text-black'
|
|
231
|
+
: 'text-[var(--ui-text)]';
|
|
232
|
+
return React__default.createElement(as, {
|
|
204
233
|
ref,
|
|
205
|
-
className: buildClassName(
|
|
234
|
+
className: buildClassName(textColorClass, size ? sizeClassMap$2[size] : xsmall ? 'text-xs/5' : small ? 'text-sm/6' : undefined, weight ? weightClassMap$2[weight] : strong ? 'font-semibold' : undefined, monospace && 'font-mono', truncate && 'truncate inline-block max-w-full', className),
|
|
235
|
+
...rest,
|
|
236
|
+
children,
|
|
206
237
|
});
|
|
207
238
|
});
|
|
208
239
|
TextContent.displayName = 'TextContent';
|
|
@@ -212,19 +243,29 @@ TextContent.displayName = 'TextContent';
|
|
|
212
243
|
*/
|
|
213
244
|
const THEMED_BTN_CLASS = {
|
|
214
245
|
primary: {
|
|
215
|
-
default: 'text-white bg-[var(--ui-primary)] hover:bg-[var(--ui-primary-hover)] focus:
|
|
216
|
-
outlined: 'text-[var(--ui-primary-text)] hover:text-white border border-[var(--ui-primary)] hover:bg-[var(--ui-primary)] focus:
|
|
217
|
-
plain: 'text-[var(--ui-primary-text)] hover:bg-black/5 dark:hover:bg-gray-700 focus:
|
|
246
|
+
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)]',
|
|
247
|
+
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)]',
|
|
248
|
+
plain: 'text-[var(--ui-primary-text)] hover:bg-black/5 dark:hover:bg-gray-700 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-primary-ring)] disabled:text-[var(--ui-primary-disabled)]',
|
|
218
249
|
},
|
|
219
250
|
danger: {
|
|
220
|
-
default: 'text-white bg-[var(--ui-danger)] hover:bg-[var(--ui-danger-hover)] focus:
|
|
221
|
-
outlined: 'text-[var(--ui-danger-text)] hover:text-white border border-[var(--ui-danger)] hover:bg-[var(--ui-danger)] focus:
|
|
222
|
-
plain: 'text-[var(--ui-danger-text)] hover:bg-black/5 dark:hover:bg-gray-700 focus:
|
|
251
|
+
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)]',
|
|
252
|
+
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)]',
|
|
253
|
+
plain: 'text-[var(--ui-danger-text)] hover:bg-black/5 dark:hover:bg-gray-700 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-danger-ring)] disabled:text-[var(--ui-danger-disabled)]',
|
|
223
254
|
},
|
|
224
255
|
secondary: {
|
|
225
|
-
default: 'text-gray-700 bg-white border border-gray-300 hover:bg-gray-100 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 focus:
|
|
226
|
-
outlined: 'text-gray-700 hover:text-white border border-gray-500 hover:bg-gray-700 dark:border-gray-500 dark:text-gray-200 dark:hover:text-white dark:hover:bg-gray-600 focus:
|
|
227
|
-
plain: 'text-gray-700 hover:bg-black/5 dark:text-gray-200 dark:hover:bg-gray-700 focus:
|
|
256
|
+
default: 'text-gray-700 bg-white border border-gray-300 hover:bg-gray-100 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 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',
|
|
257
|
+
outlined: 'text-gray-700 hover:text-white border border-gray-500 hover:bg-gray-700 dark:border-gray-500 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-gray-400',
|
|
258
|
+
plain: 'text-gray-700 hover:bg-black/5 dark:text-gray-200 dark:hover:bg-gray-700 focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-[var(--ui-focus-ring)] disabled:text-gray-400',
|
|
259
|
+
},
|
|
260
|
+
success: {
|
|
261
|
+
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',
|
|
262
|
+
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',
|
|
263
|
+
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',
|
|
264
|
+
},
|
|
265
|
+
warning: {
|
|
266
|
+
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',
|
|
267
|
+
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',
|
|
268
|
+
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',
|
|
228
269
|
},
|
|
229
270
|
};
|
|
230
271
|
const LOADER_TEXT_CLASS = {
|
|
@@ -243,6 +284,16 @@ const LOADER_TEXT_CLASS = {
|
|
|
243
284
|
outlined: 'text-gray-700 dark:text-gray-200',
|
|
244
285
|
plain: 'text-gray-700 dark:text-gray-200',
|
|
245
286
|
},
|
|
287
|
+
success: {
|
|
288
|
+
default: 'text-white',
|
|
289
|
+
outlined: 'text-emerald-600 dark:text-emerald-400',
|
|
290
|
+
plain: 'text-emerald-600 dark:text-emerald-400',
|
|
291
|
+
},
|
|
292
|
+
warning: {
|
|
293
|
+
default: 'text-white',
|
|
294
|
+
outlined: 'text-amber-600 dark:text-amber-400',
|
|
295
|
+
plain: 'text-amber-600 dark:text-amber-400',
|
|
296
|
+
},
|
|
246
297
|
};
|
|
247
298
|
/**
|
|
248
299
|
* Generate size classes.
|
|
@@ -250,13 +301,15 @@ const LOADER_TEXT_CLASS = {
|
|
|
250
301
|
const getBtnSizeClasses = (size, iconOnly = false) => {
|
|
251
302
|
switch (size) {
|
|
252
303
|
case 'xs':
|
|
253
|
-
return iconOnly ? 'p-1 rounded-sm text-xs' : 'px-2
|
|
304
|
+
return iconOnly ? 'h-7 w-7 p-1 rounded-sm text-xs' : 'h-7 px-2.5 rounded-sm gap-1 text-xs';
|
|
254
305
|
case 'sm':
|
|
255
|
-
return iconOnly ? 'p-1.5 rounded text-sm' : 'px-3
|
|
306
|
+
return iconOnly ? 'h-8 w-8 p-1.5 rounded text-sm' : 'h-8 px-3 rounded gap-1.5 text-sm';
|
|
256
307
|
case 'md':
|
|
257
|
-
return iconOnly ? 'p-2 rounded-md text-sm' : 'px-4
|
|
308
|
+
return iconOnly ? 'h-9 w-9 p-2 rounded-md text-sm' : 'h-9 px-4 rounded-md gap-2 text-sm';
|
|
258
309
|
case 'lg':
|
|
259
|
-
return iconOnly
|
|
310
|
+
return iconOnly
|
|
311
|
+
? 'h-10 w-10 p-2.5 rounded-lg text-base'
|
|
312
|
+
: 'h-10 px-5 rounded-lg gap-2 text-base';
|
|
260
313
|
}
|
|
261
314
|
};
|
|
262
315
|
/**
|
|
@@ -264,15 +317,14 @@ const getBtnSizeClasses = (size, iconOnly = false) => {
|
|
|
264
317
|
*/
|
|
265
318
|
const BASE_BTN_CLASS = 'font-medium flex items-center justify-center shrink-0 cursor-pointer ' +
|
|
266
319
|
'disabled:cursor-not-allowed disabled:pointer-events-none disabled:opacity-75 ' +
|
|
267
|
-
'transition duration-
|
|
320
|
+
'active:scale-[0.98] transition-all duration-150 ease-out focus:outline-none';
|
|
268
321
|
const Button = forwardRef(({ as, type, ...props }, ref) => {
|
|
269
|
-
const { className = '', children, theme = 'primary', variant = 'default', size = 'md', loading = false, loadingText = '', rounded = false, disabled, iconOnly = false, noOutlineOnFocus = false, ...restProps } = props;
|
|
322
|
+
const { className = '', children, theme = 'primary', variant = 'default', size = 'md', leftIcon, rightIcon, fullWidth = false, loading = false, loadingText = '', rounded = false, disabled, iconOnly = false, noOutlineOnFocus = false, ...restProps } = props;
|
|
270
323
|
const Component = as || 'button';
|
|
271
324
|
// Accessibility guard for icon-only buttons
|
|
272
325
|
if (iconOnly && !restProps['aria-label']) {
|
|
273
326
|
console.warn('[ui] <Button iconOnly> requires aria-label for accessibility.');
|
|
274
327
|
}
|
|
275
|
-
// Disable interaction when loading
|
|
276
328
|
const resolvedDisabled = disabled || loading;
|
|
277
329
|
// Clean invalid props when not rendering a real button
|
|
278
330
|
const { type: _ignoreType, disabled: _ignoreDisabled, ...cleanRest } = restProps;
|
|
@@ -282,18 +334,15 @@ const Button = forwardRef(({ as, type, ...props }, ref) => {
|
|
|
282
334
|
disabled: resolvedDisabled,
|
|
283
335
|
}
|
|
284
336
|
: {};
|
|
337
|
+
const classes = useMemo(() => buildClassName(BASE_BTN_CLASS, THEMED_BTN_CLASS[theme][variant], getBtnSizeClasses(size, iconOnly), !noOutlineOnFocus && 'focus:outline focus:outline-2 focus:outline-offset-2', rounded && 'rounded-full', fullWidth && 'w-full', variant === 'default' && 'shadow-xs hover:shadow-none disabled:shadow-none', className), [theme, variant, size, iconOnly, rounded, fullWidth, noOutlineOnFocus, className]);
|
|
285
338
|
/**
|
|
286
|
-
*
|
|
287
|
-
*/
|
|
288
|
-
const classes = useMemo(() => buildClassName(BASE_BTN_CLASS, THEMED_BTN_CLASS[theme][variant], getBtnSizeClasses(size, iconOnly), !noOutlineOnFocus && 'focus:outline focus:outline-2 focus:outline-offset-2', rounded && 'rounded-full', variant === 'default' && 'shadow-xs hover:shadow-none disabled:shadow-none', className), [theme, variant, size, iconOnly, rounded, noOutlineOnFocus, className]);
|
|
289
|
-
/**
|
|
290
|
-
* SPECIAL CASE:
|
|
291
|
-
* iconOnly + loading → show spinner only
|
|
339
|
+
* SPECIAL CASE: iconOnly + loading → show spinner only
|
|
292
340
|
*/
|
|
293
341
|
if (loading && iconOnly) {
|
|
294
|
-
return (jsx(Component, { ref: ref, className: classes, ...buttonSpecificProps, ...cleanRest, children: jsx(Loader, { "data-testid": "btn-loader-icon", size: "xs" }) }));
|
|
342
|
+
return (jsx(Component, { ref: ref, className: classes, ...buttonSpecificProps, ...cleanRest, children: jsx(Loader, { "data-testid": "btn-loader-icon", size: "xs", className: buildClassName(LOADER_TEXT_CLASS[theme][variant]) }) }));
|
|
295
343
|
}
|
|
296
|
-
|
|
344
|
+
const loaderIcon = (jsx(Loader, { "data-testid": "btn-loader-icon", size: "xs", className: buildClassName(LOADER_TEXT_CLASS[theme][variant]) }));
|
|
345
|
+
return (jsx(Component, { ref: ref, className: classes, ...buttonSpecificProps, ...cleanRest, children: loading && !loadingText ? (jsxs("span", { className: "flex items-center gap-2", "aria-live": "polite", children: [loaderIcon, children] })) : loading && loadingText ? (jsxs("span", { className: "flex items-center gap-2", "aria-live": "polite", children: [loaderIcon, jsx(TextContent, { className: buildClassName(LOADER_TEXT_CLASS[theme][variant]), children: loadingText })] })) : (jsxs(Fragment, { children: [leftIcon && jsx("span", { className: "shrink-0 flex items-center", children: leftIcon }), children, rightIcon && jsx("span", { className: "shrink-0 flex items-center", children: rightIcon })] })) }));
|
|
297
346
|
});
|
|
298
347
|
Button.displayName = 'Button';
|
|
299
348
|
|
|
@@ -324,13 +373,13 @@ const ThemeClassMap = {
|
|
|
324
373
|
info: 'bg-[var(--ui-info-light)] text-[var(--ui-info-text)] ring-[var(--ui-info-ring-subtle)]',
|
|
325
374
|
secondary: 'bg-gray-50 dark:bg-gray-700 text-gray-700 dark:text-gray-200 ring-[var(--ui-secondary-ring-subtle)]',
|
|
326
375
|
};
|
|
327
|
-
const SIZE_CLASS = {
|
|
376
|
+
const SIZE_CLASS$1 = {
|
|
328
377
|
sm: 'text-xs py-0.5 px-2',
|
|
329
378
|
md: 'text-xs py-1 px-3',
|
|
330
379
|
};
|
|
331
380
|
const Badge = forwardRef((props, ref) => {
|
|
332
381
|
const { theme = 'info', className, children, removable = false, rounded = false, onRemove, size = 'md', icon, ...restProps } = props;
|
|
333
|
-
return (jsxs(TextContent, { ref: ref, className: buildClassName('inline-flex items-center gap-1 font-medium ring-1 ring-inset', SIZE_CLASS[size], ThemeClassMap[theme], rounded ? 'rounded-full' : 'rounded-md', icon ? 'pl-2' : '', removable ? 'pr-1' : '', className), "data-testid": `badge-${theme}`, ...restProps, children: [icon && jsx("span", { className: "shrink-0 inline-flex", children: icon }), children, removable && (jsx(SvgX, { className: "size-4 shrink-0 hover:bg-black/10 dark:hover:bg-white/10 rounded-sm cursor-pointer", role: "button", tabIndex: 0, "aria-label": "Remove", onClick: () => onRemove === null || onRemove === void 0 ? void 0 : onRemove(), onKeyDown: (e) => {
|
|
382
|
+
return (jsxs(TextContent, { ref: ref, className: buildClassName('inline-flex items-center gap-1 font-medium ring-1 ring-inset', SIZE_CLASS$1[size], ThemeClassMap[theme], rounded ? 'rounded-full' : 'rounded-md', icon ? 'pl-2' : '', removable ? 'pr-1' : '', className), "data-testid": `badge-${theme}`, ...restProps, children: [icon && jsx("span", { className: "shrink-0 inline-flex", children: icon }), children, removable && (jsx(SvgX, { className: "size-4 shrink-0 hover:bg-black/10 dark:hover:bg-white/10 rounded-sm cursor-pointer", role: "button", tabIndex: 0, "aria-label": "Remove", onClick: () => onRemove === null || onRemove === void 0 ? void 0 : onRemove(), onKeyDown: (e) => {
|
|
334
383
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
335
384
|
e.preventDefault();
|
|
336
385
|
onRemove === null || onRemove === void 0 ? void 0 : onRemove();
|
|
@@ -408,21 +457,54 @@ const Banner = forwardRef((props, ref) => {
|
|
|
408
457
|
});
|
|
409
458
|
Banner.displayName = 'Banner';
|
|
410
459
|
|
|
460
|
+
const sizeClassMap$1 = {
|
|
461
|
+
xs: 'text-xs/5',
|
|
462
|
+
sm: 'text-sm/6',
|
|
463
|
+
md: 'text-base/7',
|
|
464
|
+
lg: 'text-lg/8',
|
|
465
|
+
xl: 'text-xl/9',
|
|
466
|
+
};
|
|
467
|
+
const weightClassMap$1 = {
|
|
468
|
+
light: 'font-light',
|
|
469
|
+
normal: 'font-normal',
|
|
470
|
+
medium: 'font-medium',
|
|
471
|
+
semibold: 'font-semibold',
|
|
472
|
+
bold: 'font-bold',
|
|
473
|
+
};
|
|
474
|
+
const alignClassMap$1 = {
|
|
475
|
+
left: 'text-left',
|
|
476
|
+
center: 'text-center',
|
|
477
|
+
right: 'text-right',
|
|
478
|
+
justify: 'text-justify',
|
|
479
|
+
};
|
|
480
|
+
const clampClassMap = {
|
|
481
|
+
1: 'line-clamp-1',
|
|
482
|
+
2: 'line-clamp-2',
|
|
483
|
+
3: 'line-clamp-3',
|
|
484
|
+
4: 'line-clamp-4',
|
|
485
|
+
};
|
|
411
486
|
const BodyText = forwardRef((props, ref) => {
|
|
412
|
-
const { as = 'p', small = false, strong = false, muted = false, error = false, inline = false, className,
|
|
487
|
+
const { as = 'p', size, weight, small = false, strong = false, muted = false, error = false, success = false, warning = false, info = false, inline = false, invert = false, align, truncate = false, clamp, className, children, ...rest } = props;
|
|
413
488
|
const textColorClass = error
|
|
414
489
|
? 'text-[var(--ui-text-danger)]'
|
|
415
|
-
:
|
|
416
|
-
?
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
490
|
+
: success
|
|
491
|
+
? 'text-[var(--ui-success)]'
|
|
492
|
+
: warning
|
|
493
|
+
? 'text-[var(--ui-warning)]'
|
|
494
|
+
: info
|
|
495
|
+
? 'text-[var(--ui-info)]'
|
|
496
|
+
: muted
|
|
497
|
+
? invert
|
|
498
|
+
? 'text-gray-300 dark:text-gray-600'
|
|
499
|
+
: 'text-[var(--ui-text-muted)]'
|
|
500
|
+
: invert
|
|
501
|
+
? 'text-white dark:text-black'
|
|
502
|
+
: 'text-[var(--ui-text)]';
|
|
422
503
|
return React__default.createElement(as, {
|
|
423
|
-
ref
|
|
424
|
-
className: buildClassName(textColorClass, small && 'text-sm/6', strong && 'font-semibold', inline && 'inline-flex items-center gap-2', className),
|
|
504
|
+
ref,
|
|
505
|
+
className: buildClassName(textColorClass, size ? sizeClassMap$1[size] : small && 'text-sm/6', weight ? weightClassMap$1[weight] : strong && 'font-semibold', align && alignClassMap$1[align], inline && 'inline-flex items-center gap-2', truncate && 'truncate', clamp && clampClassMap[clamp], className),
|
|
425
506
|
...rest,
|
|
507
|
+
children,
|
|
426
508
|
});
|
|
427
509
|
});
|
|
428
510
|
BodyText.displayName = 'BodyText';
|
|
@@ -446,24 +528,60 @@ var SvgSlash = function SvgSlash(props) {
|
|
|
446
528
|
})));
|
|
447
529
|
};
|
|
448
530
|
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
531
|
+
const SIZE_CONTAINER_CLASS$2 = {
|
|
532
|
+
sm: 'text-xs gap-1.5',
|
|
533
|
+
md: 'text-sm gap-2',
|
|
534
|
+
lg: 'text-base gap-2.5',
|
|
535
|
+
};
|
|
536
|
+
const SIZE_ICON_CLASS$1 = {
|
|
537
|
+
sm: 'size-3',
|
|
538
|
+
md: 'size-3.5',
|
|
539
|
+
lg: 'size-4',
|
|
453
540
|
};
|
|
541
|
+
function RenderSeparator({ separator, size, index, className, }) {
|
|
542
|
+
const testId = `breadcrumb-divider-${index}`;
|
|
543
|
+
if (separator === 'slash' || !separator) {
|
|
544
|
+
return (jsx(SvgSlash, { "data-testid": testId, className: buildClassName(SIZE_ICON_CLASS$1[size] || 'size-3.5', 'text-gray-400 dark:text-gray-500 shrink-0 select-none', className) }));
|
|
545
|
+
}
|
|
546
|
+
if (separator === 'chevron') {
|
|
547
|
+
return (jsx("svg", { "data-testid": testId, className: buildClassName(SIZE_ICON_CLASS$1[size] || 'size-3.5', 'text-gray-400 dark:text-gray-500 shrink-0 select-none', className), fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 5l7 7-7 7" }) }));
|
|
548
|
+
}
|
|
549
|
+
if (separator === 'dot') {
|
|
550
|
+
return (jsx("span", { "data-testid": testId, className: buildClassName('size-1 rounded-full bg-gray-400 dark:bg-gray-500 shrink-0 select-none mx-0.5', className) }));
|
|
551
|
+
}
|
|
552
|
+
return (jsx("span", { "data-testid": testId, className: buildClassName('shrink-0 select-none', className), children: separator }));
|
|
553
|
+
}
|
|
554
|
+
const Breadcrumb = forwardRef((props, ref) => {
|
|
555
|
+
const { items = [], render, separator = 'slash', size = 'md', maxItems, className, itemClass, activeItemClass, separatorClass, ...navProps } = props;
|
|
556
|
+
const [expanded, setExpanded] = useState(false);
|
|
557
|
+
const count = items.length;
|
|
558
|
+
const shouldTruncate = maxItems !== undefined && maxItems > 1 && count > maxItems && !expanded;
|
|
559
|
+
const getVisibleItems = () => {
|
|
560
|
+
if (!shouldTruncate)
|
|
561
|
+
return items;
|
|
562
|
+
// Always keep 1st item and last 1-2 items
|
|
563
|
+
const firstItem = items[0];
|
|
564
|
+
const lastItems = items.slice(count - (maxItems - 1));
|
|
565
|
+
return [firstItem, ...lastItems];
|
|
566
|
+
};
|
|
567
|
+
const hiddenItems = shouldTruncate ? items.slice(1, count - (maxItems - 1)) : [];
|
|
568
|
+
getVisibleItems();
|
|
569
|
+
return (jsx("nav", { ref: ref, "aria-label": "Breadcrumb", "data-testid": "breadcrumb-container", className: buildClassName('flex flex-wrap items-center text-gray-600 dark:text-gray-300 font-medium', SIZE_CONTAINER_CLASS$2[size] || SIZE_CONTAINER_CLASS$2.md, className), ...navProps, children: items.map((item, index) => {
|
|
570
|
+
const isLast = index === count - 1;
|
|
571
|
+
const isCollapsedMiddle = shouldTruncate && index > 0 && index < count - (maxItems - 1);
|
|
572
|
+
if (isCollapsedMiddle) {
|
|
573
|
+
if (index === 1) {
|
|
574
|
+
// Render the single '...' collapse pill
|
|
575
|
+
return (jsxs("span", { className: "inline-flex items-center gap-2 relative group/ellipsis", children: [jsx("button", { type: "button", onClick: () => setExpanded(true), className: "px-1.5 py-0.5 rounded-md text-xs font-semibold bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-600 dark:text-gray-300 transition-colors cursor-pointer", "aria-label": `Expand ${hiddenItems.length} hidden breadcrumbs`, title: `Show ${hiddenItems.length} hidden levels`, children: "\u2022\u2022\u2022" }), jsx(RenderSeparator, { separator: separator, size: size, index: index, className: separatorClass })] }, "breadcrumb-collapsed"));
|
|
576
|
+
}
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
const defaultContent = isLast ? (jsx("span", { className: buildClassName('text-gray-900 dark:text-white font-semibold', activeItemClass, item.className), children: item.text })) : item.to ? (jsx("a", { href: item.to, className: buildClassName('hover:text-blue-600 dark:hover:text-blue-400 transition-colors', itemClass, item.className), children: item.text })) : (jsx("span", { className: buildClassName(itemClass, item.className), children: item.text }));
|
|
580
|
+
return (jsxs("span", { className: "inline-flex items-center gap-2", "data-testid": `breadcrumb-item-${item.key}`, children: [jsxs("span", { className: "inline-flex items-center gap-1.5", ...(isLast ? { 'aria-current': 'page' } : {}), children: [item.icon && jsx("span", { className: "shrink-0 text-gray-400", children: item.icon }), isLast ? (defaultContent) : render ? (jsx("span", { className: buildClassName(itemClass, item.className), children: render(item) })) : (defaultContent)] }), !isLast && (jsx(RenderSeparator, { separator: separator, size: size, index: index, className: separatorClass }))] }, item.key));
|
|
581
|
+
}) }));
|
|
582
|
+
});
|
|
454
583
|
Breadcrumb.displayName = 'Breadcrumb';
|
|
455
584
|
|
|
456
|
-
/**
|
|
457
|
-
* Corner-rounding classes for the group's first/last button, keyed by
|
|
458
|
-
* orientation + size, matching Button's own size -> radius mapping
|
|
459
|
-
* (xs: rounded-sm, sm: rounded, md: rounded-md, lg: rounded-lg).
|
|
460
|
-
*
|
|
461
|
-
* These must stay fully static string literals (no template-literal
|
|
462
|
-
* interpolation) — Tailwind's JIT scanner only picks up class names that
|
|
463
|
-
* appear verbatim in source, and the docs site scans the library's
|
|
464
|
-
* *compiled* output, where interpolated fragments never appear as a
|
|
465
|
-
* complete class name.
|
|
466
|
-
*/
|
|
467
585
|
const EDGE_ROUNDING = {
|
|
468
586
|
horizontal: {
|
|
469
587
|
xs: { first: 'rounded-l-sm rounded-r-none', last: 'rounded-r-sm rounded-l-none' },
|
|
@@ -482,11 +600,6 @@ const PILL_EDGE_ROUNDING = {
|
|
|
482
600
|
horizontal: { first: 'rounded-l-full rounded-r-none', last: 'rounded-r-full rounded-l-none' },
|
|
483
601
|
vertical: { first: 'rounded-t-full rounded-b-none', last: 'rounded-b-full rounded-t-none' },
|
|
484
602
|
};
|
|
485
|
-
/**
|
|
486
|
-
* Resolves the corner-rounding classes for a single button at `index`
|
|
487
|
-
* out of `count` inside the group, so only the group's outer edges stay
|
|
488
|
-
* rounded and interior seams go square.
|
|
489
|
-
*/
|
|
490
603
|
const getEdgeRoundingClass = ({ index, count, size, orientation, pill, }) => {
|
|
491
604
|
if (count <= 1)
|
|
492
605
|
return '';
|
|
@@ -497,18 +610,46 @@ const getEdgeRoundingClass = ({ index, count, size, orientation, pill, }) => {
|
|
|
497
610
|
? PILL_EDGE_ROUNDING[orientation][position]
|
|
498
611
|
: EDGE_ROUNDING[orientation][size][position];
|
|
499
612
|
};
|
|
500
|
-
const ButtonGroup = ({ children, orientation = 'horizontal', theme = 'secondary', variant = 'default', size = 'md', rounded = false, fullWidth = false, label, className = '', }) => {
|
|
613
|
+
const ButtonGroup = ({ children, orientation = 'horizontal', theme = 'secondary', variant = 'default', size = 'md', value, onChange, disabled: groupDisabled = false, rounded = false, fullWidth = false, label, className = '', }) => {
|
|
501
614
|
const isVertical = orientation === 'vertical';
|
|
502
615
|
const items = useMemo(() => Children.toArray(children).filter(isValidElement), [children]);
|
|
503
616
|
const count = items.length;
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
617
|
+
const isToggleMode = value !== undefined;
|
|
618
|
+
return (jsx("div", { role: "group", "aria-label": label, className: buildClassName('inline-flex items-stretch', isVertical ? 'flex-col' : 'flex-row', fullWidth && 'flex w-full', className), children: items.map((child, index) => {
|
|
619
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
620
|
+
const childProps = child.props;
|
|
621
|
+
const childSize = (_a = childProps.size) !== null && _a !== void 0 ? _a : size;
|
|
622
|
+
const childDisabled = (_b = childProps.disabled) !== null && _b !== void 0 ? _b : groupDisabled;
|
|
623
|
+
// Resolve item value for toggle mode
|
|
624
|
+
const itemVal = (_d = (_c = childProps.value) !== null && _c !== void 0 ? _c : childProps.id) !== null && _d !== void 0 ? _d : (typeof childProps.children === 'string' || typeof childProps.children === 'number'
|
|
625
|
+
? childProps.children
|
|
626
|
+
: index);
|
|
627
|
+
const isSelected = isToggleMode
|
|
628
|
+
? Array.isArray(value)
|
|
629
|
+
? value.includes(itemVal)
|
|
630
|
+
: value === itemVal
|
|
631
|
+
: false;
|
|
632
|
+
const resolvedTheme = isToggleMode
|
|
633
|
+
? isSelected
|
|
634
|
+
? ((_e = childProps.theme) !== null && _e !== void 0 ? _e : 'primary')
|
|
635
|
+
: ((_f = childProps.theme) !== null && _f !== void 0 ? _f : 'secondary')
|
|
636
|
+
: ((_g = childProps.theme) !== null && _g !== void 0 ? _g : theme);
|
|
637
|
+
const resolvedVariant = (_h = childProps.variant) !== null && _h !== void 0 ? _h : variant;
|
|
638
|
+
const handleClick = (e) => {
|
|
639
|
+
var _a;
|
|
640
|
+
(_a = childProps.onClick) === null || _a === void 0 ? void 0 : _a.call(childProps, e);
|
|
641
|
+
if (isToggleMode && !childDisabled) {
|
|
642
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(itemVal);
|
|
643
|
+
}
|
|
644
|
+
};
|
|
507
645
|
return cloneElement(child, {
|
|
508
|
-
theme:
|
|
509
|
-
variant:
|
|
646
|
+
theme: resolvedTheme,
|
|
647
|
+
variant: resolvedVariant,
|
|
510
648
|
size: childSize,
|
|
511
|
-
|
|
649
|
+
disabled: childDisabled,
|
|
650
|
+
...(isToggleMode ? { 'aria-pressed': isSelected } : {}),
|
|
651
|
+
onClick: handleClick,
|
|
652
|
+
className: buildClassName(childProps.className, getEdgeRoundingClass({
|
|
512
653
|
index,
|
|
513
654
|
count,
|
|
514
655
|
size: childSize,
|
|
@@ -516,23 +657,66 @@ const ButtonGroup = ({ children, orientation = 'horizontal', theme = 'secondary'
|
|
|
516
657
|
pill: rounded,
|
|
517
658
|
}), count > 1 && [
|
|
518
659
|
'relative shadow-none hover:z-10 focus:z-10 focus-visible:z-10',
|
|
660
|
+
isSelected && 'z-10',
|
|
519
661
|
index > 0 && (isVertical ? '-mt-px' : '-ml-px'),
|
|
662
|
+
index > 0 &&
|
|
663
|
+
resolvedVariant === 'default' &&
|
|
664
|
+
(isVertical
|
|
665
|
+
? resolvedTheme === 'primary' || resolvedTheme === 'danger'
|
|
666
|
+
? 'border-t border-white/25 dark:border-white/20'
|
|
667
|
+
: 'border-t border-gray-300/70 dark:border-gray-700/70'
|
|
668
|
+
: resolvedTheme === 'primary' || resolvedTheme === 'danger'
|
|
669
|
+
? 'border-l border-white/25 dark:border-white/20'
|
|
670
|
+
: 'border-l border-gray-300/70 dark:border-gray-700/70'),
|
|
520
671
|
], fullWidth && 'flex-1'),
|
|
521
672
|
});
|
|
522
673
|
}) }));
|
|
523
674
|
};
|
|
524
675
|
ButtonGroup.displayName = 'ButtonGroup';
|
|
525
676
|
|
|
526
|
-
const
|
|
677
|
+
const RADIUS_CLASS = {
|
|
678
|
+
none: 'rounded-none',
|
|
679
|
+
sm: 'rounded-sm',
|
|
680
|
+
md: 'rounded-md',
|
|
681
|
+
lg: 'rounded-lg',
|
|
682
|
+
xl: 'rounded-xl',
|
|
683
|
+
'2xl': 'rounded-2xl',
|
|
684
|
+
};
|
|
685
|
+
const ASPECT_RATIO_CLASS = {
|
|
686
|
+
auto: 'aspect-auto',
|
|
687
|
+
square: 'aspect-square',
|
|
688
|
+
video: 'aspect-video',
|
|
689
|
+
wide: 'aspect-[21/9]',
|
|
690
|
+
};
|
|
527
691
|
const Card = forwardRef(({ as, ...props }, ref) => {
|
|
528
|
-
|
|
529
|
-
const { hoverable = false, bordered = true, compact = false, zeroPadding = false, ...rest } = props;
|
|
692
|
+
const { variant = 'outlined', radius = 'xl', hoverable = false, clickable = false, selected = false, bordered = true, compact = false, zeroPadding = false, ...rest } = props;
|
|
530
693
|
const Component = as || 'div';
|
|
531
|
-
// Extract className safely from rest
|
|
532
694
|
const { className, ...cleanRest } = rest;
|
|
533
|
-
const
|
|
695
|
+
const isOutlined = variant === 'outlined' && bordered;
|
|
696
|
+
const isElevated = variant === 'elevated';
|
|
697
|
+
const isFilled = variant === 'filled';
|
|
698
|
+
const isGhost = variant === 'ghost';
|
|
699
|
+
const classes = buildClassName('block transition-all duration-200', RADIUS_CLASS[radius] || RADIUS_CLASS.xl, zeroPadding ? '' : compact ? 'p-3 sm:p-4' : 'p-5 sm:p-6',
|
|
700
|
+
// Surface variants
|
|
701
|
+
isOutlined && 'bg-white dark:bg-gray-900 border border-[var(--ui-border)]', isElevated &&
|
|
702
|
+
'bg-white dark:bg-gray-900 border border-gray-100 dark:border-gray-800 shadow-md shadow-gray-200/50 dark:shadow-none', isFilled && 'bg-gray-50/80 dark:bg-gray-800/60 border border-transparent', isGhost && 'bg-transparent border border-transparent',
|
|
703
|
+
// Hoverable & Clickable states
|
|
704
|
+
hoverable && 'hover:bg-gray-100/80 dark:hover:bg-gray-800/80', clickable && 'cursor-pointer hover:-translate-y-0.5 hover:shadow-lg', selected && 'ring-2 ring-[var(--ui-primary)] border-[var(--ui-primary)]', className);
|
|
534
705
|
return jsx(Component, { ref: ref, className: classes, ...cleanRest });
|
|
535
706
|
});
|
|
707
|
+
const CardHeader = ({ className, bordered = false, children, ...props }) => (jsx("div", { className: buildClassName('flex items-center justify-between gap-4 mb-4', bordered && 'pb-4 border-b border-gray-200 dark:border-gray-800', className), ...props, children: children }));
|
|
708
|
+
const CardTitle = ({ className, as: Component = 'h3', children, ...props }) => (jsx(Component, { className: buildClassName('text-lg font-semibold text-gray-900 dark:text-white tracking-tight', className), ...props, children: children }));
|
|
709
|
+
const CardDescription = ({ className, children, ...props }) => (jsx("p", { className: buildClassName('text-sm text-gray-500 dark:text-gray-400 font-normal mt-1', className), ...props, children: children }));
|
|
710
|
+
const CardContent = ({ className, children, ...props }) => (jsx("div", { className: buildClassName('space-y-3', className), ...props, children: children }));
|
|
711
|
+
const CardFooter = ({ className, bordered = false, children, ...props }) => (jsx("div", { className: buildClassName('flex items-center justify-end gap-3 mt-4', bordered && 'pt-4 border-t border-gray-200 dark:border-gray-800', className), ...props, children: children }));
|
|
712
|
+
const CardMedia = ({ className, aspectRatio = 'video', alt = '', ...props }) => (jsx("div", { className: buildClassName('overflow-hidden -mx-5 -mt-5 mb-4 sm:-mx-6 sm:-mt-6', className), children: jsx("img", { alt: alt, className: buildClassName('w-full object-cover', ASPECT_RATIO_CLASS[aspectRatio] || ASPECT_RATIO_CLASS.video), ...props }) }));
|
|
713
|
+
Card.Header = CardHeader;
|
|
714
|
+
Card.Title = CardTitle;
|
|
715
|
+
Card.Description = CardDescription;
|
|
716
|
+
Card.Content = CardContent;
|
|
717
|
+
Card.Body = CardContent;
|
|
718
|
+
Card.Footer = CardFooter;
|
|
719
|
+
Card.Media = CardMedia;
|
|
536
720
|
Card.displayName = 'Card';
|
|
537
721
|
|
|
538
722
|
const Label = forwardRef((props, ref) => {
|
|
@@ -541,49 +725,127 @@ const Label = forwardRef((props, ref) => {
|
|
|
541
725
|
});
|
|
542
726
|
Label.displayName = 'Label';
|
|
543
727
|
|
|
728
|
+
const sizeStyles = {
|
|
729
|
+
sm: {
|
|
730
|
+
box: 'size-3.5',
|
|
731
|
+
svg: 'h-2.5 w-2.5',
|
|
732
|
+
text: 'text-xs',
|
|
733
|
+
subtext: 'text-[11px]',
|
|
734
|
+
},
|
|
735
|
+
md: {
|
|
736
|
+
box: 'size-4.5',
|
|
737
|
+
svg: 'h-3.25 w-3.25',
|
|
738
|
+
text: 'text-sm',
|
|
739
|
+
subtext: 'text-xs',
|
|
740
|
+
},
|
|
741
|
+
lg: {
|
|
742
|
+
box: 'size-5.5',
|
|
743
|
+
svg: 'h-4 w-4',
|
|
744
|
+
text: 'text-base',
|
|
745
|
+
subtext: 'text-sm',
|
|
746
|
+
},
|
|
747
|
+
};
|
|
544
748
|
const Checkbox = forwardRef((props, ref) => {
|
|
545
749
|
var _a;
|
|
546
|
-
const { id, name, label, labelClass = '', wrapperClass = '', containerClass = '', error, disabled = false, className = '',
|
|
547
|
-
// Checkbox-only props removed from rest
|
|
548
|
-
...rest } = props;
|
|
750
|
+
const { id, name, label, labelClass = '', wrapperClass = '', containerClass = '', error, helperText, disabled = false, indeterminate = false, size = 'md', variant = 'default', description, className = '', checked, onChange, ...rest } = props;
|
|
549
751
|
const generatedId = useId();
|
|
550
752
|
const checkboxId = (_a = id !== null && id !== void 0 ? id : name) !== null && _a !== void 0 ? _a : generatedId;
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
753
|
+
const innerRef = useRef(null);
|
|
754
|
+
useImperativeHandle(ref, () => innerRef.current);
|
|
755
|
+
useEffect(() => {
|
|
756
|
+
if (innerRef.current) {
|
|
757
|
+
innerRef.current.indeterminate = Boolean(indeterminate);
|
|
758
|
+
}
|
|
759
|
+
}, [indeterminate]);
|
|
760
|
+
const currentSize = sizeStyles[size] || sizeStyles.md;
|
|
761
|
+
const isCard = variant === 'card';
|
|
762
|
+
return (jsxs("div", { className: buildClassName(wrapperClass), children: [jsx("div", { className: buildClassName(isCard
|
|
763
|
+
? 'rounded-xl border p-3.5 transition-all duration-150 relative'
|
|
764
|
+
: 'flex items-start', isCard &&
|
|
765
|
+
(disabled
|
|
766
|
+
? 'border-gray-200 dark:border-gray-800 bg-gray-50/50 dark:bg-gray-900/40 opacity-70'
|
|
767
|
+
: checked || indeterminate
|
|
768
|
+
? 'border-blue-500/80 bg-blue-50/30 dark:bg-blue-950/20 dark:border-blue-500/60 shadow-xs'
|
|
769
|
+
: 'border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 hover:border-gray-300 dark:hover:border-gray-700 shadow-2xs'), containerClass), children: jsxs("label", { htmlFor: checkboxId, className: buildClassName('inline-flex items-start relative select-none w-full', disabled ? 'cursor-not-allowed opacity-70' : 'cursor-pointer'), children: [jsxs("div", { className: "relative inline-flex items-center shrink-0 pt-0.5", children: [jsx("input", { ref: innerRef, ...rest, id: checkboxId, name: name, type: "checkbox", disabled: disabled, checked: checked, onChange: onChange, "aria-checked": indeterminate ? 'mixed' : checked, className: buildClassName('peer cursor-pointer transition-all appearance-none rounded', currentSize.box, 'border focus:ring-2 focus:ring-offset-2', 'checked:bg-blue-600 checked:border-blue-600 dark:checked:bg-blue-600 dark:checked:border-blue-600', 'focus:ring-[var(--ui-focus-ring)] dark:focus:ring-offset-gray-900', disabled
|
|
770
|
+
? 'border-gray-200 dark:border-gray-700 bg-gray-100 dark:bg-gray-800 pointer-events-none'
|
|
771
|
+
: 'bg-white dark:bg-gray-700 border-gray-300 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-500 hover:shadow-xs', indeterminate &&
|
|
772
|
+
'!bg-blue-600 !border-blue-600 dark:!bg-blue-600 dark:!border-blue-600', className) }), !indeterminate && (jsx("span", { className: buildClassName('absolute text-white pointer-events-none transition-opacity top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2', checked !== undefined
|
|
773
|
+
? checked
|
|
774
|
+
? 'opacity-100'
|
|
775
|
+
: 'opacity-0'
|
|
776
|
+
: 'opacity-0 peer-checked:opacity-100'), children: jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: currentSize.svg, viewBox: "0 0 20 20", fill: "currentColor", stroke: "currentColor", strokeWidth: "1", children: jsx("path", { fillRule: "evenodd", d: "M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z", clipRule: "evenodd" }) }) })), indeterminate && (jsx("span", { className: "absolute text-white pointer-events-none top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 opacity-100 z-10", children: jsx("svg", { xmlns: "http://www.w3.org/2000/svg", className: currentSize.svg, viewBox: "0 0 20 20", fill: "currentColor", stroke: "currentColor", strokeWidth: "2", children: jsx("path", { fillRule: "evenodd", d: "M4 10a1 1 0 011-1h10a1 1 0 110 2H5a1 1 0 01-1-1z", clipRule: "evenodd" }) }) }))] }), (label || description) && (jsxs("div", { className: "ml-2.5 flex flex-col justify-center min-w-0 flex-1", children: [label && (jsx(Label, { htmlFor: checkboxId, className: buildClassName(disabled
|
|
777
|
+
? 'cursor-not-allowed text-gray-400 dark:text-gray-500'
|
|
778
|
+
: 'cursor-pointer text-gray-800 dark:text-gray-200 font-medium', currentSize.text, labelClass), children: label })), description && (jsx("span", { className: buildClassName('mt-0.5 text-gray-500 dark:text-gray-400 font-normal leading-normal', currentSize.subtext), children: description }))] }))] }) }), (error || helperText) && (jsx("div", { className: "mt-1.5 ml-0.5", children: error ? (jsx(TextContent, { error: true, small: true, children: error })) : (jsx(TextContent, { muted: true, small: true, children: helperText })) }))] }));
|
|
556
779
|
});
|
|
557
780
|
Checkbox.displayName = 'Checkbox';
|
|
558
781
|
|
|
782
|
+
const columnClasses = {
|
|
783
|
+
1: 'grid grid-cols-1 gap-3',
|
|
784
|
+
2: 'grid grid-cols-1 sm:grid-cols-2 gap-3',
|
|
785
|
+
3: 'grid grid-cols-1 sm:grid-cols-3 gap-3',
|
|
786
|
+
4: 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3',
|
|
787
|
+
};
|
|
559
788
|
const CheckboxGroup = (props) => {
|
|
560
|
-
const { name, options, label, labelClass = '', labelWrapperClass = '', labelHint, containerClass = '', value
|
|
789
|
+
const { name, options, label, labelClass = '', labelWrapperClass = '', labelHint, containerClass = '', value: controlledValue, defaultValue, onChange, inline = false, columns, size = 'md', variant = 'default', showSelectAll = false, selectAllLabel = 'Select All', error, helperText, disabled = false, } = props;
|
|
561
790
|
const groupId = useId();
|
|
791
|
+
const [internalValue, setInternalValue] = useState(defaultValue || []);
|
|
792
|
+
const isControlled = controlledValue !== undefined;
|
|
793
|
+
const currentValue = isControlled ? controlledValue : internalValue;
|
|
562
794
|
const items = useMemo(() => options.map((opt) => typeof opt === 'string' ? { label: opt, value: opt } : opt), [options]);
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
795
|
+
const enabledItems = useMemo(() => items.filter((i) => !i.disabled && !disabled), [items, disabled]);
|
|
796
|
+
// Select All State calculation
|
|
797
|
+
const allSelected = useMemo(() => {
|
|
798
|
+
if (!enabledItems.length)
|
|
799
|
+
return false;
|
|
800
|
+
return enabledItems.every((item) => currentValue.includes(item.value));
|
|
801
|
+
}, [enabledItems, currentValue]);
|
|
802
|
+
const isIndeterminate = useMemo(() => {
|
|
803
|
+
if (allSelected)
|
|
804
|
+
return false;
|
|
805
|
+
return enabledItems.some((item) => currentValue.includes(item.value));
|
|
806
|
+
}, [allSelected, enabledItems, currentValue]);
|
|
807
|
+
const handleSelectAllChange = (e) => {
|
|
808
|
+
let updated;
|
|
809
|
+
if (allSelected || isIndeterminate) {
|
|
810
|
+
const enabledValues = new Set(enabledItems.map((i) => i.value));
|
|
811
|
+
updated = currentValue.filter((v) => !enabledValues.has(v));
|
|
812
|
+
}
|
|
813
|
+
else {
|
|
814
|
+
const enabledValues = enabledItems.map((i) => i.value);
|
|
815
|
+
const uniqueSet = new Set([...currentValue, ...enabledValues]);
|
|
816
|
+
updated = Array.from(uniqueSet);
|
|
817
|
+
}
|
|
818
|
+
if (!isControlled) {
|
|
819
|
+
setInternalValue(updated);
|
|
820
|
+
}
|
|
821
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(updated, e);
|
|
822
|
+
};
|
|
567
823
|
const handleGroupChange = (e) => {
|
|
568
824
|
const raw = e.target.value;
|
|
569
825
|
const checked = e.target.checked;
|
|
570
|
-
// Convert HTML string value → typed T value
|
|
571
|
-
// Locate in normalized items
|
|
572
826
|
const matched = items.find((i) => String(i.value) === raw);
|
|
573
|
-
const itemValue = matched
|
|
827
|
+
const itemValue = matched ? matched.value : raw;
|
|
574
828
|
let updated;
|
|
575
829
|
if (checked) {
|
|
576
|
-
updated = [...
|
|
830
|
+
updated = [...currentValue, itemValue];
|
|
577
831
|
}
|
|
578
832
|
else {
|
|
579
|
-
updated =
|
|
833
|
+
updated = currentValue.filter((v) => v !== itemValue);
|
|
834
|
+
}
|
|
835
|
+
if (!isControlled) {
|
|
836
|
+
setInternalValue(updated);
|
|
580
837
|
}
|
|
581
838
|
onChange === null || onChange === void 0 ? void 0 : onChange(updated, e);
|
|
582
839
|
};
|
|
583
|
-
|
|
840
|
+
const layoutClass = columns
|
|
841
|
+
? columnClasses[columns]
|
|
842
|
+
: inline
|
|
843
|
+
? 'flex flex-wrap gap-3'
|
|
844
|
+
: 'flex flex-col gap-3';
|
|
845
|
+
return (jsxs("div", { className: buildClassName('mb-3 space-y-3', containerClass), children: [label && (jsxs("div", { className: buildClassName('flex items-center justify-between', labelWrapperClass), children: [jsx(Label, { className: labelClass, children: label }), labelHint && (jsx(BodyText, { muted: true, small: true, children: labelHint }))] })), showSelectAll && items.length > 0 && (jsx("div", { className: "pb-1 border-b border-gray-200 dark:border-gray-800", children: jsx(Checkbox, { id: `${groupId}-select-all`, label: selectAllLabel, size: size, variant: variant, checked: allSelected, indeterminate: isIndeterminate, disabled: disabled || enabledItems.length === 0, onChange: handleSelectAllChange }) })), jsx("div", { className: layoutClass, children: items.map((item, index) => {
|
|
584
846
|
const key = typeof item.value === 'string' || typeof item.value === 'number' ? item.value : index;
|
|
585
|
-
return (jsx(Checkbox, { name: name, id: `${groupId}-${String(item.value)}`, label: item.label, value: String(item.value), checked:
|
|
586
|
-
}) })] }));
|
|
847
|
+
return (jsx(Checkbox, { name: name, id: `${groupId}-${String(item.value)}`, label: item.label, description: item.description, value: String(item.value), size: size, variant: variant, disabled: disabled || item.disabled, checked: currentValue.includes(item.value), onChange: handleGroupChange }, key));
|
|
848
|
+
}) }), (error || helperText) && (jsx("div", { className: "mt-1.5 ml-0.5", children: error ? (jsx(TextContent, { error: true, small: true, children: error })) : (jsx(TextContent, { muted: true, small: true, children: helperText })) }))] }));
|
|
587
849
|
};
|
|
588
850
|
CheckboxGroup.displayName = 'CheckboxGroup';
|
|
589
851
|
|
|
@@ -1010,24 +1272,142 @@ function DataTable({ items, columns, sorting = {}, onSort, loading, pagination,
|
|
|
1010
1272
|
}
|
|
1011
1273
|
DataTable.displayName = 'DataTable';
|
|
1012
1274
|
|
|
1013
|
-
const
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1275
|
+
const defaultLevelSizeMap = {
|
|
1276
|
+
1: 'text-3xl font-bold',
|
|
1277
|
+
2: 'text-2xl font-semibold',
|
|
1278
|
+
3: 'text-xl font-semibold',
|
|
1279
|
+
4: 'text-lg font-medium',
|
|
1280
|
+
5: 'text-base font-medium',
|
|
1281
|
+
6: 'text-sm font-medium',
|
|
1282
|
+
};
|
|
1283
|
+
const sizeClassMap = {
|
|
1284
|
+
xs: 'text-xs',
|
|
1285
|
+
sm: 'text-sm',
|
|
1286
|
+
md: 'text-base',
|
|
1287
|
+
lg: 'text-lg',
|
|
1288
|
+
xl: 'text-xl',
|
|
1289
|
+
'2xl': 'text-2xl',
|
|
1290
|
+
'3xl': 'text-3xl',
|
|
1291
|
+
'4xl': 'text-4xl',
|
|
1292
|
+
'5xl': 'text-5xl',
|
|
1293
|
+
};
|
|
1294
|
+
const weightClassMap = {
|
|
1295
|
+
normal: 'font-normal',
|
|
1296
|
+
medium: 'font-medium',
|
|
1297
|
+
semibold: 'font-semibold',
|
|
1298
|
+
bold: 'font-bold',
|
|
1299
|
+
extrabold: 'font-extrabold',
|
|
1300
|
+
};
|
|
1301
|
+
const alignClassMap = {
|
|
1302
|
+
left: 'text-left',
|
|
1303
|
+
center: 'text-center',
|
|
1304
|
+
right: 'text-right',
|
|
1305
|
+
};
|
|
1306
|
+
const trackingClassMap = {
|
|
1307
|
+
tighter: 'tracking-tighter',
|
|
1308
|
+
tight: 'tracking-tight',
|
|
1309
|
+
normal: 'tracking-normal',
|
|
1310
|
+
wide: 'tracking-wide',
|
|
1311
|
+
};
|
|
1312
|
+
const HeadingTextComponent = forwardRef((props, ref) => {
|
|
1313
|
+
const { level = 1, size, weight, muted = false, error = false, success = false, warning = false, info = false, invert = false, align, tracking, truncate = false, className, children, ...rest } = props;
|
|
1314
|
+
const tag = `h${Math.min(6, Math.max(1, level))}`;
|
|
1315
|
+
const colorClass = error
|
|
1316
|
+
? 'text-[var(--ui-text-danger)]'
|
|
1317
|
+
: success
|
|
1318
|
+
? 'text-[var(--ui-success)]'
|
|
1319
|
+
: warning
|
|
1320
|
+
? 'text-[var(--ui-warning)]'
|
|
1321
|
+
: info
|
|
1322
|
+
? 'text-[var(--ui-info)]'
|
|
1323
|
+
: muted
|
|
1324
|
+
? invert
|
|
1325
|
+
? 'text-gray-300 dark:text-gray-600'
|
|
1326
|
+
: 'text-[var(--ui-text-muted)]'
|
|
1327
|
+
: invert
|
|
1328
|
+
? 'text-white dark:text-black'
|
|
1329
|
+
: 'text-[var(--ui-text)]';
|
|
1330
|
+
return React__default.createElement(tag, {
|
|
1331
|
+
ref,
|
|
1332
|
+
className: buildClassName(colorClass, size ? sizeClassMap[size] : defaultLevelSizeMap[level], weight && weightClassMap[weight], align && alignClassMap[align], tracking && trackingClassMap[tracking], truncate && 'truncate', className),
|
|
1333
|
+
...rest,
|
|
1334
|
+
children,
|
|
1335
|
+
});
|
|
1336
|
+
});
|
|
1337
|
+
HeadingTextComponent.displayName = 'HeadingText';
|
|
1338
|
+
const Title = forwardRef((props, ref) => (jsx(HeadingTextComponent, { ref: ref, level: 1, ...props })));
|
|
1339
|
+
Title.displayName = 'HeadingText.Title';
|
|
1340
|
+
const SubTitle = forwardRef((props, ref) => (jsx(HeadingTextComponent, { ref: ref, level: 2, ...props })));
|
|
1341
|
+
SubTitle.displayName = 'HeadingText.SubTitle';
|
|
1342
|
+
const SubTitle2 = forwardRef((props, ref) => (jsx(HeadingTextComponent, { ref: ref, level: 3, ...props })));
|
|
1343
|
+
SubTitle2.displayName = 'HeadingText.SubTitle2';
|
|
1344
|
+
const SubTitle3 = forwardRef((props, ref) => (jsx(HeadingTextComponent, { ref: ref, level: 4, ...props })));
|
|
1345
|
+
SubTitle3.displayName = 'HeadingText.SubTitle3';
|
|
1346
|
+
const SubTitle4 = forwardRef((props, ref) => (jsx(HeadingTextComponent, { ref: ref, level: 5, ...props })));
|
|
1347
|
+
SubTitle4.displayName = 'HeadingText.SubTitle4';
|
|
1348
|
+
const HeadingText = Object.assign(HeadingTextComponent, {
|
|
1019
1349
|
Title,
|
|
1020
1350
|
SubTitle,
|
|
1021
1351
|
SubTitle2,
|
|
1022
1352
|
SubTitle3,
|
|
1023
1353
|
SubTitle4,
|
|
1024
|
-
};
|
|
1354
|
+
});
|
|
1025
1355
|
|
|
1356
|
+
const COLUMN_GRID_CLASS = {
|
|
1357
|
+
1: 'grid-cols-1',
|
|
1358
|
+
2: 'grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-3',
|
|
1359
|
+
3: 'grid grid-cols-1 md:grid-cols-3 gap-x-6 gap-y-3',
|
|
1360
|
+
4: 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-x-6 gap-y-3',
|
|
1361
|
+
};
|
|
1362
|
+
function CopyableValue({ text, children }) {
|
|
1363
|
+
const [copied, setCopied] = useState(false);
|
|
1364
|
+
const handleCopy = async (e) => {
|
|
1365
|
+
e.stopPropagation();
|
|
1366
|
+
try {
|
|
1367
|
+
await navigator.clipboard.writeText(text);
|
|
1368
|
+
setCopied(true);
|
|
1369
|
+
setTimeout(() => setCopied(false), 2000);
|
|
1370
|
+
}
|
|
1371
|
+
catch {
|
|
1372
|
+
// Ignore clipboard error
|
|
1373
|
+
}
|
|
1374
|
+
};
|
|
1375
|
+
return (jsxs("span", { className: "inline-flex items-center gap-1.5 group/copy", children: [jsx("span", { children: children }), jsx("button", { type: "button", onClick: handleCopy, className: "opacity-0 group-hover/copy:opacity-100 focus:opacity-100 transition-opacity p-0.5 text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 cursor-pointer", title: "Copy to clipboard", "aria-label": "Copy to clipboard", children: copied ? (jsx("svg", { className: "size-3.5 text-emerald-500", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" }) })) : (jsx("svg", { className: "size-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" }) })) })] }));
|
|
1376
|
+
}
|
|
1026
1377
|
const DetailedInformation = forwardRef((props, ref) => {
|
|
1027
|
-
const { className, title, subTitle, details, compact = false, divider = true, titleClass, detailsClass,
|
|
1028
|
-
const
|
|
1378
|
+
const { className, title, subTitle, action, details = [], columns = 1, layout = 'horizontal', variant = 'flat', compact = false, bordered = false, divider = true, emptyValue = '- -', titleClass, detailsClass, labelClass, ...divProps } = props;
|
|
1379
|
+
const isCard = variant === 'card';
|
|
1380
|
+
const isBordered = bordered || variant === 'bordered' || isCard;
|
|
1381
|
+
const hasTitles = title || subTitle || action;
|
|
1029
1382
|
const filteredDetails = details.filter((detail) => !detail.hidden);
|
|
1030
|
-
|
|
1383
|
+
const isMultiColumn = columns > 1;
|
|
1384
|
+
return (jsxs("div", { ref: ref, className: buildClassName(isCard
|
|
1385
|
+
? compact
|
|
1386
|
+
? 'p-3 sm:p-4 bg-gray-50/60 dark:bg-gray-800/40 rounded-2xl border border-gray-200 dark:border-gray-700'
|
|
1387
|
+
: 'p-4 sm:p-6 bg-gray-50/60 dark:bg-gray-800/40 rounded-2xl border border-gray-200 dark:border-gray-700'
|
|
1388
|
+
: isBordered
|
|
1389
|
+
? compact
|
|
1390
|
+
? 'p-3 sm:p-4 border border-[var(--ui-border)] rounded-2xl'
|
|
1391
|
+
: 'p-4 sm:p-6 border border-[var(--ui-border)] rounded-2xl'
|
|
1392
|
+
: compact
|
|
1393
|
+
? 'p-2 md:p-3'
|
|
1394
|
+
: 'p-3 md:p-4', className), ...divProps, children: [hasTitles && (jsxs("div", { className: "mb-4 flex items-start justify-between gap-4", children: [jsxs("div", { children: [title && (jsx(HeadingText.SubTitle2, { className: buildClassName(titleClass), children: title })), subTitle && (jsx("p", { className: "mt-1 max-w-2xl text-sm/6 text-gray-500 dark:text-gray-400 font-normal", children: subTitle }))] }), action && jsx("div", { className: "shrink-0 flex items-center", children: action })] })), filteredDetails.length ? (jsx("div", { className: buildClassName('border-[var(--ui-border-muted)]', hasTitles && !isMultiColumn && 'border-t pt-2', detailsClass), children: jsx("dl", { className: buildClassName(COLUMN_GRID_CLASS[columns] || COLUMN_GRID_CLASS[1], !isMultiColumn && divider && 'divide-y divide-[var(--ui-border-muted)]'), children: filteredDetails.map((detail, index) => {
|
|
1395
|
+
var _a;
|
|
1396
|
+
const itemFallback = (_a = detail.emptyValue) !== null && _a !== void 0 ? _a : emptyValue;
|
|
1397
|
+
const hasValue = detail.value !== undefined && detail.value !== null && detail.value !== '';
|
|
1398
|
+
const displayValue = hasValue ? detail.value : itemFallback;
|
|
1399
|
+
const copyText = typeof detail.value === 'string' || typeof detail.value === 'number'
|
|
1400
|
+
? String(detail.value)
|
|
1401
|
+
: '';
|
|
1402
|
+
const isVertical = layout === 'vertical' || isMultiColumn;
|
|
1403
|
+
return (jsxs("div", { className: buildClassName(isMultiColumn && isCard
|
|
1404
|
+
? compact
|
|
1405
|
+
? 'p-3 bg-white dark:bg-gray-900 rounded-xl border border-gray-200/80 dark:border-gray-700/80 shadow-2xs flex flex-col gap-1'
|
|
1406
|
+
: 'p-4 bg-white dark:bg-gray-900 rounded-xl border border-gray-200/80 dark:border-gray-700/80 shadow-2xs flex flex-col gap-1'
|
|
1407
|
+
: isVertical
|
|
1408
|
+
? 'flex flex-col gap-1 py-2 px-1'
|
|
1409
|
+
: 'grid grid-cols-3 gap-4 items-center py-2.5 px-1', compact && !isMultiColumn ? 'py-1.5' : ''), children: [jsxs("dt", { className: buildClassName('text-sm/6 font-medium text-gray-900 dark:text-gray-100 flex items-center gap-1.5', labelClass, detail.labelClass), children: [detail.icon && jsx("span", { className: "shrink-0 text-gray-400", children: detail.icon }), jsx("span", { children: detail.label }), detail.helpText && (jsx("span", { className: "text-xs text-gray-400 font-normal cursor-help", title: detail.helpText, children: "(?)" }))] }), jsx("dd", { className: buildClassName('text-sm/6 text-gray-700 dark:text-gray-300', !isVertical && 'sm:col-span-2 sm:mt-0', detail.contentClass), children: detail.copyable && copyText ? (jsx(CopyableValue, { text: copyText, children: displayValue })) : (displayValue) })] }, detail.label || index));
|
|
1410
|
+
}) }) })) : null] }));
|
|
1031
1411
|
});
|
|
1032
1412
|
DetailedInformation.displayName = 'DetailedInformation';
|
|
1033
1413
|
|
|
@@ -1040,15 +1420,15 @@ const alignmentMap = {
|
|
|
1040
1420
|
center: 'items-center justify-center h-full',
|
|
1041
1421
|
start: 'justify-start h-full',
|
|
1042
1422
|
end: 'justify-end h-full',
|
|
1043
|
-
top: 'items-
|
|
1423
|
+
top: 'items-start justify-center w-full',
|
|
1044
1424
|
bottom: 'items-end justify-center h-full',
|
|
1045
1425
|
};
|
|
1046
1426
|
const panelAlignmentMap = {
|
|
1047
|
-
center: 'data-[closed]:scale-95 rounded-
|
|
1048
|
-
start: 'data-[closed]:-translate-x-1/2 rounded-r-
|
|
1049
|
-
end: 'data-[closed]:translate-x-1/2 rounded-l-
|
|
1050
|
-
top: 'data-[closed]:-translate-y-1/2 rounded-b-
|
|
1051
|
-
bottom: 'data-[closed]:translate-y-1/2 rounded-t-
|
|
1427
|
+
center: 'data-[closed]:scale-95 rounded-2xl',
|
|
1428
|
+
start: 'data-[closed]:-translate-x-1/2 rounded-r-2xl',
|
|
1429
|
+
end: 'data-[closed]:translate-x-1/2 rounded-l-2xl',
|
|
1430
|
+
top: 'data-[closed]:-translate-y-1/2 rounded-b-2xl',
|
|
1431
|
+
bottom: 'data-[closed]:translate-y-1/2 rounded-t-2xl md:rounded-t-3xl max-h-[92svh]',
|
|
1052
1432
|
};
|
|
1053
1433
|
const panelSizeMap = {
|
|
1054
1434
|
xs: 'max-w-xs',
|
|
@@ -1059,15 +1439,56 @@ const panelSizeMap = {
|
|
|
1059
1439
|
'2xl': 'max-w-2xl',
|
|
1060
1440
|
full: 'w-full',
|
|
1061
1441
|
};
|
|
1062
|
-
const Drawer = ({ isOpen, onClose = () => { }, align = 'end', size = 'md', backdrop = true, title, description, children, panelClass = '', contentClass = '', titleClass = '', descriptionClass = '', titleSticky = false, showBackButton = false, }) => {
|
|
1063
|
-
const showTitleSection = Boolean(title || description);
|
|
1064
|
-
|
|
1442
|
+
const Drawer = ({ isOpen, onClose = () => { }, closeOnOutsideClick = true, align = 'end', size = 'md', backdrop = true, title, description, children, footer, footerSticky = false, panelClass = '', contentClass = '', titleClass = '', descriptionClass = '', titleSticky = false, showBackButton = false, showCloseButton = true, dragHandle, }) => {
|
|
1443
|
+
const showTitleSection = Boolean(title || description || showBackButton || showCloseButton);
|
|
1444
|
+
const shouldShowDragHandle = dragHandle !== null && dragHandle !== void 0 ? dragHandle : align === 'bottom';
|
|
1445
|
+
const handleDialogClose = () => {
|
|
1446
|
+
if (closeOnOutsideClick) {
|
|
1447
|
+
onClose === null || onClose === void 0 ? void 0 : onClose();
|
|
1448
|
+
}
|
|
1449
|
+
};
|
|
1450
|
+
return (jsxs(Dialog, { open: isOpen, as: "div", className: "relative z-50 focus:outline-none", onClose: handleDialogClose, transition: true, children: [backdrop && (jsx(DialogBackdrop, { className: "fixed inset-0 bg-black/40 backdrop-blur-xs transition-opacity duration-200" })), jsx("div", { className: "fixed inset-0 w-screen h-[100svh]", children: jsx("div", { className: buildClassName('flex', alignmentMap[align]), children: jsxs(DialogPanel, { transition: true, className: buildClassName('w-full max-w-full bg-white dark:bg-gray-800 shadow-xl flex flex-col min-w-0', 'overflow-y-auto overflow-x-hidden duration-200 ease-out data-[closed]:opacity-0', panelAlignmentMap[align], panelSizeMap[size], panelClass), children: [shouldShowDragHandle && (jsx("div", { className: "w-12 h-1.5 rounded-full bg-gray-300 dark:bg-gray-600 mx-auto my-2.5 shrink-0 cursor-grab", "data-testid": "drawer-drag-handle" })), showTitleSection && (jsxs("div", { className: buildClassName('space-y-1 py-3 px-4 md:px-6 flex flex-col border-b border-gray-100 dark:border-gray-700/60 shrink-0', titleSticky && 'sticky top-0 bg-white dark:bg-gray-800 z-10'), children: [jsxs("div", { className: "flex items-center justify-between gap-3 min-w-0", children: [jsxs("div", { className: "flex items-center gap-2 min-w-0 flex-1", children: [showBackButton && (jsx(Button, { iconOnly: true, "aria-label": "Back", variant: "plain", theme: "secondary", size: "xs", onClick: onClose, children: jsx(SvgChevronLeft, { className: "size-4" }) })), title && (jsx(DialogTitle, { as: "h3", className: buildClassName('text-lg font-semibold text-gray-900 dark:text-white truncate tracking-tight min-w-0', titleClass), children: title }))] }), showCloseButton && (jsx(Button, { iconOnly: true, "aria-label": "Close drawer", variant: "plain", theme: "secondary", size: "xs", onClick: onClose, className: "rounded-full text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 shrink-0", children: jsx(SvgX, { className: "size-4" }) }))] }), description && (jsx("p", { className: buildClassName('text-sm text-gray-500 dark:text-gray-400 font-normal truncate', descriptionClass), children: description }))] })), jsx("div", { className: buildClassName('flex-1 p-4 md:p-6 space-y-4 min-w-0', contentClass), children: children }), footer && (jsx("div", { className: buildClassName('p-4 border-t border-gray-100 dark:border-gray-700/60 bg-gray-50/50 dark:bg-gray-800/80 shrink-0 min-w-0', footerSticky && 'sticky bottom-0 z-10'), children: footer }))] }) }) })] }));
|
|
1065
1451
|
};
|
|
1066
1452
|
Drawer.displayName = 'Drawer';
|
|
1067
1453
|
|
|
1068
|
-
const
|
|
1069
|
-
|
|
1070
|
-
|
|
1454
|
+
const WIDTH_CLASS = {
|
|
1455
|
+
auto: 'w-auto min-w-[120px]',
|
|
1456
|
+
sm: 'w-44',
|
|
1457
|
+
md: 'w-56',
|
|
1458
|
+
lg: 'w-64',
|
|
1459
|
+
xl: 'w-72',
|
|
1460
|
+
};
|
|
1461
|
+
const Dropdown = ({ triggerButton, renderTriggerButton, className, items, renderItem, itemsContainerClass, transition = true, anchor = 'bottom start', width = 'md', menuItemClass, onMenuClick, ...restProps }) => {
|
|
1462
|
+
var _a, _b, _c, _d;
|
|
1463
|
+
const widthClass = WIDTH_CLASS[width] || width || WIDTH_CLASS.md;
|
|
1464
|
+
const { theme, variant, size, disabled } = restProps;
|
|
1465
|
+
return (jsxs(Menu, { as: "div", className: "relative inline-block", children: [renderTriggerButton ? (jsx(MenuButton, { as: Fragment$1, children: (state) => {
|
|
1466
|
+
const rendered = renderTriggerButton(state);
|
|
1467
|
+
if (isValidElement(rendered) && className) {
|
|
1468
|
+
return cloneElement(rendered, {
|
|
1469
|
+
className: buildClassName(rendered.props.className, className),
|
|
1470
|
+
});
|
|
1471
|
+
}
|
|
1472
|
+
return rendered;
|
|
1473
|
+
} })) : isValidElement(triggerButton) ? (jsx(MenuButton, { as: Fragment$1, children: cloneElement(triggerButton, {
|
|
1474
|
+
className: buildClassName(triggerButton.props.className, className),
|
|
1475
|
+
theme: (_a = triggerButton.props.theme) !== null && _a !== void 0 ? _a : theme,
|
|
1476
|
+
variant: (_b = triggerButton.props.variant) !== null && _b !== void 0 ? _b : variant,
|
|
1477
|
+
size: (_c = triggerButton.props.size) !== null && _c !== void 0 ? _c : size,
|
|
1478
|
+
disabled: (_d = triggerButton.props.disabled) !== null && _d !== void 0 ? _d : disabled,
|
|
1479
|
+
}) })) : (jsx(MenuButton, { as: Fragment$1, children: triggerButton })), jsx(MenuItems, { transition: transition, anchor: anchor, className: buildClassName('origin-top-right rounded-xl shadow-lg p-1.5 text-sm transition duration-100 ease-out z-50', 'border border-gray-200/80 dark:border-gray-700/80 bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-200', 'focus:outline-none data-[closed]:scale-95 data-[closed]:opacity-0', '[--anchor-gap:--spacing(1)]', widthClass, itemsContainerClass), "data-testid": "dropdown-items", as: "ul", children: items.map((item, idx) => (jsxs(Fragment$1, { children: [item.groupTitle && (jsx("li", { className: "px-2.5 pt-2 pb-1 text-[11px] font-semibold text-gray-400 dark:text-gray-500 uppercase tracking-wider select-none", "data-testid": `dropdown-group-${item.id}`, children: item.groupTitle }, `group-${item.id}`)), jsx(MenuItem, { as: "li", disabled: item.disabled, children: ({ disabled, active }) => (jsx("div", { onClick: () => {
|
|
1480
|
+
var _a;
|
|
1481
|
+
if (!disabled) {
|
|
1482
|
+
(_a = item.onClick) === null || _a === void 0 ? void 0 : _a.call(item, item);
|
|
1483
|
+
onMenuClick === null || onMenuClick === void 0 ? void 0 : onMenuClick(item, idx);
|
|
1484
|
+
}
|
|
1485
|
+
}, className: buildClassName('flex items-center justify-between gap-3 w-full px-2.5 py-1.5 rounded-lg text-sm transition-colors cursor-pointer select-none', item.danger
|
|
1486
|
+
? buildClassName('text-rose-600 dark:text-rose-400', active
|
|
1487
|
+
? 'bg-rose-50 dark:bg-rose-950/40'
|
|
1488
|
+
: 'hover:bg-rose-50 dark:hover:bg-rose-950/40')
|
|
1489
|
+
: buildClassName('text-gray-700 dark:text-gray-200', active
|
|
1490
|
+
? 'bg-gray-100 dark:bg-gray-700/80'
|
|
1491
|
+
: 'hover:bg-gray-100/80 dark:hover:bg-gray-700/80'), disabled && 'opacity-40 cursor-not-allowed pointer-events-none', menuItemClass), "data-testid": `dropdown-item-${item.id}`, children: renderItem ? (renderItem(item)) : (jsxs(Fragment, { children: [jsxs("div", { className: "flex items-center gap-2.5 min-w-0 flex-1", children: [item.icon && (jsx("span", { className: buildClassName('shrink-0', item.danger ? 'text-rose-500' : 'text-gray-400 dark:text-gray-500'), children: item.icon })), jsxs("div", { className: "flex flex-col min-w-0", children: [item.label && jsx("span", { className: "font-medium truncate", children: item.label }), item.description && (jsx("span", { className: "text-xs text-gray-400 dark:text-gray-500 font-normal truncate", children: item.description }))] })] }), item.shortcut && (jsx("kbd", { className: "shrink-0 text-[10px] font-medium font-mono px-1.5 py-0.5 rounded bg-gray-100 dark:bg-gray-700/80 border border-gray-200/60 dark:border-gray-600/60 text-gray-400 dark:text-gray-400", children: item.shortcut }))] })) })) }), item.divider && idx < items.length - 1 && (jsx("li", { className: "my-1 h-px bg-gray-200/80 dark:bg-gray-700/80", "data-testid": "dropdown-divider" }, `divider-${item.id}`))] }, item.id))) })] }));
|
|
1071
1492
|
};
|
|
1072
1493
|
Dropdown.displayName = 'Dropdown';
|
|
1073
1494
|
|
|
@@ -1094,90 +1515,294 @@ const EmptyState = ({ title, description, className, action = null, }) => {
|
|
|
1094
1515
|
return (jsx("div", { className: buildClassName('w-full flex justify-center px-4 pt-16', className), children: jsxs("div", { className: "max-w-md flex flex-col items-center gap-4", children: [jsx(SvgFolderPlus, { className: "size-8 text-[var(--ui-text)]" }), jsx(HeadingText.SubTitle, { children: title }), description && (jsx(BodyText, { as: "p", className: "text-center", muted: true, small: true, children: description })), action] }) }));
|
|
1095
1516
|
};
|
|
1096
1517
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1518
|
+
const sizeClasses$4 = {
|
|
1519
|
+
sm: {
|
|
1520
|
+
input: 'px-2.5 py-1 text-xs leading-5',
|
|
1521
|
+
group: 'w-8 text-xs',
|
|
1522
|
+
icon: 'size-3.5',
|
|
1523
|
+
text: 'text-xs',
|
|
1524
|
+
},
|
|
1525
|
+
md: {
|
|
1526
|
+
input: 'px-3.5 py-2 text-sm leading-6',
|
|
1527
|
+
group: 'w-10 text-sm',
|
|
1528
|
+
icon: 'size-4',
|
|
1529
|
+
text: 'text-sm',
|
|
1530
|
+
},
|
|
1531
|
+
lg: {
|
|
1532
|
+
input: 'px-4 py-2.5 text-base leading-7',
|
|
1533
|
+
group: 'w-12 text-base',
|
|
1534
|
+
icon: 'size-5',
|
|
1535
|
+
text: 'text-base',
|
|
1536
|
+
},
|
|
1537
|
+
};
|
|
1538
|
+
function buildInputClass(className, { isFocusWithin = false, disabled = false, hasError = false, size = 'md', } = {}) {
|
|
1539
|
+
const currentSize = sizeClasses$4[size] || sizeClasses$4.md;
|
|
1540
|
+
return buildClassName('transition-all duration-150', 'block w-full rounded-lg bg-white', currentSize.input, 'text-gray-900 outline outline-1 -outline-offset-1', hasError
|
|
1541
|
+
? '!outline-red-500 !ring-1 !ring-red-500/50 dark:!outline-red-500'
|
|
1542
|
+
: 'outline-gray-300 dark:outline-gray-600', disabled
|
|
1099
1543
|
? 'pointer-events-none bg-gray-100 dark:bg-gray-800 dark:text-gray-600 dark:placeholder-gray-600 placeholder-gray-400 outline-gray-200 dark:outline-gray-700'
|
|
1100
1544
|
: 'bg-white dark:bg-gray-700 dark:text-white outline-gray-300 dark:outline-gray-600 dark:placeholder-gray-400 placeholder-gray-400', isFocusWithin
|
|
1101
1545
|
? 'focus-within:outline-[var(--ui-focus-ring)] focus-within:outline-2 focus-within:-outline-offset-2'
|
|
1102
1546
|
: 'focus:outline-[var(--ui-focus-ring)] focus:outline-2 focus:-outline-offset-2', className);
|
|
1103
1547
|
}
|
|
1104
1548
|
const Input = forwardRef((props, ref) => {
|
|
1105
|
-
const { name, id = name, type, label, labelHint, className, labelClass, labelWrapperClass, containerClass, error, showErrorMessage = true, rightGroup, leftGroup, ...inputProps } = props;
|
|
1549
|
+
const { name, id = name, type, label, labelHint, className, labelClass, labelWrapperClass, containerClass, error, showErrorMessage = true, rightGroup, leftGroup, prefix, suffix, clearable = false, onClear, showCount = false, helperText, size = 'md', value, defaultValue, onChange, maxLength, ...inputProps } = props;
|
|
1550
|
+
const innerRef = useRef(null);
|
|
1551
|
+
useImperativeHandle(ref, () => innerRef.current);
|
|
1552
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(String(defaultValue !== null && defaultValue !== void 0 ? defaultValue : ''));
|
|
1553
|
+
const isControlled = value !== undefined;
|
|
1554
|
+
const currentVal = isControlled ? String(value !== null && value !== void 0 ? value : '') : uncontrolledValue;
|
|
1555
|
+
const charLength = currentVal.length;
|
|
1556
|
+
const handleInputChange = (e) => {
|
|
1557
|
+
if (!isControlled) {
|
|
1558
|
+
setUncontrolledValue(e.target.value);
|
|
1559
|
+
}
|
|
1560
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(e);
|
|
1561
|
+
};
|
|
1562
|
+
const handleClear = () => {
|
|
1563
|
+
if (innerRef.current) {
|
|
1564
|
+
innerRef.current.value = '';
|
|
1565
|
+
const event = new Event('input', {
|
|
1566
|
+
bubbles: true,
|
|
1567
|
+
});
|
|
1568
|
+
Object.defineProperty(event, 'target', { writable: true, value: { value: '' } });
|
|
1569
|
+
if (!isControlled) {
|
|
1570
|
+
setUncontrolledValue('');
|
|
1571
|
+
}
|
|
1572
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(event);
|
|
1573
|
+
}
|
|
1574
|
+
onClear === null || onClear === void 0 ? void 0 : onClear();
|
|
1575
|
+
};
|
|
1106
1576
|
const hasError = Boolean(error);
|
|
1107
|
-
const
|
|
1577
|
+
const currentSize = sizeClasses$4[size] || sizeClasses$4.md;
|
|
1578
|
+
const showClearButton = clearable && charLength > 0 && !inputProps.disabled;
|
|
1579
|
+
const actualRightGroup = rightGroup !== null && rightGroup !== void 0 ? rightGroup : (showClearButton ? (jsx("button", { type: "button", onClick: handleClear, className: "p-1 rounded-full text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors cursor-pointer", title: "Clear text", tabIndex: -1, children: jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", className: currentSize.icon, children: jsx("path", { d: "M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" }) }) })) : null);
|
|
1580
|
+
const hasRightGroup = actualRightGroup !== null && actualRightGroup !== undefined;
|
|
1108
1581
|
const hasLeftGroup = leftGroup != null && leftGroup !== undefined;
|
|
1109
|
-
return (jsxs("div", { className: buildClassName('group', hasError && 'has-error', containerClass), children: [label && (jsxs("div", { className: buildClassName('flex items-center justify-between mb-
|
|
1582
|
+
return (jsxs("div", { className: buildClassName('group', hasError && 'has-error', containerClass), children: [label && (jsxs("div", { className: buildClassName('flex items-center justify-between mb-1.5', labelWrapperClass), children: [jsx(Label, { className: labelClass, htmlFor: id, children: label }), labelHint && jsx(TextContent, { xsmall: true, children: labelHint })] })), jsxs("div", { className: "flex rounded-lg shadow-2xs", children: [prefix && (jsx("span", { className: "inline-flex items-center px-3 rounded-l-lg border border-r-0 border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-500 dark:text-gray-400 text-xs font-medium shrink-0", children: prefix })), jsxs("div", { className: "relative flex-1 min-w-0", children: [hasLeftGroup && (jsx("span", { className: buildClassName('absolute top-0 h-full flex items-center justify-center text-gray-400 dark:text-gray-500', currentSize.group), children: leftGroup })), jsx("input", { ref: innerRef, autoComplete: "off", ...inputProps, value: value, defaultValue: defaultValue, onChange: handleInputChange, maxLength: maxLength, name: name, id: id, type: type, className: buildClassName(buildInputClass(className, {
|
|
1583
|
+
disabled: inputProps.disabled,
|
|
1584
|
+
hasError,
|
|
1585
|
+
size,
|
|
1586
|
+
}), prefix ? '!rounded-l-none' : '', suffix ? '!rounded-r-none' : '', hasRightGroup ? (size === 'lg' ? 'pe-12' : size === 'sm' ? 'pe-8' : 'pe-10') : '', hasLeftGroup ? (size === 'lg' ? 'ps-12' : size === 'sm' ? 'ps-8' : 'ps-10') : '') }), hasRightGroup && (jsx("span", { className: buildClassName('absolute end-0 top-0 h-full flex items-center justify-center text-gray-400 dark:text-gray-500', currentSize.group), children: actualRightGroup }))] }), suffix && (jsx("span", { className: "inline-flex items-center px-3 rounded-r-lg border border-l-0 border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 text-gray-500 dark:text-gray-400 text-xs font-medium shrink-0", children: suffix }))] }), (showErrorMessage && error) || helperText || showCount ? (jsxs("div", { className: "mt-1.5 flex items-center justify-between gap-2 text-xs", children: [jsx("div", { className: "min-w-0 flex-1", children: showErrorMessage && error ? (jsx(TextContent, { error: true, small: true, children: error })) : helperText ? (jsx(TextContent, { muted: true, small: true, children: helperText })) : null }), showCount && (jsxs("span", { className: buildClassName('text-[11px] font-medium shrink-0 ml-auto', maxLength && charLength >= maxLength
|
|
1587
|
+
? 'text-red-500 font-semibold'
|
|
1588
|
+
: 'text-gray-400 dark:text-gray-500'), children: [charLength, maxLength ? ` / ${maxLength}` : ''] }))] })) : null] }));
|
|
1110
1589
|
});
|
|
1111
1590
|
Input.displayName = 'Input';
|
|
1112
1591
|
|
|
1113
1592
|
const PASSWORD_TYPE = 'password';
|
|
1114
1593
|
const PASSWORD_PLACEHOLDER = '••••••••••';
|
|
1115
|
-
|
|
1116
|
-
|
|
1594
|
+
function evaluatePasswordStrength(val) {
|
|
1595
|
+
if (!val)
|
|
1596
|
+
return { score: 0, label: 'Weak', colorClass: 'bg-gray-200 dark:bg-gray-700', percent: 0 };
|
|
1597
|
+
let score = 0;
|
|
1598
|
+
if (val.length >= 8)
|
|
1599
|
+
score += 1;
|
|
1600
|
+
if (val.length >= 12)
|
|
1601
|
+
score += 1;
|
|
1602
|
+
if (/[0-9]/.test(val))
|
|
1603
|
+
score += 1;
|
|
1604
|
+
if (/[a-z]/.test(val) && /[A-Z]/.test(val))
|
|
1605
|
+
score += 1;
|
|
1606
|
+
if (/[^A-Za-z0-9]/.test(val))
|
|
1607
|
+
score += 1;
|
|
1608
|
+
if (score <= 1) {
|
|
1609
|
+
return { score: 1, label: 'Weak', colorClass: 'bg-red-500', percent: 25 };
|
|
1610
|
+
}
|
|
1611
|
+
if (score === 2) {
|
|
1612
|
+
return { score: 2, label: 'Fair', colorClass: 'bg-amber-500', percent: 50 };
|
|
1613
|
+
}
|
|
1614
|
+
if (score === 3) {
|
|
1615
|
+
return { score: 3, label: 'Good', colorClass: 'bg-blue-500', percent: 75 };
|
|
1616
|
+
}
|
|
1617
|
+
return { score: 4, label: 'Strong', colorClass: 'bg-emerald-500', percent: 100 };
|
|
1618
|
+
}
|
|
1619
|
+
const PasswordInput = forwardRef((props, ref) => {
|
|
1620
|
+
const { showStrength = false, placeholder = PASSWORD_PLACEHOLDER, onChange, value, defaultValue, ...inputProps } = props;
|
|
1117
1621
|
const [showPassword, setShowPassword] = useState(false);
|
|
1118
|
-
|
|
1119
|
-
|
|
1622
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(String(defaultValue !== null && defaultValue !== void 0 ? defaultValue : ''));
|
|
1623
|
+
const isControlled = value !== undefined;
|
|
1624
|
+
const currentVal = isControlled ? String(value !== null && value !== void 0 ? value : '') : uncontrolledValue;
|
|
1625
|
+
const handleInputChange = (e) => {
|
|
1626
|
+
if (!isControlled) {
|
|
1627
|
+
setUncontrolledValue(e.target.value);
|
|
1628
|
+
}
|
|
1629
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(e);
|
|
1630
|
+
};
|
|
1631
|
+
const strength = evaluatePasswordStrength(currentVal);
|
|
1632
|
+
return (jsxs("div", { className: "space-y-1.5", children: [jsx(Input, { ref: ref, ...inputProps, value: value, defaultValue: defaultValue, onChange: handleInputChange, type: showPassword ? 'text' : PASSWORD_TYPE, placeholder: placeholder, rightGroup: jsx(Button, { theme: "secondary", variant: "plain", size: "xs", className: "px-1.5 mr-0.5 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200", type: "button", onClick: () => setShowPassword((prev) => !prev), tabIndex: -1, "aria-label": showPassword ? 'Hide password' : 'Show password', children: showPassword ? (jsx("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", className: "size-4", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88" }) })) : (jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", className: "size-4", children: [jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" }), jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" })] })) }) }), showStrength && currentVal.length > 0 && (jsxs("div", { className: "pt-0.5 space-y-1", children: [jsxs("div", { className: "flex items-center justify-between text-[11px] font-medium text-gray-500 dark:text-gray-400", children: [jsx("span", { children: "Password Strength" }), jsx("span", { className: "font-semibold text-gray-700 dark:text-gray-200", children: strength.label })] }), jsx("div", { className: "h-1.5 w-full bg-gray-200 dark:bg-gray-800 rounded-full overflow-hidden flex gap-1 p-0.5", children: jsx("div", { className: `h-full transition-all duration-300 rounded-full ${strength.colorClass}`, style: { width: `${strength.percent}%` } }) })] }))] }));
|
|
1633
|
+
});
|
|
1634
|
+
PasswordInput.displayName = 'PasswordInput';
|
|
1120
1635
|
|
|
1636
|
+
const SIZE_MAP = {
|
|
1637
|
+
sm: 'size-4',
|
|
1638
|
+
md: 'size-4.5',
|
|
1639
|
+
lg: 'size-5.5',
|
|
1640
|
+
};
|
|
1641
|
+
const THEME_MAP = {
|
|
1642
|
+
primary: 'checked:border-[var(--ui-primary)] checked:hover:border-[var(--ui-primary)] focus:ring-[var(--ui-primary-ring)]',
|
|
1643
|
+
success: 'checked:border-emerald-600 checked:hover:border-emerald-600 focus:ring-emerald-500',
|
|
1644
|
+
danger: 'checked:border-[var(--ui-danger)] checked:hover:border-[var(--ui-danger)] focus:ring-[var(--ui-danger-ring)]',
|
|
1645
|
+
};
|
|
1121
1646
|
const Radio = forwardRef((props, ref) => {
|
|
1122
|
-
|
|
1123
|
-
const { id, name, label, labelClass = '', wrapperClass = '', containerClass = '', error, disabled = false, className = '',
|
|
1124
|
-
// RadioButton-only props removed from rest
|
|
1125
|
-
...rest } = props;
|
|
1647
|
+
const { id, name, label, description, size = 'md', theme = 'primary', labelClass = '', descriptionClass = '', wrapperClass = '', containerClass = '', error, disabled = false, className = '', ...rest } = props;
|
|
1126
1648
|
const generatedId = useId();
|
|
1127
|
-
const radioId =
|
|
1128
|
-
return (jsxs("div", { className: buildClassName(wrapperClass), children: [jsxs("div", { className: buildClassName('flex items-
|
|
1649
|
+
const radioId = id !== null && id !== void 0 ? id : (name && rest.value ? `${name}-${rest.value}` : generatedId);
|
|
1650
|
+
return (jsxs("div", { className: buildClassName(wrapperClass), children: [jsxs("div", { className: buildClassName('flex items-start gap-2.5', containerClass), children: [jsx("div", { className: "grid place-items-center shrink-0 pt-0.5", children: jsx("input", { ref: ref, ...rest, id: radioId, name: name, type: "radio", disabled: disabled, "aria-checked": rest.checked, className: buildClassName('peer transition-all col-start-1 row-start-1 appearance-none shrink-0 rounded-full', 'border focus:ring-2 focus:ring-offset-2', 'checked:border-5 checked:bg-white', 'dark:focus:ring-offset-gray-800', SIZE_MAP[size], THEME_MAP[theme], disabled
|
|
1129
1651
|
? 'border-gray-200 dark:border-gray-700 bg-gray-100 dark:bg-gray-800 pointer-events-none'
|
|
1130
|
-
:
|
|
1131
|
-
|
|
1132
|
-
|
|
1652
|
+
: 'bg-white dark:bg-gray-700 border-gray-300 dark:border-gray-600 hover:border-gray-400 dark:hover:border-gray-500 cursor-pointer', className) }) }), (label || description) && (jsxs("div", { className: "flex flex-col gap-0.5 min-w-0", children: [label && (jsx(Label, { htmlFor: radioId, className: buildClassName(disabled
|
|
1653
|
+
? 'cursor-not-allowed text-gray-400 dark:text-gray-500'
|
|
1654
|
+
: 'cursor-pointer text-gray-700 dark:text-gray-200 font-medium', labelClass), children: label })), description && (jsx("p", { className: buildClassName('text-xs leading-normal', disabled
|
|
1655
|
+
? 'text-gray-300 dark:text-gray-600'
|
|
1656
|
+
: 'text-gray-500 dark:text-gray-400', descriptionClass), children: description }))] }))] }), error && (jsx("div", { className: "mt-1", children: jsx(TextContent, { error: true, small: true, children: error }) }))] }));
|
|
1133
1657
|
});
|
|
1658
|
+
Radio.displayName = 'Radio';
|
|
1134
1659
|
|
|
1660
|
+
const COLUMN_GRID_MAP = {
|
|
1661
|
+
1: 'grid-cols-1',
|
|
1662
|
+
2: 'grid-cols-1 md:grid-cols-2',
|
|
1663
|
+
3: 'grid-cols-1 md:grid-cols-3',
|
|
1664
|
+
4: 'grid-cols-1 md:grid-cols-4',
|
|
1665
|
+
};
|
|
1135
1666
|
const RadioGroup = (props) => {
|
|
1136
|
-
const { name, label, labelClass, labelWrapperClass, labelHint, containerClass, options, value, row, error, showErrorMessage, onChange, } = props;
|
|
1667
|
+
const { name, variant = 'default', size = 'md', theme = 'primary', label, labelClass, labelWrapperClass, labelHint, containerClass, options, value, columns, disabled = false, row, error, showErrorMessage = true, onChange, } = props;
|
|
1137
1668
|
const items = useMemo(() => {
|
|
1138
1669
|
var _a;
|
|
1139
|
-
return (_a = options === null || options === void 0 ? void 0 : options.map((option) => typeof option === 'string'
|
|
1670
|
+
return (_a = options === null || options === void 0 ? void 0 : options.map((option) => typeof option === 'string'
|
|
1671
|
+
? {
|
|
1672
|
+
label: option,
|
|
1673
|
+
value: option,
|
|
1674
|
+
description: undefined,
|
|
1675
|
+
icon: undefined,
|
|
1676
|
+
disabled: undefined,
|
|
1677
|
+
}
|
|
1678
|
+
: option)) !== null && _a !== void 0 ? _a : [];
|
|
1140
1679
|
}, [options]);
|
|
1141
|
-
|
|
1680
|
+
const isCards = variant === 'cards';
|
|
1681
|
+
return (jsxs("div", { className: buildClassName(containerClass), children: [label && (jsxs("div", { className: buildClassName('flex items-center justify-between mb-3', labelWrapperClass), children: [jsx(Label, { className: labelClass, children: label }), labelHint] })), jsx("div", { className: buildClassName(columns
|
|
1682
|
+
? buildClassName('grid gap-3', COLUMN_GRID_MAP[columns])
|
|
1683
|
+
: row || isCards
|
|
1684
|
+
? 'flex flex-wrap gap-3'
|
|
1685
|
+
: 'flex flex-col gap-3'), children: items.map((item) => {
|
|
1686
|
+
const isSelected = value === item.value;
|
|
1687
|
+
const isItemDisabled = disabled || Boolean(item.disabled);
|
|
1688
|
+
if (isCards) {
|
|
1689
|
+
return (jsxs("label", { htmlFor: `${name}-${item.value}`, className: buildClassName('relative flex items-start gap-3 p-4 rounded-xl border transition-all cursor-pointer select-none min-w-[200px] flex-1', isSelected
|
|
1690
|
+
? 'border-[var(--ui-primary)] bg-blue-50/40 dark:bg-blue-950/30 ring-2 ring-[var(--ui-primary-ring)]/30'
|
|
1691
|
+
: 'border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 hover:border-gray-300 dark:hover:border-gray-600', isItemDisabled &&
|
|
1692
|
+
'opacity-50 cursor-not-allowed pointer-events-none bg-gray-50 dark:bg-gray-800/50'), children: [jsx(Radio, { name: name, id: `${name}-${item.value}`, 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));
|
|
1693
|
+
}
|
|
1694
|
+
return (jsx(Radio, { name: name, id: `${name}-${item.value}`, label: item.label, description: item.description, value: item.value, size: size, theme: theme, checked: isSelected, disabled: isItemDisabled, onChange: onChange }, item.value));
|
|
1695
|
+
}) }), showErrorMessage && error && (jsx("div", { className: "mt-1.5", children: jsx(TextContent, { error: true, small: true, children: error }) }))] }));
|
|
1142
1696
|
};
|
|
1697
|
+
RadioGroup.displayName = 'RadioGroup';
|
|
1143
1698
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1699
|
+
const SIZE_CONTAINER_CLASS$1 = {
|
|
1700
|
+
sm: 'p-1.5 gap-1 rounded-lg text-xs',
|
|
1701
|
+
md: 'p-2 gap-1.5 rounded-xl text-sm',
|
|
1702
|
+
lg: 'p-2 gap-2 rounded-2xl text-base',
|
|
1703
|
+
};
|
|
1704
|
+
const SIZE_ITEM_CLASS = {
|
|
1705
|
+
sm: 'px-4 py-2 text-xs rounded-md min-w-[64px]',
|
|
1706
|
+
md: 'px-5 py-2.5 text-sm rounded-lg min-w-[76px]',
|
|
1707
|
+
lg: 'px-7 py-3 text-base rounded-xl min-w-[92px]',
|
|
1708
|
+
};
|
|
1709
|
+
const SIZE_ITEM_DESC_CLASS = {
|
|
1710
|
+
sm: 'px-4 py-2.5 text-xs rounded-md min-w-[80px]',
|
|
1711
|
+
md: 'px-5 py-3 text-sm rounded-lg min-w-[96px]',
|
|
1712
|
+
lg: 'px-7 py-3.5 text-base rounded-xl min-w-[112px]',
|
|
1713
|
+
};
|
|
1714
|
+
const THEME_SWITCH_CLASS = {
|
|
1715
|
+
primary: 'bg-[var(--ui-primary)] text-white shadow-xs',
|
|
1716
|
+
secondary: 'bg-white dark:bg-gray-700 text-gray-900 dark:text-white shadow-xs',
|
|
1717
|
+
dark: 'bg-gray-900 dark:bg-black text-white shadow-xs',
|
|
1718
|
+
};
|
|
1719
|
+
const RadioSwitch = ({ label, items, selected, size = 'md', theme = 'primary', disabled = false, onChange, wrapperClass, switchClass, contentClass, descriptionClass, }) => {
|
|
1149
1720
|
const containerRef = useRef(null);
|
|
1150
|
-
const [style, setStyle] = useState({ left: 0, top: 0, width: 0, height: 0 });
|
|
1721
|
+
const [style, setStyle] = useState({ left: 0, top: 0, width: 0, height: 0, ready: false });
|
|
1151
1722
|
const getLabel = (i) => (typeof i === 'string' ? i : i.label);
|
|
1152
1723
|
const getValue = (i) => (typeof i === 'string' ? i : i.value);
|
|
1153
1724
|
const getDescription = (i) => { var _a; return typeof i === 'string' ? '' : ((_a = i.description) !== null && _a !== void 0 ? _a : ''); };
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1725
|
+
const isItemDisabled = (i) => disabled || (typeof i === 'string' ? false : Boolean(i.disabled));
|
|
1726
|
+
const hasDescription = items.some((i) => typeof i !== 'string' && Boolean(i.description));
|
|
1727
|
+
// Position calculation for GPU sliding highlight
|
|
1728
|
+
useLayoutEffect(() => {
|
|
1158
1729
|
if (!containerRef.current)
|
|
1159
1730
|
return;
|
|
1160
|
-
const
|
|
1161
|
-
|
|
1162
|
-
|
|
1731
|
+
const updatePosition = () => {
|
|
1732
|
+
if (!containerRef.current)
|
|
1733
|
+
return;
|
|
1734
|
+
const activeEl = containerRef.current.querySelector('[data-selected="true"]');
|
|
1735
|
+
if (!activeEl)
|
|
1736
|
+
return;
|
|
1737
|
+
const parentRect = containerRef.current.getBoundingClientRect();
|
|
1738
|
+
const rect = activeEl.getBoundingClientRect();
|
|
1739
|
+
const left = activeEl.offsetLeft || rect.left - parentRect.left;
|
|
1740
|
+
const top = activeEl.offsetTop || rect.top - parentRect.top;
|
|
1741
|
+
const width = activeEl.offsetWidth || rect.width;
|
|
1742
|
+
const height = activeEl.offsetHeight || rect.height;
|
|
1743
|
+
setStyle({
|
|
1744
|
+
left,
|
|
1745
|
+
top,
|
|
1746
|
+
width,
|
|
1747
|
+
height,
|
|
1748
|
+
ready: true,
|
|
1749
|
+
});
|
|
1750
|
+
};
|
|
1751
|
+
updatePosition();
|
|
1752
|
+
const resizeObserver = new ResizeObserver(updatePosition);
|
|
1753
|
+
resizeObserver.observe(containerRef.current);
|
|
1754
|
+
return () => resizeObserver.disconnect();
|
|
1755
|
+
}, [selected, items, size]);
|
|
1756
|
+
// ARIA Keyboard navigation handler
|
|
1757
|
+
const handleGroupKeyDown = (e) => {
|
|
1758
|
+
const enabledItems = items.filter((it) => !isItemDisabled(it));
|
|
1759
|
+
if (enabledItems.length === 0)
|
|
1163
1760
|
return;
|
|
1164
|
-
const
|
|
1165
|
-
const
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1761
|
+
const currentVal = selected;
|
|
1762
|
+
const currentEnabledIndex = enabledItems.findIndex((it) => getValue(it) === currentVal);
|
|
1763
|
+
let nextItem = null;
|
|
1764
|
+
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
|
|
1765
|
+
e.preventDefault();
|
|
1766
|
+
const nextIdx = (currentEnabledIndex + 1) % enabledItems.length;
|
|
1767
|
+
nextItem = enabledItems[nextIdx];
|
|
1768
|
+
}
|
|
1769
|
+
else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
|
|
1770
|
+
e.preventDefault();
|
|
1771
|
+
const prevIdx = (currentEnabledIndex - 1 + enabledItems.length) % enabledItems.length;
|
|
1772
|
+
nextItem = enabledItems[prevIdx];
|
|
1773
|
+
}
|
|
1774
|
+
else if (e.key === 'Home') {
|
|
1775
|
+
e.preventDefault();
|
|
1776
|
+
nextItem = enabledItems[0];
|
|
1777
|
+
}
|
|
1778
|
+
else if (e.key === 'End') {
|
|
1779
|
+
e.preventDefault();
|
|
1780
|
+
nextItem = enabledItems[enabledItems.length - 1];
|
|
1781
|
+
}
|
|
1782
|
+
if (nextItem) {
|
|
1783
|
+
onChange(getValue(nextItem));
|
|
1784
|
+
}
|
|
1785
|
+
};
|
|
1786
|
+
return (jsxs("div", { ref: containerRef, role: "radiogroup", tabIndex: 0, "aria-label": label, onKeyDown: handleGroupKeyDown, className: buildClassName('relative inline-grid grid-flow-col auto-cols-fr items-stretch bg-gray-100 dark:bg-gray-800/80 border border-gray-200/80 dark:border-gray-700/80 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary-ring)]', SIZE_CONTAINER_CLASS$1[size], disabled && 'opacity-60 pointer-events-none', wrapperClass), children: [jsx("div", { className: buildClassName('absolute transition-all duration-200 ease-[cubic-bezier(.25,.8,.25,1)] pointer-events-none', THEME_SWITCH_CLASS[theme], size === 'sm' ? 'rounded-md' : 'rounded-lg', style.ready ? 'opacity-100' : 'opacity-0', switchClass), style: {
|
|
1176
1787
|
transform: `translate(${style.left}px, ${style.top}px)`,
|
|
1177
1788
|
width: style.width,
|
|
1178
1789
|
height: style.height,
|
|
1179
|
-
} }), items.map((item) =>
|
|
1790
|
+
} }), items.map((item) => {
|
|
1791
|
+
const itemVal = getValue(item);
|
|
1792
|
+
const isSelected = selected === itemVal;
|
|
1793
|
+
const disabledState = isItemDisabled(item);
|
|
1794
|
+
return (jsx("button", { type: "button", role: "radio", "aria-checked": isSelected, "aria-disabled": disabledState, tabIndex: isSelected && !disabledState ? 0 : -1, disabled: disabledState, "data-radio-item": true, "data-value": itemVal, "data-selected": isSelected ? 'true' : undefined, onClick: () => {
|
|
1795
|
+
if (!disabledState) {
|
|
1796
|
+
onChange(itemVal);
|
|
1797
|
+
}
|
|
1798
|
+
}, className: buildClassName('relative z-10 appearance-none m-0 select-none group transition-colors flex flex-col items-center justify-center w-full text-center font-semibold focus:outline-none', hasDescription ? SIZE_ITEM_DESC_CLASS[size] : SIZE_ITEM_CLASS[size], disabledState ? 'cursor-not-allowed opacity-50' : 'cursor-pointer', isSelected
|
|
1799
|
+
? theme === 'secondary'
|
|
1800
|
+
? 'text-gray-900 dark:text-white'
|
|
1801
|
+
: 'text-white'
|
|
1802
|
+
: buildClassName('text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white', !disabledState && 'hover:bg-black/[0.04] dark:hover:bg-white/[0.06]')), children: getDescription(item) ? (jsxs("div", { className: buildClassName('flex flex-col items-center justify-center gap-1', contentClass), children: [jsx("span", { className: "font-semibold leading-tight text-center truncate", children: getLabel(item) }), jsx("span", { className: buildClassName('text-[12px] leading-tight opacity-80 font-normal text-center truncate', descriptionClass), children: getDescription(item) })] })) : (jsx("span", { className: buildClassName('truncate leading-tight text-center', contentClass), children: getLabel(item) })) }, itemVal));
|
|
1803
|
+
})] }));
|
|
1180
1804
|
};
|
|
1805
|
+
RadioSwitch.displayName = 'RadioSwitch';
|
|
1181
1806
|
|
|
1182
1807
|
/**
|
|
1183
1808
|
* Checks if a value is an array.
|
|
@@ -1207,30 +1832,86 @@ function isEmpty(value) {
|
|
|
1207
1832
|
return false; // for numbers, booleans, functions, etc.
|
|
1208
1833
|
}
|
|
1209
1834
|
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1835
|
+
const sizeClasses$3 = {
|
|
1836
|
+
sm: {
|
|
1837
|
+
track: 'h-1.5',
|
|
1838
|
+
thumb: '[&::-webkit-slider-thumb]:h-3.5 [&::-webkit-slider-thumb]:w-3.5 [&::-moz-range-thumb]:h-3.5 [&::-moz-range-thumb]:w-3.5',
|
|
1839
|
+
text: 'text-xs',
|
|
1840
|
+
},
|
|
1841
|
+
md: {
|
|
1842
|
+
track: 'h-2',
|
|
1843
|
+
thumb: '[&::-webkit-slider-thumb]:h-4.5 [&::-webkit-slider-thumb]:w-4.5 [&::-moz-range-thumb]:h-4.5 [&::-moz-range-thumb]:w-4.5',
|
|
1844
|
+
text: 'text-sm',
|
|
1845
|
+
},
|
|
1846
|
+
lg: {
|
|
1847
|
+
track: 'h-3',
|
|
1848
|
+
thumb: '[&::-webkit-slider-thumb]:h-6 [&::-webkit-slider-thumb]:w-6 [&::-moz-range-thumb]:h-6 [&::-moz-range-thumb]:w-6',
|
|
1849
|
+
text: 'text-base',
|
|
1850
|
+
},
|
|
1851
|
+
};
|
|
1220
1852
|
const RangeInput = forwardRef((props, ref) => {
|
|
1221
|
-
const { label, labelClass = '', labelHint = null, containerClass = '', className = '', showValue = true, valueSuffix = '', error = null, showErrorMessage = true, id, min = 0, max = 100, value, ...inputProps } = props;
|
|
1853
|
+
const { label, labelClass = '', labelHint = null, containerClass = '', className = '', size = 'md', showValue = true, valueSuffix = '', showTooltip = false, marks, error = null, helperText, showErrorMessage = true, id, min = 0, max = 100, value, defaultValue, onChange, ...inputProps } = props;
|
|
1854
|
+
const [hovering, setHovering] = useState(false);
|
|
1855
|
+
const [internalValue, setInternalValue] = useState(Number(defaultValue !== null && defaultValue !== void 0 ? defaultValue : min));
|
|
1856
|
+
const isControlled = value !== undefined;
|
|
1857
|
+
const currentVal = Number(isControlled ? value : internalValue);
|
|
1858
|
+
const numMin = Number(min);
|
|
1859
|
+
const numMax = Number(max);
|
|
1860
|
+
const percent = numMax > numMin ? ((currentVal - numMin) / (numMax - numMin)) * 100 : 0;
|
|
1861
|
+
const currentSize = sizeClasses$3[size] || sizeClasses$3.md;
|
|
1222
1862
|
const hasError = !isEmpty(error);
|
|
1223
|
-
|
|
1224
|
-
|
|
1863
|
+
const handleSliderChange = (e) => {
|
|
1864
|
+
if (!isControlled) {
|
|
1865
|
+
setInternalValue(Number(e.target.value));
|
|
1866
|
+
}
|
|
1867
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(e);
|
|
1868
|
+
};
|
|
1869
|
+
// Parse custom marks if object provided
|
|
1870
|
+
const markEntries = typeof marks === 'object' && marks !== null
|
|
1871
|
+
? Object.entries(marks).map(([k, v]) => ({ value: Number(k), label: v }))
|
|
1872
|
+
: typeof marks === 'boolean' && marks
|
|
1873
|
+
? [
|
|
1874
|
+
{ value: numMin, label: String(numMin) },
|
|
1875
|
+
{ value: (numMin + numMax) / 2, label: String(Math.round((numMin + numMax) / 2)) },
|
|
1876
|
+
{ value: numMax, label: String(numMax) },
|
|
1877
|
+
]
|
|
1878
|
+
: null;
|
|
1879
|
+
return (jsxs("div", { className: buildClassName('group space-y-2', containerClass), children: [label && (jsxs("div", { className: "flex items-center justify-between mb-1", children: [jsx(Label, { htmlFor: id, className: labelClass, children: label }), labelHint] })), jsxs("div", { className: "relative flex items-center gap-3", children: [jsxs("div", { className: "relative flex-1 py-2", children: [showTooltip && hovering && (jsxs("div", { className: "absolute -top-7 transform -translate-x-1/2 px-2 py-0.5 rounded bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900 text-[11px] font-semibold shadow-md pointer-events-none transition-all z-20", style: { left: `${percent}%` }, children: [currentVal, valueSuffix, jsx("div", { className: "absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent border-t-gray-900 dark:border-t-gray-100" })] })), jsx("input", { ref: ref, type: "range", id: id, min: min, max: max, value: currentVal, onChange: handleSliderChange, onMouseEnter: () => setHovering(true), onMouseLeave: () => setHovering(false), ...inputProps, style: {
|
|
1880
|
+
background: `linear-gradient(to right, ${hasError ? '#ef4444' : '#2563eb'} 0%, ${hasError ? '#ef4444' : '#2563eb'} ${percent}%, ${document.documentElement.classList.contains('dark') ? '#374151' : '#e5e7eb'} ${percent}%, ${document.documentElement.classList.contains('dark') ? '#374151' : '#e5e7eb'} 100%)`,
|
|
1881
|
+
}, className: buildClassName('w-full rounded-lg appearance-none cursor-pointer transition-all', currentSize.track, currentSize.thumb, '[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full', '[&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:shadow-md', hasError
|
|
1882
|
+
? '[&::-webkit-slider-thumb]:border-red-600 [&::-moz-range-thumb]:border-red-600'
|
|
1883
|
+
: '[&::-webkit-slider-thumb]:border-blue-600 [&::-moz-range-thumb]:border-blue-600', '[&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-white [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:shadow-md', className) }), markEntries && (jsx("div", { className: "relative w-full h-4 mt-1", children: markEntries.map((m) => {
|
|
1884
|
+
const markPercent = ((m.value - numMin) / (numMax - numMin)) * 100;
|
|
1885
|
+
return (jsxs("div", { className: "absolute top-0 transform -translate-x-1/2 flex flex-col items-center", style: { left: `${markPercent}%` }, children: [jsx("span", { className: "w-1 h-1.5 bg-gray-300 dark:bg-gray-600 rounded-full" }), jsx("span", { className: "text-[10px] font-medium text-gray-400 dark:text-gray-500 mt-0.5", children: m.label })] }, m.value));
|
|
1886
|
+
}) }))] }), showValue && (jsxs("span", { className: buildClassName('font-medium text-gray-700 dark:text-gray-300 min-w-[3rem] text-right shrink-0', currentSize.text), children: [currentVal, valueSuffix] }))] }), (showErrorMessage && hasError) || helperText ? (jsx("div", { className: "mt-1", children: showErrorMessage && hasError ? (jsx(TextContent, { error: true, small: true, children: error })) : helperText ? (jsx(TextContent, { muted: true, small: true, children: helperText })) : null })) : null] }));
|
|
1225
1887
|
});
|
|
1226
|
-
|
|
1227
|
-
|
|
1888
|
+
RangeInput.displayName = 'RangeInput';
|
|
1889
|
+
|
|
1890
|
+
const sizeClasses$2 = {
|
|
1891
|
+
sm: {
|
|
1892
|
+
track: 'h-1.5',
|
|
1893
|
+
thumb: 'h-4 w-4',
|
|
1894
|
+
thumbPx: 16,
|
|
1895
|
+
text: 'text-xs',
|
|
1896
|
+
},
|
|
1897
|
+
md: {
|
|
1898
|
+
track: 'h-2',
|
|
1899
|
+
thumb: 'h-5 w-5',
|
|
1900
|
+
thumbPx: 20,
|
|
1901
|
+
text: 'text-sm',
|
|
1902
|
+
},
|
|
1903
|
+
lg: {
|
|
1904
|
+
track: 'h-3',
|
|
1905
|
+
thumb: 'h-6 w-6',
|
|
1906
|
+
thumbPx: 24,
|
|
1907
|
+
text: 'text-base',
|
|
1908
|
+
},
|
|
1909
|
+
};
|
|
1910
|
+
const RangeSlider = ({ label, labelHint, labelClass = '', containerClass = '', className = '', size = 'md', min, max, step = 1, valueMin, valueMax, onChange, valueSuffix = '', showTooltips = false, marks, error = null, helperText, showErrorMessage = true, }) => {
|
|
1228
1911
|
const trackRef = useRef(null);
|
|
1229
|
-
// Refs for drag logic
|
|
1230
1912
|
const activeThumbRef = useRef(null);
|
|
1231
|
-
// State for UI styling
|
|
1232
1913
|
const [activeThumbState, setActiveThumbState] = useState(null);
|
|
1233
|
-
|
|
1914
|
+
const [hoveringThumb, setHoveringThumb] = useState(null);
|
|
1234
1915
|
const valueMinRef = useRef(valueMin);
|
|
1235
1916
|
const valueMaxRef = useRef(valueMax);
|
|
1236
1917
|
const onChangeRef = useRef(onChange);
|
|
@@ -1238,20 +1919,28 @@ const RangeSlider = ({ label, labelHint, labelClass = '', containerClass = '', c
|
|
|
1238
1919
|
valueMaxRef.current = valueMax;
|
|
1239
1920
|
onChangeRef.current = onChange;
|
|
1240
1921
|
const hasError = !isEmpty(error);
|
|
1241
|
-
const
|
|
1242
|
-
|
|
1243
|
-
|
|
1922
|
+
const currentSize = sizeClasses$2[size] || sizeClasses$2.md;
|
|
1923
|
+
const clampValue = (val) => Math.min(Math.max(val, min), max);
|
|
1924
|
+
const snapToStep = (val) => {
|
|
1925
|
+
const clamped = clampValue(val);
|
|
1926
|
+
const steps = Math.round((clamped - min) / step);
|
|
1927
|
+
return Math.min(Math.max(min + steps * step, min), max);
|
|
1928
|
+
};
|
|
1929
|
+
const percent = (v) => ((clampValue(v) - min) / (max - min)) * 100;
|
|
1244
1930
|
const stablePointerMoveRef = useRef((e) => {
|
|
1245
1931
|
if (!trackRef.current || !activeThumbRef.current)
|
|
1246
1932
|
return;
|
|
1247
1933
|
const rect = trackRef.current.getBoundingClientRect();
|
|
1248
1934
|
const x = Math.min(Math.max(e.clientX - rect.left, 0), rect.width);
|
|
1249
1935
|
const rawVal = min + (x / rect.width) * (max - min);
|
|
1936
|
+
const snappedVal = snapToStep(rawVal);
|
|
1250
1937
|
if (activeThumbRef.current === 'min') {
|
|
1251
|
-
|
|
1938
|
+
const nextMin = Math.min(snappedVal, valueMaxRef.current - step);
|
|
1939
|
+
onChangeRef.current(nextMin, valueMaxRef.current);
|
|
1252
1940
|
}
|
|
1253
1941
|
else {
|
|
1254
|
-
|
|
1942
|
+
const nextMax = Math.max(snappedVal, valueMinRef.current + step);
|
|
1943
|
+
onChangeRef.current(valueMinRef.current, nextMax);
|
|
1255
1944
|
}
|
|
1256
1945
|
});
|
|
1257
1946
|
const stablePointerUpRef = useRef(() => {
|
|
@@ -1260,7 +1949,6 @@ const RangeSlider = ({ label, labelHint, labelClass = '', containerClass = '', c
|
|
|
1260
1949
|
window.removeEventListener('pointermove', stablePointerMoveRef.current);
|
|
1261
1950
|
window.removeEventListener('pointerup', stablePointerUpRef.current);
|
|
1262
1951
|
});
|
|
1263
|
-
// Remove listeners on unmount in case a drag is in progress
|
|
1264
1952
|
useEffect(() => {
|
|
1265
1953
|
const move = stablePointerMoveRef.current;
|
|
1266
1954
|
const up = stablePointerUpRef.current;
|
|
@@ -1269,59 +1957,91 @@ const RangeSlider = ({ label, labelHint, labelClass = '', containerClass = '', c
|
|
|
1269
1957
|
window.removeEventListener('pointerup', up);
|
|
1270
1958
|
};
|
|
1271
1959
|
}, []);
|
|
1272
|
-
// ---------------------------------------------
|
|
1273
|
-
// Pointer Move (drag)
|
|
1274
|
-
// ---------------------------------------------
|
|
1275
1960
|
const startDrag = (thumb) => (_e) => {
|
|
1276
1961
|
activeThumbRef.current = thumb;
|
|
1277
1962
|
setActiveThumbState(thumb);
|
|
1278
1963
|
window.addEventListener('pointermove', stablePointerMoveRef.current);
|
|
1279
1964
|
window.addEventListener('pointerup', stablePointerUpRef.current);
|
|
1280
1965
|
};
|
|
1281
|
-
// ---------------------------------------------
|
|
1282
|
-
// Click Track to move closest thumb
|
|
1283
|
-
// ---------------------------------------------
|
|
1284
1966
|
const handleTrackClick = (e) => {
|
|
1285
1967
|
if (!trackRef.current)
|
|
1286
1968
|
return;
|
|
1287
1969
|
const rect = trackRef.current.getBoundingClientRect();
|
|
1288
1970
|
const x = Math.min(Math.max(e.clientX - rect.left, 0), rect.width);
|
|
1289
1971
|
const rawVal = min + (x / rect.width) * (max - min);
|
|
1972
|
+
const snappedVal = snapToStep(rawVal);
|
|
1290
1973
|
const pxMin = (percent(valueMin) / 100) * rect.width;
|
|
1291
1974
|
const pxMax = (percent(valueMax) / 100) * rect.width;
|
|
1292
|
-
// If click is left of min thumb → move min thumb
|
|
1293
1975
|
if (x <= pxMin) {
|
|
1294
|
-
onChange(
|
|
1976
|
+
onChange(Math.min(snappedVal, valueMax - step), valueMax);
|
|
1295
1977
|
return;
|
|
1296
1978
|
}
|
|
1297
|
-
// If click is right of max thumb → move max thumb
|
|
1298
1979
|
if (x >= pxMax) {
|
|
1299
|
-
onChange(valueMin,
|
|
1980
|
+
onChange(valueMin, Math.max(snappedVal, valueMin + step));
|
|
1300
1981
|
return;
|
|
1301
1982
|
}
|
|
1302
|
-
|
|
1303
|
-
const
|
|
1304
|
-
const distMax = Math.abs(rawVal - valueMax);
|
|
1983
|
+
const distMin = Math.abs(snappedVal - valueMin);
|
|
1984
|
+
const distMax = Math.abs(snappedVal - valueMax);
|
|
1305
1985
|
if (distMin < distMax) {
|
|
1306
|
-
onChange(
|
|
1986
|
+
onChange(Math.min(snappedVal, valueMax - step), valueMax);
|
|
1307
1987
|
}
|
|
1308
1988
|
else {
|
|
1309
|
-
onChange(valueMin,
|
|
1989
|
+
onChange(valueMin, Math.max(snappedVal, valueMin + step));
|
|
1310
1990
|
}
|
|
1311
1991
|
};
|
|
1312
|
-
|
|
1992
|
+
// Keyboard accessibility handler for thumbs
|
|
1993
|
+
const handleKeyDown = (thumb) => (e) => {
|
|
1994
|
+
let delta = 0;
|
|
1995
|
+
if (e.key === 'ArrowLeft' || e.key === 'ArrowDown')
|
|
1996
|
+
delta = -step;
|
|
1997
|
+
else if (e.key === 'ArrowRight' || e.key === 'ArrowUp')
|
|
1998
|
+
delta = step;
|
|
1999
|
+
else if (e.key === 'Home') {
|
|
2000
|
+
if (thumb === 'min')
|
|
2001
|
+
onChange(min, valueMax);
|
|
2002
|
+
else
|
|
2003
|
+
onChange(valueMin, valueMin + step);
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
else if (e.key === 'End') {
|
|
2007
|
+
if (thumb === 'min')
|
|
2008
|
+
onChange(valueMax - step, valueMax);
|
|
2009
|
+
else
|
|
2010
|
+
onChange(valueMin, max);
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
if (delta !== 0) {
|
|
2014
|
+
e.preventDefault();
|
|
2015
|
+
if (thumb === 'min') {
|
|
2016
|
+
const next = snapToStep(valueMin + delta);
|
|
2017
|
+
onChange(Math.min(next, valueMax - step), valueMax);
|
|
2018
|
+
}
|
|
2019
|
+
else {
|
|
2020
|
+
const next = snapToStep(valueMax + delta);
|
|
2021
|
+
onChange(valueMin, Math.max(next, valueMin + step));
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
};
|
|
2025
|
+
const markEntries = marks
|
|
2026
|
+
? Object.entries(marks).map(([k, v]) => ({ value: Number(k), label: v }))
|
|
2027
|
+
: null;
|
|
2028
|
+
return (jsxs("div", { className: buildClassName('group space-y-2', hasError && 'has-error', containerClass), children: [label && (jsxs("div", { className: "flex items-center justify-between mb-1", children: [jsx(Label, { className: labelClass, children: label }), labelHint] })), jsxs("div", { ref: trackRef, onMouseDown: handleTrackClick, className: "relative h-8 select-none w-full flex items-center cursor-pointer", "data-testid": "range-track", children: [jsx("div", { className: buildClassName('absolute top-1/2 left-0 right-0 rounded-full bg-gray-200 dark:bg-gray-700 -translate-y-1/2', currentSize.track) }), jsx("div", { className: buildClassName('absolute top-1/2 -translate-y-1/2 rounded-full transition-all duration-150 ease-out', hasError ? 'bg-red-500' : 'bg-blue-600 dark:bg-blue-500', currentSize.track), style: {
|
|
1313
2029
|
left: `${percent(valueMin)}%`,
|
|
1314
2030
|
width: `${percent(valueMax) - percent(valueMin)}%`,
|
|
1315
|
-
} }), jsx("div", { onPointerDown: startDrag('min'), className: buildClassName('absolute top-1/2 -translate-y-1/2
|
|
1316
|
-
|
|
1317
|
-
left: `calc(${percent(valueMin)}% -
|
|
2031
|
+
} }), jsx("div", { role: "slider", tabIndex: 0, "aria-label": "Minimum value", "aria-valuenow": valueMin, "aria-valuemin": min, "aria-valuemax": valueMax - step, onPointerDown: startDrag('min'), onMouseEnter: () => setHoveringThumb('min'), onMouseLeave: () => setHoveringThumb(null), onKeyDown: handleKeyDown('min'), className: buildClassName('absolute top-1/2 -translate-y-1/2 rounded-full focus:outline-none', 'bg-white border-2 shadow-md cursor-pointer touch-none', hasError ? 'border-red-600' : 'border-blue-600', 'transition-all duration-150 ease-out', currentSize.thumb, (activeThumbState === 'min' || hoveringThumb === 'min') &&
|
|
2032
|
+
(hasError ? 'ring-4 ring-red-200' : 'ring-4 ring-blue-200'), className), style: {
|
|
2033
|
+
left: `calc(${percent(valueMin)}% - ${currentSize.thumbPx / 2}px)`,
|
|
1318
2034
|
zIndex: activeThumbState === 'min' ? 50 : 30,
|
|
1319
|
-
}, "data-testid": "thumb-min" }), jsx("div", { onPointerDown: startDrag('max'), className: buildClassName('absolute top-1/2 -translate-y-1/2
|
|
1320
|
-
|
|
1321
|
-
left: `calc(${percent(valueMax)}% -
|
|
2035
|
+
}, "data-testid": "thumb-min", children: showTooltips && (activeThumbState === 'min' || hoveringThumb === 'min') && (jsxs("div", { className: "absolute -top-7 left-1/2 -translate-x-1/2 px-2 py-0.5 rounded bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900 text-[11px] font-semibold shadow-md pointer-events-none z-50 whitespace-nowrap", children: [Math.round(valueMin), valueSuffix, jsx("div", { className: "absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent border-t-gray-900 dark:border-t-gray-100" })] })) }), jsx("div", { role: "slider", tabIndex: 0, "aria-label": "Maximum value", "aria-valuenow": valueMax, "aria-valuemin": valueMin + step, "aria-valuemax": max, onPointerDown: startDrag('max'), onMouseEnter: () => setHoveringThumb('max'), onMouseLeave: () => setHoveringThumb(null), onKeyDown: handleKeyDown('max'), className: buildClassName('absolute top-1/2 -translate-y-1/2 rounded-full focus:outline-none', 'bg-white border-2 shadow-md cursor-pointer touch-none', hasError ? 'border-red-600' : 'border-blue-600', 'transition-all duration-150 ease-out', currentSize.thumb, (activeThumbState === 'max' || hoveringThumb === 'max') &&
|
|
2036
|
+
(hasError ? 'ring-4 ring-red-200' : 'ring-4 ring-blue-200'), className), style: {
|
|
2037
|
+
left: `calc(${percent(valueMax)}% - ${currentSize.thumbPx / 2}px)`,
|
|
1322
2038
|
zIndex: activeThumbState === 'max' ? 50 : 40,
|
|
1323
|
-
}, "data-testid": "thumb-max"
|
|
2039
|
+
}, "data-testid": "thumb-max", children: showTooltips && (activeThumbState === 'max' || hoveringThumb === 'max') && (jsxs("div", { className: "absolute -top-7 left-1/2 -translate-x-1/2 px-2 py-0.5 rounded bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900 text-[11px] font-semibold shadow-md pointer-events-none z-50 whitespace-nowrap", children: [Math.round(valueMax), valueSuffix, jsx("div", { className: "absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent border-t-gray-900 dark:border-t-gray-100" })] })) })] }), markEntries && (jsx("div", { className: "relative w-full h-4 mt-1", children: markEntries.map((m) => {
|
|
2040
|
+
const markPercent = percent(m.value);
|
|
2041
|
+
return (jsxs("div", { className: "absolute top-0 transform -translate-x-1/2 flex flex-col items-center", style: { left: `${markPercent}%` }, children: [jsx("span", { className: "w-1 h-1.5 bg-gray-300 dark:bg-gray-600 rounded-full" }), jsx("span", { className: "text-[10px] font-medium text-gray-400 dark:text-gray-500 mt-0.5", children: m.label })] }, m.value));
|
|
2042
|
+
}) })), jsxs("div", { className: buildClassName('flex justify-between font-medium text-gray-700 dark:text-gray-300', currentSize.text), children: [jsxs("span", { children: [Math.round(valueMin), valueSuffix] }), jsxs("span", { children: [Math.round(valueMax), valueSuffix] })] }), (showErrorMessage && hasError) || helperText ? (jsx("div", { className: "mt-1", children: showErrorMessage && hasError ? (jsx(TextContent, { error: true, small: true, children: error })) : helperText ? (jsx(TextContent, { muted: true, small: true, children: helperText })) : null })) : null] }));
|
|
1324
2043
|
};
|
|
2044
|
+
RangeSlider.displayName = 'RangeSlider';
|
|
1325
2045
|
|
|
1326
2046
|
const useDebounce = (value, delay) => {
|
|
1327
2047
|
const [debouncedValue, setDebouncedValue] = useState(value);
|
|
@@ -1389,28 +2109,69 @@ var SvgCirclePlus = function SvgCirclePlus(props) {
|
|
|
1389
2109
|
})));
|
|
1390
2110
|
};
|
|
1391
2111
|
|
|
1392
|
-
const
|
|
2112
|
+
const sizeClasses$1 = {
|
|
2113
|
+
sm: {
|
|
2114
|
+
input: 'px-2.5 py-1 text-xs leading-5 min-h-[30px]',
|
|
2115
|
+
group: 'w-8 text-xs',
|
|
2116
|
+
icon: 'size-3.5',
|
|
2117
|
+
badgeSize: 'sm',
|
|
2118
|
+
},
|
|
2119
|
+
md: {
|
|
2120
|
+
input: 'px-3.5 py-2 text-sm leading-6 min-h-[38px]',
|
|
2121
|
+
group: 'w-10 text-sm',
|
|
2122
|
+
icon: 'size-4',
|
|
2123
|
+
badgeSize: 'md',
|
|
2124
|
+
},
|
|
2125
|
+
lg: {
|
|
2126
|
+
input: 'px-4 py-2.5 text-base leading-7 min-h-[46px]',
|
|
2127
|
+
group: 'w-12 text-base',
|
|
2128
|
+
icon: 'size-5',
|
|
2129
|
+
badgeSize: 'md',
|
|
2130
|
+
},
|
|
2131
|
+
};
|
|
1393
2132
|
const SelectBox = forwardRef((props, forwardedRef) => {
|
|
1394
|
-
const { labelKey = 'label', valueKey = 'value', name, id = name, options, selected, label = null, labelWrapperClass = '', labelClass = '', labelHint = null, disabled = false, containerClass = '', onChange = () => { }, placeholder, dropdownContainerClass = 'z-20', multiple = false, maxSelection, immediate = true, showErrorMessage = true, error = null, async = false, searchable = true, required = false, allowAdd = false, addNewText = 'Create', onAdd = () => { }, allowClear = false, modalDropdown = false, noOptionsText = 'No options available', } = props;
|
|
2133
|
+
const { labelKey = 'label', valueKey = 'value', name, id = name, options = [], groups, selected, size = 'md', label = null, labelWrapperClass = '', labelClass = '', labelHint = null, disabled = false, containerClass = '', onChange = () => { }, 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 = () => { }, allowClear = false, modalDropdown = false, noOptionsText = 'No options available', } = props;
|
|
1395
2134
|
const hasError = Boolean(error);
|
|
1396
2135
|
const [query, setQuery] = useState('');
|
|
1397
2136
|
const debouncedQuery = useDebounce(query, 500);
|
|
1398
2137
|
const [optionsLoading, setOptionsLoading] = useState(false);
|
|
1399
|
-
|
|
1400
|
-
const
|
|
1401
|
-
|
|
2138
|
+
// Normalize options from options prop or groups prop
|
|
2139
|
+
const allOptions = useMemo(() => {
|
|
2140
|
+
if (groups === null || groups === void 0 ? void 0 : groups.length) {
|
|
2141
|
+
return groups.flatMap((g) => g.options);
|
|
2142
|
+
}
|
|
2143
|
+
return options;
|
|
2144
|
+
}, [groups, options]);
|
|
2145
|
+
const shouldShowTypeToSearch = async && !query && !optionsLoading && allOptions.length === 0;
|
|
2146
|
+
const currentSize = sizeClasses$1[size] || sizeClasses$1.md;
|
|
2147
|
+
const baseInputClass = useMemo(() => buildInputClass('', { disabled, hasError, size }), [disabled, hasError, size]);
|
|
2148
|
+
const chevronIconClass = buildClassName(currentSize.icon, 'stroke-gray-500 group-data-[hover]:stroke-gray-800', 'dark:stroke-gray-300 dark:group-data-[hover]:stroke-gray-100', disabled && 'dark:stroke-gray-600 stroke-gray-400');
|
|
1402
2149
|
const inputRef = useRef(null);
|
|
1403
2150
|
const containerRef = useRef(null);
|
|
1404
|
-
// Expose the input element to parent
|
|
1405
2151
|
useImperativeHandle(forwardedRef, () => inputRef.current, [inputRef]);
|
|
1406
2152
|
const filteredOptions = useMemo(() => {
|
|
1407
2153
|
if (query === '' || async)
|
|
1408
|
-
return
|
|
1409
|
-
return
|
|
1410
|
-
const
|
|
1411
|
-
return
|
|
2154
|
+
return allOptions;
|
|
2155
|
+
return allOptions.filter((opt) => {
|
|
2156
|
+
const lbl = typeof opt === 'string' ? opt : opt[labelKey];
|
|
2157
|
+
return lbl === null || lbl === void 0 ? void 0 : lbl.toLowerCase().includes(query.toLowerCase());
|
|
1412
2158
|
});
|
|
1413
|
-
}, [
|
|
2159
|
+
}, [allOptions, query, async, labelKey]);
|
|
2160
|
+
const filteredGroups = useMemo(() => {
|
|
2161
|
+
if (!(groups === null || groups === void 0 ? void 0 : groups.length))
|
|
2162
|
+
return null;
|
|
2163
|
+
if (query === '' || async)
|
|
2164
|
+
return groups;
|
|
2165
|
+
return groups
|
|
2166
|
+
.map((g) => ({
|
|
2167
|
+
...g,
|
|
2168
|
+
options: g.options.filter((opt) => {
|
|
2169
|
+
const lbl = typeof opt === 'string' ? opt : opt[labelKey];
|
|
2170
|
+
return lbl === null || lbl === void 0 ? void 0 : lbl.toLowerCase().includes(query.toLowerCase());
|
|
2171
|
+
}),
|
|
2172
|
+
}))
|
|
2173
|
+
.filter((g) => g.options.length > 0);
|
|
2174
|
+
}, [groups, query, async, labelKey]);
|
|
1414
2175
|
const getDisplayValue = useCallback((opt) => {
|
|
1415
2176
|
return typeof opt === 'string' ? opt : opt[labelKey];
|
|
1416
2177
|
}, [labelKey]);
|
|
@@ -1426,69 +2187,48 @@ const SelectBox = forwardRef((props, forwardedRef) => {
|
|
|
1426
2187
|
onChange(next);
|
|
1427
2188
|
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
1428
2189
|
};
|
|
1429
|
-
// A strongly-typed internal handler (what you expect callers to receive)
|
|
1430
2190
|
const handleOnChangeInternal = useCallback((value) => {
|
|
1431
|
-
// normal empty value
|
|
1432
2191
|
if (value === null) {
|
|
1433
2192
|
onChange(multiple ? [] : null);
|
|
1434
2193
|
setQuery('');
|
|
1435
2194
|
return;
|
|
1436
2195
|
}
|
|
1437
|
-
// enforce maxSelection
|
|
1438
2196
|
if (multiple && Array.isArray(value) && maxSelection && value.length > maxSelection) {
|
|
1439
|
-
// ignore the change
|
|
1440
2197
|
return;
|
|
1441
2198
|
}
|
|
1442
2199
|
onChange(value);
|
|
1443
2200
|
setQuery('');
|
|
1444
2201
|
}, [onChange, multiple, maxSelection]);
|
|
1445
|
-
/**
|
|
1446
|
-
* Headless UI typings are overly broad (they include null[] and EnsureArray<T> etc).
|
|
1447
|
-
* We therefore pass a wrapper to the Combobox with a cast (only at the call site),
|
|
1448
|
-
* while keeping our internal logic strongly typed.
|
|
1449
|
-
*/
|
|
1450
2202
|
const comboboxOnChangeWrapper = useCallback((raw) => {
|
|
1451
|
-
// normalize actual runtime shapes Headless UI uses: T | T[] | null
|
|
1452
2203
|
if (raw === null || raw === undefined) {
|
|
1453
2204
|
handleOnChangeInternal(null);
|
|
1454
2205
|
return;
|
|
1455
2206
|
}
|
|
1456
2207
|
if (Array.isArray(raw)) {
|
|
1457
|
-
// sometimes union typing suggests arrays of nulls — guard it
|
|
1458
2208
|
const allNulls = raw.length > 0 && raw.every((v) => v === null);
|
|
1459
2209
|
if (allNulls) {
|
|
1460
2210
|
handleOnChangeInternal([]);
|
|
1461
2211
|
return;
|
|
1462
2212
|
}
|
|
1463
|
-
// cast array to T[]
|
|
1464
2213
|
handleOnChangeInternal(raw);
|
|
1465
2214
|
return;
|
|
1466
2215
|
}
|
|
1467
|
-
// single value
|
|
1468
2216
|
handleOnChangeInternal(raw);
|
|
1469
2217
|
}, [handleOnChangeInternal]);
|
|
1470
|
-
// support create (allowAdd) as a real ComboboxOption
|
|
1471
2218
|
const handleAddAndSelect = useCallback(async (valueToAdd) => {
|
|
1472
2219
|
try {
|
|
1473
2220
|
const result = await onAdd(valueToAdd);
|
|
1474
|
-
// if the parent returned a new option (string or object), select it
|
|
1475
2221
|
if (result) {
|
|
1476
|
-
// If parent returned raw string or object, call onChange with that
|
|
1477
2222
|
handleOnChangeInternal(result);
|
|
1478
2223
|
}
|
|
1479
2224
|
else {
|
|
1480
|
-
// if parent didn't return anything, select the raw string value
|
|
1481
2225
|
handleOnChangeInternal(valueToAdd);
|
|
1482
2226
|
}
|
|
1483
2227
|
}
|
|
1484
2228
|
catch (err) {
|
|
1485
|
-
// eslint-disable-next-line no-console
|
|
1486
2229
|
console.error('onAdd handler error', err);
|
|
1487
2230
|
}
|
|
1488
2231
|
}, [onAdd, handleOnChangeInternal]);
|
|
1489
|
-
// Kept in a ref so `handleSearch` stays referentially stable even when the
|
|
1490
|
-
// caller passes a non-memoized `onSearch` — otherwise the effect below would
|
|
1491
|
-
// re-fire (and re-search) on every render instead of only on query changes.
|
|
1492
2232
|
const onSearchRef = useRef(props.onSearch);
|
|
1493
2233
|
useEffect(() => {
|
|
1494
2234
|
onSearchRef.current = props.onSearch;
|
|
@@ -1510,16 +2250,232 @@ const SelectBox = forwardRef((props, forwardedRef) => {
|
|
|
1510
2250
|
if (async && debouncedQuery)
|
|
1511
2251
|
handleSearch(debouncedQuery);
|
|
1512
2252
|
}, [debouncedQuery, async, handleSearch]);
|
|
1513
|
-
|
|
2253
|
+
const isAllSelected = useMemo(() => {
|
|
2254
|
+
if (!multiple || !Array.isArray(selected) || !allOptions.length)
|
|
2255
|
+
return false;
|
|
2256
|
+
return allOptions.length === selected.length;
|
|
2257
|
+
}, [multiple, selected, allOptions]);
|
|
2258
|
+
const handleToggleSelectAll = () => {
|
|
2259
|
+
if (isAllSelected) {
|
|
2260
|
+
onChange([]);
|
|
2261
|
+
}
|
|
2262
|
+
else {
|
|
2263
|
+
onChange(allOptions);
|
|
2264
|
+
}
|
|
2265
|
+
};
|
|
2266
|
+
const hasLeftGroup = leftGroup != null && leftGroup !== undefined;
|
|
2267
|
+
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: selected, onChange: comboboxOnChangeWrapper, onClose: () => setQuery(''), disabled: disabled, 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(selected) &&
|
|
1514
2268
|
(selected === null || selected === void 0 ? void 0 : selected.length) > 0 &&
|
|
1515
|
-
selected.map((option, idx) => (jsx(Badge, { theme: "secondary", 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", "aria-label": name, value: query, autoComplete: "off", placeholder: (selected === null || selected === void 0 ? void 0 : selected.length) ? '' : placeholder, onChange: (e) => setQuery(e.target.value), id: id, ref: inputRef, onKeyDown: (e) => {
|
|
2269
|
+
selected.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: (selected === null || selected === void 0 ? void 0 : selected.length) ? '' : placeholder, onChange: (e) => setQuery(e.target.value), id: id, ref: inputRef, onKeyDown: (e) => {
|
|
1516
2270
|
if (multiple && e.key === 'Backspace' && !query) {
|
|
1517
2271
|
if (Array.isArray(selected) && selected.length > 0) {
|
|
1518
2272
|
removeSelection(selected.length - 1);
|
|
1519
2273
|
}
|
|
1520
2274
|
}
|
|
1521
|
-
}, "data-testid": "combobox-input" }), jsx(ComboboxButton, { className: "group absolute inset-y-0 right-0 px-2.5", "data-testid": "combobox-button", children: jsx(SvgChevronDown, { className: chevronIconClass }) })] })) : (jsxs("div", { className: "relative", ref: forwardedRef, children: [jsx(ComboboxInput, { "aria-label": name, displayValue: (option) => (option ? getDisplayValue(option) : null), autoComplete: "off", placeholder: placeholder, onChange: (event) => setQuery(event.target.value), className:
|
|
2275
|
+
}, "data-testid": "combobox-input" }), 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("div", { className: "relative", 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 z-10', currentSize.group), children: leftGroup })), jsx(ComboboxInput, { "aria-label": name, displayValue: (option) => (option ? getDisplayValue(option) : null), autoComplete: "off", placeholder: placeholder, onChange: (event) => setQuery(event.target.value), 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(selected) && (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: 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', gap: 5 }, as: "div", modal: modalDropdown, className: buildClassName('w-[var(--input-width)] rounded-lg border border-gray-200 dark:border-white/5 bg-white dark:bg-gray-800 p-1 [--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-gray-200 dark:border-gray-700/80 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(selected) ? selected.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: [filteredGroups
|
|
2276
|
+
? 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((option) => {
|
|
2277
|
+
const optVal = getValue(option);
|
|
2278
|
+
const isOptSelected = multiple
|
|
2279
|
+
? isArray(selected) && selected.some((s) => getValue(s) === optVal)
|
|
2280
|
+
: selected && getValue(selected) === optVal;
|
|
2281
|
+
return (jsx(ComboboxOption, { disabled: option.disabled, value: option, className: "group rounded-lg py-1.5 px-3 select-none data-[focus]:bg-black/3 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 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));
|
|
2282
|
+
})] }, gIdx)))
|
|
2283
|
+
: /* Render Standard Flat Options */
|
|
2284
|
+
filteredOptions.map((option) => {
|
|
2285
|
+
const optVal = getValue(option);
|
|
2286
|
+
const isOptSelected = multiple
|
|
2287
|
+
? isArray(selected) && selected.some((s) => getValue(s) === optVal)
|
|
2288
|
+
: selected && getValue(selected) === optVal;
|
|
2289
|
+
return (jsx(ComboboxOption, { disabled: option.disabled, value: option, className: "group rounded-lg py-1.5 px-3 select-none data-[focus]:bg-black/3 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 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));
|
|
2290
|
+
}), filteredOptions.length === 0 && !(filteredGroups === null || filteredGroups === void 0 ? void 0 : filteredGroups.length) && (jsx("div", { "data-testid": "no-result-found", className: "flex items-center gap-2 py-1.5 px-3 select-none text-gray-500 dark:text-gray-400", children: query ? (jsxs(Fragment, { children: ["No results found for ", jsx("span", { className: "font-semibold", children: query })] })) : (noOptionsText) })), allowAdd && (jsx(ComboboxOption, { value: query, className: "group rounded-lg py-1.5 px-3 cursor-pointer text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700", onClick: () => void handleAddAndSelect(query), children: jsxs("div", { className: "flex items-center gap-2", children: [jsx(SvgCirclePlus, { className: "size-4.5" }), jsxs(TextContent, { children: [addNewText, " \"", query, "\""] })] }) }))] }))] })] }), showErrorMessage && error && (jsx("div", { className: "mt-1", children: jsx(TextContent, { error: true, small: true, children: error }) })), !error && helperText && (jsx("div", { className: "mt-1", children: jsx(TextContent, { muted: true, small: true, children: helperText }) }))] }));
|
|
2291
|
+
});
|
|
2292
|
+
SelectBox.displayName = 'SelectBox';
|
|
2293
|
+
|
|
2294
|
+
const VARIANT_CLASS = {
|
|
2295
|
+
text: 'rounded-md h-4 w-full my-1',
|
|
2296
|
+
circular: 'rounded-full shrink-0',
|
|
2297
|
+
rectangular: 'rounded-none w-full h-24',
|
|
2298
|
+
rounded: 'rounded-xl w-full h-24',
|
|
2299
|
+
};
|
|
2300
|
+
const ANIMATION_CLASS = {
|
|
2301
|
+
pulse: 'animate-pulse bg-gray-200 dark:bg-gray-700',
|
|
2302
|
+
wave: 'bg-gradient-to-r from-gray-200 via-gray-100 to-gray-200 dark:from-gray-800 dark:via-gray-700 dark:to-gray-800 bg-[length:200%_100%] animate-[shimmer_1.5s_infinite]',
|
|
2303
|
+
none: 'bg-gray-200 dark:bg-gray-700',
|
|
2304
|
+
};
|
|
2305
|
+
const Skeleton = forwardRef((props, ref) => {
|
|
2306
|
+
const { as, variant = 'text', animation = 'pulse', width, height, borderRadius, className, style: customStyle, ...restProps } = props;
|
|
2307
|
+
const Component = as || 'div';
|
|
2308
|
+
const inlineStyles = {
|
|
2309
|
+
...(width !== undefined ? { width: typeof width === 'number' ? `${width}px` : width } : {}),
|
|
2310
|
+
...(height !== undefined
|
|
2311
|
+
? { height: typeof height === 'number' ? `${height}px` : height }
|
|
2312
|
+
: {}),
|
|
2313
|
+
...(borderRadius !== undefined
|
|
2314
|
+
? { borderRadius: typeof borderRadius === 'number' ? `${borderRadius}px` : borderRadius }
|
|
2315
|
+
: {}),
|
|
2316
|
+
...customStyle,
|
|
2317
|
+
};
|
|
2318
|
+
return (jsx(Component, { ref: ref, "aria-hidden": "true", style: inlineStyles, className: buildClassName('block select-none pointer-events-none', VARIANT_CLASS[variant], ANIMATION_CLASS[animation], className), "data-testid": "skeleton", ...restProps }));
|
|
2319
|
+
});
|
|
2320
|
+
Skeleton.displayName = 'Skeleton';
|
|
2321
|
+
|
|
2322
|
+
const MinusIcon = ({ className }) => (jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", className: className, "aria-hidden": "true", children: jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" }) }));
|
|
2323
|
+
const PlusIcon = ({ className }) => (jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", className: className, "aria-hidden": "true", children: [jsx("line", { x1: "12", y1: "5", x2: "12", y2: "19" }), jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" })] }));
|
|
2324
|
+
const BUTTON_SIZE_CLASS = {
|
|
2325
|
+
sm: 'size-7',
|
|
2326
|
+
md: 'size-9',
|
|
2327
|
+
lg: 'size-10',
|
|
2328
|
+
};
|
|
2329
|
+
const ICON_SIZE_CLASS = {
|
|
2330
|
+
sm: 'size-3.5',
|
|
2331
|
+
md: 'size-4',
|
|
2332
|
+
lg: 'size-4.5',
|
|
2333
|
+
};
|
|
2334
|
+
const INPUT_TEXT_CLASS = {
|
|
2335
|
+
sm: 'w-12 text-xs py-1',
|
|
2336
|
+
md: 'w-16 text-sm py-1.5',
|
|
2337
|
+
lg: 'w-20 text-base py-2',
|
|
2338
|
+
};
|
|
2339
|
+
const QuantityStepper = forwardRef((props, ref) => {
|
|
2340
|
+
const { value, onChange, min = 0, max = 99999, step = 1, label, size = 'md', prefix, suffix, disabled = false, className, onFocus, ...restProps } = props;
|
|
2341
|
+
const handleDecrement = () => {
|
|
2342
|
+
if (disabled)
|
|
2343
|
+
return;
|
|
2344
|
+
const next = Math.max(min, Number((value - step).toFixed(4)));
|
|
2345
|
+
onChange(next);
|
|
2346
|
+
};
|
|
2347
|
+
const handleIncrement = () => {
|
|
2348
|
+
if (disabled)
|
|
2349
|
+
return;
|
|
2350
|
+
const next = Math.min(max, Number((value + step).toFixed(4)));
|
|
2351
|
+
onChange(next);
|
|
2352
|
+
};
|
|
2353
|
+
const handleInputChange = (e) => {
|
|
2354
|
+
const raw = e.target.value;
|
|
2355
|
+
if (raw === '') {
|
|
2356
|
+
onChange(0);
|
|
2357
|
+
return;
|
|
2358
|
+
}
|
|
2359
|
+
const parsed = parseFloat(raw);
|
|
2360
|
+
if (!isNaN(parsed)) {
|
|
2361
|
+
onChange(Math.max(min, Math.min(max, parsed)));
|
|
2362
|
+
}
|
|
2363
|
+
};
|
|
2364
|
+
const isAtMin = disabled || value <= min;
|
|
2365
|
+
const isAtMax = disabled || value >= max;
|
|
2366
|
+
return (jsxs("div", { className: buildClassName('flex flex-col gap-1 inline-flex', className), children: [label && (jsx("label", { className: "block text-xs font-medium text-gray-700 dark:text-gray-300", children: label })), jsxs("div", { className: buildClassName('inline-flex items-center rounded-lg border border-gray-300 bg-white dark:border-gray-600 dark:bg-gray-800 shadow-xs focus-within:ring-2 focus-within:ring-[var(--ui-primary-ring)] focus-within:border-[var(--ui-primary)] transition-all', disabled ? 'opacity-50 cursor-not-allowed bg-gray-50 dark:bg-gray-900' : ''), "data-testid": "quantity-stepper", children: [jsx("button", { type: "button", disabled: isAtMin, onClick: handleDecrement, tabIndex: -1, "aria-label": "Decrease quantity", className: buildClassName('flex items-center justify-center rounded-l-md text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 transition-colors', BUTTON_SIZE_CLASS[size], isAtMin ? 'opacity-30 hover:bg-transparent cursor-not-allowed' : 'cursor-pointer'), "data-testid": "stepper-decrement", children: jsx(MinusIcon, { className: ICON_SIZE_CLASS[size] }) }), prefix && jsx("span", { className: "pl-1 text-gray-400 dark:text-gray-500", children: prefix }), jsx("input", { ref: ref, type: "number", min: min, max: max, step: step, value: value === 0 ? '' : value, placeholder: "0", disabled: disabled, onFocus: (e) => {
|
|
2367
|
+
e.target.select();
|
|
2368
|
+
onFocus === null || onFocus === void 0 ? void 0 : onFocus(e);
|
|
2369
|
+
}, onChange: handleInputChange, className: buildClassName('text-center font-semibold text-gray-900 dark:text-white bg-transparent focus:outline-none [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none', INPUT_TEXT_CLASS[size]), "data-testid": "stepper-input", ...restProps }), suffix && jsx("span", { className: "pr-1 text-xs text-gray-500 dark:text-gray-400", children: suffix }), jsx("button", { type: "button", disabled: isAtMax, onClick: handleIncrement, tabIndex: -1, "aria-label": "Increase quantity", className: buildClassName('flex items-center justify-center rounded-r-md text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-200 transition-colors', BUTTON_SIZE_CLASS[size], isAtMax ? 'opacity-30 hover:bg-transparent cursor-not-allowed' : 'cursor-pointer'), "data-testid": "stepper-increment", children: jsx(PlusIcon, { className: ICON_SIZE_CLASS[size] }) })] })] }));
|
|
2370
|
+
});
|
|
2371
|
+
QuantityStepper.displayName = 'QuantityStepper';
|
|
2372
|
+
|
|
2373
|
+
const SIZE_CONTAINER_CLASS = {
|
|
2374
|
+
sm: 'p-0.5 gap-0.5 rounded-lg text-xs',
|
|
2375
|
+
md: 'p-1 gap-1 rounded-xl text-sm',
|
|
2376
|
+
lg: 'p-1.5 gap-1.5 rounded-xl text-base',
|
|
2377
|
+
};
|
|
2378
|
+
const SIZE_OPTION_CLASS = {
|
|
2379
|
+
sm: 'px-2 py-1 text-xs font-medium rounded-md',
|
|
2380
|
+
md: 'px-3 py-1.5 text-sm font-semibold rounded-lg',
|
|
2381
|
+
lg: 'px-4 py-2 text-base font-semibold rounded-lg',
|
|
2382
|
+
};
|
|
2383
|
+
const SIZE_ICON_CLASS = {
|
|
2384
|
+
sm: 'size-3.5',
|
|
2385
|
+
md: 'size-4',
|
|
2386
|
+
lg: 'size-5',
|
|
2387
|
+
};
|
|
2388
|
+
const SegmentedControl = forwardRef((props, ref) => {
|
|
2389
|
+
const { options, value, onChange, disabled = false, size = 'md', fullWidth = false, className, ...restProps } = props;
|
|
2390
|
+
const handleKeyDown = (e, currentIndex) => {
|
|
2391
|
+
const enabledOptions = options.filter((opt) => !opt.disabled && !disabled);
|
|
2392
|
+
if (enabledOptions.length === 0)
|
|
2393
|
+
return;
|
|
2394
|
+
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') {
|
|
2395
|
+
e.preventDefault();
|
|
2396
|
+
const currentPos = enabledOptions.findIndex((opt) => opt.value === options[currentIndex].value);
|
|
2397
|
+
const nextPos = (currentPos + 1) % enabledOptions.length;
|
|
2398
|
+
onChange(enabledOptions[nextPos].value);
|
|
2399
|
+
}
|
|
2400
|
+
else if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') {
|
|
2401
|
+
e.preventDefault();
|
|
2402
|
+
const currentPos = enabledOptions.findIndex((opt) => opt.value === options[currentIndex].value);
|
|
2403
|
+
const prevPos = (currentPos - 1 + enabledOptions.length) % enabledOptions.length;
|
|
2404
|
+
onChange(enabledOptions[prevPos].value);
|
|
2405
|
+
}
|
|
2406
|
+
else if (e.key === 'Home') {
|
|
2407
|
+
e.preventDefault();
|
|
2408
|
+
onChange(enabledOptions[0].value);
|
|
2409
|
+
}
|
|
2410
|
+
else if (e.key === 'End') {
|
|
2411
|
+
e.preventDefault();
|
|
2412
|
+
onChange(enabledOptions[enabledOptions.length - 1].value);
|
|
2413
|
+
}
|
|
2414
|
+
};
|
|
2415
|
+
return (jsx("div", { ref: ref, role: "tablist", "aria-orientation": "horizontal", className: buildClassName('inline-flex items-center border border-gray-200 bg-gray-100/80 dark:border-gray-700 dark:bg-gray-800/80', SIZE_CONTAINER_CLASS[size], fullWidth ? 'w-full flex' : '', disabled ? 'opacity-60 cursor-not-allowed' : '', className), "data-testid": "segmented-control", ...restProps, children: options.map((option, index) => {
|
|
2416
|
+
const isSelected = value === option.value;
|
|
2417
|
+
const isOptionDisabled = disabled || option.disabled;
|
|
2418
|
+
return (jsxs("button", { type: "button", role: "tab", "aria-selected": isSelected, "aria-disabled": isOptionDisabled, tabIndex: isSelected && !isOptionDisabled ? 0 : -1, disabled: isOptionDisabled, title: option.title, onClick: () => {
|
|
2419
|
+
if (!isOptionDisabled) {
|
|
2420
|
+
onChange(option.value);
|
|
2421
|
+
}
|
|
2422
|
+
}, onKeyDown: (e) => handleKeyDown(e, index), className: buildClassName('inline-flex items-center justify-center gap-1.5 transition-all duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary-ring)]', SIZE_OPTION_CLASS[size], fullWidth ? 'flex-1' : '', isOptionDisabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer', isSelected
|
|
2423
|
+
? 'bg-white text-[var(--ui-primary)] dark:bg-gray-700 dark:text-blue-400 shadow-xs font-semibold'
|
|
2424
|
+
: 'text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-100'), "data-testid": `segmented-option-${option.value}`, children: [option.icon && (jsx("span", { className: buildClassName('shrink-0 inline-flex items-center justify-center', SIZE_ICON_CLASS[size]), children: option.icon })), jsx("span", { children: option.label })] }, option.value));
|
|
2425
|
+
}) }));
|
|
1522
2426
|
});
|
|
2427
|
+
SegmentedControl.displayName = 'SegmentedControl';
|
|
2428
|
+
|
|
2429
|
+
const THEME_PILL_CLASS = {
|
|
2430
|
+
success: {
|
|
2431
|
+
bg: 'bg-emerald-500/10 text-emerald-700 dark:text-emerald-300 border-emerald-500/20 dark:border-emerald-500/30',
|
|
2432
|
+
dot: 'bg-emerald-500',
|
|
2433
|
+
},
|
|
2434
|
+
warning: {
|
|
2435
|
+
bg: 'bg-amber-500/10 text-amber-700 dark:text-amber-300 border-amber-500/20 dark:border-amber-500/30',
|
|
2436
|
+
dot: 'bg-amber-500',
|
|
2437
|
+
},
|
|
2438
|
+
danger: {
|
|
2439
|
+
bg: 'bg-rose-500/10 text-rose-700 dark:text-rose-300 border-rose-500/20 dark:border-rose-500/30',
|
|
2440
|
+
dot: 'bg-rose-500',
|
|
2441
|
+
},
|
|
2442
|
+
info: {
|
|
2443
|
+
bg: 'bg-blue-500/10 text-blue-700 dark:text-blue-300 border-blue-500/20 dark:border-blue-500/30',
|
|
2444
|
+
dot: 'bg-blue-500',
|
|
2445
|
+
},
|
|
2446
|
+
secondary: {
|
|
2447
|
+
bg: 'bg-gray-500/10 text-gray-600 dark:text-gray-400 border-gray-500/20 dark:border-gray-500/30',
|
|
2448
|
+
dot: 'bg-gray-400',
|
|
2449
|
+
},
|
|
2450
|
+
};
|
|
2451
|
+
const SIZE_CLASS = {
|
|
2452
|
+
sm: 'px-2.5 py-0.5 text-xs',
|
|
2453
|
+
md: 'px-3 py-1 text-sm',
|
|
2454
|
+
};
|
|
2455
|
+
const DOT_SIZE_CLASS = {
|
|
2456
|
+
sm: 'size-1.5',
|
|
2457
|
+
md: 'size-2',
|
|
2458
|
+
};
|
|
2459
|
+
const StatusPill = forwardRef((props, ref) => {
|
|
2460
|
+
var _a;
|
|
2461
|
+
const { theme = 'secondary', size = 'sm', dot = true, pulse = false, className, children, ...restProps } = props;
|
|
2462
|
+
const style = (_a = THEME_PILL_CLASS[theme]) !== null && _a !== void 0 ? _a : THEME_PILL_CLASS.secondary;
|
|
2463
|
+
return (jsxs("span", { ref: ref, className: buildClassName('inline-flex items-center gap-1.5 rounded-full border font-semibold whitespace-nowrap transition-colors', SIZE_CLASS[size], style.bg, className), "data-testid": `status-pill-${theme}`, ...restProps, children: [dot && (jsx("span", { className: buildClassName('shrink-0 rounded-full', DOT_SIZE_CLASS[size], style.dot, pulse ? 'animate-pulse' : ''), "data-testid": "status-pill-dot", "aria-hidden": "true" })), jsx("span", { children: children })] }));
|
|
2464
|
+
});
|
|
2465
|
+
StatusPill.displayName = 'StatusPill';
|
|
2466
|
+
|
|
2467
|
+
const ChevronUpIcon = ({ className }) => (jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: className, "aria-hidden": "true", children: jsx("polyline", { points: "18 15 12 9 6 15" }) }));
|
|
2468
|
+
const StickyActionBar = forwardRef((props, ref) => {
|
|
2469
|
+
const { summaryContent, actionsContent, drawerContent, drawerTitle = 'Summary Details', position = 'fixed', containerMaxWidth = 'max-w-5xl', className, ...restProps } = props;
|
|
2470
|
+
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
|
2471
|
+
const positionClass = position === 'fixed'
|
|
2472
|
+
? 'fixed bottom-0 left-0 right-0 z-30'
|
|
2473
|
+
: position === 'sticky'
|
|
2474
|
+
? 'sticky bottom-0 z-30'
|
|
2475
|
+
: 'relative z-10';
|
|
2476
|
+
return (jsxs(Fragment, { children: [jsx("div", { ref: ref, className: buildClassName(positionClass, 'border-t border-gray-200/80 bg-white/90 p-3.5 backdrop-blur-md shadow-lg dark:border-gray-800 dark:bg-gray-900/90 transition-all', className), "data-testid": "sticky-action-bar", ...restProps, children: jsxs("div", { className: buildClassName('mx-auto flex items-center justify-between gap-3', containerMaxWidth), children: [summaryContent && (jsx("div", { className: "flex items-center gap-2", children: drawerContent ? (jsxs("button", { type: "button", onClick: () => setIsDrawerOpen(true), className: "flex items-center gap-2 text-left transition-opacity hover:opacity-80 cursor-pointer focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ui-primary-ring)] rounded-lg p-1 -m-1", "aria-label": "Expand summary details", "data-testid": "summary-trigger", children: [jsx("div", { children: summaryContent }), jsx(ChevronUpIcon, { className: "size-4 text-[var(--ui-primary)] dark:text-blue-400 shrink-0" })] })) : (jsx("div", { children: summaryContent })) })), actionsContent && (jsx("div", { className: "flex items-center gap-2 shrink-0 ml-auto", "data-testid": "actions-stack", children: actionsContent }))] }) }), drawerContent && (jsx(Drawer, { isOpen: isDrawerOpen, onClose: () => setIsDrawerOpen(false), align: "bottom", size: "full", title: drawerTitle, showBackButton: true, contentClass: "px-4 pb-6 max-h-[calc(100svh-6rem)] overflow-y-auto", children: drawerContent }))] }));
|
|
2477
|
+
});
|
|
2478
|
+
StickyActionBar.displayName = 'StickyActionBar';
|
|
1523
2479
|
|
|
1524
2480
|
const Tabs = ({ tabs, defaultIndex = 0, selectedIndex, onChange, variant = 'underline', orientation = 'horizontal', stretch = false, className = '', listClass = '', panelsClass = '', }) => {
|
|
1525
2481
|
const isVertical = orientation === 'vertical';
|
|
@@ -1540,7 +2496,7 @@ const Tabs = ({ tabs, defaultIndex = 0, selectedIndex, onChange, variant = 'unde
|
|
|
1540
2496
|
}) })] }));
|
|
1541
2497
|
};
|
|
1542
2498
|
function buildTabClass({ selected, variant, stretch, isVertical, tabClass, }) {
|
|
1543
|
-
const base = buildClassName('inline-flex items-center gap-1.5 text-sm font-medium transition focus-visible:outline-none', 'disabled:opacity-40 disabled:cursor-not-allowed disabled:pointer-events-none', stretch && !isVertical && 'flex-1 justify-center');
|
|
2499
|
+
const base = buildClassName('inline-flex items-center gap-1.5 text-sm font-medium transition focus:outline-none focus-visible:outline-none focus:ring-0 select-none', 'disabled:opacity-40 disabled:cursor-not-allowed disabled:pointer-events-none', stretch && !isVertical && 'flex-1 justify-center');
|
|
1544
2500
|
let variantClass;
|
|
1545
2501
|
switch (variant) {
|
|
1546
2502
|
case 'underline':
|
|
@@ -1565,13 +2521,58 @@ function buildTabClass({ selected, variant, stretch, isVertical, tabClass, }) {
|
|
|
1565
2521
|
return buildClassName(base, variantClass, tabClass);
|
|
1566
2522
|
}
|
|
1567
2523
|
|
|
2524
|
+
const sizeClasses = {
|
|
2525
|
+
sm: {
|
|
2526
|
+
textarea: 'px-2.5 py-1.5 text-xs leading-5 min-h-[60px]',
|
|
2527
|
+
text: 'text-xs',
|
|
2528
|
+
},
|
|
2529
|
+
md: {
|
|
2530
|
+
textarea: 'px-3.5 py-2 text-sm leading-6 min-h-[80px]',
|
|
2531
|
+
text: 'text-sm',
|
|
2532
|
+
},
|
|
2533
|
+
lg: {
|
|
2534
|
+
textarea: 'px-4 py-2.5 text-base leading-7 min-h-[100px]',
|
|
2535
|
+
text: 'text-base',
|
|
2536
|
+
},
|
|
2537
|
+
};
|
|
2538
|
+
const resizeClasses = {
|
|
2539
|
+
none: 'resize-none',
|
|
2540
|
+
vertical: 'resize-y',
|
|
2541
|
+
horizontal: 'resize-x',
|
|
2542
|
+
both: 'resize',
|
|
2543
|
+
};
|
|
1568
2544
|
const Textarea = forwardRef((props, ref) => {
|
|
1569
|
-
const { name, id = name, label = null, labelHint = null, className = '', labelClass = '', labelWrapperClass = '', containerClass = '', error, showErrorMessage = true, rightGroup = null, leftGroup = null, ...inputProps } = props;
|
|
2545
|
+
const { name, id = name, label = null, labelHint = null, className = '', labelClass = '', labelWrapperClass = '', containerClass = '', error, showErrorMessage = true, rightGroup = null, leftGroup = null, size = 'md', autoSize = false, showCount = false, helperText, resize = 'vertical', value, defaultValue, onChange, maxLength, ...inputProps } = props;
|
|
2546
|
+
const innerRef = useRef(null);
|
|
2547
|
+
useImperativeHandle(ref, () => innerRef.current);
|
|
2548
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(String(defaultValue !== null && defaultValue !== void 0 ? defaultValue : ''));
|
|
2549
|
+
const isControlled = value !== undefined;
|
|
2550
|
+
const currentVal = isControlled ? String(value !== null && value !== void 0 ? value : '') : uncontrolledValue;
|
|
2551
|
+
const charLength = currentVal.length;
|
|
2552
|
+
// Handle Auto-Resize behavior
|
|
2553
|
+
useEffect(() => {
|
|
2554
|
+
if (autoSize && innerRef.current) {
|
|
2555
|
+
innerRef.current.style.height = 'auto';
|
|
2556
|
+
innerRef.current.style.height = `${innerRef.current.scrollHeight}px`;
|
|
2557
|
+
}
|
|
2558
|
+
}, [autoSize, currentVal]);
|
|
2559
|
+
const handleTextareaChange = (e) => {
|
|
2560
|
+
if (!isControlled) {
|
|
2561
|
+
setUncontrolledValue(e.target.value);
|
|
2562
|
+
}
|
|
2563
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(e);
|
|
2564
|
+
};
|
|
1570
2565
|
const hasError = !isEmpty(error);
|
|
1571
2566
|
const hasRightGroup = rightGroup != null && rightGroup !== undefined;
|
|
1572
2567
|
const hasLeftGroup = leftGroup != null && leftGroup !== undefined;
|
|
1573
|
-
|
|
2568
|
+
const currentSize = sizeClasses[size] || sizeClasses.md;
|
|
2569
|
+
return (jsxs("div", { className: buildClassName('group', hasError && 'has-error', containerClass), children: [label && (jsxs("div", { className: buildClassName('flex items-center justify-between mb-1.5', labelWrapperClass), children: [jsx(Label, { className: labelClass, htmlFor: id, children: label }), labelHint && jsx(TextContent, { xsmall: true, children: labelHint })] })), jsxs("div", { className: "relative", children: [hasLeftGroup && (jsx("span", { className: "absolute top-2.5 left-3 flex items-center justify-center text-gray-400 dark:text-gray-500", children: leftGroup })), jsx("textarea", { ref: innerRef, autoComplete: "off", ...inputProps, value: value, defaultValue: defaultValue, onChange: handleTextareaChange, maxLength: maxLength, name: name, id: id, className: buildClassName('transition-all duration-150', 'block w-full rounded-lg bg-white', currentSize.textarea, autoSize ? 'resize-none overflow-hidden' : resizeClasses[resize], 'text-gray-900 outline outline-1 -outline-offset-1', hasError
|
|
2570
|
+
? '!outline-red-500 !ring-1 !ring-red-500/50 dark:!outline-red-500'
|
|
2571
|
+
: 'outline-gray-300 dark:outline-gray-600', 'focus:outline-[var(--ui-focus-ring)] focus:outline-2 focus:-outline-offset-2', 'dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 placeholder:text-gray-400 dark:text-white', 'disabled:pointer-events-none disabled:bg-gray-100 disabled:outline-gray-200', 'dark:disabled:bg-gray-800 dark:disabled:text-gray-600 dark:disabled:placeholder-gray-600 dark:disabled:outline-gray-700', hasRightGroup ? 'pe-10' : '', hasLeftGroup ? 'ps-10' : '', className) }), hasRightGroup && (jsx("span", { className: "absolute end-3 top-2.5 flex items-center justify-center text-gray-400 dark:text-gray-500", children: rightGroup }))] }), (showErrorMessage && error) || helperText || showCount ? (jsxs("div", { className: "mt-1.5 flex items-center justify-between gap-2 text-xs", children: [jsx("div", { className: "min-w-0 flex-1", children: showErrorMessage && error ? (jsx(TextContent, { error: true, small: true, children: error })) : helperText ? (jsx(TextContent, { muted: true, small: true, children: helperText })) : null }), showCount && (jsxs("span", { className: buildClassName('text-[11px] font-medium shrink-0 ml-auto', maxLength && charLength >= maxLength
|
|
2572
|
+
? 'text-red-500 font-semibold'
|
|
2573
|
+
: 'text-gray-400 dark:text-gray-500'), children: [charLength, maxLength ? ` / ${maxLength}` : ''] }))] })) : null] }));
|
|
1574
2574
|
});
|
|
2575
|
+
Textarea.displayName = 'Textarea';
|
|
1575
2576
|
|
|
1576
2577
|
/**
|
|
1577
2578
|
* Icons for each toast type
|
|
@@ -1582,23 +2583,76 @@ const iconMap = {
|
|
|
1582
2583
|
info: jsx(SvgInfo, { className: "size-5 text-[var(--ui-info)]" }),
|
|
1583
2584
|
warning: jsx(SvgTriangleAlert, { className: "size-5 text-[var(--ui-warning)]" }),
|
|
1584
2585
|
};
|
|
2586
|
+
const filledIconMap = {
|
|
2587
|
+
success: jsx(SvgCircleCheck, { className: "size-5 text-white" }),
|
|
2588
|
+
error: jsx(SvgCircleAlert, { className: "size-5 text-white" }),
|
|
2589
|
+
info: jsx(SvgInfo, { className: "size-5 text-white" }),
|
|
2590
|
+
warning: jsx(SvgTriangleAlert, { className: "size-5 text-white" }),
|
|
2591
|
+
};
|
|
1585
2592
|
const textColorMap = {
|
|
1586
2593
|
success: 'text-[var(--ui-success-text)]',
|
|
1587
2594
|
error: 'text-[var(--ui-danger-text)]',
|
|
1588
2595
|
info: 'text-[var(--ui-info-text)]',
|
|
1589
2596
|
warning: 'text-[var(--ui-warning-text)]',
|
|
1590
2597
|
};
|
|
1591
|
-
const
|
|
1592
|
-
success: '
|
|
1593
|
-
error: '
|
|
1594
|
-
info: '
|
|
1595
|
-
warning: '
|
|
2598
|
+
const progressBarColorMap = {
|
|
2599
|
+
success: 'bg-[var(--ui-success)]',
|
|
2600
|
+
error: 'bg-[var(--ui-danger)]',
|
|
2601
|
+
info: 'bg-[var(--ui-info)]',
|
|
2602
|
+
warning: 'bg-[var(--ui-warning)]',
|
|
2603
|
+
};
|
|
2604
|
+
const variantStyleMap = {
|
|
2605
|
+
accent: {
|
|
2606
|
+
success: 'bg-white dark:bg-gray-900 border-l-4 border-[var(--ui-success)] text-gray-900 dark:text-gray-100 shadow-lg border-y border-r border-gray-200/80 dark:border-gray-800/80',
|
|
2607
|
+
error: 'bg-white dark:bg-gray-900 border-l-4 border-[var(--ui-danger)] text-gray-900 dark:text-gray-100 shadow-lg border-y border-r border-gray-200/80 dark:border-gray-800/80',
|
|
2608
|
+
info: 'bg-white dark:bg-gray-900 border-l-4 border-[var(--ui-info)] text-gray-900 dark:text-gray-100 shadow-lg border-y border-r border-gray-200/80 dark:border-gray-800/80',
|
|
2609
|
+
warning: 'bg-white dark:bg-gray-900 border-l-4 border-[var(--ui-warning)] text-gray-900 dark:text-gray-100 shadow-lg border-y border-r border-gray-200/80 dark:border-gray-800/80',
|
|
2610
|
+
},
|
|
2611
|
+
filled: {
|
|
2612
|
+
success: 'bg-[var(--ui-success)] text-white shadow-lg',
|
|
2613
|
+
error: 'bg-[var(--ui-danger)] text-white shadow-lg',
|
|
2614
|
+
info: 'bg-[var(--ui-info)] text-white shadow-lg',
|
|
2615
|
+
warning: 'bg-[var(--ui-warning)] text-white shadow-lg',
|
|
2616
|
+
},
|
|
2617
|
+
outlined: {
|
|
2618
|
+
success: 'bg-white dark:bg-gray-900 border-2 border-[var(--ui-success)] text-gray-900 dark:text-gray-100 shadow-md',
|
|
2619
|
+
error: 'bg-white dark:bg-gray-900 border-2 border-[var(--ui-danger)] text-gray-900 dark:text-gray-100 shadow-md',
|
|
2620
|
+
info: 'bg-white dark:bg-gray-900 border-2 border-[var(--ui-info)] text-gray-900 dark:text-gray-100 shadow-md',
|
|
2621
|
+
warning: 'bg-white dark:bg-gray-900 border-2 border-[var(--ui-warning)] text-gray-900 dark:text-gray-100 shadow-md',
|
|
2622
|
+
},
|
|
2623
|
+
glass: {
|
|
2624
|
+
success: 'backdrop-blur-md bg-white/85 dark:bg-gray-900/85 border border-emerald-500/30 text-gray-900 dark:text-gray-100 shadow-xl',
|
|
2625
|
+
error: 'backdrop-blur-md bg-white/85 dark:bg-gray-900/85 border border-rose-500/30 text-gray-900 dark:text-gray-100 shadow-xl',
|
|
2626
|
+
info: 'backdrop-blur-md bg-white/85 dark:bg-gray-900/85 border border-blue-500/30 text-gray-900 dark:text-gray-100 shadow-xl',
|
|
2627
|
+
warning: 'backdrop-blur-md bg-white/85 dark:bg-gray-900/85 border border-amber-500/30 text-gray-900 dark:text-gray-100 shadow-xl',
|
|
2628
|
+
},
|
|
1596
2629
|
};
|
|
1597
|
-
const Toast = ({ message, type, onClose, className,
|
|
2630
|
+
const Toast = ({ id, title, message, type, variant = 'accent', action, icon, showProgressBar = true, duration = 4000, isPaused = false, onClose, className, isTop = true, }) => {
|
|
2631
|
+
const [progress, setProgress] = useState(100);
|
|
2632
|
+
useEffect(() => {
|
|
2633
|
+
if (!showProgressBar || !duration || isPaused)
|
|
2634
|
+
return;
|
|
2635
|
+
const intervalTime = 50;
|
|
2636
|
+
const step = (intervalTime / duration) * 100;
|
|
2637
|
+
const timer = setInterval(() => {
|
|
2638
|
+
setProgress((prev) => {
|
|
2639
|
+
if (prev <= 0) {
|
|
2640
|
+
clearInterval(timer);
|
|
2641
|
+
return 0;
|
|
2642
|
+
}
|
|
2643
|
+
return prev - step;
|
|
2644
|
+
});
|
|
2645
|
+
}, intervalTime);
|
|
2646
|
+
return () => clearInterval(timer);
|
|
2647
|
+
}, [showProgressBar, duration, isPaused]);
|
|
1598
2648
|
const enterFrom = isTop ? 'opacity-0 -translate-y-3 scale-95' : 'opacity-0 translate-y-3 scale-95';
|
|
1599
2649
|
const leaveTo = isTop ? 'opacity-0 -translate-y-2 scale-95' : 'opacity-0 translate-y-2 scale-95';
|
|
1600
|
-
|
|
2650
|
+
const renderedIcon = icon !== null && icon !== void 0 ? icon : (variant === 'filled' ? filledIconMap[type] : iconMap[type]);
|
|
2651
|
+
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'
|
|
2652
|
+
? 'text-white hover:opacity-85'
|
|
2653
|
+
: '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', noOutlineOnFocus: true, 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)}%` } }) }))] }) }));
|
|
1601
2654
|
};
|
|
2655
|
+
Toast.displayName = 'Toast';
|
|
1602
2656
|
|
|
1603
2657
|
const ToastContext = createContext(undefined);
|
|
1604
2658
|
let toastCounter = 0;
|
|
@@ -1608,64 +2662,138 @@ const useToast = () => {
|
|
|
1608
2662
|
throw new Error('useToast must be used within ToastProvider');
|
|
1609
2663
|
return context;
|
|
1610
2664
|
};
|
|
1611
|
-
// Maximum visible toasts at once
|
|
1612
|
-
const MAX_TOASTS = 5;
|
|
1613
2665
|
const placementClass = {
|
|
1614
|
-
'top-right': 'top-
|
|
1615
|
-
'top-left': 'top-
|
|
1616
|
-
'top-center': 'top-
|
|
1617
|
-
'bottom-right': 'bottom-
|
|
1618
|
-
'bottom-left': 'bottom-
|
|
1619
|
-
'bottom-center': 'bottom-
|
|
1620
|
-
};
|
|
1621
|
-
const ToastProvider = ({ children, placement = 'top-
|
|
2666
|
+
'top-right': 'top-20 right-6 items-end',
|
|
2667
|
+
'top-left': 'top-20 left-6 items-start',
|
|
2668
|
+
'top-center': 'top-20 left-1/2 -translate-x-1/2 items-center',
|
|
2669
|
+
'bottom-right': 'bottom-6 right-6 items-end',
|
|
2670
|
+
'bottom-left': 'bottom-6 left-6 items-start',
|
|
2671
|
+
'bottom-center': 'bottom-6 left-1/2 -translate-x-1/2 items-center',
|
|
2672
|
+
};
|
|
2673
|
+
const ToastProvider = ({ children, placement = 'top-right', maxToasts = 5, className, }) => {
|
|
1622
2674
|
const [toasts, setToasts] = useState([]);
|
|
1623
|
-
const
|
|
2675
|
+
const [pausedIds, setPausedIds] = useState(new Set());
|
|
2676
|
+
const metaRefs = useRef(new Map());
|
|
1624
2677
|
const clearTimer = (id) => {
|
|
1625
|
-
const
|
|
1626
|
-
if (
|
|
1627
|
-
clearTimeout(
|
|
1628
|
-
|
|
2678
|
+
const meta = metaRefs.current.get(id);
|
|
2679
|
+
if (meta === null || meta === void 0 ? void 0 : meta.timerId) {
|
|
2680
|
+
clearTimeout(meta.timerId);
|
|
2681
|
+
meta.timerId = null;
|
|
1629
2682
|
}
|
|
1630
2683
|
};
|
|
1631
2684
|
const closeToast = useCallback((id) => {
|
|
1632
2685
|
clearTimer(id);
|
|
2686
|
+
metaRefs.current.delete(id);
|
|
2687
|
+
setPausedIds((prev) => {
|
|
2688
|
+
const next = new Set(prev);
|
|
2689
|
+
next.delete(id);
|
|
2690
|
+
return next;
|
|
2691
|
+
});
|
|
1633
2692
|
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
1634
2693
|
}, []);
|
|
1635
|
-
const
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
2694
|
+
const dismissAll = useCallback(() => {
|
|
2695
|
+
metaRefs.current.forEach((meta) => {
|
|
2696
|
+
if (meta.timerId)
|
|
2697
|
+
clearTimeout(meta.timerId);
|
|
2698
|
+
});
|
|
2699
|
+
metaRefs.current.clear();
|
|
2700
|
+
setPausedIds(new Set());
|
|
2701
|
+
setToasts([]);
|
|
2702
|
+
}, []);
|
|
2703
|
+
const startAutoClose = useCallback((toastId, delay) => {
|
|
2704
|
+
clearTimer(toastId);
|
|
2705
|
+
const timerId = setTimeout(() => {
|
|
2706
|
+
closeToast(toastId);
|
|
2707
|
+
}, delay);
|
|
2708
|
+
const meta = metaRefs.current.get(toastId);
|
|
2709
|
+
if (meta) {
|
|
2710
|
+
meta.timerId = timerId;
|
|
2711
|
+
meta.startTime = Date.now();
|
|
2712
|
+
meta.remainingTime = delay;
|
|
2713
|
+
}
|
|
2714
|
+
else {
|
|
2715
|
+
metaRefs.current.set(toastId, {
|
|
2716
|
+
timerId,
|
|
2717
|
+
startTime: Date.now(),
|
|
2718
|
+
remainingTime: delay,
|
|
2719
|
+
});
|
|
2720
|
+
}
|
|
1643
2721
|
}, [closeToast]);
|
|
1644
2722
|
useEffect(() => {
|
|
1645
|
-
const refs =
|
|
1646
|
-
return () => refs.forEach((
|
|
2723
|
+
const refs = metaRefs.current;
|
|
2724
|
+
return () => refs.forEach((meta) => {
|
|
2725
|
+
if (meta.timerId)
|
|
2726
|
+
clearTimeout(meta.timerId);
|
|
2727
|
+
});
|
|
1647
2728
|
}, []);
|
|
1648
2729
|
const showToast = useCallback((message, options) => {
|
|
1649
|
-
var _a, _b, _c, _d;
|
|
2730
|
+
var _a, _b, _c, _d, _e, _f;
|
|
1650
2731
|
const id = ++toastCounter;
|
|
1651
2732
|
const newToast = {
|
|
1652
2733
|
id,
|
|
2734
|
+
title: options === null || options === void 0 ? void 0 : options.title,
|
|
1653
2735
|
message,
|
|
1654
2736
|
type: (_a = options === null || options === void 0 ? void 0 : options.type) !== null && _a !== void 0 ? _a : 'info',
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
2737
|
+
variant: (_b = options === null || options === void 0 ? void 0 : options.variant) !== null && _b !== void 0 ? _b : 'accent',
|
|
2738
|
+
duration: (_c = options === null || options === void 0 ? void 0 : options.duration) !== null && _c !== void 0 ? _c : 4000,
|
|
2739
|
+
autoClose: (_d = options === null || options === void 0 ? void 0 : options.autoClose) !== null && _d !== void 0 ? _d : true,
|
|
2740
|
+
pauseOnHover: (_e = options === null || options === void 0 ? void 0 : options.pauseOnHover) !== null && _e !== void 0 ? _e : true,
|
|
2741
|
+
showProgressBar: (_f = options === null || options === void 0 ? void 0 : options.showProgressBar) !== null && _f !== void 0 ? _f : (options === null || options === void 0 ? void 0 : options.autoClose) !== false,
|
|
2742
|
+
action: options === null || options === void 0 ? void 0 : options.action,
|
|
2743
|
+
icon: options === null || options === void 0 ? void 0 : options.icon,
|
|
1658
2744
|
};
|
|
1659
2745
|
setToasts((prev) => {
|
|
1660
2746
|
const updated = [...prev, newToast];
|
|
1661
|
-
if (updated.length >
|
|
2747
|
+
if (updated.length > maxToasts)
|
|
1662
2748
|
updated.shift();
|
|
1663
2749
|
return updated;
|
|
1664
2750
|
});
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
2751
|
+
if (newToast.autoClose) {
|
|
2752
|
+
startAutoClose(id, newToast.duration);
|
|
2753
|
+
}
|
|
2754
|
+
return id;
|
|
2755
|
+
}, [maxToasts, startAutoClose]);
|
|
2756
|
+
// Hover pauses timer and tracks exact remaining milliseconds
|
|
2757
|
+
const handleMouseEnter = (toast) => {
|
|
2758
|
+
if (!toast.pauseOnHover || !toast.autoClose)
|
|
2759
|
+
return;
|
|
2760
|
+
const meta = metaRefs.current.get(toast.id);
|
|
2761
|
+
if (meta) {
|
|
2762
|
+
const elapsed = Date.now() - meta.startTime;
|
|
2763
|
+
meta.remainingTime = Math.max(0, meta.remainingTime - elapsed);
|
|
2764
|
+
clearTimer(toast.id);
|
|
2765
|
+
setPausedIds((prev) => new Set(prev).add(toast.id));
|
|
2766
|
+
}
|
|
2767
|
+
};
|
|
2768
|
+
// Mouse leave resumes timer with exact remaining milliseconds
|
|
2769
|
+
const handleMouseLeave = (toast) => {
|
|
2770
|
+
if (!toast.pauseOnHover || !toast.autoClose)
|
|
2771
|
+
return;
|
|
2772
|
+
setPausedIds((prev) => {
|
|
2773
|
+
const next = new Set(prev);
|
|
2774
|
+
next.delete(toast.id);
|
|
2775
|
+
return next;
|
|
2776
|
+
});
|
|
2777
|
+
const meta = metaRefs.current.get(toast.id);
|
|
2778
|
+
if (meta && meta.remainingTime > 0) {
|
|
2779
|
+
startAutoClose(toast.id, meta.remainingTime);
|
|
2780
|
+
}
|
|
2781
|
+
else if (meta && meta.remainingTime <= 0) {
|
|
2782
|
+
closeToast(toast.id);
|
|
2783
|
+
}
|
|
2784
|
+
};
|
|
2785
|
+
const toastHelpers = useMemo(() => ({
|
|
2786
|
+
show: (message, options) => showToast(message, options),
|
|
2787
|
+
success: (message, options) => showToast(message, { ...options, type: 'success' }),
|
|
2788
|
+
error: (message, options) => showToast(message, { ...options, type: 'error' }),
|
|
2789
|
+
info: (message, options) => showToast(message, { ...options, type: 'info' }),
|
|
2790
|
+
warning: (message, options) => showToast(message, { ...options, type: 'warning' }),
|
|
2791
|
+
dismiss: (id) => closeToast(id),
|
|
2792
|
+
dismissAll: () => dismissAll(),
|
|
2793
|
+
}), [showToast, closeToast, dismissAll]);
|
|
2794
|
+
return (jsxs(ToastContext.Provider, { value: { showToast, closeToast, toast: toastHelpers }, children: [children, jsx("div", { className: buildClassName('fixed z-[9999] flex flex-col gap-3 pointer-events-none', placementClass[placement], className), children: toasts.map((toastItem) => (jsx("div", { className: "pointer-events-auto", onMouseEnter: () => handleMouseEnter(toastItem), onMouseLeave: () => handleMouseLeave(toastItem), children: jsx(Toast, { ...toastItem, isPaused: pausedIds.has(toastItem.id), isTop: placement.startsWith('top'), onClose: () => closeToast(toastItem.id) }) }, toastItem.id))) })] }));
|
|
2795
|
+
};
|
|
2796
|
+
ToastProvider.displayName = 'ToastProvider';
|
|
1669
2797
|
|
|
1670
2798
|
const DENSITY_CELL_CLASS$1 = {
|
|
1671
2799
|
compact: 'px-2 py-1.5',
|
|
@@ -1849,5 +2977,5 @@ const useIsTabletOrMobile = () => {
|
|
|
1849
2977
|
return isMobile || isTablet;
|
|
1850
2978
|
};
|
|
1851
2979
|
|
|
1852
|
-
export { Accordion, Alert, Badge, Banner, BodyText, Breadcrumb, Button, ButtonGroup, Card, Checkbox, CheckboxGroup, DOTS, DataTable, DetailedInformation, Dot, Drawer, Dropdown, EmptyState, EmptyTableRow, HeadingText, Input, Label, Loader, PASSWORD_PLACEHOLDER, PASSWORD_TYPE, Pagination, PasswordInput, Radio, RadioGroup, RadioSwitch, RangeInput, RangeSlider, SelectBox, SortIconMap, Table, TableBody, TableCell, TableFoot, TableHead, TableHeaderCell, TableRow, Tabs, TextContent, Textarea, Toast, ToastProvider, VirtualizedDataTable, buildClassName, buildInputClass, get, getSortIcon, isArray, isEmpty, nextSortDirection, updateSortQuery, useDebounce, useDebounceFn, useIsMobile, useIsTablet, useIsTabletOrMobile, usePagination, useRowSelection, useToast };
|
|
2980
|
+
export { Accordion, Alert, Badge, Banner, BodyText, Breadcrumb, Button, ButtonGroup, Card, CardContent, CardDescription, CardFooter, CardHeader, CardMedia, CardTitle, Checkbox, CheckboxGroup, DOTS, DataTable, DetailedInformation, Dot, Drawer, Dropdown, EmptyState, EmptyTableRow, HeadingText, Input, Label, Loader, PASSWORD_PLACEHOLDER, PASSWORD_TYPE, Pagination, PasswordInput, QuantityStepper, Radio, RadioGroup, RadioSwitch, RangeInput, RangeSlider, SegmentedControl, SelectBox, Skeleton, SortIconMap, StatusPill, StickyActionBar, Table, TableBody, TableCell, TableFoot, TableHead, TableHeaderCell, TableRow, Tabs, TextContent, Textarea, Toast, ToastProvider, VirtualizedDataTable, buildClassName, buildInputClass, evaluatePasswordStrength, get, getSortIcon, isArray, isEmpty, nextSortDirection, updateSortQuery, useDebounce, useDebounceFn, useIsMobile, useIsTablet, useIsTabletOrMobile, usePagination, useRowSelection, useToast };
|
|
1853
2981
|
//# sourceMappingURL=react-tailwind.es.js.map
|