@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/cjs/index.js +59 -20
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +59 -20
- package/dist/esm/index.js.map +1 -1
- package/dist/index.d.ts +241 -54
- package/package.json +1 -1
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,
|
|
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',
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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:
|
|
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
|
|