@aurora-ds/components 1.1.5 → 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 +662 -209
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +660 -211
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.ts +169 -4
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -181,24 +181,32 @@ const skeletonShimmerAnimation = theme.keyframes({
|
|
|
181
181
|
'100%': { backgroundPosition: '0% 50%' },
|
|
182
182
|
});
|
|
183
183
|
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
184
|
+
/** Default duration in milliseconds for mount/unmount transition animations. */
|
|
185
|
+
const DEFAULT_TRANSITION_DURATION_MS = 250;
|
|
186
|
+
const DEFAULT_BUTTON_HEIGHT = 40;
|
|
187
|
+
const DEFAULT_DRAWER_ITEM_SIZE = 40;
|
|
188
|
+
/** Drawer widths (px) — single source of truth for both the Drawer and its items. */
|
|
189
|
+
const EXPANDED_DRAWER_WIDTH = 240;
|
|
190
|
+
const COLLAPSED_DRAWER_WIDTH = 58;
|
|
191
|
+
/**
|
|
192
|
+
* Horizontal padding (px) applied by `Drawer.Body` on EACH side of its items
|
|
193
|
+
* (Box `px="sm"` → theme.spacing.sm = 0.5rem = 8px).
|
|
194
|
+
*/
|
|
195
|
+
const DRAWER_BODY_HORIZONTAL_PADDING = 8;
|
|
196
|
+
/**
|
|
197
|
+
* DrawerItem widths (px), always derived from the drawer width minus the body
|
|
198
|
+
* horizontal padding. Using explicit widths (instead of `width: 100%`) lets the
|
|
199
|
+
* item animate its own width in sync with the drawer for a smooth transition.
|
|
200
|
+
*/
|
|
201
|
+
const EXPANDED_DRAWER_ITEM_WIDTH = EXPANDED_DRAWER_WIDTH - DRAWER_BODY_HORIZONTAL_PADDING * 2; // 264
|
|
202
|
+
const COLLAPSED_DRAWER_ITEM_WIDTH = COLLAPSED_DRAWER_WIDTH - DRAWER_BODY_HORIZONTAL_PADDING * 2; // 44
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Returns the complete root styles for an action button (base + color/variant),
|
|
206
|
+
* all in one object so CSS transitions work correctly.
|
|
207
|
+
* Size-specific styles (height, padding, fontSize) are added by the caller.
|
|
208
|
+
*/
|
|
209
|
+
const buildActionButtonRootStyle = (theme, variant, color) => {
|
|
202
210
|
const c = theme.colors;
|
|
203
211
|
const intents = {
|
|
204
212
|
primary: {
|
|
@@ -237,57 +245,60 @@ const buildActionButtonCompoundVariants = (theme) => {
|
|
|
237
245
|
fg: c.errorMain, fgHover: c.errorHover, border: c.errorMain,
|
|
238
246
|
},
|
|
239
247
|
};
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
};
|
|
248
|
+
const intent = intents[color];
|
|
249
|
+
const colorVariantStyles = variant === 'contained'
|
|
250
|
+
? {
|
|
251
|
+
backgroundColor: intent.main,
|
|
252
|
+
borderColor: intent.main,
|
|
253
|
+
color: intent.on,
|
|
254
|
+
boxShadow: theme.shadows.xs,
|
|
255
|
+
':hover:not(:disabled)': { backgroundColor: intent.hover, borderColor: intent.hover, boxShadow: theme.shadows.sm },
|
|
256
|
+
':active:not(:disabled)': { backgroundColor: intent.active, borderColor: intent.active, boxShadow: theme.shadows.none },
|
|
250
257
|
}
|
|
251
|
-
|
|
252
|
-
|
|
258
|
+
: variant === 'outlined'
|
|
259
|
+
? {
|
|
253
260
|
backgroundColor: 'transparent',
|
|
254
261
|
borderColor: intent.border,
|
|
255
262
|
color: intent.fg,
|
|
256
263
|
':hover:not(:disabled)': { backgroundColor: intent.subtleHover, color: intent.fgHover },
|
|
257
264
|
':active:not(:disabled)': { backgroundColor: intent.subtleActive, borderColor: intent.active, color: intent.active },
|
|
265
|
+
}
|
|
266
|
+
: {
|
|
267
|
+
backgroundColor: 'transparent',
|
|
268
|
+
borderColor: 'transparent',
|
|
269
|
+
color: intent.fg,
|
|
270
|
+
':hover:not(:disabled)': { backgroundColor: intent.subtleHover, color: intent.fgHover },
|
|
271
|
+
':active:not(:disabled)': { backgroundColor: intent.subtleActive, color: intent.active },
|
|
258
272
|
};
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
273
|
+
return {
|
|
274
|
+
position: 'relative',
|
|
275
|
+
display: 'inline-flex',
|
|
276
|
+
alignItems: 'center',
|
|
277
|
+
justifyContent: 'center',
|
|
278
|
+
boxSizing: 'border-box',
|
|
279
|
+
border: '1px solid transparent',
|
|
280
|
+
borderRadius: theme.radius.md,
|
|
281
|
+
fontFamily: 'inherit',
|
|
282
|
+
userSelect: 'none',
|
|
283
|
+
height: DEFAULT_BUTTON_HEIGHT,
|
|
284
|
+
cursor: 'pointer',
|
|
285
|
+
outline: 'none',
|
|
286
|
+
transition: `background-color ${theme.transition.normal}, border-color ${theme.transition.normal}, color ${theme.transition.normal}, box-shadow ${theme.transition.normal}`,
|
|
287
|
+
...colorVariantStyles,
|
|
288
|
+
':focus-visible': { boxShadow: theme.shadows.focus },
|
|
289
|
+
':disabled': { cursor: 'not-allowed', opacity: theme.opacity.high, boxShadow: 'none' },
|
|
267
290
|
};
|
|
268
|
-
return Object.keys(intents).flatMap((color) => APPEARANCES.map((appearance) => ({
|
|
269
|
-
color,
|
|
270
|
-
variant: appearance,
|
|
271
|
-
styles: appearanceStyles(intents[color], appearance),
|
|
272
|
-
})));
|
|
273
291
|
};
|
|
274
292
|
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
size
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
variant: { contained: {}, outlined: {}, text: {} },
|
|
285
|
-
color: { primary: {}, secondary: {}, neutral: {}, info: {}, success: {}, warning: {}, error: {} },
|
|
286
|
-
},
|
|
287
|
-
defaultVariants: { size: 'md', variant: 'contained', color: 'primary' },
|
|
288
|
-
compoundVariants: buildActionButtonCompoundVariants(theme),
|
|
289
|
-
}), { id: 'button' });
|
|
290
|
-
const BUTTON_STYLES = theme.createStyles({
|
|
293
|
+
const BUTTON_STYLES = theme.createStyles((theme) => ({
|
|
294
|
+
root: ({ variant, color, size }) => ({
|
|
295
|
+
...buildActionButtonRootStyle(theme, variant, color),
|
|
296
|
+
...(size === 'sm'
|
|
297
|
+
? { height: '2rem', padding: `0 ${theme.spacing.sm}`, fontSize: theme.fontSize.xs }
|
|
298
|
+
: size === 'lg'
|
|
299
|
+
? { height: '3rem', padding: `0 ${theme.spacing.lg}`, fontSize: theme.fontSize.md }
|
|
300
|
+
: { height: '2.5rem', padding: `0 ${theme.spacing.md}`, fontSize: theme.fontSize.sm }),
|
|
301
|
+
}),
|
|
291
302
|
/** Inner wrapper holding icons + label, centered by the button. */
|
|
292
303
|
content: {
|
|
293
304
|
display: 'inline-flex',
|
|
@@ -310,7 +321,14 @@ const BUTTON_STYLES = theme.createStyles({
|
|
|
310
321
|
animation: `${spinAnimation} 0.75s linear infinite`,
|
|
311
322
|
'@media (prefers-reduced-motion: reduce)': { animation: 'none' },
|
|
312
323
|
},
|
|
313
|
-
}, { id: 'button
|
|
324
|
+
}), { id: 'button' });
|
|
325
|
+
// Pre-generate CSS for all variant/color/size combinations at module load.
|
|
326
|
+
// This ensures the CSS is already in the stylesheet before the first user interaction,
|
|
327
|
+
// preventing the "first click is instant" issue caused by lazy CSS injection.
|
|
328
|
+
const BUTTON_VARIANT_VALUES = ['contained', 'outlined', 'text'];
|
|
329
|
+
const BUTTON_COLOR_VALUES = ['primary', 'secondary', 'neutral', 'info', 'success', 'warning', 'error'];
|
|
330
|
+
const BUTTON_SIZE_VALUES = ['sm', 'md', 'lg'];
|
|
331
|
+
BUTTON_VARIANT_VALUES.forEach(variant => BUTTON_COLOR_VALUES.forEach(color => BUTTON_SIZE_VALUES.forEach(size => BUTTON_STYLES.root({ variant, color, size }))));
|
|
314
332
|
|
|
315
333
|
const ICON_STYLES = theme.createStyles((theme) => ({
|
|
316
334
|
root: ({ size, strokeColor, fill, backgroundColor, padding, borderRadius }) => ({
|
|
@@ -579,7 +597,7 @@ const ICON_SIZE$2 = {
|
|
|
579
597
|
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 }) => {
|
|
580
598
|
const isDisabled = disabled || isLoading;
|
|
581
599
|
const iconSize = ICON_SIZE$2[size];
|
|
582
|
-
const rootClassName =
|
|
600
|
+
const rootClassName = theme.cx(BUTTON_STYLES.root({ variant, color, size }), className);
|
|
583
601
|
const mergedStyle = {
|
|
584
602
|
...style,
|
|
585
603
|
...(width !== undefined ? { width } : {}),
|
|
@@ -590,21 +608,15 @@ const Button = ({ ref, variant = 'contained', color = 'primary', size = 'md', wi
|
|
|
590
608
|
};
|
|
591
609
|
Button.displayName = 'Button';
|
|
592
610
|
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
size
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
color: { primary: {}, secondary: {}, neutral: {}, info: {}, success: {}, warning: {}, error: {} },
|
|
603
|
-
},
|
|
604
|
-
defaultVariants: { size: 'md', variant: 'contained', color: 'primary' },
|
|
605
|
-
compoundVariants: buildActionButtonCompoundVariants(theme),
|
|
606
|
-
}), { id: 'icon-button' });
|
|
607
|
-
const ICON_BUTTON_STYLES = theme.createStyles({
|
|
611
|
+
const ICON_BUTTON_STYLES = theme.createStyles((theme) => ({
|
|
612
|
+
root: ({ variant, color, size }) => ({
|
|
613
|
+
...buildActionButtonRootStyle(theme, variant, color),
|
|
614
|
+
...(size === 'sm'
|
|
615
|
+
? { width: '2rem', height: '2rem', padding: '0' }
|
|
616
|
+
: size === 'lg'
|
|
617
|
+
? { width: '3rem', height: '3rem', padding: '0' }
|
|
618
|
+
: { width: '2.5rem', height: '2.5rem', padding: '0' }),
|
|
619
|
+
}),
|
|
608
620
|
/** Spinning animation applied to the SpinnerIcon. */
|
|
609
621
|
spinnerIcon: {
|
|
610
622
|
animation: `${spinAnimation} 0.75s linear infinite`,
|
|
@@ -620,7 +632,11 @@ const ICON_BUTTON_STYLES = theme.createStyles({
|
|
|
620
632
|
alignItems: 'center',
|
|
621
633
|
justifyContent: 'center',
|
|
622
634
|
},
|
|
623
|
-
}, { id: 'icon-button
|
|
635
|
+
}), { id: 'icon-button' });
|
|
636
|
+
const ICON_BUTTON_VARIANT_VALUES = ['contained', 'outlined', 'text'];
|
|
637
|
+
const ICON_BUTTON_COLOR_VALUES = ['primary', 'secondary', 'neutral', 'info', 'success', 'warning', 'error'];
|
|
638
|
+
const ICON_BUTTON_SIZE_VALUES = ['sm', 'md', 'lg'];
|
|
639
|
+
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 }))));
|
|
624
640
|
|
|
625
641
|
/** Maps the icon-button size to an Icon size token. */
|
|
626
642
|
const ICON_SIZE$1 = {
|
|
@@ -639,7 +655,7 @@ const ICON_SIZE$1 = {
|
|
|
639
655
|
const IconButton = ({ ref, icon: IconComponent, ariaLabel, variant = 'contained', color = 'primary', size = 'md', isLoading = false, className, type = 'button', disabled, ...rest }) => {
|
|
640
656
|
const isDisabled = disabled || isLoading;
|
|
641
657
|
const iconSize = ICON_SIZE$1[size];
|
|
642
|
-
const rootClassName =
|
|
658
|
+
const rootClassName = theme.cx(ICON_BUTTON_STYLES.root({ variant, color, size }), className);
|
|
643
659
|
return (jsxRuntime.jsxs("button", { ref: ref, type: type, className: rootClassName, disabled: isDisabled, "aria-label": ariaLabel, "aria-busy": isLoading || undefined, ...rest, children: [isLoading && (jsxRuntime.jsx("span", { className: ICON_BUTTON_STYLES.spinnerWrap, children: jsxRuntime.jsx(Icon, { icon: SpinnerIcon, size: iconSize, className: ICON_BUTTON_STYLES.spinnerIcon }) })), jsxRuntime.jsx(Icon, { icon: IconComponent, size: iconSize, className: theme.cx(isLoading && ICON_BUTTON_STYLES.iconHidden) })] }));
|
|
644
660
|
};
|
|
645
661
|
IconButton.displayName = 'IconButton';
|
|
@@ -855,7 +871,7 @@ const BADGE_VARIANTS = theme.createVariants((theme) => ({
|
|
|
855
871
|
size: {
|
|
856
872
|
sm: {
|
|
857
873
|
height: '1.25rem',
|
|
858
|
-
padding: `0.125rem ${theme.spacing
|
|
874
|
+
padding: `0.125rem ${theme.spacing.xsPlus}`,
|
|
859
875
|
fontSize: theme.fontSize['2xs'],
|
|
860
876
|
},
|
|
861
877
|
md: {
|
|
@@ -1256,6 +1272,12 @@ const SKELETON_VARIANTS = theme.createVariants((theme) => ({
|
|
|
1256
1272
|
const Skeleton = ({ ref, variant = 'rectangular', animation = 'shimmer', width, height, className, style, ...rest }) => (jsxRuntime.jsx("span", { ref: ref, className: SKELETON_VARIANTS({ variant, animation: animation === false ? 'none' : animation }, className), style: { width, height, ...style }, "aria-hidden": true, ...rest }));
|
|
1257
1273
|
Skeleton.displayName = 'Skeleton';
|
|
1258
1274
|
|
|
1275
|
+
const FORM_STYLES = theme.createStyles(() => ({
|
|
1276
|
+
root: {
|
|
1277
|
+
display: 'contents',
|
|
1278
|
+
},
|
|
1279
|
+
}));
|
|
1280
|
+
|
|
1259
1281
|
/**
|
|
1260
1282
|
* Thin wrapper around `<form>`. Prevents the default browser submit and
|
|
1261
1283
|
* delegates to the `onSubmit` callback.
|
|
@@ -1271,7 +1293,7 @@ const Form = ({ children, onSubmit, 'aria-label': ariaLabel, 'aria-labelledby':
|
|
|
1271
1293
|
event.preventDefault();
|
|
1272
1294
|
onSubmit(event);
|
|
1273
1295
|
};
|
|
1274
|
-
return (jsxRuntime.jsx("form", { onSubmit: handleSubmit, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, noValidate: true, children: children }));
|
|
1296
|
+
return (jsxRuntime.jsx("form", { onSubmit: handleSubmit, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, className: FORM_STYLES.root, noValidate: true, children: children }));
|
|
1275
1297
|
};
|
|
1276
1298
|
Form.displayName = 'Form';
|
|
1277
1299
|
|
|
@@ -1579,6 +1601,18 @@ Box.displayName = 'Box';
|
|
|
1579
1601
|
const Stack = ({ flexDirection = 'row', display = 'flex', gap = 'sm', ...rest }) => (jsxRuntime.jsx(Box, { display: display, flexDirection: flexDirection, gap: gap, ...rest }));
|
|
1580
1602
|
Stack.displayName = 'Stack';
|
|
1581
1603
|
|
|
1604
|
+
const HELPER_COLOR_MAP = {
|
|
1605
|
+
default: 'textSecondary',
|
|
1606
|
+
error: 'errorHover',
|
|
1607
|
+
success: 'successHover',
|
|
1608
|
+
warning: 'warningHover',
|
|
1609
|
+
};
|
|
1610
|
+
const FormHelperText = ({ id, status = 'default', ariaLive, className, children, }) => {
|
|
1611
|
+
const resolvedAriaLive = ariaLive ?? (status === 'error' ? 'assertive' : 'polite');
|
|
1612
|
+
return (jsxRuntime.jsx(Text, { id: id, variant: 'span', fontSize: 'xs', color: HELPER_COLOR_MAP[status], "aria-live": resolvedAriaLive, className: className, children: children }));
|
|
1613
|
+
};
|
|
1614
|
+
FormHelperText.displayName = 'FormHelperText';
|
|
1615
|
+
|
|
1582
1616
|
const TEXTFIELD_WRAPPER_VARIANTS = theme.createVariants((theme) => {
|
|
1583
1617
|
const c = theme.colors;
|
|
1584
1618
|
return {
|
|
@@ -1701,18 +1735,11 @@ const ICON_BUTTON_SIZE_MAP = {
|
|
|
1701
1735
|
md: 'sm',
|
|
1702
1736
|
lg: 'md',
|
|
1703
1737
|
};
|
|
1704
|
-
/** Maps status to a theme color token used for the helper text. */
|
|
1705
|
-
const HELPER_COLOR_MAP$1 = {
|
|
1706
|
-
default: 'textSecondary',
|
|
1707
|
-
error: 'errorHover',
|
|
1708
|
-
success: 'successHover',
|
|
1709
|
-
warning: 'warningHover',
|
|
1710
|
-
};
|
|
1711
1738
|
/**
|
|
1712
1739
|
* Business logic for the TextField component: id resolution, ref merging,
|
|
1713
1740
|
* password visibility toggling and size/status derived tokens.
|
|
1714
1741
|
*/
|
|
1715
|
-
const useTextField = ({ id, ref, type, size,
|
|
1742
|
+
const useTextField = ({ id, ref, type, size, endAction, }) => {
|
|
1716
1743
|
const generatedId = React.useId();
|
|
1717
1744
|
const fieldId = id ?? generatedId;
|
|
1718
1745
|
const helperId = `${fieldId}-helper`;
|
|
@@ -1734,7 +1761,6 @@ const useTextField = ({ id, ref, type, size, status, endAction, }) => {
|
|
|
1734
1761
|
resolvedType,
|
|
1735
1762
|
iconSize: ICON_SIZE_MAP$1[size],
|
|
1736
1763
|
iconButtonSize: ICON_BUTTON_SIZE_MAP[size],
|
|
1737
|
-
helperColor: HELPER_COLOR_MAP$1[status],
|
|
1738
1764
|
hasEndSection: endAction !== undefined || isPassword,
|
|
1739
1765
|
focusInput,
|
|
1740
1766
|
};
|
|
@@ -1752,8 +1778,8 @@ const useTextField = ({ id, ref, type, size, status, endAction, }) => {
|
|
|
1752
1778
|
* @example <TextField label="Search" startIcon={SearchIcon} endAction={<ClearButton />} />
|
|
1753
1779
|
*/
|
|
1754
1780
|
const TextField = ({ ref, label, helperText, size = 'md', status = 'default', startIcon: StartIcon, endAction, type, id, disabled, required, ...rest }) => {
|
|
1755
|
-
const { fieldId, helperId, mergedRef, isPassword, showPassword, togglePassword, resolvedType, iconSize, iconButtonSize,
|
|
1756
|
-
return (jsxRuntime.jsxs(Stack, { flexDirection: 'column', gap: 'xs', children: [label !== undefined && (jsxRuntime.jsxs(Text, { variant: 'label', fontSize: 'sm', fontWeight: 'medium', color: 'textSecondary', htmlFor: fieldId, children: [label, required && (jsxRuntime.jsx(Text, { variant: 'span', color: 'errorMain', "aria-hidden": true, children: ' *' }))] })), jsxRuntime.jsxs("div", { className: TEXTFIELD_WRAPPER_VARIANTS({ size, status }), "data-disabled": disabled || undefined, children: [StartIcon && (jsxRuntime.jsx("span", { className: TEXTFIELD_STYLES.startIconWrap, onClick: focusInput, children: jsxRuntime.jsx(Icon, { icon: StartIcon, size: iconSize, strokeColor: 'textSecondary' }) })), jsxRuntime.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 && (jsxRuntime.jsxs("span", { className: TEXTFIELD_STYLES.endActionWrap, children: [endAction, isPassword && (jsxRuntime.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 && (jsxRuntime.jsx(
|
|
1781
|
+
const { fieldId, helperId, mergedRef, isPassword, showPassword, togglePassword, resolvedType, iconSize, iconButtonSize, hasEndSection, focusInput, } = useTextField({ id, ref, type, size, endAction });
|
|
1782
|
+
return (jsxRuntime.jsxs(Stack, { flexDirection: 'column', gap: 'xs', children: [label !== undefined && (jsxRuntime.jsxs(Text, { variant: 'label', fontSize: 'sm', fontWeight: 'medium', color: 'textSecondary', htmlFor: fieldId, children: [label, required && (jsxRuntime.jsx(Text, { variant: 'span', color: 'errorMain', "aria-hidden": true, children: ' *' }))] })), jsxRuntime.jsxs("div", { className: TEXTFIELD_WRAPPER_VARIANTS({ size, status }), "data-disabled": disabled || undefined, children: [StartIcon && (jsxRuntime.jsx("span", { className: TEXTFIELD_STYLES.startIconWrap, onClick: focusInput, children: jsxRuntime.jsx(Icon, { icon: StartIcon, size: iconSize, strokeColor: 'textSecondary' }) })), jsxRuntime.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 && (jsxRuntime.jsxs("span", { className: TEXTFIELD_STYLES.endActionWrap, children: [endAction, isPassword && (jsxRuntime.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 && (jsxRuntime.jsx(FormHelperText, { id: helperId, status: status, children: helperText }))] }));
|
|
1757
1783
|
};
|
|
1758
1784
|
TextField.displayName = 'TextField';
|
|
1759
1785
|
|
|
@@ -2189,21 +2215,15 @@ const SelectTrigger = ({ ref, size = 'md', status = 'default', open, hasValue, s
|
|
|
2189
2215
|
};
|
|
2190
2216
|
SelectTrigger.displayName = 'SelectTrigger';
|
|
2191
2217
|
|
|
2192
|
-
/** Maps status to a theme color token used for the helper text. */
|
|
2193
|
-
const HELPER_COLOR_MAP = {
|
|
2194
|
-
default: 'textSecondary',
|
|
2195
|
-
error: 'errorHover',
|
|
2196
|
-
success: 'successHover',
|
|
2197
|
-
warning: 'warningHover',
|
|
2198
|
-
};
|
|
2199
2218
|
/**
|
|
2200
2219
|
* Business logic for the Select component: id resolution, ref merging,
|
|
2201
2220
|
* controlled/uncontrolled value handling, open state, option grouping and
|
|
2202
2221
|
* focus restoration to the trigger when the menu closes.
|
|
2203
2222
|
*/
|
|
2204
|
-
const useSelect = ({ id, ref, value, defaultValue, onChange, options,
|
|
2223
|
+
const useSelect = ({ id, ref, value, defaultValue, onChange, options, disabled, }) => {
|
|
2205
2224
|
const generatedId = React.useId();
|
|
2206
2225
|
const fieldId = id ?? generatedId;
|
|
2226
|
+
const helperId = `${fieldId}-helper`;
|
|
2207
2227
|
const menuId = `${fieldId}-menu`;
|
|
2208
2228
|
const triggerRef = React.useRef(null);
|
|
2209
2229
|
const mergedRef = useMergedRefs(ref, triggerRef);
|
|
@@ -2248,6 +2268,7 @@ const useSelect = ({ id, ref, value, defaultValue, onChange, options, status, di
|
|
|
2248
2268
|
const close = React.useCallback(() => setOpen(false), []);
|
|
2249
2269
|
return {
|
|
2250
2270
|
fieldId,
|
|
2271
|
+
helperId,
|
|
2251
2272
|
menuId,
|
|
2252
2273
|
triggerRef,
|
|
2253
2274
|
mergedRef,
|
|
@@ -2258,19 +2279,187 @@ const useSelect = ({ id, ref, value, defaultValue, onChange, options, status, di
|
|
|
2258
2279
|
selectedOption,
|
|
2259
2280
|
groupedOptions,
|
|
2260
2281
|
handleSelect,
|
|
2261
|
-
helperColor: HELPER_COLOR_MAP[status],
|
|
2262
2282
|
};
|
|
2263
2283
|
};
|
|
2264
2284
|
|
|
2265
2285
|
const Select = ({ ref, value, defaultValue, onChange, options, label, helperText, placeholder, size = 'md', status = 'default', disabled, required, width, id, }) => {
|
|
2266
|
-
const { fieldId, menuId, triggerRef, mergedRef, open, toggle, close, currentValue, selectedOption, groupedOptions, handleSelect,
|
|
2267
|
-
return (jsxRuntime.jsxs(Stack, { flexDirection: 'column', gap: 'xs', style: { width: width ?? '100%' }, children: [label !== undefined && (jsxRuntime.jsxs(Text, { variant: 'label', fontSize: 'sm', fontWeight: 'medium', color: 'textSecondary', htmlFor: fieldId, children: [label, required && (jsxRuntime.jsx(Text, { variant: 'span', color: 'errorMain', "aria-hidden": true, children: ' *' }))] })), jsxRuntime.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 }), jsxRuntime.jsx(Menu, { open: open, onClose: close, anchorEl: triggerRef.current, id: menuId, children: Array.from(groupedOptions.entries()).map(([groupKey, groupOpts], groupIndex) => {
|
|
2286
|
+
const { fieldId, helperId, menuId, triggerRef, mergedRef, open, toggle, close, currentValue, selectedOption, groupedOptions, handleSelect, } = useSelect({ id, ref, value, defaultValue, onChange, options, disabled });
|
|
2287
|
+
return (jsxRuntime.jsxs(Stack, { flexDirection: 'column', gap: 'xs', style: { width: width ?? '100%' }, children: [label !== undefined && (jsxRuntime.jsxs(Text, { variant: 'label', fontSize: 'sm', fontWeight: 'medium', color: 'textSecondary', htmlFor: fieldId, children: [label, required && (jsxRuntime.jsx(Text, { variant: 'span', color: 'errorMain', "aria-hidden": true, children: ' *' }))] })), jsxRuntime.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 }), jsxRuntime.jsx(Menu, { open: open, onClose: close, anchorEl: triggerRef.current, id: menuId, children: Array.from(groupedOptions.entries()).map(([groupKey, groupOpts], groupIndex) => {
|
|
2268
2288
|
const items = groupOpts.map((opt) => (jsxRuntime.jsx(Menu.Item, { label: opt.label, icon: opt.icon, selected: opt.value === currentValue, disabled: opt.disabled, onClick: () => handleSelect(opt.value) }, opt.value)));
|
|
2269
2289
|
return groupKey !== undefined ? (jsxRuntime.jsx(Menu.Group, { label: groupKey, divider: groupIndex > 0, children: items }, groupKey)) : (jsxRuntime.jsx(Menu.Group, { divider: groupIndex > 0, children: items }, '__ungrouped'));
|
|
2270
|
-
}) }), helperText !== undefined && (jsxRuntime.jsx(
|
|
2290
|
+
}) }), helperText !== undefined && (jsxRuntime.jsx(FormHelperText, { id: helperId, status: status, children: helperText }))] }));
|
|
2271
2291
|
};
|
|
2272
2292
|
Select.displayName = 'Select';
|
|
2273
2293
|
|
|
2294
|
+
const CHECKBOX_ROOT_VARIANTS = theme.createVariants((theme) => ({
|
|
2295
|
+
base: {
|
|
2296
|
+
display: 'inline-flex',
|
|
2297
|
+
alignItems: 'center',
|
|
2298
|
+
gap: theme.spacing.sm,
|
|
2299
|
+
cursor: 'pointer',
|
|
2300
|
+
userSelect: 'none',
|
|
2301
|
+
},
|
|
2302
|
+
variants: {
|
|
2303
|
+
disabled: {
|
|
2304
|
+
true: {
|
|
2305
|
+
cursor: 'not-allowed',
|
|
2306
|
+
opacity: theme.opacity.high,
|
|
2307
|
+
},
|
|
2308
|
+
false: {},
|
|
2309
|
+
},
|
|
2310
|
+
},
|
|
2311
|
+
defaultVariants: {
|
|
2312
|
+
disabled: 'false',
|
|
2313
|
+
},
|
|
2314
|
+
}), { id: 'checkbox-root' });
|
|
2315
|
+
const CHECKBOX_INPUT_VARIANTS = theme.createVariants((theme) => {
|
|
2316
|
+
const c = theme.colors;
|
|
2317
|
+
return {
|
|
2318
|
+
base: {
|
|
2319
|
+
appearance: 'none',
|
|
2320
|
+
position: 'relative',
|
|
2321
|
+
margin: 0,
|
|
2322
|
+
borderWidth: '1px',
|
|
2323
|
+
borderStyle: 'solid',
|
|
2324
|
+
borderColor: c.borderStrong,
|
|
2325
|
+
borderRadius: theme.radius.sm,
|
|
2326
|
+
backgroundColor: c.surfacePaper,
|
|
2327
|
+
transition: `border-color ${theme.transition.fast}, background-color ${theme.transition.fast}`,
|
|
2328
|
+
cursor: 'pointer',
|
|
2329
|
+
'&:focus-visible': {
|
|
2330
|
+
outline: '2px solid transparent',
|
|
2331
|
+
boxShadow: `0 0 0 2px ${c.primarySubtleActive}`,
|
|
2332
|
+
},
|
|
2333
|
+
'&::after': {
|
|
2334
|
+
content: '""',
|
|
2335
|
+
position: 'absolute',
|
|
2336
|
+
left: '50%',
|
|
2337
|
+
top: '50%',
|
|
2338
|
+
transform: 'translate(-50%, -56%) rotate(45deg)',
|
|
2339
|
+
width: '0.25rem',
|
|
2340
|
+
height: '0.5rem',
|
|
2341
|
+
borderRight: `2px solid ${c.textInverse}`,
|
|
2342
|
+
borderBottom: `2px solid ${c.textInverse}`,
|
|
2343
|
+
opacity: 0,
|
|
2344
|
+
},
|
|
2345
|
+
'&:checked::after': {
|
|
2346
|
+
opacity: 1,
|
|
2347
|
+
},
|
|
2348
|
+
'&:indeterminate::after': {
|
|
2349
|
+
width: '0.5rem',
|
|
2350
|
+
height: '0',
|
|
2351
|
+
borderRight: '0',
|
|
2352
|
+
borderBottom: `2px solid ${c.textInverse}`,
|
|
2353
|
+
transform: 'translate(-50%, -50%)',
|
|
2354
|
+
opacity: 1,
|
|
2355
|
+
},
|
|
2356
|
+
},
|
|
2357
|
+
variants: {
|
|
2358
|
+
size: {
|
|
2359
|
+
sm: {
|
|
2360
|
+
width: '1rem',
|
|
2361
|
+
height: '1rem',
|
|
2362
|
+
},
|
|
2363
|
+
md: {
|
|
2364
|
+
width: '1.125rem',
|
|
2365
|
+
height: '1.125rem',
|
|
2366
|
+
},
|
|
2367
|
+
lg: {
|
|
2368
|
+
width: '1.25rem',
|
|
2369
|
+
height: '1.25rem',
|
|
2370
|
+
},
|
|
2371
|
+
},
|
|
2372
|
+
status: {
|
|
2373
|
+
default: {
|
|
2374
|
+
'&:checked, &:indeterminate': {
|
|
2375
|
+
borderColor: c.primaryMain,
|
|
2376
|
+
backgroundColor: c.primaryMain,
|
|
2377
|
+
},
|
|
2378
|
+
},
|
|
2379
|
+
error: {
|
|
2380
|
+
borderColor: c.errorMain,
|
|
2381
|
+
'&:checked, &:indeterminate': {
|
|
2382
|
+
borderColor: c.errorMain,
|
|
2383
|
+
backgroundColor: c.errorMain,
|
|
2384
|
+
},
|
|
2385
|
+
},
|
|
2386
|
+
success: {
|
|
2387
|
+
borderColor: c.successMain,
|
|
2388
|
+
'&:checked, &:indeterminate': {
|
|
2389
|
+
borderColor: c.successMain,
|
|
2390
|
+
backgroundColor: c.successMain,
|
|
2391
|
+
},
|
|
2392
|
+
},
|
|
2393
|
+
warning: {
|
|
2394
|
+
borderColor: c.warningMain,
|
|
2395
|
+
'&:checked, &:indeterminate': {
|
|
2396
|
+
borderColor: c.warningMain,
|
|
2397
|
+
backgroundColor: c.warningMain,
|
|
2398
|
+
},
|
|
2399
|
+
},
|
|
2400
|
+
},
|
|
2401
|
+
disabled: {
|
|
2402
|
+
true: {
|
|
2403
|
+
cursor: 'not-allowed',
|
|
2404
|
+
backgroundColor: c.disabledMain,
|
|
2405
|
+
borderColor: c.disabledMain,
|
|
2406
|
+
'&:checked, &:indeterminate': {
|
|
2407
|
+
backgroundColor: c.disabledText,
|
|
2408
|
+
borderColor: c.disabledText,
|
|
2409
|
+
},
|
|
2410
|
+
},
|
|
2411
|
+
false: {},
|
|
2412
|
+
},
|
|
2413
|
+
},
|
|
2414
|
+
defaultVariants: {
|
|
2415
|
+
size: 'md',
|
|
2416
|
+
status: 'default',
|
|
2417
|
+
disabled: 'false',
|
|
2418
|
+
},
|
|
2419
|
+
};
|
|
2420
|
+
}, { id: 'checkbox-input' });
|
|
2421
|
+
const CHECKBOX_STYLES = theme.createStyles((theme) => ({
|
|
2422
|
+
wrapper: {
|
|
2423
|
+
display: 'inline-flex',
|
|
2424
|
+
flexDirection: 'column',
|
|
2425
|
+
gap: theme.spacing.xs,
|
|
2426
|
+
},
|
|
2427
|
+
helper: {
|
|
2428
|
+
marginLeft: `calc(1.125rem + ${theme.spacing.sm})`,
|
|
2429
|
+
},
|
|
2430
|
+
}), { id: 'checkbox-extra' });
|
|
2431
|
+
|
|
2432
|
+
/** Handles id generation, ref merging and native indeterminate state sync. */
|
|
2433
|
+
const useCheckbox = ({ id, ref, indeterminate = false }) => {
|
|
2434
|
+
const generatedId = React.useId();
|
|
2435
|
+
const checkboxId = id ?? generatedId;
|
|
2436
|
+
const helperId = `${checkboxId}-helper`;
|
|
2437
|
+
const inputRef = React.useRef(null);
|
|
2438
|
+
const mergedRef = useMergedRefs(ref, inputRef);
|
|
2439
|
+
React.useEffect(() => {
|
|
2440
|
+
if (inputRef.current) {
|
|
2441
|
+
inputRef.current.indeterminate = indeterminate;
|
|
2442
|
+
}
|
|
2443
|
+
}, [indeterminate]);
|
|
2444
|
+
return {
|
|
2445
|
+
checkboxId,
|
|
2446
|
+
helperId,
|
|
2447
|
+
mergedRef,
|
|
2448
|
+
inputRef,
|
|
2449
|
+
};
|
|
2450
|
+
};
|
|
2451
|
+
|
|
2452
|
+
const Checkbox = ({ ref, label, helperText, size = 'md', status = 'default', indeterminate = false, error, id, disabled, required, ...rest }) => {
|
|
2453
|
+
const resolvedStatus = error ? 'error' : status;
|
|
2454
|
+
const { checkboxId, helperId, mergedRef } = useCheckbox({ id, ref, indeterminate });
|
|
2455
|
+
return (jsxRuntime.jsxs("div", { className: CHECKBOX_STYLES.wrapper, children: [jsxRuntime.jsxs("label", { htmlFor: checkboxId, className: CHECKBOX_ROOT_VARIANTS({ disabled: disabled ? 'true' : 'false' }), children: [jsxRuntime.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({
|
|
2456
|
+
size,
|
|
2457
|
+
status: resolvedStatus,
|
|
2458
|
+
disabled: disabled ? 'true' : 'false',
|
|
2459
|
+
}), ...rest }), label !== undefined && (jsxRuntime.jsx(Text, { variant: 'span', fontSize: size === 'lg' ? 'md' : 'sm', color: disabled ? 'textDisabled' : 'textSecondary', children: label }))] }), helperText !== undefined && (jsxRuntime.jsx(FormHelperText, { id: helperId, status: resolvedStatus, className: CHECKBOX_STYLES.helper, children: helperText }))] }));
|
|
2460
|
+
};
|
|
2461
|
+
Checkbox.displayName = 'Checkbox';
|
|
2462
|
+
|
|
2274
2463
|
const CARD_VARIANTS = theme.createVariants((theme) => ({
|
|
2275
2464
|
base: {
|
|
2276
2465
|
boxSizing: 'border-box',
|
|
@@ -2354,6 +2543,361 @@ const Grid = ({ display = 'grid', columns, rows, autoFlow, autoColumns, autoRows
|
|
|
2354
2543
|
};
|
|
2355
2544
|
Grid.displayName = 'Grid';
|
|
2356
2545
|
|
|
2546
|
+
const DrawerContext = React.createContext({
|
|
2547
|
+
isExpanded: true,
|
|
2548
|
+
});
|
|
2549
|
+
const useDrawerContext = () => React.useContext(DrawerContext);
|
|
2550
|
+
|
|
2551
|
+
const TRANSITION$2 = `${DEFAULT_TRANSITION_DURATION_MS}ms ease`;
|
|
2552
|
+
const DRAWER_STYLES = theme.createStyles((theme) => ({
|
|
2553
|
+
root: ({ isExpanded }) => ({
|
|
2554
|
+
display: 'flex',
|
|
2555
|
+
flexDirection: 'column',
|
|
2556
|
+
width: isExpanded ? EXPANDED_DRAWER_WIDTH : COLLAPSED_DRAWER_WIDTH,
|
|
2557
|
+
transition: `width ${theme.transition.slow}`,
|
|
2558
|
+
overflow: 'hidden',
|
|
2559
|
+
backgroundColor: theme.colors.surfacePaper,
|
|
2560
|
+
borderRight: `1px solid ${theme.colors.borderMain}`,
|
|
2561
|
+
boxSizing: 'border-box',
|
|
2562
|
+
flexShrink: 0,
|
|
2563
|
+
}),
|
|
2564
|
+
/** Temporary variant: slides in from the left as a fixed portal overlay. */
|
|
2565
|
+
temporaryPanel: {
|
|
2566
|
+
position: 'fixed',
|
|
2567
|
+
top: 0,
|
|
2568
|
+
left: 0,
|
|
2569
|
+
bottom: 0,
|
|
2570
|
+
width: EXPANDED_DRAWER_WIDTH,
|
|
2571
|
+
zIndex: theme.zIndex.modal,
|
|
2572
|
+
display: 'flex',
|
|
2573
|
+
flexDirection: 'column',
|
|
2574
|
+
backgroundColor: theme.colors.surfacePaper,
|
|
2575
|
+
borderRight: `1px solid ${theme.colors.borderMain}`,
|
|
2576
|
+
boxSizing: 'border-box',
|
|
2577
|
+
overflowY: 'auto',
|
|
2578
|
+
overflowX: 'hidden',
|
|
2579
|
+
willChange: 'transform',
|
|
2580
|
+
transform: 'translateX(-100%)',
|
|
2581
|
+
transition: `transform ${TRANSITION$2}`,
|
|
2582
|
+
boxShadow: theme.shadows.xl,
|
|
2583
|
+
},
|
|
2584
|
+
temporaryPanelVisible: {
|
|
2585
|
+
transform: 'translateX(0)',
|
|
2586
|
+
},
|
|
2587
|
+
}));
|
|
2588
|
+
|
|
2589
|
+
/**
|
|
2590
|
+
* Responsive breakpoints (min-width, mobile-first).
|
|
2591
|
+
* Keep in sync with themeBreakpoints.
|
|
2592
|
+
*/
|
|
2593
|
+
const BREAKPOINTS = {
|
|
2594
|
+
sm: 640};
|
|
2595
|
+
/** Max-width media query strings (max = breakpoint - 1px). */
|
|
2596
|
+
const MEDIA_MAX = {
|
|
2597
|
+
sm: `max-width: ${BREAKPOINTS.sm - 1}px`};
|
|
2598
|
+
|
|
2599
|
+
const MOBILE_MQ = `(max-width: ${BREAKPOINTS.sm - 1}px)`;
|
|
2600
|
+
/**
|
|
2601
|
+
* Resolves the effective drawer variant based on the explicit `variant` prop
|
|
2602
|
+
* and the current viewport width.
|
|
2603
|
+
*
|
|
2604
|
+
* - If `variant` is explicitly provided (`'permanent'` or `'temporary'`), returns it as-is.
|
|
2605
|
+
* - Otherwise, auto-detects: `'temporary'` on mobile (< sm breakpoint), `'permanent'` on desktop.
|
|
2606
|
+
*
|
|
2607
|
+
* Reacts to viewport changes (window resize) so switching between mobile and desktop
|
|
2608
|
+
* automatically updates the variant without requiring a page reload.
|
|
2609
|
+
*/
|
|
2610
|
+
const useDrawerVariant = (variant) => {
|
|
2611
|
+
const [isMobile, setIsMobile] = React.useState(() => {
|
|
2612
|
+
if (typeof window === 'undefined') {
|
|
2613
|
+
return false;
|
|
2614
|
+
}
|
|
2615
|
+
return window.matchMedia(MOBILE_MQ).matches;
|
|
2616
|
+
});
|
|
2617
|
+
React.useEffect(() => {
|
|
2618
|
+
if (typeof window === 'undefined') {
|
|
2619
|
+
return;
|
|
2620
|
+
}
|
|
2621
|
+
const mq = window.matchMedia(MOBILE_MQ);
|
|
2622
|
+
const handler = (e) => setIsMobile(e.matches);
|
|
2623
|
+
mq.addEventListener('change', handler);
|
|
2624
|
+
return () => mq.removeEventListener('change', handler);
|
|
2625
|
+
}, []);
|
|
2626
|
+
if (variant !== undefined) {
|
|
2627
|
+
return variant;
|
|
2628
|
+
}
|
|
2629
|
+
return isMobile ? 'temporary' : 'permanent';
|
|
2630
|
+
};
|
|
2631
|
+
|
|
2632
|
+
const TRANSITION$1 = `${DEFAULT_TRANSITION_DURATION_MS}ms ease`;
|
|
2633
|
+
const BACKDROP_STYLES = theme.createStyles((theme) => ({
|
|
2634
|
+
root: {
|
|
2635
|
+
position: 'fixed',
|
|
2636
|
+
inset: 0,
|
|
2637
|
+
zIndex: theme.zIndex.modal - 1,
|
|
2638
|
+
backgroundColor: 'rgba(0, 0, 0, 0)',
|
|
2639
|
+
transition: `background-color ${TRANSITION$1}`,
|
|
2640
|
+
},
|
|
2641
|
+
visible: {
|
|
2642
|
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
2643
|
+
},
|
|
2644
|
+
}), { id: 'backdrop' });
|
|
2645
|
+
|
|
2646
|
+
/**
|
|
2647
|
+
* Semi-transparent full-screen overlay used to visually block page content
|
|
2648
|
+
* while a modal element (dialog, temporary drawer…) is open.
|
|
2649
|
+
*
|
|
2650
|
+
* The `visible` prop drives the opacity transition: `false` = transparent,
|
|
2651
|
+
* `true` = `rgba(0,0,0,0.5)`. Mount/unmount is managed by the parent.
|
|
2652
|
+
*
|
|
2653
|
+
* @example
|
|
2654
|
+
* <Backdrop visible={isFadingIn} onClick={onClose} />
|
|
2655
|
+
*/
|
|
2656
|
+
const Backdrop = ({ visible, onClick }) => (jsxRuntime.jsx("div", { className: theme.cx(BACKDROP_STYLES.root, visible && BACKDROP_STYLES.visible), onClick: onClick, "aria-hidden": true }));
|
|
2657
|
+
Backdrop.displayName = 'Backdrop';
|
|
2658
|
+
|
|
2659
|
+
/**
|
|
2660
|
+
* Manages mount/unmount transitions with a two-phase approach (visible → fading-in).
|
|
2661
|
+
*
|
|
2662
|
+
* Opening sequence:
|
|
2663
|
+
* 1. `useLayoutEffect` sets `isVisible=true` synchronously before the browser paints
|
|
2664
|
+
* → element is in the DOM at its initial hidden state on the very first frame.
|
|
2665
|
+
* 2. Double `requestAnimationFrame` in `useEffect` waits for two rendered frames
|
|
2666
|
+
* before setting `isFadingIn=true`, guaranteeing the CSS transition always starts
|
|
2667
|
+
* from the painted hidden state (prevents flickering).
|
|
2668
|
+
*
|
|
2669
|
+
* Closing sequence:
|
|
2670
|
+
* `isFadingIn=false` → CSS transition plays → after `duration` ms → `isVisible=false`.
|
|
2671
|
+
*
|
|
2672
|
+
* @param isOpen - Whether the element should be shown.
|
|
2673
|
+
* @param duration - Transition duration in milliseconds (defaults to `DEFAULT_TRANSITION_DURATION_MS`).
|
|
2674
|
+
*/
|
|
2675
|
+
const useTransitionRender = (isOpen, duration = DEFAULT_TRANSITION_DURATION_MS) => {
|
|
2676
|
+
const [isVisible, setIsVisible] = React.useState(isOpen);
|
|
2677
|
+
const [isFadingIn, setIsFadingIn] = React.useState(isOpen);
|
|
2678
|
+
// Mount synchronously before paint so the element is in the DOM at opacity:0
|
|
2679
|
+
// on the very first frame — no extra render cycle between null and the initial state.
|
|
2680
|
+
React.useLayoutEffect(() => {
|
|
2681
|
+
if (isOpen) {
|
|
2682
|
+
setIsVisible(true);
|
|
2683
|
+
}
|
|
2684
|
+
}, [isOpen]);
|
|
2685
|
+
React.useEffect(() => {
|
|
2686
|
+
if (isOpen) {
|
|
2687
|
+
// Double RAF: frame 1 → element rendered; frame 2 → element painted at hidden
|
|
2688
|
+
// state → THEN trigger the CSS transition.
|
|
2689
|
+
let raf2;
|
|
2690
|
+
const raf1 = requestAnimationFrame(() => {
|
|
2691
|
+
raf2 = requestAnimationFrame(() => setIsFadingIn(true));
|
|
2692
|
+
});
|
|
2693
|
+
return () => {
|
|
2694
|
+
cancelAnimationFrame(raf1);
|
|
2695
|
+
cancelAnimationFrame(raf2);
|
|
2696
|
+
};
|
|
2697
|
+
}
|
|
2698
|
+
else {
|
|
2699
|
+
setIsFadingIn(false);
|
|
2700
|
+
const timeout = setTimeout(() => setIsVisible(false), duration);
|
|
2701
|
+
return () => clearTimeout(timeout);
|
|
2702
|
+
}
|
|
2703
|
+
}, [isOpen, duration]);
|
|
2704
|
+
return { isVisible, isFadingIn };
|
|
2705
|
+
};
|
|
2706
|
+
|
|
2707
|
+
/**
|
|
2708
|
+
* Locks scrolling on `document.body` while `active` is true.
|
|
2709
|
+
*
|
|
2710
|
+
* Preserves the current scroll position and keeps the scrollbar gutter
|
|
2711
|
+
* (`overflow-y: scroll`) to avoid horizontal layout shift when the scrollbar
|
|
2712
|
+
* disappears. The original styles and scroll position are restored on cleanup.
|
|
2713
|
+
*
|
|
2714
|
+
* @example useBodyScrollLock(isDialogOpen)
|
|
2715
|
+
*/
|
|
2716
|
+
const useBodyScrollLock = (active) => {
|
|
2717
|
+
React.useEffect(() => {
|
|
2718
|
+
if (!active) {
|
|
2719
|
+
return;
|
|
2720
|
+
}
|
|
2721
|
+
const scrollY = window.scrollY;
|
|
2722
|
+
const body = document.body;
|
|
2723
|
+
body.style.position = 'fixed';
|
|
2724
|
+
body.style.top = `-${scrollY}px`;
|
|
2725
|
+
body.style.overflowY = 'scroll';
|
|
2726
|
+
body.style.width = '100%';
|
|
2727
|
+
return () => {
|
|
2728
|
+
body.style.position = '';
|
|
2729
|
+
body.style.top = '';
|
|
2730
|
+
body.style.overflowY = '';
|
|
2731
|
+
body.style.width = '';
|
|
2732
|
+
window.scrollTo(0, scrollY);
|
|
2733
|
+
};
|
|
2734
|
+
}, [active]);
|
|
2735
|
+
};
|
|
2736
|
+
|
|
2737
|
+
const DrawerHeader = ({ children, role, ariaLabel, ariaLabelledBy, ariaDescribedBy, ...rest }) => {
|
|
2738
|
+
return (jsxRuntime.jsx(Box, { px: 'sm', py: 'xs', role: role, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, ...rest, children: children }));
|
|
2739
|
+
};
|
|
2740
|
+
|
|
2741
|
+
const DrawerBody = ({ children, role, ariaLabel, ariaLabelledBy, ariaDescribedBy, ...rest }) => {
|
|
2742
|
+
return (jsxRuntime.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 }));
|
|
2743
|
+
};
|
|
2744
|
+
|
|
2745
|
+
const DrawerFooter = ({ children, role, ariaLabel, ariaLabelledBy, ariaDescribedBy, ...rest }) => {
|
|
2746
|
+
return (jsxRuntime.jsx(Box, { px: 'sm', py: 'xs', role: role, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, ...rest, children: children }));
|
|
2747
|
+
};
|
|
2748
|
+
|
|
2749
|
+
const DRAWER_ITEM_STYLES = theme.createStyles((theme) => ({
|
|
2750
|
+
root: ({ selected, isExpanded }) => ({
|
|
2751
|
+
position: 'relative',
|
|
2752
|
+
display: 'flex',
|
|
2753
|
+
alignItems: 'center',
|
|
2754
|
+
width: isExpanded ? EXPANDED_DRAWER_ITEM_WIDTH : COLLAPSED_DRAWER_ITEM_WIDTH,
|
|
2755
|
+
gap: theme.spacing.sm,
|
|
2756
|
+
padding: `0 ${theme.spacing.smPlus}`,
|
|
2757
|
+
border: '1px solid transparent',
|
|
2758
|
+
borderRadius: theme.radius.md,
|
|
2759
|
+
fontFamily: 'inherit',
|
|
2760
|
+
fontSize: theme.fontSize.sm,
|
|
2761
|
+
userSelect: 'none',
|
|
2762
|
+
cursor: 'pointer',
|
|
2763
|
+
outline: 'none',
|
|
2764
|
+
textDecoration: 'none',
|
|
2765
|
+
boxSizing: 'border-box',
|
|
2766
|
+
transition: `width ${theme.transition.slow}, background-color ${theme.transition.fast}, color ${theme.transition.fast}`,
|
|
2767
|
+
whiteSpace: 'nowrap',
|
|
2768
|
+
overflow: 'hidden',
|
|
2769
|
+
height: DEFAULT_DRAWER_ITEM_SIZE,
|
|
2770
|
+
...(selected
|
|
2771
|
+
? {
|
|
2772
|
+
backgroundColor: theme.colors.primarySubtle,
|
|
2773
|
+
color: theme.colors.primaryMain,
|
|
2774
|
+
':hover': { backgroundColor: theme.colors.primarySubtleHover, color: theme.colors.primaryHover },
|
|
2775
|
+
':active': { backgroundColor: theme.colors.primarySubtleActive, color: theme.colors.primaryActive },
|
|
2776
|
+
}
|
|
2777
|
+
: {
|
|
2778
|
+
backgroundColor: 'transparent',
|
|
2779
|
+
color: theme.colors.defaultMain,
|
|
2780
|
+
':hover:not(:disabled)': { backgroundColor: theme.colors.defaultSubtleHover, color: theme.colors.defaultHover },
|
|
2781
|
+
':active:not(:disabled)': { backgroundColor: theme.colors.defaultSubtleActive, color: theme.colors.defaultActive },
|
|
2782
|
+
}),
|
|
2783
|
+
':focus-visible': { boxShadow: theme.shadows.focus },
|
|
2784
|
+
':disabled': { cursor: 'not-allowed', opacity: theme.opacity.high },
|
|
2785
|
+
}),
|
|
2786
|
+
iconWrap: {
|
|
2787
|
+
display: 'flex',
|
|
2788
|
+
alignItems: 'center',
|
|
2789
|
+
justifyContent: 'center',
|
|
2790
|
+
flexShrink: 0,
|
|
2791
|
+
},
|
|
2792
|
+
labelWrapper: ({ isFadingIn }) => ({
|
|
2793
|
+
display: 'flex',
|
|
2794
|
+
alignItems: 'center',
|
|
2795
|
+
flexDirection: 'row',
|
|
2796
|
+
gap: theme.spacing.sm,
|
|
2797
|
+
width: '100%',
|
|
2798
|
+
justifyContent: 'space-between',
|
|
2799
|
+
flex: 1,
|
|
2800
|
+
overflow: 'hidden',
|
|
2801
|
+
minWidth: 0,
|
|
2802
|
+
opacity: isFadingIn ? 1 : 0,
|
|
2803
|
+
transition: `opacity ${DEFAULT_TRANSITION_DURATION_MS}ms ease`,
|
|
2804
|
+
}),
|
|
2805
|
+
label: {
|
|
2806
|
+
flex: 1,
|
|
2807
|
+
overflow: 'hidden',
|
|
2808
|
+
textOverflow: 'ellipsis',
|
|
2809
|
+
fontWeight: theme.fontWeight.medium,
|
|
2810
|
+
},
|
|
2811
|
+
endContent: {
|
|
2812
|
+
display: 'flex',
|
|
2813
|
+
alignItems: 'center',
|
|
2814
|
+
flexShrink: 0,
|
|
2815
|
+
marginLeft: 'auto',
|
|
2816
|
+
},
|
|
2817
|
+
}));
|
|
2818
|
+
|
|
2819
|
+
/**
|
|
2820
|
+
* Navigation/action item for use inside Drawer.Body or Drawer.Footer.
|
|
2821
|
+
*
|
|
2822
|
+
* - In **expanded** mode: shows icon + label (fade-in) + optional end content.
|
|
2823
|
+
* - In **collapsed** mode: shows only the icon; the label fades out before unmounting
|
|
2824
|
+
* and appears as a right-side tooltip on hover.
|
|
2825
|
+
* - Renders as `<a>` when `href` is provided, otherwise as `<button>`.
|
|
2826
|
+
* - The `selected` prop applies a primary-color highlight.
|
|
2827
|
+
*/
|
|
2828
|
+
const DrawerItem = ({ startIcon, label, selected = false, endContent, href, onClick, disabled = false, ariaLabel, ariaLabelledBy, ariaDescribedBy, ariaControls, ariaExpanded, ariaHasPopup, ariaCurrent, }) => {
|
|
2829
|
+
const { isExpanded } = useDrawerContext();
|
|
2830
|
+
const { isVisible: isLabelVisible, isFadingIn: isLabelFadingIn } = useTransitionRender(isExpanded);
|
|
2831
|
+
const rootClassName = DRAWER_ITEM_STYLES.root({ selected, isExpanded });
|
|
2832
|
+
const computedAriaLabel = ariaLabel ?? (!isExpanded ? label : undefined);
|
|
2833
|
+
const computedAriaCurrent = ariaCurrent ?? (selected ? 'page' : undefined);
|
|
2834
|
+
const handleClick = (e) => {
|
|
2835
|
+
if (disabled) {
|
|
2836
|
+
e.preventDefault();
|
|
2837
|
+
return;
|
|
2838
|
+
}
|
|
2839
|
+
onClick?.();
|
|
2840
|
+
};
|
|
2841
|
+
const innerContent = (jsxRuntime.jsxs(Stack, { width: '100%', justifyContent: 'start', children: [jsxRuntime.jsx("span", { className: DRAWER_ITEM_STYLES.iconWrap, children: jsxRuntime.jsx(Icon, { icon: startIcon, size: 'md' }) }), isLabelVisible && (jsxRuntime.jsxs("span", { className: DRAWER_ITEM_STYLES.labelWrapper({ isFadingIn: isLabelFadingIn }), children: [jsxRuntime.jsx(Text, { variant: 'span', fontSize: 'sm', fontWeight: 'medium', className: DRAWER_ITEM_STYLES.label, textAlign: 'start', children: label }), endContent && (jsxRuntime.jsx("span", { className: DRAWER_ITEM_STYLES.endContent, children: endContent }))] }))] }));
|
|
2842
|
+
const item = href ? (jsxRuntime.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 })) : (jsxRuntime.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 }));
|
|
2843
|
+
return (jsxRuntime.jsx(Tooltip, { label: label, placement: 'right', inline: true, withArrow: true, disabled: isExpanded || disabled, children: item }));
|
|
2844
|
+
};
|
|
2845
|
+
DrawerItem.displayName = 'DrawerItem';
|
|
2846
|
+
|
|
2847
|
+
/**
|
|
2848
|
+
* Internal component: renders the temporary drawer as a fixed portal overlay
|
|
2849
|
+
* that slides in from the left with a backdrop, animated via `useTransitionRender`.
|
|
2850
|
+
*/
|
|
2851
|
+
const DrawerTemporaryPanel = ({ isExpanded, onClose, children, role, ariaLabel, ariaLabelledBy, ariaDescribedBy, }) => {
|
|
2852
|
+
const { isVisible, isFadingIn } = useTransitionRender(isExpanded);
|
|
2853
|
+
useBodyScrollLock(isExpanded);
|
|
2854
|
+
useKeyPress({ Escape: onClose }, { enabled: isExpanded });
|
|
2855
|
+
if (!isVisible) {
|
|
2856
|
+
return null;
|
|
2857
|
+
}
|
|
2858
|
+
return reactDom.createPortal(jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Backdrop, { visible: isFadingIn, onClick: onClose }), jsxRuntime.jsx(DrawerContext.Provider, { value: { isExpanded: true }, children: jsxRuntime.jsx("nav", { className: theme.cx(DRAWER_STYLES.temporaryPanel, isFadingIn && DRAWER_STYLES.temporaryPanelVisible), role: role, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, children: children }) })] }), document.body);
|
|
2859
|
+
};
|
|
2860
|
+
DrawerTemporaryPanel.displayName = 'DrawerTemporaryPanel';
|
|
2861
|
+
// ─── Main Drawer ─────────────────────────────────────────────────────────────
|
|
2862
|
+
/**
|
|
2863
|
+
* Collapsible side navigation drawer with controlled expanded/collapsed state.
|
|
2864
|
+
*
|
|
2865
|
+
* **Variants**
|
|
2866
|
+
* - `'permanent'` (default on desktop): inline drawer that pushes page content.
|
|
2867
|
+
* Toggles between `expanded` and `collapsed` states with animated width.
|
|
2868
|
+
* - `'temporary'` (default on mobile): portal overlay with a backdrop that slides
|
|
2869
|
+
* in from the left. `isExpanded` controls open/closed; `onClose` is called on
|
|
2870
|
+
* backdrop click or Escape.
|
|
2871
|
+
* - Omit `variant` to auto-detect based on viewport width.
|
|
2872
|
+
*
|
|
2873
|
+
* @example Permanent
|
|
2874
|
+
* <Drawer isExpanded={open} onExpandedChange={setOpen} height="100dvh">
|
|
2875
|
+
* <Drawer.Header>…</Drawer.Header>
|
|
2876
|
+
* <Drawer.Body>
|
|
2877
|
+
* <Drawer.Item startIcon={HomeIcon} label="Home" selected />
|
|
2878
|
+
* </Drawer.Body>
|
|
2879
|
+
* </Drawer>
|
|
2880
|
+
*
|
|
2881
|
+
* @example Temporary
|
|
2882
|
+
* <Drawer variant="temporary" isExpanded={open} onClose={() => setOpen(false)}>
|
|
2883
|
+
* …
|
|
2884
|
+
* </Drawer>
|
|
2885
|
+
*/
|
|
2886
|
+
const DrawerBase = ({ height = '100dvh', isExpanded, onExpandedChange, onClose, variant, children, role = 'navigation', ariaLabel = 'Navigation', ariaLabelledBy, ariaDescribedBy, }) => {
|
|
2887
|
+
const resolvedVariant = useDrawerVariant(variant);
|
|
2888
|
+
const handleClose = onClose ?? (() => onExpandedChange?.(false));
|
|
2889
|
+
if (resolvedVariant === 'temporary') {
|
|
2890
|
+
return (jsxRuntime.jsx(DrawerTemporaryPanel, { isExpanded: isExpanded, onClose: handleClose, height: height, role: role, ariaLabel: ariaLabel, ariaLabelledBy: ariaLabelledBy, ariaDescribedBy: ariaDescribedBy, children: children }));
|
|
2891
|
+
}
|
|
2892
|
+
return (jsxRuntime.jsx(DrawerContext.Provider, { value: { isExpanded }, children: jsxRuntime.jsx("nav", { className: DRAWER_STYLES.root({ isExpanded }), style: { height }, role: role, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, children: children }) }));
|
|
2893
|
+
};
|
|
2894
|
+
DrawerBase.displayName = 'Drawer';
|
|
2895
|
+
const Drawer = DrawerBase;
|
|
2896
|
+
Drawer.Header = DrawerHeader;
|
|
2897
|
+
Drawer.Body = DrawerBody;
|
|
2898
|
+
Drawer.Footer = DrawerFooter;
|
|
2899
|
+
Drawer.Item = DrawerItem;
|
|
2900
|
+
|
|
2357
2901
|
const AlertContext = React.createContext({
|
|
2358
2902
|
variant: 'default',
|
|
2359
2903
|
accentColor: 'defaultActive',
|
|
@@ -2425,31 +2969,8 @@ const Alert = AlertBase;
|
|
|
2425
2969
|
Alert.Title = AlertTitle;
|
|
2426
2970
|
Alert.Body = AlertBody;
|
|
2427
2971
|
|
|
2428
|
-
/**
|
|
2429
|
-
* Responsive breakpoints (min-width, mobile-first).
|
|
2430
|
-
* Keep in sync with themeBreakpoints.
|
|
2431
|
-
*/
|
|
2432
|
-
const BREAKPOINTS = {
|
|
2433
|
-
sm: 640};
|
|
2434
|
-
/** Max-width media query strings (max = breakpoint - 1px). */
|
|
2435
|
-
const MEDIA_MAX = {
|
|
2436
|
-
sm: `max-width: ${BREAKPOINTS.sm - 1}px`};
|
|
2437
|
-
|
|
2438
|
-
/** Default duration in milliseconds for mount/unmount transition animations. */
|
|
2439
|
-
const DEFAULT_TRANSITION_DURATION_MS = 250;
|
|
2440
|
-
|
|
2441
2972
|
const TRANSITION = `${DEFAULT_TRANSITION_DURATION_MS}ms ease`;
|
|
2442
2973
|
const DIALOG_STYLES = theme.createStyles((theme) => ({
|
|
2443
|
-
backdrop: {
|
|
2444
|
-
position: 'fixed',
|
|
2445
|
-
inset: 0,
|
|
2446
|
-
zIndex: theme.zIndex.modal - 1,
|
|
2447
|
-
backgroundColor: 'rgba(0, 0, 0, 0)',
|
|
2448
|
-
transition: `background-color ${TRANSITION}`,
|
|
2449
|
-
},
|
|
2450
|
-
backdropVisible: {
|
|
2451
|
-
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
2452
|
-
},
|
|
2453
2974
|
panel: {
|
|
2454
2975
|
position: 'fixed',
|
|
2455
2976
|
inset: 0,
|
|
@@ -2507,84 +3028,6 @@ const DialogContext = React.createContext({
|
|
|
2507
3028
|
CloseIconComponent: null,
|
|
2508
3029
|
});
|
|
2509
3030
|
|
|
2510
|
-
/**
|
|
2511
|
-
* Manages mount/unmount transitions with a two-phase approach (visible → fading-in).
|
|
2512
|
-
*
|
|
2513
|
-
* Opening sequence:
|
|
2514
|
-
* 1. `useLayoutEffect` sets `isVisible=true` synchronously before the browser paints
|
|
2515
|
-
* → element is in the DOM at its initial hidden state on the very first frame.
|
|
2516
|
-
* 2. Double `requestAnimationFrame` in `useEffect` waits for two rendered frames
|
|
2517
|
-
* before setting `isFadingIn=true`, guaranteeing the CSS transition always starts
|
|
2518
|
-
* from the painted hidden state (prevents flickering).
|
|
2519
|
-
*
|
|
2520
|
-
* Closing sequence:
|
|
2521
|
-
* `isFadingIn=false` → CSS transition plays → after `duration` ms → `isVisible=false`.
|
|
2522
|
-
*
|
|
2523
|
-
* @param isOpen - Whether the element should be shown.
|
|
2524
|
-
* @param duration - Transition duration in milliseconds (defaults to `DEFAULT_TRANSITION_DURATION_MS`).
|
|
2525
|
-
*/
|
|
2526
|
-
const useTransitionRender = (isOpen, duration = DEFAULT_TRANSITION_DURATION_MS) => {
|
|
2527
|
-
const [isVisible, setIsVisible] = React.useState(isOpen);
|
|
2528
|
-
const [isFadingIn, setIsFadingIn] = React.useState(isOpen);
|
|
2529
|
-
// Mount synchronously before paint so the element is in the DOM at opacity:0
|
|
2530
|
-
// on the very first frame — no extra render cycle between null and the initial state.
|
|
2531
|
-
React.useLayoutEffect(() => {
|
|
2532
|
-
if (isOpen) {
|
|
2533
|
-
setIsVisible(true);
|
|
2534
|
-
}
|
|
2535
|
-
}, [isOpen]);
|
|
2536
|
-
React.useEffect(() => {
|
|
2537
|
-
if (isOpen) {
|
|
2538
|
-
// Double RAF: frame 1 → element rendered; frame 2 → element painted at hidden
|
|
2539
|
-
// state → THEN trigger the CSS transition.
|
|
2540
|
-
let raf2;
|
|
2541
|
-
const raf1 = requestAnimationFrame(() => {
|
|
2542
|
-
raf2 = requestAnimationFrame(() => setIsFadingIn(true));
|
|
2543
|
-
});
|
|
2544
|
-
return () => {
|
|
2545
|
-
cancelAnimationFrame(raf1);
|
|
2546
|
-
cancelAnimationFrame(raf2);
|
|
2547
|
-
};
|
|
2548
|
-
}
|
|
2549
|
-
else {
|
|
2550
|
-
setIsFadingIn(false);
|
|
2551
|
-
const timeout = setTimeout(() => setIsVisible(false), duration);
|
|
2552
|
-
return () => clearTimeout(timeout);
|
|
2553
|
-
}
|
|
2554
|
-
}, [isOpen, duration]);
|
|
2555
|
-
return { isVisible, isFadingIn };
|
|
2556
|
-
};
|
|
2557
|
-
|
|
2558
|
-
/**
|
|
2559
|
-
* Locks scrolling on `document.body` while `active` is true.
|
|
2560
|
-
*
|
|
2561
|
-
* Preserves the current scroll position and keeps the scrollbar gutter
|
|
2562
|
-
* (`overflow-y: scroll`) to avoid horizontal layout shift when the scrollbar
|
|
2563
|
-
* disappears. The original styles and scroll position are restored on cleanup.
|
|
2564
|
-
*
|
|
2565
|
-
* @example useBodyScrollLock(isDialogOpen)
|
|
2566
|
-
*/
|
|
2567
|
-
const useBodyScrollLock = (active) => {
|
|
2568
|
-
React.useEffect(() => {
|
|
2569
|
-
if (!active) {
|
|
2570
|
-
return;
|
|
2571
|
-
}
|
|
2572
|
-
const scrollY = window.scrollY;
|
|
2573
|
-
const body = document.body;
|
|
2574
|
-
body.style.position = 'fixed';
|
|
2575
|
-
body.style.top = `-${scrollY}px`;
|
|
2576
|
-
body.style.overflowY = 'scroll';
|
|
2577
|
-
body.style.width = '100%';
|
|
2578
|
-
return () => {
|
|
2579
|
-
body.style.position = '';
|
|
2580
|
-
body.style.top = '';
|
|
2581
|
-
body.style.overflowY = '';
|
|
2582
|
-
body.style.width = '';
|
|
2583
|
-
window.scrollTo(0, scrollY);
|
|
2584
|
-
};
|
|
2585
|
-
}, [active]);
|
|
2586
|
-
};
|
|
2587
|
-
|
|
2588
3031
|
const FOCUSABLE_SELECTOR = [
|
|
2589
3032
|
'a[href]',
|
|
2590
3033
|
'button:not([disabled])',
|
|
@@ -2720,7 +3163,7 @@ const DialogBase = ({ open, onClose, children, closeOnBackdropClick = false, ful
|
|
|
2720
3163
|
if (!isVisible) {
|
|
2721
3164
|
return null;
|
|
2722
3165
|
}
|
|
2723
|
-
return reactDom.createPortal(jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(
|
|
3166
|
+
return reactDom.createPortal(jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Backdrop, { visible: isFadingIn, onClick: handleBackdropClick }), jsxRuntime.jsx("div", { ref: panelRef, role: 'dialog', "aria-modal": true, "aria-labelledby": labelledBy, "aria-label": ariaLabel, tabIndex: -1, className: theme.cx(DIALOG_STYLES.panel, isFadingIn && DIALOG_STYLES.panelVisible, fullscreen && DIALOG_STYLES.panelFullscreen), style: cssVars, children: jsxRuntime.jsx(DialogContext.Provider, { value: { titleId, CloseIconComponent: CloseIcon }, children: children }) })] }), document.body);
|
|
2724
3167
|
};
|
|
2725
3168
|
DialogBase.displayName = 'Dialog';
|
|
2726
3169
|
const Dialog = DialogBase;
|
|
@@ -2931,13 +3374,19 @@ const themeShadows = {
|
|
|
2931
3374
|
|
|
2932
3375
|
/**
|
|
2933
3376
|
* Default spacing tokens
|
|
3377
|
+
*
|
|
3378
|
+
* ⚠️ Token keys MUST stay CSS-custom-property safe (letters, digits, hyphens only).
|
|
3379
|
+
* They are turned into CSS variables (e.g. `smPlus` → `--theme-spacing-sm-plus`) by
|
|
3380
|
+
* the theme proxy, so characters like `+` would produce invalid variable names and
|
|
3381
|
+
* silently break any style that uses them.
|
|
2934
3382
|
*/
|
|
2935
3383
|
const themeSpacing = {
|
|
2936
3384
|
none: '0',
|
|
2937
3385
|
'2xs': '0.125rem', // 2px
|
|
2938
3386
|
xs: '0.25rem', // 4px
|
|
2939
|
-
|
|
3387
|
+
xsPlus: '0.375rem', // 6px
|
|
2940
3388
|
sm: '0.5rem', // 8px
|
|
3389
|
+
smPlus: '0.75rem', // 12px
|
|
2941
3390
|
md: '1rem', // 16px
|
|
2942
3391
|
lg: '1.5rem', // 24px
|
|
2943
3392
|
xl: '2rem', // 32px
|
|
@@ -2951,9 +3400,9 @@ const themeSpacing = {
|
|
|
2951
3400
|
* Default transition tokens
|
|
2952
3401
|
*/
|
|
2953
3402
|
const themeTransition = {
|
|
2954
|
-
fast: '150ms ease-out',
|
|
2955
|
-
normal: '250ms ease-out',
|
|
2956
|
-
slow: '350ms ease-out',
|
|
3403
|
+
fast: '150ms ease-in-out',
|
|
3404
|
+
normal: '250ms ease-in-out',
|
|
3405
|
+
slow: '350ms ease-in-out',
|
|
2957
3406
|
};
|
|
2958
3407
|
|
|
2959
3408
|
/**
|
|
@@ -3109,11 +3558,14 @@ const darkTheme = theme.createTheme({
|
|
|
3109
3558
|
});
|
|
3110
3559
|
|
|
3111
3560
|
exports.Alert = Alert;
|
|
3561
|
+
exports.Backdrop = Backdrop;
|
|
3112
3562
|
exports.Badge = Badge;
|
|
3113
3563
|
exports.Box = Box;
|
|
3114
3564
|
exports.Button = Button;
|
|
3115
3565
|
exports.Card = Card;
|
|
3566
|
+
exports.Checkbox = Checkbox;
|
|
3116
3567
|
exports.Dialog = Dialog;
|
|
3568
|
+
exports.Drawer = Drawer;
|
|
3117
3569
|
exports.Form = Form;
|
|
3118
3570
|
exports.Grid = Grid;
|
|
3119
3571
|
exports.Icon = Icon;
|
|
@@ -3130,4 +3582,5 @@ exports.TextField = TextField;
|
|
|
3130
3582
|
exports.Tooltip = Tooltip;
|
|
3131
3583
|
exports.darkTheme = darkTheme;
|
|
3132
3584
|
exports.lightTheme = lightTheme;
|
|
3585
|
+
exports.useDrawerContext = useDrawerContext;
|
|
3133
3586
|
//# sourceMappingURL=index.js.map
|