@aurora-ds/components 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +216 -34
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +205 -35
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.ts +305 -13
- package/package.json +1 -1
package/dist/esm/index.js
CHANGED
|
@@ -359,8 +359,8 @@ const buildActionButtonRootStyle = (theme, variant, color, focusOptions) => {
|
|
|
359
359
|
backgroundColor: intent.main,
|
|
360
360
|
borderColor: intent.main,
|
|
361
361
|
color: intent.on,
|
|
362
|
-
':hover:not(:disabled)': { backgroundColor: intent.hover
|
|
363
|
-
':active:not(:disabled)': { backgroundColor: intent.active
|
|
362
|
+
':hover:not(:disabled)': { backgroundColor: intent.hover },
|
|
363
|
+
':active:not(:disabled)': { backgroundColor: intent.active },
|
|
364
364
|
}
|
|
365
365
|
: variant === 'outlined'
|
|
366
366
|
? {
|
|
@@ -368,7 +368,7 @@ const buildActionButtonRootStyle = (theme, variant, color, focusOptions) => {
|
|
|
368
368
|
borderColor: intent.border,
|
|
369
369
|
color: intent.fg,
|
|
370
370
|
':hover:not(:disabled)': { backgroundColor: intent.subtleHover, color: intent.fgHover },
|
|
371
|
-
':active:not(:disabled)': { backgroundColor: intent.subtleActive,
|
|
371
|
+
':active:not(:disabled)': { backgroundColor: intent.subtleActive, color: intent.active },
|
|
372
372
|
}
|
|
373
373
|
: {
|
|
374
374
|
backgroundColor: 'transparent',
|
|
@@ -390,7 +390,7 @@ const buildActionButtonRootStyle = (theme, variant, color, focusOptions) => {
|
|
|
390
390
|
height: DEFAULT_BUTTON_HEIGHT,
|
|
391
391
|
cursor: 'pointer',
|
|
392
392
|
outline: 'none',
|
|
393
|
-
transition: `background-color ${theme.transition.normal},
|
|
393
|
+
transition: `background-color ${theme.transition.normal}, color ${theme.transition.normal}`,
|
|
394
394
|
...colorVariantStyles,
|
|
395
395
|
':focus-visible': getFocusRingStyles(theme, focusOptions),
|
|
396
396
|
':disabled': { cursor: 'not-allowed', opacity: theme.opacity.high },
|
|
@@ -952,13 +952,13 @@ const LINK_STYLES = createStyles((theme) => ({
|
|
|
952
952
|
* In that case the component stays accessible: it gets `role="link"`,
|
|
953
953
|
* `tabIndex={0}` and keyboard Enter support automatically.
|
|
954
954
|
*
|
|
955
|
-
* @example <Link href='/about'
|
|
956
|
-
* @example <Link href='https://example.com'
|
|
957
|
-
* @example <Link href='/profile' underline='always' startIcon={UserIcon}
|
|
958
|
-
* @example <Link href='/terms' underline='none'
|
|
959
|
-
* @example <Link onClick={() => navigate('/about')}
|
|
955
|
+
* @example <Link href='/about' label='About' />
|
|
956
|
+
* @example <Link href='https://example.com' label='External site' external />
|
|
957
|
+
* @example <Link href='/profile' underline='always' startIcon={UserIcon} label='Profile' />
|
|
958
|
+
* @example <Link href='/terms' underline='none' label='Terms' />
|
|
959
|
+
* @example <Link onClick={() => navigate('/about')} label='About (SPA)' />
|
|
960
960
|
*/
|
|
961
|
-
const Link = ({ ref, underline = 'hover', color = 'default', external = false, disabled = false, startIcon: StartIcon, endIcon: EndIcon,
|
|
961
|
+
const Link = ({ ref, label, fontSize = 'md', underline = 'hover', color = 'default', external = false, disabled = false, startIcon: StartIcon, endIcon: EndIcon, className, href, onClick, onKeyDown, ...rest }) => {
|
|
962
962
|
// An <a> without href has no implicit ARIA role and is not focusable.
|
|
963
963
|
// When used for SPA navigation (onClick only), we restore both behaviours.
|
|
964
964
|
const hasHref = !!href;
|
|
@@ -984,7 +984,7 @@ const Link = ({ ref, underline = 'hover', color = 'default', external = false, d
|
|
|
984
984
|
// With href: the browser handles focusability natively (no tabIndex needed).
|
|
985
985
|
tabIndex: disabled ? -1 : (!hasHref ? 0 : undefined),
|
|
986
986
|
// Without href: <a> has no implicit ARIA role — add role="link" explicitly.
|
|
987
|
-
role: !hasHref ? 'link' : undefined, target: external ? '_blank' : undefined, rel: external ? 'noopener noreferrer' : undefined, onClick: handleClick, onKeyDown: handleKeyDown, ...rest, children: [StartIcon && (jsx("span", { className: LINK_STYLES.icon, "aria-hidden": true, children: jsx(StartIcon, { width: '1em', height: '1em' }) })), children, EndIcon && (jsx("span", { className: LINK_STYLES.icon, "aria-hidden": true, children: jsx(EndIcon, { width: '1em', height: '1em' }) }))] }));
|
|
987
|
+
role: !hasHref ? 'link' : undefined, target: external ? '_blank' : undefined, rel: external ? 'noopener noreferrer' : undefined, onClick: handleClick, onKeyDown: handleKeyDown, ...rest, children: [StartIcon && (jsx("span", { className: LINK_STYLES.icon, "aria-hidden": true, children: jsx(StartIcon, { width: '1em', height: '1em' }) })), jsx(Text, { as: 'span', fontSize: fontSize, children: label }), EndIcon && (jsx("span", { className: LINK_STYLES.icon, "aria-hidden": true, children: jsx(EndIcon, { width: '1em', height: '1em' }) }))] }));
|
|
988
988
|
};
|
|
989
989
|
Link.displayName = 'Link';
|
|
990
990
|
|
|
@@ -3278,17 +3278,17 @@ const TEXTFIELD_WRAPPER_VARIANTS = createVariants((theme) => {
|
|
|
3278
3278
|
return {
|
|
3279
3279
|
base: {
|
|
3280
3280
|
display: 'flex',
|
|
3281
|
-
|
|
3282
|
-
|
|
3281
|
+
// `stretch` lets the inner input fill the full height of the box so
|
|
3282
|
+
// clicking anywhere (including the vertical padding area) hits it.
|
|
3283
|
+
alignItems: 'stretch',
|
|
3283
3284
|
boxSizing: 'border-box',
|
|
3284
3285
|
borderWidth: '1px',
|
|
3285
3286
|
borderStyle: 'solid',
|
|
3286
3287
|
borderRadius: theme.radius.md,
|
|
3287
3288
|
backgroundColor: c.surfacePaper,
|
|
3289
|
+
// No focus ring on text inputs: focus is conveyed by the status
|
|
3290
|
+
// border colour change (see the `status` variants below).
|
|
3288
3291
|
transition: `border-color ${theme.transition.fast}`,
|
|
3289
|
-
// Full, always-visible focus ring when the inner input is focused
|
|
3290
|
-
// (single source of truth) — complements the status border colour.
|
|
3291
|
-
':focus-within': getFocusRingStyles(theme),
|
|
3292
3292
|
'&[data-disabled]': {
|
|
3293
3293
|
opacity: theme.opacity.high,
|
|
3294
3294
|
backgroundColor: c.disabledMain,
|
|
@@ -3297,10 +3297,12 @@ const TEXTFIELD_WRAPPER_VARIANTS = createVariants((theme) => {
|
|
|
3297
3297
|
},
|
|
3298
3298
|
variants: {
|
|
3299
3299
|
size: {
|
|
3300
|
+
// Horizontal padding lives on the input itself (see TEXTFIELD_INPUT_VARIANTS),
|
|
3301
|
+
// not here, so the whole bordered area is part of the clickable input.
|
|
3300
3302
|
// Font-size is set on the wrapper so the native input inherits it via `fontSize: 'inherit'`.
|
|
3301
|
-
sm: { height: '2rem',
|
|
3302
|
-
md: { height: '2.5rem',
|
|
3303
|
-
lg: { height: '3rem',
|
|
3303
|
+
sm: { height: '2rem', fontSize: theme.fontSize.xs },
|
|
3304
|
+
md: { height: '2.5rem', fontSize: theme.fontSize.sm },
|
|
3305
|
+
lg: { height: '3rem', fontSize: theme.fontSize.md },
|
|
3304
3306
|
},
|
|
3305
3307
|
status: {
|
|
3306
3308
|
default: {
|
|
@@ -3325,11 +3327,17 @@ const TEXTFIELD_WRAPPER_VARIANTS = createVariants((theme) => {
|
|
|
3325
3327
|
defaultVariants: { size: 'md', status: 'default' },
|
|
3326
3328
|
};
|
|
3327
3329
|
}, { id: 'textfield-wrapper' });
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3330
|
+
/**
|
|
3331
|
+
* The native input carries the horizontal padding and fills 100% of the
|
|
3332
|
+
* wrapper height, so the entire bordered box is part of the clickable input
|
|
3333
|
+
* (no dead zone between the text and the border).
|
|
3334
|
+
*/
|
|
3335
|
+
const TEXTFIELD_INPUT_VARIANTS = createVariants((theme) => ({
|
|
3336
|
+
base: {
|
|
3331
3337
|
flex: 1,
|
|
3332
3338
|
minWidth: 0,
|
|
3339
|
+
height: '100%',
|
|
3340
|
+
boxSizing: 'border-box',
|
|
3333
3341
|
border: 'none',
|
|
3334
3342
|
outline: 'none',
|
|
3335
3343
|
background: 'transparent',
|
|
@@ -3337,15 +3345,25 @@ const TEXTFIELD_STYLES = createStyles((theme) => ({
|
|
|
3337
3345
|
fontFamily: 'inherit',
|
|
3338
3346
|
fontSize: 'inherit',
|
|
3339
3347
|
lineHeight: 'normal',
|
|
3340
|
-
padding: '0',
|
|
3341
3348
|
'&::placeholder': { color: theme.colors.textTertiary },
|
|
3342
3349
|
'&:disabled': { cursor: 'not-allowed', color: theme.colors.textDisabled },
|
|
3343
3350
|
},
|
|
3351
|
+
variants: {
|
|
3352
|
+
size: {
|
|
3353
|
+
sm: { paddingLeft: theme.spacing.sm, paddingRight: theme.spacing.sm },
|
|
3354
|
+
md: { paddingLeft: theme.spacing.sm, paddingRight: theme.spacing.sm },
|
|
3355
|
+
lg: { paddingLeft: theme.spacing.md, paddingRight: theme.spacing.md },
|
|
3356
|
+
},
|
|
3357
|
+
},
|
|
3358
|
+
defaultVariants: { size: 'md' },
|
|
3359
|
+
}), { id: 'textfield-input' });
|
|
3360
|
+
const TEXTFIELD_STYLES = createStyles((theme) => ({
|
|
3344
3361
|
/** Wrapper for the start icon — aligned with the input baseline. Clickable to focus the input. */
|
|
3345
3362
|
startIconWrap: {
|
|
3346
3363
|
display: 'flex',
|
|
3347
3364
|
alignItems: 'center',
|
|
3348
3365
|
flexShrink: 0,
|
|
3366
|
+
paddingLeft: theme.spacing.sm,
|
|
3349
3367
|
cursor: 'pointer',
|
|
3350
3368
|
},
|
|
3351
3369
|
/** Wrapper for end actions (custom content + optional password toggle). */
|
|
@@ -3353,6 +3371,7 @@ const TEXTFIELD_STYLES = createStyles((theme) => ({
|
|
|
3353
3371
|
display: 'flex',
|
|
3354
3372
|
alignItems: 'center',
|
|
3355
3373
|
flexShrink: 0,
|
|
3374
|
+
paddingRight: theme.spacing.xs,
|
|
3356
3375
|
gap: theme.spacing['2xs'],
|
|
3357
3376
|
},
|
|
3358
3377
|
}), { id: 'textfield-extra' });
|
|
@@ -3413,7 +3432,11 @@ const useTextField = ({ id, ref, type, size, endAction, }) => {
|
|
|
3413
3432
|
*/
|
|
3414
3433
|
const TextField = ({ ref, label, helperText, size = 'md', status = 'default', startIcon: StartIcon, endAction, type, id, disabled, required, ...rest }) => {
|
|
3415
3434
|
const { fieldId, helperId, mergedRef, isPassword, showPassword, togglePassword, resolvedType, iconSize, iconButtonSize, hasEndSection, focusInput, } = useTextField({ id, ref, type, size, endAction });
|
|
3416
|
-
|
|
3435
|
+
// Associate the label with the input via `aria-labelledby` (and NOT `htmlFor`)
|
|
3436
|
+
// so the label stays accessible without natively focusing the input on click —
|
|
3437
|
+
// only clicking inside the bordered box should focus the field.
|
|
3438
|
+
const labelId = `${fieldId}-label`;
|
|
3439
|
+
return (jsxs(Stack, { flexDirection: 'column', gap: 'xs', children: [label !== undefined && (jsxs(Text, { variant: 'label', fontSize: 'sm', fontWeight: 'medium', color: 'textSecondary', id: labelId, 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, "aria-hidden": true, 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-labelledby": label !== undefined ? labelId : undefined, "aria-describedby": helperText !== undefined ? helperId : undefined, "aria-errormessage": status === 'error' && helperText !== undefined ? helperId : undefined, className: TEXTFIELD_INPUT_VARIANTS({ size }), ...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', onClick: togglePassword }))] }))] }), helperText !== undefined && (jsx(FormHelperText, { id: helperId, status: status, children: helperText }))] }));
|
|
3417
3440
|
};
|
|
3418
3441
|
TextField.displayName = 'TextField';
|
|
3419
3442
|
|
|
@@ -4006,8 +4029,8 @@ const SELECT_TRIGGER_VARIANTS = createVariants((theme) => {
|
|
|
4006
4029
|
transition: `border-color ${theme.transition.fast}`,
|
|
4007
4030
|
outline: 'none',
|
|
4008
4031
|
fontFamily: 'inherit',
|
|
4009
|
-
//
|
|
4010
|
-
|
|
4032
|
+
// No focus ring on the trigger: focus is conveyed by the border
|
|
4033
|
+
// colour change (see the `status` variants below).
|
|
4011
4034
|
'&[data-open]': {
|
|
4012
4035
|
borderColor: c.primaryMain,
|
|
4013
4036
|
},
|
|
@@ -4044,6 +4067,9 @@ const SELECT_TRIGGER_VARIANTS = createVariants((theme) => {
|
|
|
4044
4067
|
':hover:not([data-disabled]):not([data-open])': {
|
|
4045
4068
|
borderColor: c.borderStrong,
|
|
4046
4069
|
},
|
|
4070
|
+
'&:focus-visible:not([data-open])': {
|
|
4071
|
+
borderColor: c.primaryMain,
|
|
4072
|
+
},
|
|
4047
4073
|
},
|
|
4048
4074
|
error: {
|
|
4049
4075
|
borderColor: c.errorMain,
|
|
@@ -5626,7 +5652,13 @@ const DRAWER_STYLES = createStyles((theme) => ({
|
|
|
5626
5652
|
* Keep in sync with themeBreakpoints.
|
|
5627
5653
|
*/
|
|
5628
5654
|
const BREAKPOINTS = {
|
|
5629
|
-
|
|
5655
|
+
xs: 480,
|
|
5656
|
+
sm: 640,
|
|
5657
|
+
md: 768,
|
|
5658
|
+
lg: 1024,
|
|
5659
|
+
xl: 1280,
|
|
5660
|
+
'2xl': 1536,
|
|
5661
|
+
};
|
|
5630
5662
|
/** Max-width media query strings (max = breakpoint - 1px). */
|
|
5631
5663
|
const MEDIA_MAX = {
|
|
5632
5664
|
sm: `max-width: ${BREAKPOINTS.sm - 1}px`};
|
|
@@ -6525,6 +6557,13 @@ const AlertContext = createContext({
|
|
|
6525
6557
|
});
|
|
6526
6558
|
const useAlertContext = () => useContext(AlertContext);
|
|
6527
6559
|
|
|
6560
|
+
const VARIANT_DISMISS_COLOR = {
|
|
6561
|
+
default: 'neutral',
|
|
6562
|
+
success: 'success',
|
|
6563
|
+
error: 'error',
|
|
6564
|
+
warning: 'warning',
|
|
6565
|
+
info: 'info',
|
|
6566
|
+
};
|
|
6528
6567
|
const VARIANT_ICONS = {
|
|
6529
6568
|
success: AlertSuccessIcon,
|
|
6530
6569
|
error: AlertErrorIcon,
|
|
@@ -6537,7 +6576,7 @@ const isSvgComponent = (value) => typeof value === 'function';
|
|
|
6537
6576
|
* Alert title row: renders the variant icon alongside the title text.
|
|
6538
6577
|
* Must be used inside an `<Alert>` component.
|
|
6539
6578
|
*/
|
|
6540
|
-
const AlertTitle = ({ children, icon }) => {
|
|
6579
|
+
const AlertTitle = ({ children, icon, onDismiss }) => {
|
|
6541
6580
|
const { variant, accentColor } = useAlertContext();
|
|
6542
6581
|
// Resolve which icon to render:
|
|
6543
6582
|
// - custom icon prop always takes precedence over the built-in variant icon
|
|
@@ -6547,7 +6586,7 @@ const AlertTitle = ({ children, icon }) => {
|
|
|
6547
6586
|
const ResolvedIcon = icon && isSvgComponent(icon) ? icon : (icon === undefined ? (builtInIcon ?? null) : null);
|
|
6548
6587
|
const customNode = icon && !isSvgComponent(icon) ? icon : null;
|
|
6549
6588
|
const hasIcon = ResolvedIcon !== null || customNode !== null;
|
|
6550
|
-
return (jsxs(Stack, { flexDirection: 'row', alignItems: 'center', gap: 'sm', children: [hasIcon && (jsx(Stack, { flexShrink: 0, alignItems: 'center', color: accentColor, width: '1.25rem', height: '1.25rem', "aria-hidden": true, children: ResolvedIcon ? (jsx(ResolvedIcon, { width: 20, height: 20 })) : (customNode) })), jsx(Text, { fontWeight: 'semibold', fontSize: 'sm', color: accentColor, children: children })] }));
|
|
6589
|
+
return (jsxs(Stack, { flexDirection: 'row', alignItems: 'center', gap: 'sm', justifyContent: onDismiss ? 'space-between' : undefined, children: [jsxs(Stack, { flexDirection: 'row', alignItems: 'center', gap: 'sm', children: [hasIcon && (jsx(Stack, { flexShrink: 0, alignItems: 'center', color: accentColor, width: '1.25rem', height: '1.25rem', "aria-hidden": true, children: ResolvedIcon ? (jsx(ResolvedIcon, { width: 20, height: 20 })) : (customNode) })), jsx(Text, { fontWeight: 'semibold', fontSize: 'sm', color: accentColor, children: children })] }), onDismiss && (jsx(IconButton, { icon: CloseIcon, ariaLabel: 'Dismiss', variant: 'text', color: VARIANT_DISMISS_COLOR[variant], size: 'sm', onClick: onDismiss }))] }));
|
|
6551
6590
|
};
|
|
6552
6591
|
AlertTitle.displayName = 'Alert.Title';
|
|
6553
6592
|
|
|
@@ -6557,6 +6596,22 @@ AlertTitle.displayName = 'Alert.Title';
|
|
|
6557
6596
|
const AlertBody = ({ children }) => (jsx(Text, { as: 'p', fontSize: 'sm', color: 'textSecondary', children: children }));
|
|
6558
6597
|
AlertBody.displayName = 'Alert.Body';
|
|
6559
6598
|
|
|
6599
|
+
/**
|
|
6600
|
+
* Alert actions row: renders action elements (buttons, links…) at the bottom of an `<Alert>`.
|
|
6601
|
+
* Must be used inside an `<Alert>` component.
|
|
6602
|
+
*
|
|
6603
|
+
* @example
|
|
6604
|
+
* <Alert variant="error">
|
|
6605
|
+
* <Alert.Title>Something went wrong</Alert.Title>
|
|
6606
|
+
* <Alert.Body>Please try again.</Alert.Body>
|
|
6607
|
+
* <Alert.Actions>
|
|
6608
|
+
* <Button size="sm" variant="outlined" color="error">Retry</Button>
|
|
6609
|
+
* </Alert.Actions>
|
|
6610
|
+
* </Alert>
|
|
6611
|
+
*/
|
|
6612
|
+
const AlertActions = ({ children }) => (jsx(Stack, { flexDirection: 'row', alignItems: 'center', flexWrap: 'wrap', justifyContent: 'end', gap: 'sm', paddingTop: 'xs', children: children }));
|
|
6613
|
+
AlertActions.displayName = 'Alert.Actions';
|
|
6614
|
+
|
|
6560
6615
|
const VARIANT_TOKENS = {
|
|
6561
6616
|
default: { backgroundColor: 'surfacePaper', borderColor: 'defaultMain', accentColor: 'defaultActive' },
|
|
6562
6617
|
success: { backgroundColor: 'successSubtle', borderColor: 'successMain', accentColor: 'successActive' },
|
|
@@ -6598,6 +6653,7 @@ AlertBase.displayName = 'Alert';
|
|
|
6598
6653
|
const Alert = AlertBase;
|
|
6599
6654
|
Alert.Title = AlertTitle;
|
|
6600
6655
|
Alert.Body = AlertBody;
|
|
6656
|
+
Alert.Actions = AlertActions;
|
|
6601
6657
|
|
|
6602
6658
|
const TRANSITION = `${DEFAULT_TRANSITION_DURATION_MS}ms ease`;
|
|
6603
6659
|
const DIALOG_STYLES = createStyles((theme) => ({
|
|
@@ -6644,12 +6700,18 @@ const DIALOG_STYLES = createStyles((theme) => ({
|
|
|
6644
6700
|
transform: 'translateY(0)',
|
|
6645
6701
|
},
|
|
6646
6702
|
},
|
|
6647
|
-
// Full-
|
|
6703
|
+
// Full-viewport variant (applied via `fullscreen` prop — all screen sizes)
|
|
6648
6704
|
panelFullscreen: {
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6705
|
+
top: 0,
|
|
6706
|
+
left: 0,
|
|
6707
|
+
right: 0,
|
|
6708
|
+
bottom: 0,
|
|
6709
|
+
margin: 0,
|
|
6710
|
+
width: '100%',
|
|
6711
|
+
height: '100dvh',
|
|
6712
|
+
maxWidth: 'none',
|
|
6713
|
+
maxHeight: 'none',
|
|
6714
|
+
borderRadius: 0,
|
|
6653
6715
|
},
|
|
6654
6716
|
}), { id: 'dialog' });
|
|
6655
6717
|
|
|
@@ -6746,6 +6808,114 @@ const Dialog = DialogBase;
|
|
|
6746
6808
|
Dialog.Header = DialogHeader;
|
|
6747
6809
|
Dialog.Body = DialogBody;
|
|
6748
6810
|
|
|
6811
|
+
/**
|
|
6812
|
+
* Listens to a CSS media query string and returns whether it currently matches.
|
|
6813
|
+
*
|
|
6814
|
+
* @param query - A valid CSS media query string, e.g. `'(max-width: 639px)'`.
|
|
6815
|
+
* @returns `true` when the media query matches, `false` otherwise.
|
|
6816
|
+
*
|
|
6817
|
+
* @example
|
|
6818
|
+
* const isMobile = useMediaQuery('(max-width: 639px)')
|
|
6819
|
+
*/
|
|
6820
|
+
const useMediaQuery = (query) => {
|
|
6821
|
+
const [matches, setMatches] = useState(() => {
|
|
6822
|
+
if (typeof window === 'undefined') {
|
|
6823
|
+
return false;
|
|
6824
|
+
}
|
|
6825
|
+
return window.matchMedia(query).matches;
|
|
6826
|
+
});
|
|
6827
|
+
useEffect(() => {
|
|
6828
|
+
if (typeof window === 'undefined') {
|
|
6829
|
+
return;
|
|
6830
|
+
}
|
|
6831
|
+
const mediaQueryList = window.matchMedia(query);
|
|
6832
|
+
setMatches(mediaQueryList.matches);
|
|
6833
|
+
const listener = (event) => setMatches(event.matches);
|
|
6834
|
+
mediaQueryList.addEventListener('change', listener);
|
|
6835
|
+
return () => mediaQueryList.removeEventListener('change', listener);
|
|
6836
|
+
}, [query]);
|
|
6837
|
+
return matches;
|
|
6838
|
+
};
|
|
6839
|
+
/**
|
|
6840
|
+
* Returns `true` when the viewport width is **below** the given breakpoint (mobile-first max).
|
|
6841
|
+
*
|
|
6842
|
+
* @param breakpoint - One of the Aurora breakpoints: `xs` | `sm` | `md` | `lg` | `xl` | `2xl`.
|
|
6843
|
+
*
|
|
6844
|
+
* @example
|
|
6845
|
+
* const isMobile = useBreakpointMax('sm') // true when width < 640px
|
|
6846
|
+
*/
|
|
6847
|
+
const useBreakpointMax = (breakpoint) => useMediaQuery(`(max-width: ${BREAKPOINTS[breakpoint] - 1}px)`);
|
|
6848
|
+
/**
|
|
6849
|
+
* Returns `true` when the viewport width is **at or above** the given breakpoint.
|
|
6850
|
+
*
|
|
6851
|
+
* @param breakpoint - One of the Aurora breakpoints: `xs` | `sm` | `md` | `lg` | `xl` | `2xl`.
|
|
6852
|
+
*
|
|
6853
|
+
* @example
|
|
6854
|
+
* const isDesktop = useBreakpointMin('md') // true when width >= 768px
|
|
6855
|
+
*/
|
|
6856
|
+
const useBreakpointMin = (breakpoint) => useMediaQuery(`(min-width: ${BREAKPOINTS[breakpoint]}px)`);
|
|
6857
|
+
|
|
6858
|
+
/**
|
|
6859
|
+
* Manages keyboard navigation for a listbox-style menu.
|
|
6860
|
+
*
|
|
6861
|
+
* Binds ArrowDown, ArrowUp, Home, End, and Enter using `useKeyPress`.
|
|
6862
|
+
* Automatically skips disabled items and optionally wraps around (loop).
|
|
6863
|
+
* Resets focus when `enabled` toggles.
|
|
6864
|
+
*/
|
|
6865
|
+
const useListKeyNav = ({ itemCount, enabled, onSelect, isDisabled, loop = true, initialIndex = 0, }) => {
|
|
6866
|
+
const [focusedIndex, setFocusedIndex] = useState(-1);
|
|
6867
|
+
const initialIndexRef = useRef(initialIndex);
|
|
6868
|
+
useEffect(() => {
|
|
6869
|
+
initialIndexRef.current = initialIndex;
|
|
6870
|
+
}, [initialIndex]);
|
|
6871
|
+
useEffect(() => {
|
|
6872
|
+
setFocusedIndex(enabled ? initialIndexRef.current : -1);
|
|
6873
|
+
}, [enabled]);
|
|
6874
|
+
const getNextIndex = useCallback((current, direction) => {
|
|
6875
|
+
if (itemCount === 0) {
|
|
6876
|
+
return -1;
|
|
6877
|
+
}
|
|
6878
|
+
let next = current + direction;
|
|
6879
|
+
for (let i = 0; i < itemCount; i++) {
|
|
6880
|
+
if (next < 0) {
|
|
6881
|
+
next = loop ? itemCount - 1 : 0;
|
|
6882
|
+
}
|
|
6883
|
+
if (next >= itemCount) {
|
|
6884
|
+
next = loop ? 0 : itemCount - 1;
|
|
6885
|
+
}
|
|
6886
|
+
if (!isDisabled?.(next)) {
|
|
6887
|
+
return next;
|
|
6888
|
+
}
|
|
6889
|
+
next += direction;
|
|
6890
|
+
}
|
|
6891
|
+
return current;
|
|
6892
|
+
}, [itemCount, loop, isDisabled]);
|
|
6893
|
+
useKeyPress({
|
|
6894
|
+
ArrowDown: (e) => {
|
|
6895
|
+
e.preventDefault();
|
|
6896
|
+
setFocusedIndex((prev) => getNextIndex(prev, 1));
|
|
6897
|
+
},
|
|
6898
|
+
ArrowUp: (e) => {
|
|
6899
|
+
e.preventDefault();
|
|
6900
|
+
setFocusedIndex((prev) => getNextIndex(prev, -1));
|
|
6901
|
+
},
|
|
6902
|
+
Home: (e) => {
|
|
6903
|
+
e.preventDefault();
|
|
6904
|
+
setFocusedIndex(getNextIndex(-1, 1));
|
|
6905
|
+
},
|
|
6906
|
+
End: (e) => {
|
|
6907
|
+
e.preventDefault();
|
|
6908
|
+
setFocusedIndex(getNextIndex(itemCount, -1));
|
|
6909
|
+
},
|
|
6910
|
+
Enter: () => {
|
|
6911
|
+
if (focusedIndex >= 0) {
|
|
6912
|
+
onSelect(focusedIndex);
|
|
6913
|
+
}
|
|
6914
|
+
},
|
|
6915
|
+
}, { enabled });
|
|
6916
|
+
return { focusedIndex, setFocusedIndex };
|
|
6917
|
+
};
|
|
6918
|
+
|
|
6749
6919
|
const lightPalette = {
|
|
6750
6920
|
// Surface
|
|
6751
6921
|
surfaceBackground: '#f8fafc',
|
|
@@ -7154,5 +7324,5 @@ const darkTheme = createTheme({
|
|
|
7154
7324
|
breakpoints: themeBreakpoints,
|
|
7155
7325
|
});
|
|
7156
7326
|
|
|
7157
|
-
export { Accordion, Alert, Article, Aside, Avatar, AvatarGroup, Backdrop, Badge, Box, Breadcrumb, Button, Card, Checkbox, DatePicker, DefaultErrorFallback, Dialog, Drawer, ErrorBoundary, Fab, Footer, Form, Grid, Header, Icon, IconButton, Image, InfoBubble, Link, LoaderScreen, Main, Menu, Nav, Pagination, RadioButton, RadioGroup, Section, Select, Separator, Skeleton, Stack, SvgImage, Switch, Table, Tabs, Text, TextField, ToggleButton, ToggleGroup, ToggleIconButton, Tooltip, darkTheme, lightTheme, useDrawerContext };
|
|
7327
|
+
export { Accordion, Alert, Article, Aside, Avatar, AvatarGroup, Backdrop, Badge, Box, Breadcrumb, Button, Card, Checkbox, DatePicker, DefaultErrorFallback, Dialog, Drawer, ErrorBoundary, Fab, Footer, Form, Grid, Header, Icon, IconButton, Image, InfoBubble, Link, LoaderScreen, Main, Menu, Nav, Pagination, RadioButton, RadioGroup, Section, Select, Separator, Skeleton, Stack, SvgImage, Switch, Table, Tabs, Text, TextField, ToggleButton, ToggleGroup, ToggleIconButton, Tooltip, darkTheme, lightTheme, useBodyScrollLock, useBreakpointMax, useBreakpointMin, useControllableState, useDrawerContext, useFocusTrap, useKeyPress, useListKeyNav, useMediaQuery, useMenuPosition, useMergedRefs, useTooltipPosition, useTransitionRender };
|
|
7158
7328
|
//# sourceMappingURL=index.js.map
|