@aurora-ds/components 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/index.js CHANGED
@@ -379,8 +379,8 @@ const buildActionButtonRootStyle = (theme, variant, color, focusOptions) => {
379
379
  backgroundColor: intent.main,
380
380
  borderColor: intent.main,
381
381
  color: intent.on,
382
- ':hover:not(:disabled)': { backgroundColor: intent.hover, borderColor: intent.hover },
383
- ':active:not(:disabled)': { backgroundColor: intent.active, borderColor: intent.active },
382
+ ':hover:not(:disabled)': { backgroundColor: intent.hover },
383
+ ':active:not(:disabled)': { backgroundColor: intent.active },
384
384
  }
385
385
  : variant === 'outlined'
386
386
  ? {
@@ -388,7 +388,7 @@ const buildActionButtonRootStyle = (theme, variant, color, focusOptions) => {
388
388
  borderColor: intent.border,
389
389
  color: intent.fg,
390
390
  ':hover:not(:disabled)': { backgroundColor: intent.subtleHover, color: intent.fgHover },
391
- ':active:not(:disabled)': { backgroundColor: intent.subtleActive, borderColor: intent.active, color: intent.active },
391
+ ':active:not(:disabled)': { backgroundColor: intent.subtleActive, color: intent.active },
392
392
  }
393
393
  : {
394
394
  backgroundColor: 'transparent',
@@ -410,7 +410,7 @@ const buildActionButtonRootStyle = (theme, variant, color, focusOptions) => {
410
410
  height: DEFAULT_BUTTON_HEIGHT,
411
411
  cursor: 'pointer',
412
412
  outline: 'none',
413
- transition: `background-color ${theme.transition.normal}, border-color ${theme.transition.normal}, color ${theme.transition.normal}`,
413
+ transition: `background-color ${theme.transition.normal}, color ${theme.transition.normal}`,
414
414
  ...colorVariantStyles,
415
415
  ':focus-visible': getFocusRingStyles(theme, focusOptions),
416
416
  ':disabled': { cursor: 'not-allowed', opacity: theme.opacity.high },
@@ -3298,17 +3298,17 @@ const TEXTFIELD_WRAPPER_VARIANTS = theme.createVariants((theme) => {
3298
3298
  return {
3299
3299
  base: {
3300
3300
  display: 'flex',
3301
- alignItems: 'center',
3302
- gap: theme.spacing.sm,
3301
+ // `stretch` lets the inner input fill the full height of the box so
3302
+ // clicking anywhere (including the vertical padding area) hits it.
3303
+ alignItems: 'stretch',
3303
3304
  boxSizing: 'border-box',
3304
3305
  borderWidth: '1px',
3305
3306
  borderStyle: 'solid',
3306
3307
  borderRadius: theme.radius.md,
3307
3308
  backgroundColor: c.surfacePaper,
3309
+ // No focus ring on text inputs: focus is conveyed by the status
3310
+ // border colour change (see the `status` variants below).
3308
3311
  transition: `border-color ${theme.transition.fast}`,
3309
- // Full, always-visible focus ring when the inner input is focused
3310
- // (single source of truth) — complements the status border colour.
3311
- ':focus-within': getFocusRingStyles(theme),
3312
3312
  '&[data-disabled]': {
3313
3313
  opacity: theme.opacity.high,
3314
3314
  backgroundColor: c.disabledMain,
@@ -3317,10 +3317,12 @@ const TEXTFIELD_WRAPPER_VARIANTS = theme.createVariants((theme) => {
3317
3317
  },
3318
3318
  variants: {
3319
3319
  size: {
3320
+ // Horizontal padding lives on the input itself (see TEXTFIELD_INPUT_VARIANTS),
3321
+ // not here, so the whole bordered area is part of the clickable input.
3320
3322
  // Font-size is set on the wrapper so the native input inherits it via `fontSize: 'inherit'`.
3321
- sm: { height: '2rem', paddingLeft: theme.spacing.sm, paddingRight: theme.spacing.xs, fontSize: theme.fontSize.xs },
3322
- md: { height: '2.5rem', paddingLeft: theme.spacing.sm, paddingRight: theme.spacing.xs, fontSize: theme.fontSize.sm },
3323
- lg: { height: '3rem', paddingLeft: theme.spacing.md, paddingRight: theme.spacing.sm, fontSize: theme.fontSize.md },
3323
+ sm: { height: '2rem', fontSize: theme.fontSize.xs },
3324
+ md: { height: '2.5rem', fontSize: theme.fontSize.sm },
3325
+ lg: { height: '3rem', fontSize: theme.fontSize.md },
3324
3326
  },
3325
3327
  status: {
3326
3328
  default: {
@@ -3345,11 +3347,17 @@ const TEXTFIELD_WRAPPER_VARIANTS = theme.createVariants((theme) => {
3345
3347
  defaultVariants: { size: 'md', status: 'default' },
3346
3348
  };
3347
3349
  }, { id: 'textfield-wrapper' });
3348
- const TEXTFIELD_STYLES = theme.createStyles((theme) => ({
3349
- /** Visually hidden native input fills the remaining space inside the wrapper. */
3350
- input: {
3350
+ /**
3351
+ * The native input carries the horizontal padding and fills 100% of the
3352
+ * wrapper height, so the entire bordered box is part of the clickable input
3353
+ * (no dead zone between the text and the border).
3354
+ */
3355
+ const TEXTFIELD_INPUT_VARIANTS = theme.createVariants((theme) => ({
3356
+ base: {
3351
3357
  flex: 1,
3352
3358
  minWidth: 0,
3359
+ height: '100%',
3360
+ boxSizing: 'border-box',
3353
3361
  border: 'none',
3354
3362
  outline: 'none',
3355
3363
  background: 'transparent',
@@ -3357,15 +3365,25 @@ const TEXTFIELD_STYLES = theme.createStyles((theme) => ({
3357
3365
  fontFamily: 'inherit',
3358
3366
  fontSize: 'inherit',
3359
3367
  lineHeight: 'normal',
3360
- padding: '0',
3361
3368
  '&::placeholder': { color: theme.colors.textTertiary },
3362
3369
  '&:disabled': { cursor: 'not-allowed', color: theme.colors.textDisabled },
3363
3370
  },
3371
+ variants: {
3372
+ size: {
3373
+ sm: { paddingLeft: theme.spacing.sm, paddingRight: theme.spacing.sm },
3374
+ md: { paddingLeft: theme.spacing.sm, paddingRight: theme.spacing.sm },
3375
+ lg: { paddingLeft: theme.spacing.md, paddingRight: theme.spacing.md },
3376
+ },
3377
+ },
3378
+ defaultVariants: { size: 'md' },
3379
+ }), { id: 'textfield-input' });
3380
+ const TEXTFIELD_STYLES = theme.createStyles((theme) => ({
3364
3381
  /** Wrapper for the start icon — aligned with the input baseline. Clickable to focus the input. */
3365
3382
  startIconWrap: {
3366
3383
  display: 'flex',
3367
3384
  alignItems: 'center',
3368
3385
  flexShrink: 0,
3386
+ paddingLeft: theme.spacing.sm,
3369
3387
  cursor: 'pointer',
3370
3388
  },
3371
3389
  /** Wrapper for end actions (custom content + optional password toggle). */
@@ -3373,6 +3391,7 @@ const TEXTFIELD_STYLES = theme.createStyles((theme) => ({
3373
3391
  display: 'flex',
3374
3392
  alignItems: 'center',
3375
3393
  flexShrink: 0,
3394
+ paddingRight: theme.spacing.xs,
3376
3395
  gap: theme.spacing['2xs'],
3377
3396
  },
3378
3397
  }), { id: 'textfield-extra' });
@@ -3433,7 +3452,11 @@ const useTextField = ({ id, ref, type, size, endAction, }) => {
3433
3452
  */
3434
3453
  const TextField = ({ ref, label, helperText, size = 'md', status = 'default', startIcon: StartIcon, endAction, type, id, disabled, required, ...rest }) => {
3435
3454
  const { fieldId, helperId, mergedRef, isPassword, showPassword, togglePassword, resolvedType, iconSize, iconButtonSize, hasEndSection, focusInput, } = useTextField({ id, ref, type, size, endAction });
3436
- 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, "aria-hidden": true, 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, "aria-errormessage": status === 'error' && 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', onClick: togglePassword }))] }))] }), helperText !== undefined && (jsxRuntime.jsx(FormHelperText, { id: helperId, status: status, children: helperText }))] }));
3455
+ // Associate the label with the input via `aria-labelledby` (and NOT `htmlFor`)
3456
+ // so the label stays accessible without natively focusing the input on click —
3457
+ // only clicking inside the bordered box should focus the field.
3458
+ const labelId = `${fieldId}-label`;
3459
+ return (jsxRuntime.jsxs(Stack, { flexDirection: 'column', gap: 'xs', children: [label !== undefined && (jsxRuntime.jsxs(Text, { variant: 'label', fontSize: 'sm', fontWeight: 'medium', color: 'textSecondary', id: labelId, 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, "aria-hidden": true, 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-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 && (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', onClick: togglePassword }))] }))] }), helperText !== undefined && (jsxRuntime.jsx(FormHelperText, { id: helperId, status: status, children: helperText }))] }));
3437
3460
  };
3438
3461
  TextField.displayName = 'TextField';
3439
3462
 
@@ -4026,8 +4049,8 @@ const SELECT_TRIGGER_VARIANTS = theme.createVariants((theme) => {
4026
4049
  transition: `border-color ${theme.transition.fast}`,
4027
4050
  outline: 'none',
4028
4051
  fontFamily: 'inherit',
4029
- // Full, always-visible focus ring (single source of truth).
4030
- ':focus-visible': getFocusRingStyles(theme),
4052
+ // No focus ring on the trigger: focus is conveyed by the border
4053
+ // colour change (see the `status` variants below).
4031
4054
  '&[data-open]': {
4032
4055
  borderColor: c.primaryMain,
4033
4056
  },
@@ -4064,6 +4087,9 @@ const SELECT_TRIGGER_VARIANTS = theme.createVariants((theme) => {
4064
4087
  ':hover:not([data-disabled]):not([data-open])': {
4065
4088
  borderColor: c.borderStrong,
4066
4089
  },
4090
+ '&:focus-visible:not([data-open])': {
4091
+ borderColor: c.primaryMain,
4092
+ },
4067
4093
  },
4068
4094
  error: {
4069
4095
  borderColor: c.errorMain,
@@ -6766,6 +6792,67 @@ const Dialog = DialogBase;
6766
6792
  Dialog.Header = DialogHeader;
6767
6793
  Dialog.Body = DialogBody;
6768
6794
 
6795
+ /**
6796
+ * Manages keyboard navigation for a listbox-style menu.
6797
+ *
6798
+ * Binds ArrowDown, ArrowUp, Home, End, and Enter using `useKeyPress`.
6799
+ * Automatically skips disabled items and optionally wraps around (loop).
6800
+ * Resets focus when `enabled` toggles.
6801
+ */
6802
+ const useListKeyNav = ({ itemCount, enabled, onSelect, isDisabled, loop = true, initialIndex = 0, }) => {
6803
+ const [focusedIndex, setFocusedIndex] = React.useState(-1);
6804
+ const initialIndexRef = React.useRef(initialIndex);
6805
+ React.useEffect(() => {
6806
+ initialIndexRef.current = initialIndex;
6807
+ }, [initialIndex]);
6808
+ React.useEffect(() => {
6809
+ setFocusedIndex(enabled ? initialIndexRef.current : -1);
6810
+ }, [enabled]);
6811
+ const getNextIndex = React.useCallback((current, direction) => {
6812
+ if (itemCount === 0) {
6813
+ return -1;
6814
+ }
6815
+ let next = current + direction;
6816
+ for (let i = 0; i < itemCount; i++) {
6817
+ if (next < 0) {
6818
+ next = loop ? itemCount - 1 : 0;
6819
+ }
6820
+ if (next >= itemCount) {
6821
+ next = loop ? 0 : itemCount - 1;
6822
+ }
6823
+ if (!isDisabled?.(next)) {
6824
+ return next;
6825
+ }
6826
+ next += direction;
6827
+ }
6828
+ return current;
6829
+ }, [itemCount, loop, isDisabled]);
6830
+ useKeyPress({
6831
+ ArrowDown: (e) => {
6832
+ e.preventDefault();
6833
+ setFocusedIndex((prev) => getNextIndex(prev, 1));
6834
+ },
6835
+ ArrowUp: (e) => {
6836
+ e.preventDefault();
6837
+ setFocusedIndex((prev) => getNextIndex(prev, -1));
6838
+ },
6839
+ Home: (e) => {
6840
+ e.preventDefault();
6841
+ setFocusedIndex(getNextIndex(-1, 1));
6842
+ },
6843
+ End: (e) => {
6844
+ e.preventDefault();
6845
+ setFocusedIndex(getNextIndex(itemCount, -1));
6846
+ },
6847
+ Enter: () => {
6848
+ if (focusedIndex >= 0) {
6849
+ onSelect(focusedIndex);
6850
+ }
6851
+ },
6852
+ }, { enabled });
6853
+ return { focusedIndex, setFocusedIndex };
6854
+ };
6855
+
6769
6856
  const lightPalette = {
6770
6857
  // Surface
6771
6858
  surfaceBackground: '#f8fafc',
@@ -7226,5 +7313,14 @@ exports.ToggleIconButton = ToggleIconButton;
7226
7313
  exports.Tooltip = Tooltip;
7227
7314
  exports.darkTheme = darkTheme;
7228
7315
  exports.lightTheme = lightTheme;
7316
+ exports.useBodyScrollLock = useBodyScrollLock;
7317
+ exports.useControllableState = useControllableState;
7229
7318
  exports.useDrawerContext = useDrawerContext;
7319
+ exports.useFocusTrap = useFocusTrap;
7320
+ exports.useKeyPress = useKeyPress;
7321
+ exports.useListKeyNav = useListKeyNav;
7322
+ exports.useMenuPosition = useMenuPosition;
7323
+ exports.useMergedRefs = useMergedRefs;
7324
+ exports.useTooltipPosition = useTooltipPosition;
7325
+ exports.useTransitionRender = useTransitionRender;
7230
7326
  //# sourceMappingURL=index.js.map