@aurora-ds/components 1.1.6 → 1.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +655 -208
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +653 -210
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.ts +169 -4
- package/package.json +1 -1
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
|
|
2
|
-
import { keyframes,
|
|
2
|
+
import { keyframes, createStyles, useTheme, cx, createVariants, createTheme } from '@aurora-ds/theme';
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
import { createElement, Fragment, useRef, useState, useCallback, useEffect, useId, isValidElement, cloneElement, useLayoutEffect, useMemo, createContext, useContext } from 'react';
|
|
5
5
|
import { createPortal } from 'react-dom';
|
|
@@ -161,24 +161,32 @@ const skeletonShimmerAnimation = keyframes({
|
|
|
161
161
|
'100%': { backgroundPosition: '0% 50%' },
|
|
162
162
|
});
|
|
163
163
|
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const
|
|
164
|
+
/** Default duration in milliseconds for mount/unmount transition animations. */
|
|
165
|
+
const DEFAULT_TRANSITION_DURATION_MS = 250;
|
|
166
|
+
const DEFAULT_BUTTON_HEIGHT = 40;
|
|
167
|
+
const DEFAULT_DRAWER_ITEM_SIZE = 40;
|
|
168
|
+
/** Drawer widths (px) — single source of truth for both the Drawer and its items. */
|
|
169
|
+
const EXPANDED_DRAWER_WIDTH = 240;
|
|
170
|
+
const COLLAPSED_DRAWER_WIDTH = 58;
|
|
171
|
+
/**
|
|
172
|
+
* Horizontal padding (px) applied by `Drawer.Body` on EACH side of its items
|
|
173
|
+
* (Box `px="sm"` → theme.spacing.sm = 0.5rem = 8px).
|
|
174
|
+
*/
|
|
175
|
+
const DRAWER_BODY_HORIZONTAL_PADDING = 8;
|
|
176
|
+
/**
|
|
177
|
+
* DrawerItem widths (px), always derived from the drawer width minus the body
|
|
178
|
+
* horizontal padding. Using explicit widths (instead of `width: 100%`) lets the
|
|
179
|
+
* item animate its own width in sync with the drawer for a smooth transition.
|
|
180
|
+
*/
|
|
181
|
+
const EXPANDED_DRAWER_ITEM_WIDTH = EXPANDED_DRAWER_WIDTH - DRAWER_BODY_HORIZONTAL_PADDING * 2; // 264
|
|
182
|
+
const COLLAPSED_DRAWER_ITEM_WIDTH = COLLAPSED_DRAWER_WIDTH - DRAWER_BODY_HORIZONTAL_PADDING * 2; // 44
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Returns the complete root styles for an action button (base + color/variant),
|
|
186
|
+
* all in one object so CSS transitions work correctly.
|
|
187
|
+
* Size-specific styles (height, padding, fontSize) are added by the caller.
|
|
188
|
+
*/
|
|
189
|
+
const buildActionButtonRootStyle = (theme, variant, color) => {
|
|
182
190
|
const c = theme.colors;
|
|
183
191
|
const intents = {
|
|
184
192
|
primary: {
|
|
@@ -217,57 +225,60 @@ const buildActionButtonCompoundVariants = (theme) => {
|
|
|
217
225
|
fg: c.errorMain, fgHover: c.errorHover, border: c.errorMain,
|
|
218
226
|
},
|
|
219
227
|
};
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
};
|
|
228
|
+
const intent = intents[color];
|
|
229
|
+
const colorVariantStyles = variant === 'contained'
|
|
230
|
+
? {
|
|
231
|
+
backgroundColor: intent.main,
|
|
232
|
+
borderColor: intent.main,
|
|
233
|
+
color: intent.on,
|
|
234
|
+
boxShadow: theme.shadows.xs,
|
|
235
|
+
':hover:not(:disabled)': { backgroundColor: intent.hover, borderColor: intent.hover, boxShadow: theme.shadows.sm },
|
|
236
|
+
':active:not(:disabled)': { backgroundColor: intent.active, borderColor: intent.active, boxShadow: theme.shadows.none },
|
|
230
237
|
}
|
|
231
|
-
|
|
232
|
-
|
|
238
|
+
: variant === 'outlined'
|
|
239
|
+
? {
|
|
233
240
|
backgroundColor: 'transparent',
|
|
234
241
|
borderColor: intent.border,
|
|
235
242
|
color: intent.fg,
|
|
236
243
|
':hover:not(:disabled)': { backgroundColor: intent.subtleHover, color: intent.fgHover },
|
|
237
244
|
':active:not(:disabled)': { backgroundColor: intent.subtleActive, borderColor: intent.active, color: intent.active },
|
|
245
|
+
}
|
|
246
|
+
: {
|
|
247
|
+
backgroundColor: 'transparent',
|
|
248
|
+
borderColor: 'transparent',
|
|
249
|
+
color: intent.fg,
|
|
250
|
+
':hover:not(:disabled)': { backgroundColor: intent.subtleHover, color: intent.fgHover },
|
|
251
|
+
':active:not(:disabled)': { backgroundColor: intent.subtleActive, color: intent.active },
|
|
238
252
|
};
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
253
|
+
return {
|
|
254
|
+
position: 'relative',
|
|
255
|
+
display: 'inline-flex',
|
|
256
|
+
alignItems: 'center',
|
|
257
|
+
justifyContent: 'center',
|
|
258
|
+
boxSizing: 'border-box',
|
|
259
|
+
border: '1px solid transparent',
|
|
260
|
+
borderRadius: theme.radius.md,
|
|
261
|
+
fontFamily: 'inherit',
|
|
262
|
+
userSelect: 'none',
|
|
263
|
+
height: DEFAULT_BUTTON_HEIGHT,
|
|
264
|
+
cursor: 'pointer',
|
|
265
|
+
outline: 'none',
|
|
266
|
+
transition: `background-color ${theme.transition.normal}, border-color ${theme.transition.normal}, color ${theme.transition.normal}, box-shadow ${theme.transition.normal}`,
|
|
267
|
+
...colorVariantStyles,
|
|
268
|
+
':focus-visible': { boxShadow: theme.shadows.focus },
|
|
269
|
+
':disabled': { cursor: 'not-allowed', opacity: theme.opacity.high, boxShadow: 'none' },
|
|
247
270
|
};
|
|
248
|
-
return Object.keys(intents).flatMap((color) => APPEARANCES.map((appearance) => ({
|
|
249
|
-
color,
|
|
250
|
-
variant: appearance,
|
|
251
|
-
styles: appearanceStyles(intents[color], appearance),
|
|
252
|
-
})));
|
|
253
271
|
};
|
|
254
272
|
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
size
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
variant: { contained: {}, outlined: {}, text: {} },
|
|
265
|
-
color: { primary: {}, secondary: {}, neutral: {}, info: {}, success: {}, warning: {}, error: {} },
|
|
266
|
-
},
|
|
267
|
-
defaultVariants: { size: 'md', variant: 'contained', color: 'primary' },
|
|
268
|
-
compoundVariants: buildActionButtonCompoundVariants(theme),
|
|
269
|
-
}), { id: 'button' });
|
|
270
|
-
const BUTTON_STYLES = createStyles({
|
|
273
|
+
const BUTTON_STYLES = createStyles((theme) => ({
|
|
274
|
+
root: ({ variant, color, size }) => ({
|
|
275
|
+
...buildActionButtonRootStyle(theme, variant, color),
|
|
276
|
+
...(size === 'sm'
|
|
277
|
+
? { height: '2rem', padding: `0 ${theme.spacing.sm}`, fontSize: theme.fontSize.xs }
|
|
278
|
+
: size === 'lg'
|
|
279
|
+
? { height: '3rem', padding: `0 ${theme.spacing.lg}`, fontSize: theme.fontSize.md }
|
|
280
|
+
: { height: '2.5rem', padding: `0 ${theme.spacing.md}`, fontSize: theme.fontSize.sm }),
|
|
281
|
+
}),
|
|
271
282
|
/** Inner wrapper holding icons + label, centered by the button. */
|
|
272
283
|
content: {
|
|
273
284
|
display: 'inline-flex',
|
|
@@ -290,7 +301,14 @@ const BUTTON_STYLES = createStyles({
|
|
|
290
301
|
animation: `${spinAnimation} 0.75s linear infinite`,
|
|
291
302
|
'@media (prefers-reduced-motion: reduce)': { animation: 'none' },
|
|
292
303
|
},
|
|
293
|
-
}, { id: 'button
|
|
304
|
+
}), { id: 'button' });
|
|
305
|
+
// Pre-generate CSS for all variant/color/size combinations at module load.
|
|
306
|
+
// This ensures the CSS is already in the stylesheet before the first user interaction,
|
|
307
|
+
// preventing the "first click is instant" issue caused by lazy CSS injection.
|
|
308
|
+
const BUTTON_VARIANT_VALUES = ['contained', 'outlined', 'text'];
|
|
309
|
+
const BUTTON_COLOR_VALUES = ['primary', 'secondary', 'neutral', 'info', 'success', 'warning', 'error'];
|
|
310
|
+
const BUTTON_SIZE_VALUES = ['sm', 'md', 'lg'];
|
|
311
|
+
BUTTON_VARIANT_VALUES.forEach(variant => BUTTON_COLOR_VALUES.forEach(color => BUTTON_SIZE_VALUES.forEach(size => BUTTON_STYLES.root({ variant, color, size }))));
|
|
294
312
|
|
|
295
313
|
const ICON_STYLES = createStyles((theme) => ({
|
|
296
314
|
root: ({ size, strokeColor, fill, backgroundColor, padding, borderRadius }) => ({
|
|
@@ -559,7 +577,7 @@ const ICON_SIZE$2 = {
|
|
|
559
577
|
const Button = ({ ref, variant = 'contained', color = 'primary', size = 'md', width, flexGrow, flexShrink, isLoading = false, startIcon: StartIcon, endIcon: EndIcon, label, className, type = 'button', disabled, style, ...rest }) => {
|
|
560
578
|
const isDisabled = disabled || isLoading;
|
|
561
579
|
const iconSize = ICON_SIZE$2[size];
|
|
562
|
-
const rootClassName =
|
|
580
|
+
const rootClassName = cx(BUTTON_STYLES.root({ variant, color, size }), className);
|
|
563
581
|
const mergedStyle = {
|
|
564
582
|
...style,
|
|
565
583
|
...(width !== undefined ? { width } : {}),
|
|
@@ -570,21 +588,15 @@ const Button = ({ ref, variant = 'contained', color = 'primary', size = 'md', wi
|
|
|
570
588
|
};
|
|
571
589
|
Button.displayName = 'Button';
|
|
572
590
|
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
size
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
color: { primary: {}, secondary: {}, neutral: {}, info: {}, success: {}, warning: {}, error: {} },
|
|
583
|
-
},
|
|
584
|
-
defaultVariants: { size: 'md', variant: 'contained', color: 'primary' },
|
|
585
|
-
compoundVariants: buildActionButtonCompoundVariants(theme),
|
|
586
|
-
}), { id: 'icon-button' });
|
|
587
|
-
const ICON_BUTTON_STYLES = createStyles({
|
|
591
|
+
const ICON_BUTTON_STYLES = createStyles((theme) => ({
|
|
592
|
+
root: ({ variant, color, size }) => ({
|
|
593
|
+
...buildActionButtonRootStyle(theme, variant, color),
|
|
594
|
+
...(size === 'sm'
|
|
595
|
+
? { width: '2rem', height: '2rem', padding: '0' }
|
|
596
|
+
: size === 'lg'
|
|
597
|
+
? { width: '3rem', height: '3rem', padding: '0' }
|
|
598
|
+
: { width: '2.5rem', height: '2.5rem', padding: '0' }),
|
|
599
|
+
}),
|
|
588
600
|
/** Spinning animation applied to the SpinnerIcon. */
|
|
589
601
|
spinnerIcon: {
|
|
590
602
|
animation: `${spinAnimation} 0.75s linear infinite`,
|
|
@@ -600,7 +612,11 @@ const ICON_BUTTON_STYLES = createStyles({
|
|
|
600
612
|
alignItems: 'center',
|
|
601
613
|
justifyContent: 'center',
|
|
602
614
|
},
|
|
603
|
-
}, { id: 'icon-button
|
|
615
|
+
}), { id: 'icon-button' });
|
|
616
|
+
const ICON_BUTTON_VARIANT_VALUES = ['contained', 'outlined', 'text'];
|
|
617
|
+
const ICON_BUTTON_COLOR_VALUES = ['primary', 'secondary', 'neutral', 'info', 'success', 'warning', 'error'];
|
|
618
|
+
const ICON_BUTTON_SIZE_VALUES = ['sm', 'md', 'lg'];
|
|
619
|
+
ICON_BUTTON_VARIANT_VALUES.forEach(variant => ICON_BUTTON_COLOR_VALUES.forEach(color => ICON_BUTTON_SIZE_VALUES.forEach(size => ICON_BUTTON_STYLES.root({ variant, color, size }))));
|
|
604
620
|
|
|
605
621
|
/** Maps the icon-button size to an Icon size token. */
|
|
606
622
|
const ICON_SIZE$1 = {
|
|
@@ -619,7 +635,7 @@ const ICON_SIZE$1 = {
|
|
|
619
635
|
const IconButton = ({ ref, icon: IconComponent, ariaLabel, variant = 'contained', color = 'primary', size = 'md', isLoading = false, className, type = 'button', disabled, ...rest }) => {
|
|
620
636
|
const isDisabled = disabled || isLoading;
|
|
621
637
|
const iconSize = ICON_SIZE$1[size];
|
|
622
|
-
const rootClassName =
|
|
638
|
+
const rootClassName = cx(ICON_BUTTON_STYLES.root({ variant, color, size }), className);
|
|
623
639
|
return (jsxs("button", { ref: ref, type: type, className: rootClassName, disabled: isDisabled, "aria-label": ariaLabel, "aria-busy": isLoading || undefined, ...rest, children: [isLoading && (jsx("span", { className: ICON_BUTTON_STYLES.spinnerWrap, children: jsx(Icon, { icon: SpinnerIcon, size: iconSize, className: ICON_BUTTON_STYLES.spinnerIcon }) })), jsx(Icon, { icon: IconComponent, size: iconSize, className: cx(isLoading && ICON_BUTTON_STYLES.iconHidden) })] }));
|
|
624
640
|
};
|
|
625
641
|
IconButton.displayName = 'IconButton';
|
|
@@ -835,7 +851,7 @@ const BADGE_VARIANTS = createVariants((theme) => ({
|
|
|
835
851
|
size: {
|
|
836
852
|
sm: {
|
|
837
853
|
height: '1.25rem',
|
|
838
|
-
padding: `0.125rem ${theme.spacing
|
|
854
|
+
padding: `0.125rem ${theme.spacing.xsPlus}`,
|
|
839
855
|
fontSize: theme.fontSize['2xs'],
|
|
840
856
|
},
|
|
841
857
|
md: {
|
|
@@ -1565,6 +1581,18 @@ Box.displayName = 'Box';
|
|
|
1565
1581
|
const Stack = ({ flexDirection = 'row', display = 'flex', gap = 'sm', ...rest }) => (jsx(Box, { display: display, flexDirection: flexDirection, gap: gap, ...rest }));
|
|
1566
1582
|
Stack.displayName = 'Stack';
|
|
1567
1583
|
|
|
1584
|
+
const HELPER_COLOR_MAP = {
|
|
1585
|
+
default: 'textSecondary',
|
|
1586
|
+
error: 'errorHover',
|
|
1587
|
+
success: 'successHover',
|
|
1588
|
+
warning: 'warningHover',
|
|
1589
|
+
};
|
|
1590
|
+
const FormHelperText = ({ id, status = 'default', ariaLive, className, children, }) => {
|
|
1591
|
+
const resolvedAriaLive = ariaLive ?? (status === 'error' ? 'assertive' : 'polite');
|
|
1592
|
+
return (jsx(Text, { id: id, variant: 'span', fontSize: 'xs', color: HELPER_COLOR_MAP[status], "aria-live": resolvedAriaLive, className: className, children: children }));
|
|
1593
|
+
};
|
|
1594
|
+
FormHelperText.displayName = 'FormHelperText';
|
|
1595
|
+
|
|
1568
1596
|
const TEXTFIELD_WRAPPER_VARIANTS = createVariants((theme) => {
|
|
1569
1597
|
const c = theme.colors;
|
|
1570
1598
|
return {
|
|
@@ -1687,18 +1715,11 @@ const ICON_BUTTON_SIZE_MAP = {
|
|
|
1687
1715
|
md: 'sm',
|
|
1688
1716
|
lg: 'md',
|
|
1689
1717
|
};
|
|
1690
|
-
/** Maps status to a theme color token used for the helper text. */
|
|
1691
|
-
const HELPER_COLOR_MAP$1 = {
|
|
1692
|
-
default: 'textSecondary',
|
|
1693
|
-
error: 'errorHover',
|
|
1694
|
-
success: 'successHover',
|
|
1695
|
-
warning: 'warningHover',
|
|
1696
|
-
};
|
|
1697
1718
|
/**
|
|
1698
1719
|
* Business logic for the TextField component: id resolution, ref merging,
|
|
1699
1720
|
* password visibility toggling and size/status derived tokens.
|
|
1700
1721
|
*/
|
|
1701
|
-
const useTextField = ({ id, ref, type, size,
|
|
1722
|
+
const useTextField = ({ id, ref, type, size, endAction, }) => {
|
|
1702
1723
|
const generatedId = useId();
|
|
1703
1724
|
const fieldId = id ?? generatedId;
|
|
1704
1725
|
const helperId = `${fieldId}-helper`;
|
|
@@ -1720,7 +1741,6 @@ const useTextField = ({ id, ref, type, size, status, endAction, }) => {
|
|
|
1720
1741
|
resolvedType,
|
|
1721
1742
|
iconSize: ICON_SIZE_MAP$1[size],
|
|
1722
1743
|
iconButtonSize: ICON_BUTTON_SIZE_MAP[size],
|
|
1723
|
-
helperColor: HELPER_COLOR_MAP$1[status],
|
|
1724
1744
|
hasEndSection: endAction !== undefined || isPassword,
|
|
1725
1745
|
focusInput,
|
|
1726
1746
|
};
|
|
@@ -1738,8 +1758,8 @@ const useTextField = ({ id, ref, type, size, status, endAction, }) => {
|
|
|
1738
1758
|
* @example <TextField label="Search" startIcon={SearchIcon} endAction={<ClearButton />} />
|
|
1739
1759
|
*/
|
|
1740
1760
|
const TextField = ({ ref, label, helperText, size = 'md', status = 'default', startIcon: StartIcon, endAction, type, id, disabled, required, ...rest }) => {
|
|
1741
|
-
const { fieldId, helperId, mergedRef, isPassword, showPassword, togglePassword, resolvedType, iconSize, iconButtonSize,
|
|
1742
|
-
return (jsxs(Stack, { flexDirection: 'column', gap: 'xs', children: [label !== undefined && (jsxs(Text, { variant: 'label', fontSize: 'sm', fontWeight: 'medium', color: 'textSecondary', htmlFor: fieldId, children: [label, required && (jsx(Text, { variant: 'span', color: 'errorMain', "aria-hidden": true, children: ' *' }))] })), jsxs("div", { className: TEXTFIELD_WRAPPER_VARIANTS({ size, status }), "data-disabled": disabled || undefined, children: [StartIcon && (jsx("span", { className: TEXTFIELD_STYLES.startIconWrap, onClick: focusInput, children: jsx(Icon, { icon: StartIcon, size: iconSize, strokeColor: 'textSecondary' }) })), jsx("input", { ref: mergedRef, id: fieldId, type: resolvedType, disabled: disabled, required: required, "aria-required": required || undefined, "aria-invalid": status === 'error' || undefined, "aria-describedby": helperText !== undefined ? helperId : undefined, className: TEXTFIELD_STYLES.input, ...rest }), hasEndSection && (jsxs("span", { className: TEXTFIELD_STYLES.endActionWrap, children: [endAction, isPassword && (jsx(IconButton, { icon: showPassword ? EyeSlashIcon : EyeIcon, ariaLabel: showPassword ? 'Hide password' : 'Show password', variant: 'text', color: 'neutral', size: iconButtonSize, type: 'button', tabIndex: -1, onClick: togglePassword }))] }))] }), helperText !== undefined && (jsx(
|
|
1761
|
+
const { fieldId, helperId, mergedRef, isPassword, showPassword, togglePassword, resolvedType, iconSize, iconButtonSize, hasEndSection, focusInput, } = useTextField({ id, ref, type, size, endAction });
|
|
1762
|
+
return (jsxs(Stack, { flexDirection: 'column', gap: 'xs', children: [label !== undefined && (jsxs(Text, { variant: 'label', fontSize: 'sm', fontWeight: 'medium', color: 'textSecondary', htmlFor: fieldId, children: [label, required && (jsx(Text, { variant: 'span', color: 'errorMain', "aria-hidden": true, children: ' *' }))] })), jsxs("div", { className: TEXTFIELD_WRAPPER_VARIANTS({ size, status }), "data-disabled": disabled || undefined, children: [StartIcon && (jsx("span", { className: TEXTFIELD_STYLES.startIconWrap, onClick: focusInput, children: jsx(Icon, { icon: StartIcon, size: iconSize, strokeColor: 'textSecondary' }) })), jsx("input", { ref: mergedRef, id: fieldId, type: resolvedType, disabled: disabled, required: required, "aria-required": required || undefined, "aria-invalid": status === 'error' || undefined, "aria-describedby": helperText !== undefined ? helperId : undefined, className: TEXTFIELD_STYLES.input, ...rest }), hasEndSection && (jsxs("span", { className: TEXTFIELD_STYLES.endActionWrap, children: [endAction, isPassword && (jsx(IconButton, { icon: showPassword ? EyeSlashIcon : EyeIcon, ariaLabel: showPassword ? 'Hide password' : 'Show password', variant: 'text', color: 'neutral', size: iconButtonSize, type: 'button', tabIndex: -1, onClick: togglePassword }))] }))] }), helperText !== undefined && (jsx(FormHelperText, { id: helperId, status: status, children: helperText }))] }));
|
|
1743
1763
|
};
|
|
1744
1764
|
TextField.displayName = 'TextField';
|
|
1745
1765
|
|
|
@@ -2175,21 +2195,15 @@ const SelectTrigger = ({ ref, size = 'md', status = 'default', open, hasValue, s
|
|
|
2175
2195
|
};
|
|
2176
2196
|
SelectTrigger.displayName = 'SelectTrigger';
|
|
2177
2197
|
|
|
2178
|
-
/** Maps status to a theme color token used for the helper text. */
|
|
2179
|
-
const HELPER_COLOR_MAP = {
|
|
2180
|
-
default: 'textSecondary',
|
|
2181
|
-
error: 'errorHover',
|
|
2182
|
-
success: 'successHover',
|
|
2183
|
-
warning: 'warningHover',
|
|
2184
|
-
};
|
|
2185
2198
|
/**
|
|
2186
2199
|
* Business logic for the Select component: id resolution, ref merging,
|
|
2187
2200
|
* controlled/uncontrolled value handling, open state, option grouping and
|
|
2188
2201
|
* focus restoration to the trigger when the menu closes.
|
|
2189
2202
|
*/
|
|
2190
|
-
const useSelect = ({ id, ref, value, defaultValue, onChange, options,
|
|
2203
|
+
const useSelect = ({ id, ref, value, defaultValue, onChange, options, disabled, }) => {
|
|
2191
2204
|
const generatedId = useId();
|
|
2192
2205
|
const fieldId = id ?? generatedId;
|
|
2206
|
+
const helperId = `${fieldId}-helper`;
|
|
2193
2207
|
const menuId = `${fieldId}-menu`;
|
|
2194
2208
|
const triggerRef = useRef(null);
|
|
2195
2209
|
const mergedRef = useMergedRefs(ref, triggerRef);
|
|
@@ -2234,6 +2248,7 @@ const useSelect = ({ id, ref, value, defaultValue, onChange, options, status, di
|
|
|
2234
2248
|
const close = useCallback(() => setOpen(false), []);
|
|
2235
2249
|
return {
|
|
2236
2250
|
fieldId,
|
|
2251
|
+
helperId,
|
|
2237
2252
|
menuId,
|
|
2238
2253
|
triggerRef,
|
|
2239
2254
|
mergedRef,
|
|
@@ -2244,19 +2259,187 @@ const useSelect = ({ id, ref, value, defaultValue, onChange, options, status, di
|
|
|
2244
2259
|
selectedOption,
|
|
2245
2260
|
groupedOptions,
|
|
2246
2261
|
handleSelect,
|
|
2247
|
-
helperColor: HELPER_COLOR_MAP[status],
|
|
2248
2262
|
};
|
|
2249
2263
|
};
|
|
2250
2264
|
|
|
2251
2265
|
const Select = ({ ref, value, defaultValue, onChange, options, label, helperText, placeholder, size = 'md', status = 'default', disabled, required, width, id, }) => {
|
|
2252
|
-
const { fieldId, menuId, triggerRef, mergedRef, open, toggle, close, currentValue, selectedOption, groupedOptions, handleSelect,
|
|
2253
|
-
return (jsxs(Stack, { flexDirection: 'column', gap: 'xs', style: { width: width ?? '100%' }, children: [label !== undefined && (jsxs(Text, { variant: 'label', fontSize: 'sm', fontWeight: 'medium', color: 'textSecondary', htmlFor: fieldId, children: [label, required && (jsx(Text, { variant: 'span', color: 'errorMain', "aria-hidden": true, children: ' *' }))] })), jsx(SelectTrigger, { ref: mergedRef, id: fieldId, size: size, status: status, open: open, hasValue: selectedOption !== undefined, disabled: disabled, required: required, "aria-haspopup": 'listbox', "aria-expanded": open, "aria-controls": menuId, "aria-invalid": status === 'error' || undefined, onClick: toggle, children: selectedOption !== undefined ? selectedOption.label : placeholder }), jsx(Menu, { open: open, onClose: close, anchorEl: triggerRef.current, id: menuId, children: Array.from(groupedOptions.entries()).map(([groupKey, groupOpts], groupIndex) => {
|
|
2266
|
+
const { fieldId, helperId, menuId, triggerRef, mergedRef, open, toggle, close, currentValue, selectedOption, groupedOptions, handleSelect, } = useSelect({ id, ref, value, defaultValue, onChange, options, disabled });
|
|
2267
|
+
return (jsxs(Stack, { flexDirection: 'column', gap: 'xs', style: { width: width ?? '100%' }, children: [label !== undefined && (jsxs(Text, { variant: 'label', fontSize: 'sm', fontWeight: 'medium', color: 'textSecondary', htmlFor: fieldId, children: [label, required && (jsx(Text, { variant: 'span', color: 'errorMain', "aria-hidden": true, children: ' *' }))] })), jsx(SelectTrigger, { ref: mergedRef, id: fieldId, size: size, status: status, open: open, hasValue: selectedOption !== undefined, disabled: disabled, required: required, "aria-haspopup": 'listbox', "aria-expanded": open, "aria-controls": menuId, "aria-invalid": status === 'error' || undefined, "aria-describedby": helperText !== undefined ? helperId : undefined, onClick: toggle, children: selectedOption !== undefined ? selectedOption.label : placeholder }), jsx(Menu, { open: open, onClose: close, anchorEl: triggerRef.current, id: menuId, children: Array.from(groupedOptions.entries()).map(([groupKey, groupOpts], groupIndex) => {
|
|
2254
2268
|
const items = groupOpts.map((opt) => (jsx(Menu.Item, { label: opt.label, icon: opt.icon, selected: opt.value === currentValue, disabled: opt.disabled, onClick: () => handleSelect(opt.value) }, opt.value)));
|
|
2255
2269
|
return groupKey !== undefined ? (jsx(Menu.Group, { label: groupKey, divider: groupIndex > 0, children: items }, groupKey)) : (jsx(Menu.Group, { divider: groupIndex > 0, children: items }, '__ungrouped'));
|
|
2256
|
-
}) }), helperText !== undefined && (jsx(
|
|
2270
|
+
}) }), helperText !== undefined && (jsx(FormHelperText, { id: helperId, status: status, children: helperText }))] }));
|
|
2257
2271
|
};
|
|
2258
2272
|
Select.displayName = 'Select';
|
|
2259
2273
|
|
|
2274
|
+
const CHECKBOX_ROOT_VARIANTS = createVariants((theme) => ({
|
|
2275
|
+
base: {
|
|
2276
|
+
display: 'inline-flex',
|
|
2277
|
+
alignItems: 'center',
|
|
2278
|
+
gap: theme.spacing.sm,
|
|
2279
|
+
cursor: 'pointer',
|
|
2280
|
+
userSelect: 'none',
|
|
2281
|
+
},
|
|
2282
|
+
variants: {
|
|
2283
|
+
disabled: {
|
|
2284
|
+
true: {
|
|
2285
|
+
cursor: 'not-allowed',
|
|
2286
|
+
opacity: theme.opacity.high,
|
|
2287
|
+
},
|
|
2288
|
+
false: {},
|
|
2289
|
+
},
|
|
2290
|
+
},
|
|
2291
|
+
defaultVariants: {
|
|
2292
|
+
disabled: 'false',
|
|
2293
|
+
},
|
|
2294
|
+
}), { id: 'checkbox-root' });
|
|
2295
|
+
const CHECKBOX_INPUT_VARIANTS = createVariants((theme) => {
|
|
2296
|
+
const c = theme.colors;
|
|
2297
|
+
return {
|
|
2298
|
+
base: {
|
|
2299
|
+
appearance: 'none',
|
|
2300
|
+
position: 'relative',
|
|
2301
|
+
margin: 0,
|
|
2302
|
+
borderWidth: '1px',
|
|
2303
|
+
borderStyle: 'solid',
|
|
2304
|
+
borderColor: c.borderStrong,
|
|
2305
|
+
borderRadius: theme.radius.sm,
|
|
2306
|
+
backgroundColor: c.surfacePaper,
|
|
2307
|
+
transition: `border-color ${theme.transition.fast}, background-color ${theme.transition.fast}`,
|
|
2308
|
+
cursor: 'pointer',
|
|
2309
|
+
'&:focus-visible': {
|
|
2310
|
+
outline: '2px solid transparent',
|
|
2311
|
+
boxShadow: `0 0 0 2px ${c.primarySubtleActive}`,
|
|
2312
|
+
},
|
|
2313
|
+
'&::after': {
|
|
2314
|
+
content: '""',
|
|
2315
|
+
position: 'absolute',
|
|
2316
|
+
left: '50%',
|
|
2317
|
+
top: '50%',
|
|
2318
|
+
transform: 'translate(-50%, -56%) rotate(45deg)',
|
|
2319
|
+
width: '0.25rem',
|
|
2320
|
+
height: '0.5rem',
|
|
2321
|
+
borderRight: `2px solid ${c.textInverse}`,
|
|
2322
|
+
borderBottom: `2px solid ${c.textInverse}`,
|
|
2323
|
+
opacity: 0,
|
|
2324
|
+
},
|
|
2325
|
+
'&:checked::after': {
|
|
2326
|
+
opacity: 1,
|
|
2327
|
+
},
|
|
2328
|
+
'&:indeterminate::after': {
|
|
2329
|
+
width: '0.5rem',
|
|
2330
|
+
height: '0',
|
|
2331
|
+
borderRight: '0',
|
|
2332
|
+
borderBottom: `2px solid ${c.textInverse}`,
|
|
2333
|
+
transform: 'translate(-50%, -50%)',
|
|
2334
|
+
opacity: 1,
|
|
2335
|
+
},
|
|
2336
|
+
},
|
|
2337
|
+
variants: {
|
|
2338
|
+
size: {
|
|
2339
|
+
sm: {
|
|
2340
|
+
width: '1rem',
|
|
2341
|
+
height: '1rem',
|
|
2342
|
+
},
|
|
2343
|
+
md: {
|
|
2344
|
+
width: '1.125rem',
|
|
2345
|
+
height: '1.125rem',
|
|
2346
|
+
},
|
|
2347
|
+
lg: {
|
|
2348
|
+
width: '1.25rem',
|
|
2349
|
+
height: '1.25rem',
|
|
2350
|
+
},
|
|
2351
|
+
},
|
|
2352
|
+
status: {
|
|
2353
|
+
default: {
|
|
2354
|
+
'&:checked, &:indeterminate': {
|
|
2355
|
+
borderColor: c.primaryMain,
|
|
2356
|
+
backgroundColor: c.primaryMain,
|
|
2357
|
+
},
|
|
2358
|
+
},
|
|
2359
|
+
error: {
|
|
2360
|
+
borderColor: c.errorMain,
|
|
2361
|
+
'&:checked, &:indeterminate': {
|
|
2362
|
+
borderColor: c.errorMain,
|
|
2363
|
+
backgroundColor: c.errorMain,
|
|
2364
|
+
},
|
|
2365
|
+
},
|
|
2366
|
+
success: {
|
|
2367
|
+
borderColor: c.successMain,
|
|
2368
|
+
'&:checked, &:indeterminate': {
|
|
2369
|
+
borderColor: c.successMain,
|
|
2370
|
+
backgroundColor: c.successMain,
|
|
2371
|
+
},
|
|
2372
|
+
},
|
|
2373
|
+
warning: {
|
|
2374
|
+
borderColor: c.warningMain,
|
|
2375
|
+
'&:checked, &:indeterminate': {
|
|
2376
|
+
borderColor: c.warningMain,
|
|
2377
|
+
backgroundColor: c.warningMain,
|
|
2378
|
+
},
|
|
2379
|
+
},
|
|
2380
|
+
},
|
|
2381
|
+
disabled: {
|
|
2382
|
+
true: {
|
|
2383
|
+
cursor: 'not-allowed',
|
|
2384
|
+
backgroundColor: c.disabledMain,
|
|
2385
|
+
borderColor: c.disabledMain,
|
|
2386
|
+
'&:checked, &:indeterminate': {
|
|
2387
|
+
backgroundColor: c.disabledText,
|
|
2388
|
+
borderColor: c.disabledText,
|
|
2389
|
+
},
|
|
2390
|
+
},
|
|
2391
|
+
false: {},
|
|
2392
|
+
},
|
|
2393
|
+
},
|
|
2394
|
+
defaultVariants: {
|
|
2395
|
+
size: 'md',
|
|
2396
|
+
status: 'default',
|
|
2397
|
+
disabled: 'false',
|
|
2398
|
+
},
|
|
2399
|
+
};
|
|
2400
|
+
}, { id: 'checkbox-input' });
|
|
2401
|
+
const CHECKBOX_STYLES = createStyles((theme) => ({
|
|
2402
|
+
wrapper: {
|
|
2403
|
+
display: 'inline-flex',
|
|
2404
|
+
flexDirection: 'column',
|
|
2405
|
+
gap: theme.spacing.xs,
|
|
2406
|
+
},
|
|
2407
|
+
helper: {
|
|
2408
|
+
marginLeft: `calc(1.125rem + ${theme.spacing.sm})`,
|
|
2409
|
+
},
|
|
2410
|
+
}), { id: 'checkbox-extra' });
|
|
2411
|
+
|
|
2412
|
+
/** Handles id generation, ref merging and native indeterminate state sync. */
|
|
2413
|
+
const useCheckbox = ({ id, ref, indeterminate = false }) => {
|
|
2414
|
+
const generatedId = useId();
|
|
2415
|
+
const checkboxId = id ?? generatedId;
|
|
2416
|
+
const helperId = `${checkboxId}-helper`;
|
|
2417
|
+
const inputRef = useRef(null);
|
|
2418
|
+
const mergedRef = useMergedRefs(ref, inputRef);
|
|
2419
|
+
useEffect(() => {
|
|
2420
|
+
if (inputRef.current) {
|
|
2421
|
+
inputRef.current.indeterminate = indeterminate;
|
|
2422
|
+
}
|
|
2423
|
+
}, [indeterminate]);
|
|
2424
|
+
return {
|
|
2425
|
+
checkboxId,
|
|
2426
|
+
helperId,
|
|
2427
|
+
mergedRef,
|
|
2428
|
+
inputRef,
|
|
2429
|
+
};
|
|
2430
|
+
};
|
|
2431
|
+
|
|
2432
|
+
const Checkbox = ({ ref, label, helperText, size = 'md', status = 'default', indeterminate = false, error, id, disabled, required, ...rest }) => {
|
|
2433
|
+
const resolvedStatus = error ? 'error' : status;
|
|
2434
|
+
const { checkboxId, helperId, mergedRef } = useCheckbox({ id, ref, indeterminate });
|
|
2435
|
+
return (jsxs("div", { className: CHECKBOX_STYLES.wrapper, children: [jsxs("label", { htmlFor: checkboxId, className: CHECKBOX_ROOT_VARIANTS({ disabled: disabled ? 'true' : 'false' }), children: [jsx("input", { ref: mergedRef, id: checkboxId, type: 'checkbox', disabled: disabled, required: required, "aria-required": required || undefined, "aria-invalid": resolvedStatus === 'error' || undefined, "aria-describedby": helperText !== undefined ? helperId : undefined, className: CHECKBOX_INPUT_VARIANTS({
|
|
2436
|
+
size,
|
|
2437
|
+
status: resolvedStatus,
|
|
2438
|
+
disabled: disabled ? 'true' : 'false',
|
|
2439
|
+
}), ...rest }), label !== undefined && (jsx(Text, { variant: 'span', fontSize: size === 'lg' ? 'md' : 'sm', color: disabled ? 'textDisabled' : 'textSecondary', children: label }))] }), helperText !== undefined && (jsx(FormHelperText, { id: helperId, status: resolvedStatus, className: CHECKBOX_STYLES.helper, children: helperText }))] }));
|
|
2440
|
+
};
|
|
2441
|
+
Checkbox.displayName = 'Checkbox';
|
|
2442
|
+
|
|
2260
2443
|
const CARD_VARIANTS = createVariants((theme) => ({
|
|
2261
2444
|
base: {
|
|
2262
2445
|
boxSizing: 'border-box',
|
|
@@ -2340,6 +2523,361 @@ const Grid = ({ display = 'grid', columns, rows, autoFlow, autoColumns, autoRows
|
|
|
2340
2523
|
};
|
|
2341
2524
|
Grid.displayName = 'Grid';
|
|
2342
2525
|
|
|
2526
|
+
const DrawerContext = createContext({
|
|
2527
|
+
isExpanded: true,
|
|
2528
|
+
});
|
|
2529
|
+
const useDrawerContext = () => useContext(DrawerContext);
|
|
2530
|
+
|
|
2531
|
+
const TRANSITION$2 = `${DEFAULT_TRANSITION_DURATION_MS}ms ease`;
|
|
2532
|
+
const DRAWER_STYLES = createStyles((theme) => ({
|
|
2533
|
+
root: ({ isExpanded }) => ({
|
|
2534
|
+
display: 'flex',
|
|
2535
|
+
flexDirection: 'column',
|
|
2536
|
+
width: isExpanded ? EXPANDED_DRAWER_WIDTH : COLLAPSED_DRAWER_WIDTH,
|
|
2537
|
+
transition: `width ${theme.transition.slow}`,
|
|
2538
|
+
overflow: 'hidden',
|
|
2539
|
+
backgroundColor: theme.colors.surfacePaper,
|
|
2540
|
+
borderRight: `1px solid ${theme.colors.borderMain}`,
|
|
2541
|
+
boxSizing: 'border-box',
|
|
2542
|
+
flexShrink: 0,
|
|
2543
|
+
}),
|
|
2544
|
+
/** Temporary variant: slides in from the left as a fixed portal overlay. */
|
|
2545
|
+
temporaryPanel: {
|
|
2546
|
+
position: 'fixed',
|
|
2547
|
+
top: 0,
|
|
2548
|
+
left: 0,
|
|
2549
|
+
bottom: 0,
|
|
2550
|
+
width: EXPANDED_DRAWER_WIDTH,
|
|
2551
|
+
zIndex: theme.zIndex.modal,
|
|
2552
|
+
display: 'flex',
|
|
2553
|
+
flexDirection: 'column',
|
|
2554
|
+
backgroundColor: theme.colors.surfacePaper,
|
|
2555
|
+
borderRight: `1px solid ${theme.colors.borderMain}`,
|
|
2556
|
+
boxSizing: 'border-box',
|
|
2557
|
+
overflowY: 'auto',
|
|
2558
|
+
overflowX: 'hidden',
|
|
2559
|
+
willChange: 'transform',
|
|
2560
|
+
transform: 'translateX(-100%)',
|
|
2561
|
+
transition: `transform ${TRANSITION$2}`,
|
|
2562
|
+
boxShadow: theme.shadows.xl,
|
|
2563
|
+
},
|
|
2564
|
+
temporaryPanelVisible: {
|
|
2565
|
+
transform: 'translateX(0)',
|
|
2566
|
+
},
|
|
2567
|
+
}));
|
|
2568
|
+
|
|
2569
|
+
/**
|
|
2570
|
+
* Responsive breakpoints (min-width, mobile-first).
|
|
2571
|
+
* Keep in sync with themeBreakpoints.
|
|
2572
|
+
*/
|
|
2573
|
+
const BREAKPOINTS = {
|
|
2574
|
+
sm: 640};
|
|
2575
|
+
/** Max-width media query strings (max = breakpoint - 1px). */
|
|
2576
|
+
const MEDIA_MAX = {
|
|
2577
|
+
sm: `max-width: ${BREAKPOINTS.sm - 1}px`};
|
|
2578
|
+
|
|
2579
|
+
const MOBILE_MQ = `(max-width: ${BREAKPOINTS.sm - 1}px)`;
|
|
2580
|
+
/**
|
|
2581
|
+
* Resolves the effective drawer variant based on the explicit `variant` prop
|
|
2582
|
+
* and the current viewport width.
|
|
2583
|
+
*
|
|
2584
|
+
* - If `variant` is explicitly provided (`'permanent'` or `'temporary'`), returns it as-is.
|
|
2585
|
+
* - Otherwise, auto-detects: `'temporary'` on mobile (< sm breakpoint), `'permanent'` on desktop.
|
|
2586
|
+
*
|
|
2587
|
+
* Reacts to viewport changes (window resize) so switching between mobile and desktop
|
|
2588
|
+
* automatically updates the variant without requiring a page reload.
|
|
2589
|
+
*/
|
|
2590
|
+
const useDrawerVariant = (variant) => {
|
|
2591
|
+
const [isMobile, setIsMobile] = useState(() => {
|
|
2592
|
+
if (typeof window === 'undefined') {
|
|
2593
|
+
return false;
|
|
2594
|
+
}
|
|
2595
|
+
return window.matchMedia(MOBILE_MQ).matches;
|
|
2596
|
+
});
|
|
2597
|
+
useEffect(() => {
|
|
2598
|
+
if (typeof window === 'undefined') {
|
|
2599
|
+
return;
|
|
2600
|
+
}
|
|
2601
|
+
const mq = window.matchMedia(MOBILE_MQ);
|
|
2602
|
+
const handler = (e) => setIsMobile(e.matches);
|
|
2603
|
+
mq.addEventListener('change', handler);
|
|
2604
|
+
return () => mq.removeEventListener('change', handler);
|
|
2605
|
+
}, []);
|
|
2606
|
+
if (variant !== undefined) {
|
|
2607
|
+
return variant;
|
|
2608
|
+
}
|
|
2609
|
+
return isMobile ? 'temporary' : 'permanent';
|
|
2610
|
+
};
|
|
2611
|
+
|
|
2612
|
+
const TRANSITION$1 = `${DEFAULT_TRANSITION_DURATION_MS}ms ease`;
|
|
2613
|
+
const BACKDROP_STYLES = createStyles((theme) => ({
|
|
2614
|
+
root: {
|
|
2615
|
+
position: 'fixed',
|
|
2616
|
+
inset: 0,
|
|
2617
|
+
zIndex: theme.zIndex.modal - 1,
|
|
2618
|
+
backgroundColor: 'rgba(0, 0, 0, 0)',
|
|
2619
|
+
transition: `background-color ${TRANSITION$1}`,
|
|
2620
|
+
},
|
|
2621
|
+
visible: {
|
|
2622
|
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
2623
|
+
},
|
|
2624
|
+
}), { id: 'backdrop' });
|
|
2625
|
+
|
|
2626
|
+
/**
|
|
2627
|
+
* Semi-transparent full-screen overlay used to visually block page content
|
|
2628
|
+
* while a modal element (dialog, temporary drawer…) is open.
|
|
2629
|
+
*
|
|
2630
|
+
* The `visible` prop drives the opacity transition: `false` = transparent,
|
|
2631
|
+
* `true` = `rgba(0,0,0,0.5)`. Mount/unmount is managed by the parent.
|
|
2632
|
+
*
|
|
2633
|
+
* @example
|
|
2634
|
+
* <Backdrop visible={isFadingIn} onClick={onClose} />
|
|
2635
|
+
*/
|
|
2636
|
+
const Backdrop = ({ visible, onClick }) => (jsx("div", { className: cx(BACKDROP_STYLES.root, visible && BACKDROP_STYLES.visible), onClick: onClick, "aria-hidden": true }));
|
|
2637
|
+
Backdrop.displayName = 'Backdrop';
|
|
2638
|
+
|
|
2639
|
+
/**
|
|
2640
|
+
* Manages mount/unmount transitions with a two-phase approach (visible → fading-in).
|
|
2641
|
+
*
|
|
2642
|
+
* Opening sequence:
|
|
2643
|
+
* 1. `useLayoutEffect` sets `isVisible=true` synchronously before the browser paints
|
|
2644
|
+
* → element is in the DOM at its initial hidden state on the very first frame.
|
|
2645
|
+
* 2. Double `requestAnimationFrame` in `useEffect` waits for two rendered frames
|
|
2646
|
+
* before setting `isFadingIn=true`, guaranteeing the CSS transition always starts
|
|
2647
|
+
* from the painted hidden state (prevents flickering).
|
|
2648
|
+
*
|
|
2649
|
+
* Closing sequence:
|
|
2650
|
+
* `isFadingIn=false` → CSS transition plays → after `duration` ms → `isVisible=false`.
|
|
2651
|
+
*
|
|
2652
|
+
* @param isOpen - Whether the element should be shown.
|
|
2653
|
+
* @param duration - Transition duration in milliseconds (defaults to `DEFAULT_TRANSITION_DURATION_MS`).
|
|
2654
|
+
*/
|
|
2655
|
+
const useTransitionRender = (isOpen, duration = DEFAULT_TRANSITION_DURATION_MS) => {
|
|
2656
|
+
const [isVisible, setIsVisible] = useState(isOpen);
|
|
2657
|
+
const [isFadingIn, setIsFadingIn] = useState(isOpen);
|
|
2658
|
+
// Mount synchronously before paint so the element is in the DOM at opacity:0
|
|
2659
|
+
// on the very first frame — no extra render cycle between null and the initial state.
|
|
2660
|
+
useLayoutEffect(() => {
|
|
2661
|
+
if (isOpen) {
|
|
2662
|
+
setIsVisible(true);
|
|
2663
|
+
}
|
|
2664
|
+
}, [isOpen]);
|
|
2665
|
+
useEffect(() => {
|
|
2666
|
+
if (isOpen) {
|
|
2667
|
+
// Double RAF: frame 1 → element rendered; frame 2 → element painted at hidden
|
|
2668
|
+
// state → THEN trigger the CSS transition.
|
|
2669
|
+
let raf2;
|
|
2670
|
+
const raf1 = requestAnimationFrame(() => {
|
|
2671
|
+
raf2 = requestAnimationFrame(() => setIsFadingIn(true));
|
|
2672
|
+
});
|
|
2673
|
+
return () => {
|
|
2674
|
+
cancelAnimationFrame(raf1);
|
|
2675
|
+
cancelAnimationFrame(raf2);
|
|
2676
|
+
};
|
|
2677
|
+
}
|
|
2678
|
+
else {
|
|
2679
|
+
setIsFadingIn(false);
|
|
2680
|
+
const timeout = setTimeout(() => setIsVisible(false), duration);
|
|
2681
|
+
return () => clearTimeout(timeout);
|
|
2682
|
+
}
|
|
2683
|
+
}, [isOpen, duration]);
|
|
2684
|
+
return { isVisible, isFadingIn };
|
|
2685
|
+
};
|
|
2686
|
+
|
|
2687
|
+
/**
|
|
2688
|
+
* Locks scrolling on `document.body` while `active` is true.
|
|
2689
|
+
*
|
|
2690
|
+
* Preserves the current scroll position and keeps the scrollbar gutter
|
|
2691
|
+
* (`overflow-y: scroll`) to avoid horizontal layout shift when the scrollbar
|
|
2692
|
+
* disappears. The original styles and scroll position are restored on cleanup.
|
|
2693
|
+
*
|
|
2694
|
+
* @example useBodyScrollLock(isDialogOpen)
|
|
2695
|
+
*/
|
|
2696
|
+
const useBodyScrollLock = (active) => {
|
|
2697
|
+
useEffect(() => {
|
|
2698
|
+
if (!active) {
|
|
2699
|
+
return;
|
|
2700
|
+
}
|
|
2701
|
+
const scrollY = window.scrollY;
|
|
2702
|
+
const body = document.body;
|
|
2703
|
+
body.style.position = 'fixed';
|
|
2704
|
+
body.style.top = `-${scrollY}px`;
|
|
2705
|
+
body.style.overflowY = 'scroll';
|
|
2706
|
+
body.style.width = '100%';
|
|
2707
|
+
return () => {
|
|
2708
|
+
body.style.position = '';
|
|
2709
|
+
body.style.top = '';
|
|
2710
|
+
body.style.overflowY = '';
|
|
2711
|
+
body.style.width = '';
|
|
2712
|
+
window.scrollTo(0, scrollY);
|
|
2713
|
+
};
|
|
2714
|
+
}, [active]);
|
|
2715
|
+
};
|
|
2716
|
+
|
|
2717
|
+
const DrawerHeader = ({ children, role, ariaLabel, ariaLabelledBy, ariaDescribedBy, ...rest }) => {
|
|
2718
|
+
return (jsx(Box, { px: 'sm', py: 'xs', role: role, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, ...rest, children: children }));
|
|
2719
|
+
};
|
|
2720
|
+
|
|
2721
|
+
const DrawerBody = ({ children, role, ariaLabel, ariaLabelledBy, ariaDescribedBy, ...rest }) => {
|
|
2722
|
+
return (jsx(Stack, { px: 'sm', py: 'xs', height: '100%', flexDirection: 'column', gap: 'none', role: role, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, overflow: 'auto', ...rest, children: children }));
|
|
2723
|
+
};
|
|
2724
|
+
|
|
2725
|
+
const DrawerFooter = ({ children, role, ariaLabel, ariaLabelledBy, ariaDescribedBy, ...rest }) => {
|
|
2726
|
+
return (jsx(Box, { px: 'sm', py: 'xs', role: role, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, ...rest, children: children }));
|
|
2727
|
+
};
|
|
2728
|
+
|
|
2729
|
+
const DRAWER_ITEM_STYLES = createStyles((theme) => ({
|
|
2730
|
+
root: ({ selected, isExpanded }) => ({
|
|
2731
|
+
position: 'relative',
|
|
2732
|
+
display: 'flex',
|
|
2733
|
+
alignItems: 'center',
|
|
2734
|
+
width: isExpanded ? EXPANDED_DRAWER_ITEM_WIDTH : COLLAPSED_DRAWER_ITEM_WIDTH,
|
|
2735
|
+
gap: theme.spacing.sm,
|
|
2736
|
+
padding: `0 ${theme.spacing.smPlus}`,
|
|
2737
|
+
border: '1px solid transparent',
|
|
2738
|
+
borderRadius: theme.radius.md,
|
|
2739
|
+
fontFamily: 'inherit',
|
|
2740
|
+
fontSize: theme.fontSize.sm,
|
|
2741
|
+
userSelect: 'none',
|
|
2742
|
+
cursor: 'pointer',
|
|
2743
|
+
outline: 'none',
|
|
2744
|
+
textDecoration: 'none',
|
|
2745
|
+
boxSizing: 'border-box',
|
|
2746
|
+
transition: `width ${theme.transition.slow}, background-color ${theme.transition.fast}, color ${theme.transition.fast}`,
|
|
2747
|
+
whiteSpace: 'nowrap',
|
|
2748
|
+
overflow: 'hidden',
|
|
2749
|
+
height: DEFAULT_DRAWER_ITEM_SIZE,
|
|
2750
|
+
...(selected
|
|
2751
|
+
? {
|
|
2752
|
+
backgroundColor: theme.colors.primarySubtle,
|
|
2753
|
+
color: theme.colors.primaryMain,
|
|
2754
|
+
':hover': { backgroundColor: theme.colors.primarySubtleHover, color: theme.colors.primaryHover },
|
|
2755
|
+
':active': { backgroundColor: theme.colors.primarySubtleActive, color: theme.colors.primaryActive },
|
|
2756
|
+
}
|
|
2757
|
+
: {
|
|
2758
|
+
backgroundColor: 'transparent',
|
|
2759
|
+
color: theme.colors.defaultMain,
|
|
2760
|
+
':hover:not(:disabled)': { backgroundColor: theme.colors.defaultSubtleHover, color: theme.colors.defaultHover },
|
|
2761
|
+
':active:not(:disabled)': { backgroundColor: theme.colors.defaultSubtleActive, color: theme.colors.defaultActive },
|
|
2762
|
+
}),
|
|
2763
|
+
':focus-visible': { boxShadow: theme.shadows.focus },
|
|
2764
|
+
':disabled': { cursor: 'not-allowed', opacity: theme.opacity.high },
|
|
2765
|
+
}),
|
|
2766
|
+
iconWrap: {
|
|
2767
|
+
display: 'flex',
|
|
2768
|
+
alignItems: 'center',
|
|
2769
|
+
justifyContent: 'center',
|
|
2770
|
+
flexShrink: 0,
|
|
2771
|
+
},
|
|
2772
|
+
labelWrapper: ({ isFadingIn }) => ({
|
|
2773
|
+
display: 'flex',
|
|
2774
|
+
alignItems: 'center',
|
|
2775
|
+
flexDirection: 'row',
|
|
2776
|
+
gap: theme.spacing.sm,
|
|
2777
|
+
width: '100%',
|
|
2778
|
+
justifyContent: 'space-between',
|
|
2779
|
+
flex: 1,
|
|
2780
|
+
overflow: 'hidden',
|
|
2781
|
+
minWidth: 0,
|
|
2782
|
+
opacity: isFadingIn ? 1 : 0,
|
|
2783
|
+
transition: `opacity ${DEFAULT_TRANSITION_DURATION_MS}ms ease`,
|
|
2784
|
+
}),
|
|
2785
|
+
label: {
|
|
2786
|
+
flex: 1,
|
|
2787
|
+
overflow: 'hidden',
|
|
2788
|
+
textOverflow: 'ellipsis',
|
|
2789
|
+
fontWeight: theme.fontWeight.medium,
|
|
2790
|
+
},
|
|
2791
|
+
endContent: {
|
|
2792
|
+
display: 'flex',
|
|
2793
|
+
alignItems: 'center',
|
|
2794
|
+
flexShrink: 0,
|
|
2795
|
+
marginLeft: 'auto',
|
|
2796
|
+
},
|
|
2797
|
+
}));
|
|
2798
|
+
|
|
2799
|
+
/**
|
|
2800
|
+
* Navigation/action item for use inside Drawer.Body or Drawer.Footer.
|
|
2801
|
+
*
|
|
2802
|
+
* - In **expanded** mode: shows icon + label (fade-in) + optional end content.
|
|
2803
|
+
* - In **collapsed** mode: shows only the icon; the label fades out before unmounting
|
|
2804
|
+
* and appears as a right-side tooltip on hover.
|
|
2805
|
+
* - Renders as `<a>` when `href` is provided, otherwise as `<button>`.
|
|
2806
|
+
* - The `selected` prop applies a primary-color highlight.
|
|
2807
|
+
*/
|
|
2808
|
+
const DrawerItem = ({ startIcon, label, selected = false, endContent, href, onClick, disabled = false, ariaLabel, ariaLabelledBy, ariaDescribedBy, ariaControls, ariaExpanded, ariaHasPopup, ariaCurrent, }) => {
|
|
2809
|
+
const { isExpanded } = useDrawerContext();
|
|
2810
|
+
const { isVisible: isLabelVisible, isFadingIn: isLabelFadingIn } = useTransitionRender(isExpanded);
|
|
2811
|
+
const rootClassName = DRAWER_ITEM_STYLES.root({ selected, isExpanded });
|
|
2812
|
+
const computedAriaLabel = ariaLabel ?? (!isExpanded ? label : undefined);
|
|
2813
|
+
const computedAriaCurrent = ariaCurrent ?? (selected ? 'page' : undefined);
|
|
2814
|
+
const handleClick = (e) => {
|
|
2815
|
+
if (disabled) {
|
|
2816
|
+
e.preventDefault();
|
|
2817
|
+
return;
|
|
2818
|
+
}
|
|
2819
|
+
onClick?.();
|
|
2820
|
+
};
|
|
2821
|
+
const innerContent = (jsxs(Stack, { width: '100%', justifyContent: 'start', children: [jsx("span", { className: DRAWER_ITEM_STYLES.iconWrap, children: jsx(Icon, { icon: startIcon, size: 'md' }) }), isLabelVisible && (jsxs("span", { className: DRAWER_ITEM_STYLES.labelWrapper({ isFadingIn: isLabelFadingIn }), children: [jsx(Text, { variant: 'span', fontSize: 'sm', fontWeight: 'medium', className: DRAWER_ITEM_STYLES.label, textAlign: 'start', children: label }), endContent && (jsx("span", { className: DRAWER_ITEM_STYLES.endContent, children: endContent }))] }))] }));
|
|
2822
|
+
const item = href ? (jsx("a", { href: href, className: rootClassName, "aria-disabled": disabled || undefined, "aria-current": computedAriaCurrent, "aria-label": computedAriaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-controls": ariaControls, "aria-expanded": ariaExpanded, "aria-haspopup": ariaHasPopup, tabIndex: disabled ? -1 : undefined, onClick: handleClick, children: innerContent })) : (jsx("button", { type: 'button', className: rootClassName, disabled: disabled, "aria-current": computedAriaCurrent, "aria-label": computedAriaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-controls": ariaControls, "aria-expanded": ariaExpanded, "aria-haspopup": ariaHasPopup, onClick: handleClick, children: innerContent }));
|
|
2823
|
+
return (jsx(Tooltip, { label: label, placement: 'right', inline: true, withArrow: true, disabled: isExpanded || disabled, children: item }));
|
|
2824
|
+
};
|
|
2825
|
+
DrawerItem.displayName = 'DrawerItem';
|
|
2826
|
+
|
|
2827
|
+
/**
|
|
2828
|
+
* Internal component: renders the temporary drawer as a fixed portal overlay
|
|
2829
|
+
* that slides in from the left with a backdrop, animated via `useTransitionRender`.
|
|
2830
|
+
*/
|
|
2831
|
+
const DrawerTemporaryPanel = ({ isExpanded, onClose, children, role, ariaLabel, ariaLabelledBy, ariaDescribedBy, }) => {
|
|
2832
|
+
const { isVisible, isFadingIn } = useTransitionRender(isExpanded);
|
|
2833
|
+
useBodyScrollLock(isExpanded);
|
|
2834
|
+
useKeyPress({ Escape: onClose }, { enabled: isExpanded });
|
|
2835
|
+
if (!isVisible) {
|
|
2836
|
+
return null;
|
|
2837
|
+
}
|
|
2838
|
+
return createPortal(jsxs(Fragment$1, { children: [jsx(Backdrop, { visible: isFadingIn, onClick: onClose }), jsx(DrawerContext.Provider, { value: { isExpanded: true }, children: jsx("nav", { className: cx(DRAWER_STYLES.temporaryPanel, isFadingIn && DRAWER_STYLES.temporaryPanelVisible), role: role, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, children: children }) })] }), document.body);
|
|
2839
|
+
};
|
|
2840
|
+
DrawerTemporaryPanel.displayName = 'DrawerTemporaryPanel';
|
|
2841
|
+
// ─── Main Drawer ─────────────────────────────────────────────────────────────
|
|
2842
|
+
/**
|
|
2843
|
+
* Collapsible side navigation drawer with controlled expanded/collapsed state.
|
|
2844
|
+
*
|
|
2845
|
+
* **Variants**
|
|
2846
|
+
* - `'permanent'` (default on desktop): inline drawer that pushes page content.
|
|
2847
|
+
* Toggles between `expanded` and `collapsed` states with animated width.
|
|
2848
|
+
* - `'temporary'` (default on mobile): portal overlay with a backdrop that slides
|
|
2849
|
+
* in from the left. `isExpanded` controls open/closed; `onClose` is called on
|
|
2850
|
+
* backdrop click or Escape.
|
|
2851
|
+
* - Omit `variant` to auto-detect based on viewport width.
|
|
2852
|
+
*
|
|
2853
|
+
* @example Permanent
|
|
2854
|
+
* <Drawer isExpanded={open} onExpandedChange={setOpen} height="100dvh">
|
|
2855
|
+
* <Drawer.Header>…</Drawer.Header>
|
|
2856
|
+
* <Drawer.Body>
|
|
2857
|
+
* <Drawer.Item startIcon={HomeIcon} label="Home" selected />
|
|
2858
|
+
* </Drawer.Body>
|
|
2859
|
+
* </Drawer>
|
|
2860
|
+
*
|
|
2861
|
+
* @example Temporary
|
|
2862
|
+
* <Drawer variant="temporary" isExpanded={open} onClose={() => setOpen(false)}>
|
|
2863
|
+
* …
|
|
2864
|
+
* </Drawer>
|
|
2865
|
+
*/
|
|
2866
|
+
const DrawerBase = ({ height = '100dvh', isExpanded, onExpandedChange, onClose, variant, children, role = 'navigation', ariaLabel = 'Navigation', ariaLabelledBy, ariaDescribedBy, }) => {
|
|
2867
|
+
const resolvedVariant = useDrawerVariant(variant);
|
|
2868
|
+
const handleClose = onClose ?? (() => onExpandedChange?.(false));
|
|
2869
|
+
if (resolvedVariant === 'temporary') {
|
|
2870
|
+
return (jsx(DrawerTemporaryPanel, { isExpanded: isExpanded, onClose: handleClose, height: height, role: role, ariaLabel: ariaLabel, ariaLabelledBy: ariaLabelledBy, ariaDescribedBy: ariaDescribedBy, children: children }));
|
|
2871
|
+
}
|
|
2872
|
+
return (jsx(DrawerContext.Provider, { value: { isExpanded }, children: jsx("nav", { className: DRAWER_STYLES.root({ isExpanded }), style: { height }, role: role, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, children: children }) }));
|
|
2873
|
+
};
|
|
2874
|
+
DrawerBase.displayName = 'Drawer';
|
|
2875
|
+
const Drawer = DrawerBase;
|
|
2876
|
+
Drawer.Header = DrawerHeader;
|
|
2877
|
+
Drawer.Body = DrawerBody;
|
|
2878
|
+
Drawer.Footer = DrawerFooter;
|
|
2879
|
+
Drawer.Item = DrawerItem;
|
|
2880
|
+
|
|
2343
2881
|
const AlertContext = createContext({
|
|
2344
2882
|
variant: 'default',
|
|
2345
2883
|
accentColor: 'defaultActive',
|
|
@@ -2411,31 +2949,8 @@ const Alert = AlertBase;
|
|
|
2411
2949
|
Alert.Title = AlertTitle;
|
|
2412
2950
|
Alert.Body = AlertBody;
|
|
2413
2951
|
|
|
2414
|
-
/**
|
|
2415
|
-
* Responsive breakpoints (min-width, mobile-first).
|
|
2416
|
-
* Keep in sync with themeBreakpoints.
|
|
2417
|
-
*/
|
|
2418
|
-
const BREAKPOINTS = {
|
|
2419
|
-
sm: 640};
|
|
2420
|
-
/** Max-width media query strings (max = breakpoint - 1px). */
|
|
2421
|
-
const MEDIA_MAX = {
|
|
2422
|
-
sm: `max-width: ${BREAKPOINTS.sm - 1}px`};
|
|
2423
|
-
|
|
2424
|
-
/** Default duration in milliseconds for mount/unmount transition animations. */
|
|
2425
|
-
const DEFAULT_TRANSITION_DURATION_MS = 250;
|
|
2426
|
-
|
|
2427
2952
|
const TRANSITION = `${DEFAULT_TRANSITION_DURATION_MS}ms ease`;
|
|
2428
2953
|
const DIALOG_STYLES = createStyles((theme) => ({
|
|
2429
|
-
backdrop: {
|
|
2430
|
-
position: 'fixed',
|
|
2431
|
-
inset: 0,
|
|
2432
|
-
zIndex: theme.zIndex.modal - 1,
|
|
2433
|
-
backgroundColor: 'rgba(0, 0, 0, 0)',
|
|
2434
|
-
transition: `background-color ${TRANSITION}`,
|
|
2435
|
-
},
|
|
2436
|
-
backdropVisible: {
|
|
2437
|
-
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
2438
|
-
},
|
|
2439
2954
|
panel: {
|
|
2440
2955
|
position: 'fixed',
|
|
2441
2956
|
inset: 0,
|
|
@@ -2493,84 +3008,6 @@ const DialogContext = createContext({
|
|
|
2493
3008
|
CloseIconComponent: null,
|
|
2494
3009
|
});
|
|
2495
3010
|
|
|
2496
|
-
/**
|
|
2497
|
-
* Manages mount/unmount transitions with a two-phase approach (visible → fading-in).
|
|
2498
|
-
*
|
|
2499
|
-
* Opening sequence:
|
|
2500
|
-
* 1. `useLayoutEffect` sets `isVisible=true` synchronously before the browser paints
|
|
2501
|
-
* → element is in the DOM at its initial hidden state on the very first frame.
|
|
2502
|
-
* 2. Double `requestAnimationFrame` in `useEffect` waits for two rendered frames
|
|
2503
|
-
* before setting `isFadingIn=true`, guaranteeing the CSS transition always starts
|
|
2504
|
-
* from the painted hidden state (prevents flickering).
|
|
2505
|
-
*
|
|
2506
|
-
* Closing sequence:
|
|
2507
|
-
* `isFadingIn=false` → CSS transition plays → after `duration` ms → `isVisible=false`.
|
|
2508
|
-
*
|
|
2509
|
-
* @param isOpen - Whether the element should be shown.
|
|
2510
|
-
* @param duration - Transition duration in milliseconds (defaults to `DEFAULT_TRANSITION_DURATION_MS`).
|
|
2511
|
-
*/
|
|
2512
|
-
const useTransitionRender = (isOpen, duration = DEFAULT_TRANSITION_DURATION_MS) => {
|
|
2513
|
-
const [isVisible, setIsVisible] = useState(isOpen);
|
|
2514
|
-
const [isFadingIn, setIsFadingIn] = useState(isOpen);
|
|
2515
|
-
// Mount synchronously before paint so the element is in the DOM at opacity:0
|
|
2516
|
-
// on the very first frame — no extra render cycle between null and the initial state.
|
|
2517
|
-
useLayoutEffect(() => {
|
|
2518
|
-
if (isOpen) {
|
|
2519
|
-
setIsVisible(true);
|
|
2520
|
-
}
|
|
2521
|
-
}, [isOpen]);
|
|
2522
|
-
useEffect(() => {
|
|
2523
|
-
if (isOpen) {
|
|
2524
|
-
// Double RAF: frame 1 → element rendered; frame 2 → element painted at hidden
|
|
2525
|
-
// state → THEN trigger the CSS transition.
|
|
2526
|
-
let raf2;
|
|
2527
|
-
const raf1 = requestAnimationFrame(() => {
|
|
2528
|
-
raf2 = requestAnimationFrame(() => setIsFadingIn(true));
|
|
2529
|
-
});
|
|
2530
|
-
return () => {
|
|
2531
|
-
cancelAnimationFrame(raf1);
|
|
2532
|
-
cancelAnimationFrame(raf2);
|
|
2533
|
-
};
|
|
2534
|
-
}
|
|
2535
|
-
else {
|
|
2536
|
-
setIsFadingIn(false);
|
|
2537
|
-
const timeout = setTimeout(() => setIsVisible(false), duration);
|
|
2538
|
-
return () => clearTimeout(timeout);
|
|
2539
|
-
}
|
|
2540
|
-
}, [isOpen, duration]);
|
|
2541
|
-
return { isVisible, isFadingIn };
|
|
2542
|
-
};
|
|
2543
|
-
|
|
2544
|
-
/**
|
|
2545
|
-
* Locks scrolling on `document.body` while `active` is true.
|
|
2546
|
-
*
|
|
2547
|
-
* Preserves the current scroll position and keeps the scrollbar gutter
|
|
2548
|
-
* (`overflow-y: scroll`) to avoid horizontal layout shift when the scrollbar
|
|
2549
|
-
* disappears. The original styles and scroll position are restored on cleanup.
|
|
2550
|
-
*
|
|
2551
|
-
* @example useBodyScrollLock(isDialogOpen)
|
|
2552
|
-
*/
|
|
2553
|
-
const useBodyScrollLock = (active) => {
|
|
2554
|
-
useEffect(() => {
|
|
2555
|
-
if (!active) {
|
|
2556
|
-
return;
|
|
2557
|
-
}
|
|
2558
|
-
const scrollY = window.scrollY;
|
|
2559
|
-
const body = document.body;
|
|
2560
|
-
body.style.position = 'fixed';
|
|
2561
|
-
body.style.top = `-${scrollY}px`;
|
|
2562
|
-
body.style.overflowY = 'scroll';
|
|
2563
|
-
body.style.width = '100%';
|
|
2564
|
-
return () => {
|
|
2565
|
-
body.style.position = '';
|
|
2566
|
-
body.style.top = '';
|
|
2567
|
-
body.style.overflowY = '';
|
|
2568
|
-
body.style.width = '';
|
|
2569
|
-
window.scrollTo(0, scrollY);
|
|
2570
|
-
};
|
|
2571
|
-
}, [active]);
|
|
2572
|
-
};
|
|
2573
|
-
|
|
2574
3011
|
const FOCUSABLE_SELECTOR = [
|
|
2575
3012
|
'a[href]',
|
|
2576
3013
|
'button:not([disabled])',
|
|
@@ -2706,7 +3143,7 @@ const DialogBase = ({ open, onClose, children, closeOnBackdropClick = false, ful
|
|
|
2706
3143
|
if (!isVisible) {
|
|
2707
3144
|
return null;
|
|
2708
3145
|
}
|
|
2709
|
-
return createPortal(jsxs(Fragment$1, { children: [jsx(
|
|
3146
|
+
return createPortal(jsxs(Fragment$1, { children: [jsx(Backdrop, { visible: isFadingIn, onClick: handleBackdropClick }), jsx("div", { ref: panelRef, role: 'dialog', "aria-modal": true, "aria-labelledby": labelledBy, "aria-label": ariaLabel, tabIndex: -1, className: cx(DIALOG_STYLES.panel, isFadingIn && DIALOG_STYLES.panelVisible, fullscreen && DIALOG_STYLES.panelFullscreen), style: cssVars, children: jsx(DialogContext.Provider, { value: { titleId, CloseIconComponent: CloseIcon }, children: children }) })] }), document.body);
|
|
2710
3147
|
};
|
|
2711
3148
|
DialogBase.displayName = 'Dialog';
|
|
2712
3149
|
const Dialog = DialogBase;
|
|
@@ -2917,13 +3354,19 @@ const themeShadows = {
|
|
|
2917
3354
|
|
|
2918
3355
|
/**
|
|
2919
3356
|
* Default spacing tokens
|
|
3357
|
+
*
|
|
3358
|
+
* ⚠️ Token keys MUST stay CSS-custom-property safe (letters, digits, hyphens only).
|
|
3359
|
+
* They are turned into CSS variables (e.g. `smPlus` → `--theme-spacing-sm-plus`) by
|
|
3360
|
+
* the theme proxy, so characters like `+` would produce invalid variable names and
|
|
3361
|
+
* silently break any style that uses them.
|
|
2920
3362
|
*/
|
|
2921
3363
|
const themeSpacing = {
|
|
2922
3364
|
none: '0',
|
|
2923
3365
|
'2xs': '0.125rem', // 2px
|
|
2924
3366
|
xs: '0.25rem', // 4px
|
|
2925
|
-
|
|
3367
|
+
xsPlus: '0.375rem', // 6px
|
|
2926
3368
|
sm: '0.5rem', // 8px
|
|
3369
|
+
smPlus: '0.75rem', // 12px
|
|
2927
3370
|
md: '1rem', // 16px
|
|
2928
3371
|
lg: '1.5rem', // 24px
|
|
2929
3372
|
xl: '2rem', // 32px
|
|
@@ -2937,9 +3380,9 @@ const themeSpacing = {
|
|
|
2937
3380
|
* Default transition tokens
|
|
2938
3381
|
*/
|
|
2939
3382
|
const themeTransition = {
|
|
2940
|
-
fast: '150ms ease-out',
|
|
2941
|
-
normal: '250ms ease-out',
|
|
2942
|
-
slow: '350ms ease-out',
|
|
3383
|
+
fast: '150ms ease-in-out',
|
|
3384
|
+
normal: '250ms ease-in-out',
|
|
3385
|
+
slow: '350ms ease-in-out',
|
|
2943
3386
|
};
|
|
2944
3387
|
|
|
2945
3388
|
/**
|
|
@@ -3094,5 +3537,5 @@ const darkTheme = createTheme({
|
|
|
3094
3537
|
breakpoints: themeBreakpoints,
|
|
3095
3538
|
});
|
|
3096
3539
|
|
|
3097
|
-
export { Alert, Badge, Box, Button, Card, Dialog, Form, Grid, Icon, IconButton, InfoBubble, Link, Menu, Select, Skeleton, Stack, Switch, Text, TextField, Tooltip, darkTheme, lightTheme };
|
|
3540
|
+
export { Alert, Backdrop, Badge, Box, Button, Card, Checkbox, Dialog, Drawer, Form, Grid, Icon, IconButton, InfoBubble, Link, Menu, Select, Skeleton, Stack, Switch, Text, TextField, Tooltip, darkTheme, lightTheme, useDrawerContext };
|
|
3098
3541
|
//# sourceMappingURL=index.js.map
|