@aurora-ds/components 1.1.7 → 1.2.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/esm/index.js CHANGED
@@ -574,7 +574,7 @@ const ICON_SIZE$2 = {
574
574
  * @example <Button label='Delete' variant='outlined' color='error' startIcon={IconRegistry.CloseIcon} />
575
575
  * @example <Button label='Submitting…' color='success' isLoading width='100%' />
576
576
  */
577
- const Button = ({ ref, variant = 'contained', color = 'primary', size = 'md', width, flexGrow, flexShrink, isLoading = false, startIcon: StartIcon, endIcon: EndIcon, label, className, type = 'button', disabled, style, ...rest }) => {
577
+ const Button = ({ ref, variant = 'contained', color = 'primary', size = 'md', width, flexGrow, flexShrink, isLoading = false, startIcon: StartIcon, endIcon: EndIcon, label, children, className, type = 'button', disabled, style, ...rest }) => {
578
578
  const isDisabled = disabled || isLoading;
579
579
  const iconSize = ICON_SIZE$2[size];
580
580
  const rootClassName = cx(BUTTON_STYLES.root({ variant, color, size }), className);
@@ -584,7 +584,7 @@ const Button = ({ ref, variant = 'contained', color = 'primary', size = 'md', wi
584
584
  ...(flexGrow !== undefined ? { flexGrow } : {}),
585
585
  ...(flexShrink !== undefined ? { flexShrink } : {}),
586
586
  };
587
- return (jsxs("button", { ref: ref, type: type, className: rootClassName, disabled: isDisabled, "aria-busy": isLoading || undefined, style: mergedStyle, ...rest, children: [isLoading && (jsx("span", { className: BUTTON_STYLES.spinnerWrap, children: jsx(Icon, { icon: SpinnerIcon, size: iconSize, className: BUTTON_STYLES.spinnerIcon }) })), jsxs("span", { className: cx(BUTTON_STYLES.content, isLoading && BUTTON_STYLES.contentHidden), children: [StartIcon && (jsx(Icon, { icon: StartIcon, size: iconSize })), label && (jsx(Text, { variant: 'span', fontSize: LABEL_FONT_SIZE$1[size], fontWeight: 'medium', lineHeight: 'none', children: label })), EndIcon && (jsx(Icon, { icon: EndIcon, size: iconSize }))] })] }));
587
+ return (jsxs("button", { ref: ref, type: type, className: rootClassName, disabled: isDisabled, "aria-busy": isLoading || undefined, style: mergedStyle, ...rest, children: [isLoading && (jsx("span", { className: BUTTON_STYLES.spinnerWrap, children: jsx(Icon, { icon: SpinnerIcon, size: iconSize, className: BUTTON_STYLES.spinnerIcon }) })), jsxs("span", { className: cx(BUTTON_STYLES.content, isLoading && BUTTON_STYLES.contentHidden), children: [StartIcon && (jsx(Icon, { icon: StartIcon, size: iconSize })), (label !== undefined || children !== undefined) && (jsx(Text, { variant: 'span', fontSize: LABEL_FONT_SIZE$1[size], fontWeight: 'medium', lineHeight: 'none', children: label ?? children })), EndIcon && (jsx(Icon, { icon: EndIcon, size: iconSize }))] })] }));
588
588
  };
589
589
  Button.displayName = 'Button';
590
590
 
@@ -683,12 +683,20 @@ const LINK_STYLES = createStyles((theme) => ({
683
683
  /**
684
684
  * Theme-aware anchor element with optional icons and underline control.
685
685
  *
686
+ * Supports SPA navigation (e.g. React Router) via `onClick` without `href`.
687
+ * In that case the component stays accessible: it gets `role="link"`,
688
+ * `tabIndex={0}` and keyboard Enter support automatically.
689
+ *
686
690
  * @example <Link href='/about'>About</Link>
687
691
  * @example <Link href='https://example.com' external>External site</Link>
688
692
  * @example <Link href='/profile' underline='always' startIcon={UserIcon}>Profile</Link>
689
693
  * @example <Link href='/terms' underline='none'>Terms</Link>
694
+ * @example <Link onClick={() => navigate('/about')}>About (SPA)</Link>
690
695
  */
691
- const Link = ({ ref, underline = 'hover', external = false, disabled = false, startIcon: StartIcon, endIcon: EndIcon, children, className, onClick, onKeyDown, ...rest }) => {
696
+ const Link = ({ ref, underline = 'hover', external = false, disabled = false, startIcon: StartIcon, endIcon: EndIcon, children, className, href, onClick, onKeyDown, ...rest }) => {
697
+ // An <a> without href has no implicit ARIA role and is not focusable.
698
+ // When used for SPA navigation (onClick only), we restore both behaviours.
699
+ const hasHref = !!href;
692
700
  const handleClick = (e) => {
693
701
  if (disabled) {
694
702
  e.preventDefault();
@@ -696,14 +704,22 @@ const Link = ({ ref, underline = 'hover', external = false, disabled = false, st
696
704
  }
697
705
  onClick?.(e);
698
706
  };
699
- // Prevents Enter navigation when disabled; satisfies jsx-a11y/click-events-have-key-events.
700
707
  const handleKeyDown = (e) => {
701
708
  if (disabled && e.key === 'Enter') {
702
709
  e.preventDefault();
703
710
  }
711
+ // Without href, the browser does not fire a click on Enter natively.
712
+ if (!hasHref && !disabled && e.key === 'Enter') {
713
+ e.currentTarget.click();
714
+ }
704
715
  onKeyDown?.(e);
705
716
  };
706
- return (jsxs("a", { ref: ref, className: cx(LINK_STYLES.root({ underline }), className), "aria-disabled": disabled || undefined, tabIndex: disabled ? -1 : 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' }) }))] }));
717
+ return (jsxs("a", { ref: ref, href: href, className: cx(LINK_STYLES.root({ underline }), className), "aria-disabled": disabled || undefined,
718
+ // Without href: must be explicitly put in the tab order.
719
+ // With href: the browser handles focusability natively (no tabIndex needed).
720
+ tabIndex: disabled ? -1 : (!hasHref ? 0 : undefined),
721
+ // Without href: <a> has no implicit ARIA role — add role="link" explicitly.
722
+ 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' }) }))] }));
707
723
  };
708
724
  Link.displayName = 'Link';
709
725
 
@@ -1759,7 +1775,7 @@ const useTextField = ({ id, ref, type, size, endAction, }) => {
1759
1775
  */
1760
1776
  const TextField = ({ ref, label, helperText, size = 'md', status = 'default', startIcon: StartIcon, endAction, type, id, disabled, required, ...rest }) => {
1761
1777
  const { fieldId, helperId, mergedRef, isPassword, showPassword, togglePassword, resolvedType, iconSize, iconButtonSize, hasEndSection, focusInput, } = useTextField({ id, ref, type, size, endAction });
1762
- return (jsxs(Stack, { flexDirection: 'column', gap: 'xs', children: [label !== undefined && (jsxs(Text, { variant: 'label', fontSize: 'sm', fontWeight: 'medium', color: 'textSecondary', htmlFor: fieldId, children: [label, required && (jsx(Text, { variant: 'span', color: 'errorMain', "aria-hidden": true, children: ' *' }))] })), jsxs("div", { className: TEXTFIELD_WRAPPER_VARIANTS({ size, status }), "data-disabled": disabled || undefined, children: [StartIcon && (jsx("span", { className: TEXTFIELD_STYLES.startIconWrap, onClick: focusInput, children: jsx(Icon, { icon: StartIcon, size: iconSize, strokeColor: 'textSecondary' }) })), jsx("input", { ref: mergedRef, id: fieldId, type: resolvedType, disabled: disabled, required: required, "aria-required": required || undefined, "aria-invalid": status === 'error' || undefined, "aria-describedby": helperText !== undefined ? helperId : undefined, className: TEXTFIELD_STYLES.input, ...rest }), hasEndSection && (jsxs("span", { className: TEXTFIELD_STYLES.endActionWrap, children: [endAction, isPassword && (jsx(IconButton, { icon: showPassword ? EyeSlashIcon : EyeIcon, ariaLabel: showPassword ? 'Hide password' : 'Show password', variant: 'text', color: 'neutral', size: iconButtonSize, type: 'button', tabIndex: -1, onClick: togglePassword }))] }))] }), helperText !== undefined && (jsx(FormHelperText, { id: helperId, status: status, children: helperText }))] }));
1778
+ return (jsxs(Stack, { flexDirection: 'column', gap: 'xs', children: [label !== undefined && (jsxs(Text, { variant: 'label', fontSize: 'sm', fontWeight: 'medium', color: 'textSecondary', htmlFor: fieldId, children: [label, required && (jsx(Text, { variant: 'span', color: 'errorMain', "aria-hidden": true, children: ' *' }))] })), jsxs("div", { className: TEXTFIELD_WRAPPER_VARIANTS({ size, status }), "data-disabled": disabled || undefined, children: [StartIcon && (jsx("span", { className: TEXTFIELD_STYLES.startIconWrap, onClick: focusInput, "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-describedby": helperText !== undefined ? helperId : undefined, "aria-errormessage": status === 'error' && helperText !== undefined ? helperId : undefined, className: TEXTFIELD_STYLES.input, ...rest }), hasEndSection && (jsxs("span", { className: TEXTFIELD_STYLES.endActionWrap, children: [endAction, isPassword && (jsx(IconButton, { icon: showPassword ? EyeSlashIcon : EyeIcon, ariaLabel: showPassword ? 'Hide password' : 'Show password', variant: 'text', color: 'neutral', size: iconButtonSize, type: 'button', onClick: togglePassword }))] }))] }), helperText !== undefined && (jsx(FormHelperText, { id: helperId, status: status, children: helperText }))] }));
1763
1779
  };
1764
1780
  TextField.displayName = 'TextField';
1765
1781
 
@@ -1894,7 +1910,9 @@ const useMenuPosition = ({ anchorEl, open, menuRef, minWidth, gap = 4, }) => {
1894
1910
  */
1895
1911
  const useMenu = ({ open, onClose, anchorEl, minWidth }) => {
1896
1912
  const panelRef = useRef(null);
1913
+ const baseId = useId();
1897
1914
  const [focusedIndex, setFocusedIndex] = useState(-1);
1915
+ const [activeDescendant, setActiveDescendant] = useState(undefined);
1898
1916
  const { style } = useMenuPosition({ anchorEl, open, menuRef: panelRef, minWidth });
1899
1917
  /** Returns all non-disabled option elements inside the panel. */
1900
1918
  const getOptions = useCallback(() => {
@@ -1917,22 +1935,31 @@ const useMenu = ({ open, onClose, anchorEl, minWidth }) => {
1917
1935
  });
1918
1936
  return () => cancelAnimationFrame(raf);
1919
1937
  }, [open, getOptions]);
1920
- // Keep data-focused in sync with focusedIndex
1938
+ // Keep data-focused (visual highlight) and aria-activedescendant (screen reader
1939
+ // announcement) in sync with focusedIndex. Each option is assigned a stable id
1940
+ // so the listbox can reference the active one via aria-activedescendant.
1921
1941
  useEffect(() => {
1922
1942
  if (!open) {
1943
+ setActiveDescendant(undefined);
1923
1944
  return;
1924
1945
  }
1925
1946
  const options = getOptions();
1947
+ let activeId;
1926
1948
  options.forEach((el, idx) => {
1949
+ if (!el.id) {
1950
+ el.id = `${baseId}-option-${idx}`;
1951
+ }
1927
1952
  if (idx === focusedIndex) {
1928
1953
  el.setAttribute('data-focused', 'true');
1929
1954
  el.scrollIntoView({ block: 'nearest' });
1955
+ activeId = el.id;
1930
1956
  }
1931
1957
  else {
1932
1958
  el.removeAttribute('data-focused');
1933
1959
  }
1934
1960
  });
1935
- }, [focusedIndex, open, getOptions]);
1961
+ setActiveDescendant(activeId);
1962
+ }, [focusedIndex, open, getOptions, baseId]);
1936
1963
  useKeyPress({
1937
1964
  Escape: onClose,
1938
1965
  ArrowDown: (e) => {
@@ -1970,7 +1997,7 @@ const useMenu = ({ open, onClose, anchorEl, minWidth }) => {
1970
1997
  }
1971
1998
  },
1972
1999
  }, { enabled: open });
1973
- return { panelRef, style };
2000
+ return { panelRef, style, activeDescendant };
1974
2001
  };
1975
2002
 
1976
2003
  const MENU_GROUP_STYLES = createStyles((theme) => {
@@ -2011,7 +2038,8 @@ const MENU_GROUP_STYLES = createStyles((theme) => {
2011
2038
  }, { id: 'menu-group' });
2012
2039
 
2013
2040
  const MenuGroup = ({ label, divider, children, }) => {
2014
- return (jsxs("div", { className: MENU_GROUP_STYLES.root, children: [divider && (jsx("div", { className: MENU_GROUP_STYLES.divider, role: 'separator', "aria-hidden": true })), label !== undefined && (jsx("span", { className: MENU_GROUP_STYLES.label, children: label })), jsx("ul", { className: MENU_GROUP_STYLES.list, role: 'group', "aria-label": label, children: children })] }));
2041
+ const labelId = useId();
2042
+ return (jsxs("div", { className: MENU_GROUP_STYLES.root, children: [divider && (jsx("div", { className: MENU_GROUP_STYLES.divider, role: 'separator', "aria-hidden": true })), label !== undefined && (jsx("span", { id: labelId, className: MENU_GROUP_STYLES.label, "aria-hidden": true, children: label })), jsx("ul", { className: MENU_GROUP_STYLES.list, role: 'group', "aria-labelledby": label !== undefined ? labelId : undefined, children: children })] }));
2015
2043
  };
2016
2044
  MenuGroup.displayName = 'MenuGroup';
2017
2045
 
@@ -2062,12 +2090,12 @@ const MenuItem = ({ ref, label, icon, selected, focused, disabled, onClick, ...r
2062
2090
  };
2063
2091
  MenuItem.displayName = 'MenuItem';
2064
2092
 
2065
- const MenuBase = ({ open, onClose, anchorEl, minWidth, maxHeight = '20rem', id, children, }) => {
2066
- const { panelRef, style } = useMenu({ open, onClose, anchorEl, minWidth });
2093
+ const MenuBase = ({ open, onClose, anchorEl, minWidth, maxHeight = '20rem', id, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, children, }) => {
2094
+ const { panelRef, style, activeDescendant } = useMenu({ open, onClose, anchorEl, minWidth });
2067
2095
  if (!open) {
2068
2096
  return null;
2069
2097
  }
2070
- return createPortal(jsxs(Fragment$1, { children: [jsx("div", { className: MENU_STYLES.backdrop, onClick: onClose, "aria-hidden": true }), jsx("div", { ref: panelRef, id: id, role: 'listbox', tabIndex: -1, className: MENU_STYLES.panel, style: { ...style, maxHeight, outline: 'none' }, children: children })] }), document.body);
2098
+ return createPortal(jsxs(Fragment$1, { children: [jsx("div", { className: MENU_STYLES.backdrop, onClick: onClose, "aria-hidden": true }), jsx("div", { ref: panelRef, id: id, role: 'listbox', tabIndex: -1, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-activedescendant": activeDescendant, className: MENU_STYLES.panel, style: { ...style, maxHeight, outline: 'none' }, children: children })] }), document.body);
2071
2099
  };
2072
2100
  MenuBase.displayName = 'Menu';
2073
2101
  const Menu = MenuBase;
@@ -2189,9 +2217,9 @@ const ICON_SIZE_MAP = {
2189
2217
  md: 'md',
2190
2218
  lg: 'lg',
2191
2219
  };
2192
- const SelectTrigger = ({ ref, size = 'md', status = 'default', open, hasValue, startIcon, disabled, children, ...rest }) => {
2220
+ const SelectTrigger = ({ ref, size = 'md', status = 'default', open, hasValue, startIcon, disabled, children, 'aria-expanded': ariaExpanded, 'aria-controls': ariaControls, ...rest }) => {
2193
2221
  const iconSize = ICON_SIZE_MAP[size];
2194
- return (jsxs("button", { type: 'button', ref: ref, className: SELECT_TRIGGER_VARIANTS({ size, status }), "data-open": open || undefined, "data-disabled": disabled || undefined, disabled: disabled, ...rest, children: [startIcon !== undefined && (jsx(Icon, { icon: startIcon, size: iconSize, strokeColor: 'textSecondary' })), jsx("span", { className: hasValue ? SELECT_TRIGGER_STYLES.value : SELECT_TRIGGER_STYLES.placeholder, children: children }), jsx(Icon, { icon: ChevronDownIcon, size: iconSize, className: cx(SELECT_TRIGGER_STYLES.chevron, open ? SELECT_TRIGGER_STYLES.chevronOpen : undefined), strokeColor: 'textSecondary' })] }));
2222
+ return (jsxs("button", { type: 'button', role: 'combobox', ref: ref, className: SELECT_TRIGGER_VARIANTS({ size, status }), "data-open": open || undefined, "data-disabled": disabled || undefined, disabled: disabled, "aria-expanded": ariaExpanded, "aria-controls": ariaControls, ...rest, children: [startIcon !== undefined && (jsx(Icon, { icon: startIcon, size: iconSize, strokeColor: 'textSecondary' })), jsx("span", { className: hasValue ? SELECT_TRIGGER_STYLES.value : SELECT_TRIGGER_STYLES.placeholder, children: children }), jsx(Icon, { icon: ChevronDownIcon, size: iconSize, className: cx(SELECT_TRIGGER_STYLES.chevron, open ? SELECT_TRIGGER_STYLES.chevronOpen : undefined), strokeColor: 'textSecondary' })] }));
2195
2223
  };
2196
2224
  SelectTrigger.displayName = 'SelectTrigger';
2197
2225
 
@@ -2205,6 +2233,7 @@ const useSelect = ({ id, ref, value, defaultValue, onChange, options, disabled,
2205
2233
  const fieldId = id ?? generatedId;
2206
2234
  const helperId = `${fieldId}-helper`;
2207
2235
  const menuId = `${fieldId}-menu`;
2236
+ const labelId = `${fieldId}-label`;
2208
2237
  const triggerRef = useRef(null);
2209
2238
  const mergedRef = useMergedRefs(ref, triggerRef);
2210
2239
  const [open, setOpen] = useState(false);
@@ -2248,6 +2277,7 @@ const useSelect = ({ id, ref, value, defaultValue, onChange, options, disabled,
2248
2277
  const close = useCallback(() => setOpen(false), []);
2249
2278
  return {
2250
2279
  fieldId,
2280
+ labelId,
2251
2281
  helperId,
2252
2282
  menuId,
2253
2283
  triggerRef,
@@ -2263,8 +2293,8 @@ const useSelect = ({ id, ref, value, defaultValue, onChange, options, disabled,
2263
2293
  };
2264
2294
 
2265
2295
  const Select = ({ ref, value, defaultValue, onChange, options, label, helperText, placeholder, size = 'md', status = 'default', disabled, required, width, id, }) => {
2266
- const { fieldId, helperId, menuId, triggerRef, mergedRef, open, toggle, close, currentValue, selectedOption, groupedOptions, handleSelect, } = useSelect({ id, ref, value, defaultValue, onChange, options, disabled });
2267
- return (jsxs(Stack, { flexDirection: 'column', gap: 'xs', style: { width: width ?? '100%' }, children: [label !== undefined && (jsxs(Text, { variant: 'label', fontSize: 'sm', fontWeight: 'medium', color: 'textSecondary', htmlFor: fieldId, children: [label, required && (jsx(Text, { variant: 'span', color: 'errorMain', "aria-hidden": true, children: ' *' }))] })), jsx(SelectTrigger, { ref: mergedRef, id: fieldId, size: size, status: status, open: open, hasValue: selectedOption !== undefined, disabled: disabled, required: required, "aria-haspopup": 'listbox', "aria-expanded": open, "aria-controls": menuId, "aria-invalid": status === 'error' || undefined, "aria-describedby": helperText !== undefined ? helperId : undefined, onClick: toggle, children: selectedOption !== undefined ? selectedOption.label : placeholder }), jsx(Menu, { open: open, onClose: close, anchorEl: triggerRef.current, id: menuId, children: Array.from(groupedOptions.entries()).map(([groupKey, groupOpts], groupIndex) => {
2296
+ const { fieldId, labelId, helperId, menuId, triggerRef, mergedRef, open, toggle, close, currentValue, selectedOption, groupedOptions, handleSelect, } = useSelect({ id, ref, value, defaultValue, onChange, options, disabled });
2297
+ return (jsxs(Stack, { flexDirection: 'column', gap: 'xs', style: { width: width ?? '100%' }, children: [label !== undefined && (jsxs(Text, { variant: 'label', fontSize: 'sm', fontWeight: 'medium', color: 'textSecondary', htmlFor: fieldId, id: labelId, children: [label, required && (jsx(Text, { variant: 'span', color: 'errorMain', "aria-hidden": true, children: ' *' }))] })), jsx(SelectTrigger, { ref: mergedRef, id: fieldId, size: size, status: status, open: open, hasValue: selectedOption !== undefined, disabled: disabled, "aria-haspopup": 'listbox', "aria-expanded": open, "aria-controls": menuId, "aria-labelledby": label !== undefined ? `${labelId} ${fieldId}` : undefined, "aria-required": required || undefined, "aria-invalid": status === 'error' || undefined, "aria-errormessage": status === 'error' && helperText !== undefined ? helperId : undefined, "aria-describedby": helperText !== undefined ? helperId : undefined, onClick: toggle, children: selectedOption !== undefined ? selectedOption.label : placeholder }), jsx(Menu, { open: open, onClose: close, anchorEl: triggerRef.current, id: menuId, "aria-labelledby": label !== undefined ? labelId : undefined, "aria-label": label === undefined ? placeholder : undefined, children: Array.from(groupedOptions.entries()).map(([groupKey, groupOpts], groupIndex) => {
2268
2298
  const items = groupOpts.map((opt) => (jsx(Menu.Item, { label: opt.label, icon: opt.icon, selected: opt.value === currentValue, disabled: opt.disabled, onClick: () => handleSelect(opt.value) }, opt.value)));
2269
2299
  return groupKey !== undefined ? (jsx(Menu.Group, { label: groupKey, divider: groupIndex > 0, children: items }, groupKey)) : (jsx(Menu.Group, { divider: groupIndex > 0, children: items }, '__ungrouped'));
2270
2300
  }) }), helperText !== undefined && (jsx(FormHelperText, { id: helperId, status: status, children: helperText }))] }));
@@ -2432,7 +2462,7 @@ const useCheckbox = ({ id, ref, indeterminate = false }) => {
2432
2462
  const Checkbox = ({ ref, label, helperText, size = 'md', status = 'default', indeterminate = false, error, id, disabled, required, ...rest }) => {
2433
2463
  const resolvedStatus = error ? 'error' : status;
2434
2464
  const { checkboxId, helperId, mergedRef } = useCheckbox({ id, ref, indeterminate });
2435
- return (jsxs("div", { className: CHECKBOX_STYLES.wrapper, children: [jsxs("label", { htmlFor: checkboxId, className: CHECKBOX_ROOT_VARIANTS({ disabled: disabled ? 'true' : 'false' }), children: [jsx("input", { ref: mergedRef, id: checkboxId, type: 'checkbox', disabled: disabled, required: required, "aria-required": required || undefined, "aria-invalid": resolvedStatus === 'error' || undefined, "aria-describedby": helperText !== undefined ? helperId : undefined, className: CHECKBOX_INPUT_VARIANTS({
2465
+ return (jsxs("div", { className: CHECKBOX_STYLES.wrapper, children: [jsxs("label", { htmlFor: checkboxId, className: CHECKBOX_ROOT_VARIANTS({ disabled: disabled ? 'true' : 'false' }), children: [jsx("input", { ref: mergedRef, id: checkboxId, type: 'checkbox', disabled: disabled, required: required, "aria-required": required || undefined, "aria-invalid": resolvedStatus === 'error' || undefined, "aria-describedby": helperText !== undefined ? helperId : undefined, "aria-errormessage": resolvedStatus === 'error' && helperText !== undefined ? helperId : undefined, className: CHECKBOX_INPUT_VARIANTS({
2436
2466
  size,
2437
2467
  status: resolvedStatus,
2438
2468
  disabled: disabled ? 'true' : 'false',
@@ -2930,6 +2960,15 @@ const VARIANT_ARIA_LIVE = {
2930
2960
  warning: 'polite',
2931
2961
  info: 'polite',
2932
2962
  };
2963
+ /** role="alert" implies aria-live="assertive" — reserve it for errors.
2964
+ * Other variants use role="status" (implies aria-live="polite"). */
2965
+ const VARIANT_ROLE = {
2966
+ default: 'status',
2967
+ success: 'status',
2968
+ error: 'alert',
2969
+ warning: 'status',
2970
+ info: 'status',
2971
+ };
2933
2972
  /**
2934
2973
  * Inline alert banner with 5 visual variants: default, success, error, warning, info.
2935
2974
  * Use `Alert.Title` (icon + heading) and `Alert.Body` (message) as children.
@@ -2942,7 +2981,7 @@ const VARIANT_ARIA_LIVE = {
2942
2981
  */
2943
2982
  const AlertBase = ({ variant = 'default', children, width = '100%', outline = false, shadow = 'none', }) => {
2944
2983
  const { backgroundColor, borderColor, accentColor } = VARIANT_TOKENS[variant];
2945
- return (jsx(AlertContext.Provider, { value: { variant, accentColor }, children: jsx(Stack, { role: 'alert', "aria-live": VARIANT_ARIA_LIVE[variant], flexDirection: 'column', gap: 'xs', padding: 'md', borderRadius: 'lg', backgroundColor: backgroundColor, borderColor: outline ? borderColor : undefined, borderWidth: outline ? '1px' : undefined, borderStyle: outline ? 'solid' : undefined, boxShadow: shadow, width: width, children: children }) }));
2984
+ return (jsx(AlertContext.Provider, { value: { variant, accentColor }, children: jsx(Stack, { role: VARIANT_ROLE[variant], "aria-live": VARIANT_ARIA_LIVE[variant], flexDirection: 'column', gap: 'xs', padding: 'md', borderRadius: 'lg', backgroundColor: backgroundColor, borderColor: outline ? borderColor : undefined, borderWidth: outline ? '1px' : undefined, borderStyle: outline ? 'solid' : undefined, boxShadow: shadow, width: width, children: children }) }));
2946
2985
  };
2947
2986
  AlertBase.displayName = 'Alert';
2948
2987
  const Alert = AlertBase;
@@ -3109,7 +3148,7 @@ const useDialog = ({ open, onClose, closeOnBackdropClick, maxWidth, maxHeight, m
3109
3148
  */
3110
3149
  const DialogHeader = ({ title, onClose }) => {
3111
3150
  const { titleId, CloseIconComponent } = useContext(DialogContext);
3112
- return (jsxs(Stack, { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 'md', paddingTop: 'md', paddingBottom: 'md', paddingLeft: 'lg', paddingRight: 'md', flexShrink: 0, children: [jsx(Text, { id: titleId, variant: 'span', fontSize: 'md', fontWeight: 'semibold', color: 'textPrimary', children: title }), jsx(IconButton, { icon: CloseIconComponent, ariaLabel: 'Close dialog', variant: 'text', color: 'neutral', size: 'sm', type: 'button', onClick: onClose })] }));
3151
+ return (jsxs(Stack, { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 'md', paddingTop: 'md', paddingBottom: 'md', paddingLeft: 'lg', paddingRight: 'md', flexShrink: 0, children: [jsx(Text, { id: titleId, variant: 'span', as: 'h2', fontSize: 'md', fontWeight: 'semibold', color: 'textPrimary', children: title }), jsx(IconButton, { icon: CloseIconComponent, ariaLabel: 'Close dialog', variant: 'text', color: 'neutral', size: 'sm', type: 'button', onClick: onClose })] }));
3113
3152
  };
3114
3153
  DialogHeader.displayName = 'Dialog.Header';
3115
3154