@tangible/ui 0.0.6 → 0.0.8
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/components/Accordion/Accordion.js +11 -3
- package/components/Avatar/Avatar.js +4 -3
- package/components/Avatar/AvatarGroup.js +7 -5
- package/components/Avatar/index.d.ts +2 -2
- package/components/Avatar/index.js +1 -1
- package/components/Avatar/types.d.ts +27 -0
- package/components/Avatar/types.js +8 -0
- package/components/Button/Button.js +4 -2
- package/components/Button/index.d.ts +2 -1
- package/components/Button/index.js +1 -0
- package/components/Button/types.d.ts +10 -0
- package/components/Button/types.js +3 -1
- package/components/Checkbox/Checkbox.js +46 -11
- package/components/Checkbox/types.d.ts +9 -0
- package/components/Combobox/Combobox.d.ts +1 -1
- package/components/Combobox/Combobox.js +28 -7
- package/components/Combobox/index.d.ts +2 -1
- package/components/Combobox/index.js +1 -0
- package/components/Combobox/types.d.ts +14 -0
- package/components/Combobox/types.js +3 -1
- package/components/Dropdown/Dropdown.js +16 -4
- package/components/Field/Field.d.ts +4 -1
- package/components/Field/Field.js +38 -7
- package/components/Field/FieldContext.d.ts +18 -0
- package/components/Field/FieldContext.js +3 -0
- package/components/Field/index.d.ts +2 -1
- package/components/Field/index.js +1 -0
- package/components/MoveHandle/MoveHandle.js +1 -1
- package/components/MoveHandle/types.d.ts +1 -1
- package/components/MultiSelect/MultiSelect.d.ts +1 -1
- package/components/MultiSelect/MultiSelect.js +37 -19
- package/components/MultiSelect/index.d.ts +2 -1
- package/components/MultiSelect/index.js +1 -0
- package/components/MultiSelect/types.d.ts +34 -0
- package/components/MultiSelect/types.js +10 -0
- package/components/Pager/Pager.d.ts +7 -1
- package/components/Pager/Pager.js +7 -5
- package/components/Pager/index.d.ts +2 -0
- package/components/Pager/index.js +1 -0
- package/components/Pager/types.d.ts +37 -0
- package/components/Pager/types.js +12 -0
- package/components/Radio/Radio.d.ts +4 -0
- package/components/Radio/Radio.js +15 -5
- package/components/Radio/RadioGroup.d.ts +1 -1
- package/components/Radio/RadioGroup.js +2 -2
- package/components/Radio/types.d.ts +10 -0
- package/components/Rating/Rating.d.ts +2 -32
- package/components/Rating/Rating.js +5 -3
- package/components/Rating/index.d.ts +2 -1
- package/components/Rating/index.js +1 -0
- package/components/Rating/types.d.ts +41 -0
- package/components/Rating/types.js +4 -0
- package/components/SegmentedControl/SegmentedControl.js +6 -5
- package/components/SegmentedControl/types.d.ts +17 -5
- package/components/Select/Select.d.ts +1 -0
- package/components/Select/Select.js +109 -77
- package/components/Select/SelectContext.d.ts +4 -16
- package/components/Select/SelectContext.js +5 -35
- package/components/Select/types.d.ts +19 -19
- package/components/Sidebar/Sidebar.js +25 -20
- package/components/StepIndicator/StepIndicator.js +11 -8
- package/components/StepIndicator/index.d.ts +2 -1
- package/components/StepIndicator/index.js +1 -0
- package/components/StepIndicator/types.d.ts +18 -0
- package/components/StepIndicator/types.js +7 -1
- package/components/Switch/Switch.js +28 -14
- package/components/Table/BulkActionsBar.d.ts +4 -1
- package/components/Table/BulkActionsBar.js +5 -4
- package/components/Table/DataTable.d.ts +4 -1
- package/components/Table/DataTable.js +10 -8
- package/components/Table/index.d.ts +3 -0
- package/components/Table/index.js +2 -0
- package/components/Table/types.d.ts +20 -0
- package/components/Table/types.js +11 -0
- package/components/Tabs/Tabs.js +11 -4
- package/components/TextInput/TextInput.js +2 -1
- package/components/TextInput/types.d.ts +7 -1
- package/components/Textarea/Textarea.js +3 -2
- package/components/Textarea/types.d.ts +6 -1
- package/icons/icons.svg +29 -15
- package/icons/lms/index.d.ts +8 -0
- package/icons/lms/index.js +48 -4
- package/icons/manifest.json +112 -0
- package/icons/player/index.js +9 -9
- package/icons/registry.d.ts +28 -0
- package/icons/registry.js +14 -0
- package/icons/system/index.d.ts +20 -0
- package/icons/system/index.js +112 -2
- package/package.json +1 -1
- package/styles/all.css +1 -1
- package/styles/all.expanded.css +426 -136
- package/styles/all.expanded.unlayered.css +426 -136
- package/styles/all.unlayered.css +1 -1
- package/styles/components/input/index.scss +29 -7
- package/styles/system/_constants.scss +1 -1
- package/styles/system/_tokens.scss +1 -0
- package/styles/utilities/_index.scss +14 -4
- package/tui-manifest.json +102 -46
- package/utils/use-roving-group.js +9 -6
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import React, { forwardRef, useId, useMemo, cloneElement, isValidElement, Children, } from 'react';
|
|
2
|
+
import React, { forwardRef, useEffect, useId, useMemo, useRef, cloneElement, isValidElement, Children, } from 'react';
|
|
3
3
|
import { cx } from '../../utils/cx.js';
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import { isDev } from '../../utils/is-dev.js';
|
|
5
|
+
import { FieldContext, useFieldContext, defaultFieldLabels } from './FieldContext.js';
|
|
6
|
+
export const Field = forwardRef(function Field({ error = false, required = false, disabled = false, inline = false, labels: labelsProp, className, children, }, ref) {
|
|
6
7
|
const baseId = useId();
|
|
7
8
|
const controlId = `${baseId}-control`;
|
|
9
|
+
const labelId = `${baseId}-label`;
|
|
8
10
|
const helperTextId = `${baseId}-helper`;
|
|
9
11
|
const errorId = `${baseId}-error`;
|
|
12
|
+
const labelRendered = useRef(false);
|
|
13
|
+
// Reset on each render — Label will set it back to true during its render
|
|
14
|
+
labelRendered.current = false;
|
|
15
|
+
const labels = useMemo(() => ({ ...defaultFieldLabels, ...labelsProp }), [labelsProp]);
|
|
10
16
|
const contextValue = useMemo(() => ({
|
|
11
17
|
controlId,
|
|
18
|
+
labelId,
|
|
12
19
|
helperTextId,
|
|
13
20
|
errorId,
|
|
14
21
|
hasError: error,
|
|
15
22
|
required,
|
|
16
23
|
disabled,
|
|
17
|
-
|
|
24
|
+
labelRendered,
|
|
25
|
+
labels,
|
|
26
|
+
}), [controlId, labelId, helperTextId, errorId, error, required, disabled, labels]);
|
|
18
27
|
const classes = cx('tui-field', error && 'is-error', disabled && 'is-disabled', inline && 'is-layout-inline', className);
|
|
19
28
|
return (_jsx(FieldContext.Provider, { value: contextValue, children: _jsx("div", { ref: ref, className: classes, children: children }) }));
|
|
20
29
|
});
|
|
@@ -22,16 +31,20 @@ export const Field = forwardRef(function Field({ error = false, required = false
|
|
|
22
31
|
// Field.Label
|
|
23
32
|
// =============================================================================
|
|
24
33
|
function FieldLabel({ hidden = false, className, children, ...rest }) {
|
|
25
|
-
const { controlId, required } = useFieldContext();
|
|
34
|
+
const { controlId, labelId, required, labelRendered, labels } = useFieldContext();
|
|
35
|
+
// Signal to Field.Control that a label element exists in the tree.
|
|
36
|
+
// This is read synchronously during the same render pass (Label renders
|
|
37
|
+
// before Control in JSX order) to decide whether aria-labelledby is safe.
|
|
38
|
+
labelRendered.current = true;
|
|
26
39
|
const classes = cx('tui-field__label', hidden && 'tui-visually-hidden', className);
|
|
27
|
-
return (_jsxs("label", { htmlFor: controlId, className: classes, ...rest, children: [children, required && (_jsxs(_Fragment, { children: [_jsx("span", { className: "tui-field__required", "aria-hidden": "true", children: "*" }), _jsx("span", { className: "tui-visually-hidden", children:
|
|
40
|
+
return (_jsxs("label", { id: labelId, htmlFor: controlId, className: classes, ...rest, children: [children, required && (_jsxs(_Fragment, { children: [_jsx("span", { className: "tui-field__required", "aria-hidden": "true", children: "*" }), _jsx("span", { className: "tui-visually-hidden", children: labels.required })] }))] }));
|
|
28
41
|
}
|
|
29
42
|
FieldLabel.displayName = 'Field.Label';
|
|
30
43
|
// =============================================================================
|
|
31
44
|
// Field.Control
|
|
32
45
|
// =============================================================================
|
|
33
46
|
function FieldControl({ children }) {
|
|
34
|
-
const { controlId, helperTextId, errorId, hasError, required, disabled, } = useFieldContext();
|
|
47
|
+
const { controlId, labelId, helperTextId, errorId, hasError, required, disabled, labelRendered, } = useFieldContext();
|
|
35
48
|
const child = Children.only(children);
|
|
36
49
|
if (!isValidElement(child)) {
|
|
37
50
|
throw new Error('Field.Control expects a single React element as its child');
|
|
@@ -48,10 +61,28 @@ function FieldControl({ children }) {
|
|
|
48
61
|
describedByParts.push(errorId);
|
|
49
62
|
}
|
|
50
63
|
const describedBy = describedByParts.join(' ');
|
|
64
|
+
// Only inject aria-labelledby when Field.Label is present in the tree.
|
|
65
|
+
// Without a rendered label, the ID points at nothing — and per AccName 1.2
|
|
66
|
+
// step 2B, aria-labelledby overrides ALL other name sources (including
|
|
67
|
+
// implicit <label> wrapping). A phantom ID produces an empty name, which
|
|
68
|
+
// strips the accessible name from self-labelling controls like Checkbox.
|
|
69
|
+
const childLabelledBy = childProps['aria-labelledby'];
|
|
70
|
+
const injectedLabelledBy = labelRendered.current;
|
|
71
|
+
const labelledBy = childLabelledBy ?? (injectedLabelledBy ? labelId : undefined);
|
|
72
|
+
// DEV: warn if Field.Label appears after Field.Control in JSX order —
|
|
73
|
+
// labelRendered will be false at render time but true after mount.
|
|
74
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (isDev() && !injectedLabelledBy && !childLabelledBy && labelRendered.current) {
|
|
77
|
+
console.warn('Field: Field.Label appears after Field.Control in the JSX tree. ' +
|
|
78
|
+
'Move Field.Label before Field.Control so aria-labelledby is wired correctly.');
|
|
79
|
+
}
|
|
80
|
+
}, []);
|
|
51
81
|
// Clone child with a11y props
|
|
52
82
|
// Note: aria-invalid and aria-required must be string "true", not boolean
|
|
53
83
|
return cloneElement(child, {
|
|
54
84
|
id: controlId,
|
|
85
|
+
'aria-labelledby': labelledBy,
|
|
55
86
|
'aria-describedby': describedBy,
|
|
56
87
|
'aria-invalid': hasError ? 'true' : undefined,
|
|
57
88
|
'aria-required': required ? 'true' : undefined,
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overridable strings for Field i18n.
|
|
3
|
+
*/
|
|
4
|
+
export type FieldLabels = {
|
|
5
|
+
/** Visually-hidden text appended to required field labels. @default "required" */
|
|
6
|
+
required?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const defaultFieldLabels: Required<FieldLabels>;
|
|
1
9
|
export type FieldContextValue = {
|
|
2
10
|
/** ID for the form control element */
|
|
3
11
|
controlId: string;
|
|
12
|
+
/** ID for the label element (for aria-labelledby on non-labelable controls) */
|
|
13
|
+
labelId: string;
|
|
4
14
|
/** ID for helper text (for aria-describedby) */
|
|
5
15
|
helperTextId: string;
|
|
6
16
|
/** ID for error message (for aria-describedby) */
|
|
@@ -11,6 +21,14 @@ export type FieldContextValue = {
|
|
|
11
21
|
required: boolean;
|
|
12
22
|
/** Whether the field is disabled */
|
|
13
23
|
disabled: boolean;
|
|
24
|
+
/** Mutable flag — set by Field.Label during render so Field.Control knows
|
|
25
|
+
* whether to inject aria-labelledby. Avoids injecting a phantom ID that
|
|
26
|
+
* overrides implicit <label> associations in the accessible name algorithm. */
|
|
27
|
+
labelRendered: {
|
|
28
|
+
current: boolean;
|
|
29
|
+
};
|
|
30
|
+
/** Resolved i18n labels for Field sub-components */
|
|
31
|
+
labels: Required<FieldLabels>;
|
|
14
32
|
};
|
|
15
33
|
export declare const FieldContext: import("react").Context<FieldContextValue | null>;
|
|
16
34
|
export declare function useFieldContext(): FieldContextValue;
|
|
@@ -83,7 +83,7 @@ export const MoveHandle = forwardRef(function MoveHandle({ mode = 'full', size =
|
|
|
83
83
|
// Uses ref directly (not mergedRef) — innerRef is unused for this path.
|
|
84
84
|
// Focus recovery and dev warning effects early-return for handle mode.
|
|
85
85
|
if (mode === 'handle') {
|
|
86
|
-
return (_jsx("button", { ref: ref, type: "button", className: cx('tui-move-handle', 'is-handle', className), "aria-label": resolvedDragLabel, ...restDragProps, children: _jsx(Icon, { name: "system/drag" }) }));
|
|
86
|
+
return (_jsx("button", { ref: ref, type: "button", className: cx('tui-move-handle', 'is-handle', `is-size-${size}`, className), "aria-label": resolvedDragLabel, ...restDragProps, children: _jsx(Icon, { name: "system/drag" }) }));
|
|
87
87
|
}
|
|
88
88
|
// ----- Full mode -----
|
|
89
89
|
const hasIndex = index != null;
|
|
@@ -17,7 +17,7 @@ export interface MoveHandleLabels {
|
|
|
17
17
|
export interface MoveHandleProps {
|
|
18
18
|
/** Structural mode. 'full' (default) shows background panel with arrows/index. 'handle' shows only the bare drag icon button. */
|
|
19
19
|
mode?: MoveHandleMode;
|
|
20
|
-
/** Component scale. sm = 32px, md = 40px.
|
|
20
|
+
/** Component scale. Full mode: sm = 32px, md = 40px. Handle mode: sm = 24px, md = 32px. */
|
|
21
21
|
size?: MoveHandleSize;
|
|
22
22
|
/** Position index. When provided, shows number at rest, drag handle on hover. */
|
|
23
23
|
index?: number;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type MultiSelectProps, type MultiSelectTriggerProps, type MultiSelectContentProps, type MultiSelectOptionProps, type MultiSelectGroupProps, type MultiSelectLabelProps } from './types';
|
|
2
|
-
declare function MultiSelectRoot({ id: triggerIdProp, value: controlledValue, defaultValue, onValueChange, open: controlledOpen, defaultOpen, onOpenChange, disabled, placeholder, size, display, maxChips, max, onMaxReached, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, children, }: MultiSelectProps): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
declare function MultiSelectRoot({ id: triggerIdProp, value: controlledValue, defaultValue, onValueChange, open: controlledOpen, defaultOpen, onOpenChange, disabled, placeholder, size, display, maxChips, max, onMaxReached, labels: labelsProp, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, children, }: MultiSelectProps): import("react/jsx-runtime").JSX.Element;
|
|
3
3
|
declare namespace MultiSelectRoot {
|
|
4
4
|
var displayName: string;
|
|
5
5
|
}
|
|
@@ -6,15 +6,17 @@ import { cx } from '../../utils/cx.js';
|
|
|
6
6
|
import { getPortalRootFor } from '../../utils/portal.js';
|
|
7
7
|
import { Icon } from '../Icon/index.js';
|
|
8
8
|
import { MultiSelectActionsContext, MultiSelectStateContext, MultiSelectContentContext, useMultiSelectContext, useMultiSelectContentContext, } from './MultiSelectContext.js';
|
|
9
|
-
import { toKey, } from './types.js';
|
|
9
|
+
import { toKey, defaultMultiSelectLabels, } from './types.js';
|
|
10
10
|
// =============================================================================
|
|
11
11
|
// MultiSelect Root
|
|
12
12
|
// =============================================================================
|
|
13
|
-
function MultiSelectRoot({ id: triggerIdProp, value: controlledValue, defaultValue, onValueChange, open: controlledOpen, defaultOpen, onOpenChange, disabled = false, placeholder = '', size = 'md', display = 'count', maxChips = 3, max, onMaxReached, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, children, }) {
|
|
13
|
+
function MultiSelectRoot({ id: triggerIdProp, value: controlledValue, defaultValue, onValueChange, open: controlledOpen, defaultOpen, onOpenChange, disabled = false, placeholder = '', size = 'md', display = 'count', maxChips = 3, max, onMaxReached, labels: labelsProp, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, children, }) {
|
|
14
|
+
// Merge caller labels with defaults
|
|
15
|
+
const labels = useMemo(() => ({ ...defaultMultiSelectLabels, ...labelsProp }), [labelsProp]);
|
|
14
16
|
// Controlled/uncontrolled value
|
|
15
17
|
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue ?? []);
|
|
16
|
-
const isValueControlled = controlledValue !== undefined;
|
|
17
|
-
const value = isValueControlled ? controlledValue : uncontrolledValue;
|
|
18
|
+
const isValueControlled = useRef(controlledValue !== undefined).current;
|
|
19
|
+
const value = (isValueControlled ? controlledValue : uncontrolledValue);
|
|
18
20
|
// Track selected text values for display when closed
|
|
19
21
|
const selectedTextMapRef = useRef(new Map());
|
|
20
22
|
// Option registration
|
|
@@ -273,6 +275,7 @@ function MultiSelectRoot({ id: triggerIdProp, value: controlledValue, defaultVal
|
|
|
273
275
|
maxChips,
|
|
274
276
|
max,
|
|
275
277
|
size,
|
|
278
|
+
labels,
|
|
276
279
|
// ARIA IDs (stable)
|
|
277
280
|
triggerId,
|
|
278
281
|
listboxId,
|
|
@@ -300,6 +303,7 @@ function MultiSelectRoot({ id: triggerIdProp, value: controlledValue, defaultVal
|
|
|
300
303
|
maxChips,
|
|
301
304
|
max,
|
|
302
305
|
size,
|
|
306
|
+
labels,
|
|
303
307
|
// IDs are stable (from useId)
|
|
304
308
|
triggerId,
|
|
305
309
|
listboxId,
|
|
@@ -356,7 +360,7 @@ MultiSelectRoot.displayName = 'MultiSelect';
|
|
|
356
360
|
// MultiSelect.Trigger
|
|
357
361
|
// =============================================================================
|
|
358
362
|
function MultiSelectTriggerComponent({ asChild = false, className, children, }) {
|
|
359
|
-
const { open, setOpen, disabled, placeholder, size, display, maxChips, max, maxReached, triggerId, listboxId, ariaLabel, ariaLabelledBy, ariaDescribedBy, getSelectedOptions, clearAll, refs, getReferenceProps, activeIndex, orderedOptions, toggleOption, isSelected, } = useMultiSelectContext();
|
|
363
|
+
const { open, setOpen, disabled, placeholder, size, display, maxChips, max, maxReached, labels, triggerId, listboxId, ariaLabel, ariaLabelledBy, ariaDescribedBy, getSelectedOptions, clearAll, refs, getReferenceProps, activeIndex, orderedOptions, toggleOption, isSelected, } = useMultiSelectContext();
|
|
360
364
|
const sizeClass = size !== 'md' ? `is-size-${size}` : undefined;
|
|
361
365
|
const selectedOptions = getSelectedOptions();
|
|
362
366
|
const hasSelection = selectedOptions.length > 0;
|
|
@@ -422,19 +426,17 @@ function MultiSelectTriggerComponent({ asChild = false, className, children, })
|
|
|
422
426
|
return _jsx("span", { className: "tui-multiselect__placeholder", children: placeholder });
|
|
423
427
|
}
|
|
424
428
|
if (display === 'count') {
|
|
425
|
-
return (
|
|
429
|
+
return (_jsx("span", { className: "tui-multiselect__count", children: labels.selected(selectedOptions.length) }));
|
|
426
430
|
}
|
|
427
431
|
// chips mode
|
|
428
432
|
const visibleChips = selectedOptions.slice(0, maxChips);
|
|
429
433
|
const overflow = selectedOptions.length - maxChips;
|
|
430
|
-
return (_jsxs("span", { className: "tui-multiselect__chips", children: [visibleChips.map((opt) => (_jsx("span", { className: "tui-multiselect__chip", children: opt.textValue }, toKey(opt.value)))), overflow > 0 && (
|
|
434
|
+
return (_jsxs("span", { className: "tui-multiselect__chips", children: [visibleChips.map((opt) => (_jsx("span", { className: "tui-multiselect__chip", children: opt.textValue }, toKey(opt.value)))), overflow > 0 && (_jsx("span", { className: "tui-multiselect__more", children: labels.more(overflow) }))] }));
|
|
431
435
|
};
|
|
432
436
|
// Default trigger content (when not using asChild or custom children)
|
|
433
437
|
const defaultTriggerContent = (_jsxs(_Fragment, { children: [_jsx("span", { className: "tui-multiselect__value", children: renderTriggerContent() }), hasSelection && (_jsx("span", { className: "tui-multiselect__clear", onClick: handleClearClick, "aria-hidden": "true", children: _jsx(Icon, { name: "system/close", size: "sm" }) })), _jsx(Icon, { name: "system/chevron-down", size: "sm", className: "tui-multiselect__icon", "aria-hidden": "true" })] }));
|
|
434
438
|
// Generate status message for screen readers
|
|
435
|
-
const statusMessage =
|
|
436
|
-
? `${selectedOptions.length} item${selectedOptions.length === 1 ? '' : 's'} selected${maxReached && max ? `. Maximum of ${max} reached` : ''}`
|
|
437
|
-
: '';
|
|
439
|
+
const statusMessage = labels.status(selectedOptions.length, max);
|
|
438
440
|
// Live region (rendered outside button, sibling to trigger)
|
|
439
441
|
const liveRegion = (_jsx("span", { className: "tui-visually-hidden", role: "status", "aria-live": "polite", "aria-atomic": "true", children: statusMessage }));
|
|
440
442
|
// Base trigger props
|
|
@@ -446,9 +448,8 @@ function MultiSelectTriggerComponent({ asChild = false, className, children, })
|
|
|
446
448
|
'aria-haspopup': 'listbox',
|
|
447
449
|
'aria-expanded': open,
|
|
448
450
|
'aria-controls': listboxId,
|
|
449
|
-
'aria-
|
|
451
|
+
'aria-keyshortcuts': hasSelection && !open ? 'Delete' : undefined,
|
|
450
452
|
'data-state': open ? 'open' : 'closed',
|
|
451
|
-
'data-disabled': disabled || undefined,
|
|
452
453
|
...floatingProps,
|
|
453
454
|
};
|
|
454
455
|
// asChild: merge props onto child element
|
|
@@ -481,6 +482,7 @@ function MultiSelectTriggerComponent({ asChild = false, className, children, })
|
|
|
481
482
|
'aria-controls': listboxId,
|
|
482
483
|
'aria-activedescendant': floatingProps['aria-activedescendant'],
|
|
483
484
|
'aria-describedby': ariaDescribedBy,
|
|
485
|
+
// asChild: use aria-disabled + data-disabled since element may not support native disabled
|
|
484
486
|
'aria-disabled': disabled || undefined,
|
|
485
487
|
'data-state': open ? 'open' : 'closed',
|
|
486
488
|
'data-disabled': disabled || undefined,
|
|
@@ -505,7 +507,7 @@ function MultiSelectTriggerComponent({ asChild = false, className, children, })
|
|
|
505
507
|
}
|
|
506
508
|
// Default: render button with optional custom content
|
|
507
509
|
const triggerContent = children ?? defaultTriggerContent;
|
|
508
|
-
return (_jsxs(_Fragment, { children: [_jsx("button", { ref: refs.setReference, type: "button", id: triggerId, className: cx('tui-multiselect__trigger', sizeClass, className), disabled: disabled, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-haspopup": "listbox", "aria-expanded": open, "aria-controls": listboxId, "
|
|
510
|
+
return (_jsxs(_Fragment, { children: [_jsx("button", { ref: refs.setReference, type: "button", id: triggerId, className: cx('tui-multiselect__trigger', sizeClass, className), disabled: disabled, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-haspopup": "listbox", "aria-expanded": open, "aria-controls": listboxId, "data-state": open ? 'open' : 'closed', ...floatingProps,
|
|
509
511
|
// Handle Backspace/Delete AFTER floatingProps to ensure we catch it
|
|
510
512
|
// (typeahead may intercept these for its buffer)
|
|
511
513
|
onKeyDown: (e) => {
|
|
@@ -532,7 +534,7 @@ function MultiSelectContentComponent({ className, children }) {
|
|
|
532
534
|
activeIndex,
|
|
533
535
|
orderedOptions,
|
|
534
536
|
}), [listRef, activeIndex, orderedOptions]);
|
|
535
|
-
return (_jsxs(_Fragment, { children: [!open && (_jsx("div", { style: { display: 'none' }, "aria-hidden": "true", children: _jsx(MultiSelectContentContext.Provider, { value: contentContext, children: children }) })), open && (_jsx(FloatingPortal, { root: portalRoot, children: _jsx("div", { ref: refs.setFloating, id: listboxId, role: "listbox", "aria-labelledby": triggerId, "aria-multiselectable": "true", className: cx('tui-multiselect__content', className), style: {
|
|
537
|
+
return (_jsxs(_Fragment, { children: [!open && (_jsx("div", { id: listboxId, role: "listbox", style: { display: 'none' }, "aria-hidden": "true", children: _jsx(MultiSelectContentContext.Provider, { value: contentContext, children: children }) })), open && (_jsx(FloatingPortal, { root: portalRoot, children: _jsx("div", { ref: refs.setFloating, id: listboxId, role: "listbox", "aria-labelledby": triggerId, "aria-multiselectable": "true", className: cx('tui-multiselect__content', className), style: {
|
|
536
538
|
...floatingStyles,
|
|
537
539
|
minWidth: refs.reference.current?.offsetWidth,
|
|
538
540
|
pointerEvents: 'auto',
|
|
@@ -548,9 +550,11 @@ function MultiSelectOptionComponent({ value: optionValue, disabled = false, text
|
|
|
548
550
|
const ref = useRef(null);
|
|
549
551
|
// Derive textValue from children if not explicitly provided
|
|
550
552
|
const textValue = explicitTextValue ?? (typeof children === 'string' ? children : '');
|
|
551
|
-
// Warn in dev if textValue couldn't be derived
|
|
553
|
+
// Warn in dev if textValue couldn't be derived (fire once per mount)
|
|
554
|
+
const warnedTextValueRef = useRef(false);
|
|
552
555
|
useEffect(() => {
|
|
553
|
-
if (isDev() && !textValue) {
|
|
556
|
+
if (isDev() && !textValue && !warnedTextValueRef.current) {
|
|
557
|
+
warnedTextValueRef.current = true;
|
|
554
558
|
console.warn(`MultiSelect.Option with value="${optionValue}" has no textValue. Provide textValue prop when children is not a string.`);
|
|
555
559
|
}
|
|
556
560
|
}, [textValue, optionValue]);
|
|
@@ -593,7 +597,16 @@ MultiSelectOptionComponent.displayName = 'MultiSelect.Option';
|
|
|
593
597
|
// =============================================================================
|
|
594
598
|
function MultiSelectGroupComponent({ className, children }) {
|
|
595
599
|
const groupId = useId();
|
|
596
|
-
|
|
600
|
+
const groupRef = useRef(null);
|
|
601
|
+
const [hasLabel, setHasLabel] = useState(false);
|
|
602
|
+
// Check if a Label child rendered — guard aria-labelledby to prevent dangling reference
|
|
603
|
+
useLayoutEffect(() => {
|
|
604
|
+
if (groupRef.current) {
|
|
605
|
+
const labelEl = groupRef.current.querySelector(`#${CSS.escape(`${groupId}-label`)}`);
|
|
606
|
+
setHasLabel(!!labelEl);
|
|
607
|
+
}
|
|
608
|
+
}, [groupId, children]);
|
|
609
|
+
return (_jsx("div", { ref: groupRef, role: "group", "aria-labelledby": hasLabel ? `${groupId}-label` : undefined, className: cx('tui-multiselect__group', className), children: _jsx(MultiSelectGroupContext.Provider, { value: { groupId }, children: children }) }));
|
|
597
610
|
}
|
|
598
611
|
MultiSelectGroupComponent.displayName = 'MultiSelect.Group';
|
|
599
612
|
const MultiSelectGroupContext = React.createContext(null);
|
|
@@ -602,8 +615,13 @@ const MultiSelectGroupContext = React.createContext(null);
|
|
|
602
615
|
// =============================================================================
|
|
603
616
|
function MultiSelectLabelComponent({ className, children }) {
|
|
604
617
|
const groupContext = React.useContext(MultiSelectGroupContext);
|
|
605
|
-
|
|
606
|
-
|
|
618
|
+
const warnedRef = useRef(false);
|
|
619
|
+
useEffect(() => {
|
|
620
|
+
if (isDev() && !groupContext && !warnedRef.current) {
|
|
621
|
+
warnedRef.current = true;
|
|
622
|
+
console.warn('MultiSelect.Label should be used inside MultiSelect.Group for accessibility.');
|
|
623
|
+
}
|
|
624
|
+
}, [groupContext]);
|
|
607
625
|
return (_jsx("div", { id: groupContext ? `${groupContext.groupId}-label` : undefined, className: cx('tui-multiselect__label', className), children: children }));
|
|
608
626
|
}
|
|
609
627
|
MultiSelectLabelComponent.displayName = 'MultiSelect.Label';
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { MultiSelect, MultiSelectTrigger, MultiSelectContent, MultiSelectOption, MultiSelectGroup, MultiSelectLabel, useMultiSelect, } from './MultiSelect';
|
|
2
|
-
export
|
|
2
|
+
export { defaultMultiSelectLabels } from './types';
|
|
3
|
+
export type { MultiSelectProps, MultiSelectTriggerProps, MultiSelectContentProps, MultiSelectOptionProps, MultiSelectGroupProps, MultiSelectLabelProps, MultiSelectLabels, MultiSelectValue, OptionValue, DisplayMode, RegisteredOption as MultiSelectRegisteredOption, } from './types';
|
|
@@ -11,6 +11,34 @@ export type MultiSelectValue = Array<string | number>;
|
|
|
11
11
|
* Display mode for the trigger.
|
|
12
12
|
*/
|
|
13
13
|
export type DisplayMode = 'count' | 'chips';
|
|
14
|
+
/**
|
|
15
|
+
* Overridable label strings for i18n.
|
|
16
|
+
*
|
|
17
|
+
* Static strings use plain `string`, dynamic strings use function signatures.
|
|
18
|
+
* All keys are optional — defaults are English.
|
|
19
|
+
*/
|
|
20
|
+
export type MultiSelectLabels = {
|
|
21
|
+
/**
|
|
22
|
+
* Trigger text in count display mode.
|
|
23
|
+
* @default (count) => `${count} selected`
|
|
24
|
+
*/
|
|
25
|
+
selected?: (count: number) => string;
|
|
26
|
+
/**
|
|
27
|
+
* Overflow badge text in chips display mode.
|
|
28
|
+
* @default (count) => `+${count} more`
|
|
29
|
+
*/
|
|
30
|
+
more?: (count: number) => string;
|
|
31
|
+
/**
|
|
32
|
+
* Screen reader status announcement. Called on every selection change.
|
|
33
|
+
* When `max` is defined and `count >= max`, include a "maximum reached" note.
|
|
34
|
+
* @default (count, max) => count === 0 ? '0 items selected' : `${count} item${count === 1 ? '' : 's'} selected${max !== undefined && count >= max ? `. Maximum of ${max} reached` : ''}`
|
|
35
|
+
*/
|
|
36
|
+
status?: (count: number, max?: number) => string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Default English labels. Exported for reference or spread-override patterns.
|
|
40
|
+
*/
|
|
41
|
+
export declare const defaultMultiSelectLabels: Required<MultiSelectLabels>;
|
|
14
42
|
export type MultiSelectProps = {
|
|
15
43
|
/**
|
|
16
44
|
* Control size.
|
|
@@ -73,6 +101,11 @@ export type MultiSelectProps = {
|
|
|
73
101
|
* Callback when max selections is reached.
|
|
74
102
|
*/
|
|
75
103
|
onMaxReached?: () => void;
|
|
104
|
+
/**
|
|
105
|
+
* Override internal display and screen reader strings for i18n.
|
|
106
|
+
* All keys are optional — omitted keys use English defaults.
|
|
107
|
+
*/
|
|
108
|
+
labels?: MultiSelectLabels;
|
|
76
109
|
/**
|
|
77
110
|
* Accessible name for the select.
|
|
78
111
|
*/
|
|
@@ -169,6 +202,7 @@ export type MultiSelectActionsContextValue = {
|
|
|
169
202
|
maxChips: number;
|
|
170
203
|
max: number | undefined;
|
|
171
204
|
size: SizeStandard;
|
|
205
|
+
labels: Required<MultiSelectLabels>;
|
|
172
206
|
triggerId: string;
|
|
173
207
|
listboxId: string;
|
|
174
208
|
ariaLabel?: string;
|
|
@@ -1,3 +1,13 @@
|
|
|
1
1
|
import { toKey } from '../../utils/value-key.js';
|
|
2
2
|
// Re-export shared value-key types so existing consumers don't break
|
|
3
3
|
export { toKey };
|
|
4
|
+
/**
|
|
5
|
+
* Default English labels. Exported for reference or spread-override patterns.
|
|
6
|
+
*/
|
|
7
|
+
export const defaultMultiSelectLabels = {
|
|
8
|
+
selected: (count) => `${count} selected`,
|
|
9
|
+
more: (count) => `+${count} more`,
|
|
10
|
+
status: (count, max) => count === 0
|
|
11
|
+
? '0 items selected'
|
|
12
|
+
: `${count} item${count === 1 ? '' : 's'} selected${max !== undefined && count >= max ? `. Maximum of ${max} reached` : ''}`,
|
|
13
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PagerLabels } from './types';
|
|
1
2
|
export type PagerMode = 'simple' | 'ends' | 'full' | 'smart';
|
|
2
3
|
export type PagerProps = {
|
|
3
4
|
/** Current page (1-indexed) */
|
|
@@ -22,5 +23,10 @@ export type PagerProps = {
|
|
|
22
23
|
hidden?: boolean;
|
|
23
24
|
/** Additional class name */
|
|
24
25
|
className?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Overridable label strings for i18n.
|
|
28
|
+
* All keys are optional — defaults are English.
|
|
29
|
+
*/
|
|
30
|
+
labels?: PagerLabels;
|
|
25
31
|
};
|
|
26
|
-
export declare function Pager({ currentPage, totalPages, onPageChange, pageSize, pageSizeOptions, onPageSizeChange, mode, maxNumbers, navStyle, hidden, className, }: PagerProps): import("react/jsx-runtime").JSX.Element | null;
|
|
32
|
+
export declare function Pager({ currentPage, totalPages, onPageChange, pageSize, pageSizeOptions, onPageSizeChange, mode, maxNumbers, navStyle, hidden, className, labels: labelsProp, }: PagerProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -3,6 +3,7 @@ import * as React from 'react';
|
|
|
3
3
|
import { cx } from '../../utils/cx.js';
|
|
4
4
|
import { Button } from '../Button/index.js';
|
|
5
5
|
import { IconButton } from '../IconButton/index.js';
|
|
6
|
+
import { defaultPagerLabels } from './types.js';
|
|
6
7
|
// =============================================================================
|
|
7
8
|
// Pager Component
|
|
8
9
|
// =============================================================================
|
|
@@ -121,7 +122,8 @@ function buildItems(total, current, mode, maxNumbers) {
|
|
|
121
122
|
// -----------------------------------------------------------------------------
|
|
122
123
|
// Component
|
|
123
124
|
// -----------------------------------------------------------------------------
|
|
124
|
-
export function Pager({ currentPage, totalPages, onPageChange, pageSize, pageSizeOptions, onPageSizeChange, mode = 'smart', maxNumbers = DEFAULT_MAX_SLOTS, navStyle = 'text', hidden = false, className, }) {
|
|
125
|
+
export function Pager({ currentPage, totalPages, onPageChange, pageSize, pageSizeOptions, onPageSizeChange, mode = 'smart', maxNumbers = DEFAULT_MAX_SLOTS, navStyle = 'text', hidden = false, className, labels: labelsProp, }) {
|
|
126
|
+
const labels = React.useMemo(() => ({ ...defaultPagerLabels, ...labelsProp }), [labelsProp]);
|
|
125
127
|
// Normalise inputs
|
|
126
128
|
const total = Math.max(1, totalPages);
|
|
127
129
|
const current = Math.min(total, Math.max(1, currentPage));
|
|
@@ -130,13 +132,13 @@ export function Pager({ currentPage, totalPages, onPageChange, pageSize, pageSiz
|
|
|
130
132
|
if (hidden)
|
|
131
133
|
return null;
|
|
132
134
|
const showPageSizeSelector = pageSizeOptions && pageSizeOptions.length > 0 && onPageSizeChange;
|
|
133
|
-
return (_jsxs("div", { className: cx('tui-pager', className), children: [_jsx("nav", { className: "tui-pager__nav", "aria-label":
|
|
135
|
+
return (_jsxs("div", { className: cx('tui-pager', className), children: [_jsx("nav", { className: "tui-pager__nav", "aria-label": labels.navigation, children: items.map((it, i) => {
|
|
134
136
|
if (it.kind === 'ellipsis') {
|
|
135
137
|
return (_jsx("span", { className: "tui-pager__ellipsis", "aria-hidden": true, children: "..." }, `e${i}`));
|
|
136
138
|
}
|
|
137
139
|
if (it.kind === 'prev' || it.kind === 'next') {
|
|
138
140
|
const isPrev = it.kind === 'prev';
|
|
139
|
-
const label = isPrev ?
|
|
141
|
+
const label = isPrev ? labels.previous : labels.next;
|
|
140
142
|
if (navStyle === 'icon') {
|
|
141
143
|
return (_jsx(IconButton, { icon: isPrev ? 'system/chevron-left' : 'system/chevron-right', label: label, showTooltip: true, size: "sm", theme: "secondary", variant: "outline", disabled: it.disabled, onClick: () => go(it.page), className: "tui-pager__item" }, it.kind));
|
|
142
144
|
}
|
|
@@ -144,8 +146,8 @@ export function Pager({ currentPage, totalPages, onPageChange, pageSize, pageSiz
|
|
|
144
146
|
}
|
|
145
147
|
// Page number
|
|
146
148
|
if (it.kind === 'page') {
|
|
147
|
-
return (_jsx(Button, { size: "sm", theme: it.current ? 'primary' : 'secondary', variant: it.current ? 'solid' : 'outline', "aria-current": it.current ? 'page' : undefined, "aria-label":
|
|
149
|
+
return (_jsx(Button, { size: "sm", theme: it.current ? 'primary' : 'secondary', variant: it.current ? 'solid' : 'outline', "aria-current": it.current ? 'page' : undefined, "aria-label": labels.page(it.page), onClick: () => go(it.page), className: "tui-pager__item", children: it.page }, it.page));
|
|
148
150
|
}
|
|
149
151
|
return null;
|
|
150
|
-
}) }), _jsxs("div", { className: "tui-pager__info", children: [
|
|
152
|
+
}) }), _jsxs("div", { className: "tui-pager__info", children: [_jsx("span", { children: labels.pageStatus(current, total) }), showPageSizeSelector && (_jsxs("label", { className: "tui-pager__page-size-label", children: [_jsx("span", { className: "tui-visually-hidden", children: labels.itemsPerPage }), _jsx("select", { className: "tui-input", value: pageSize, onChange: (e) => onPageSizeChange(Number(e.target.value)), children: pageSizeOptions.map((s) => (_jsx("option", { value: s, children: labels.perPage(s) }, s))) })] }))] })] }));
|
|
151
153
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Overridable label strings for i18n.
|
|
3
|
+
*
|
|
4
|
+
* Static strings use plain `string`, dynamic strings use function signatures.
|
|
5
|
+
* All keys are optional — defaults are English.
|
|
6
|
+
*/
|
|
7
|
+
export type PagerLabels = {
|
|
8
|
+
/** Label for previous page button (aria-label on icon style, aria-label on text style).
|
|
9
|
+
* @default "Previous page"
|
|
10
|
+
*/
|
|
11
|
+
previous?: string;
|
|
12
|
+
/** Label for next page button.
|
|
13
|
+
* @default "Next page"
|
|
14
|
+
*/
|
|
15
|
+
next?: string;
|
|
16
|
+
/** Label for individual page buttons.
|
|
17
|
+
* @default (n) => `Page ${n}`
|
|
18
|
+
*/
|
|
19
|
+
page?: (n: number) => string;
|
|
20
|
+
/** aria-label for the nav landmark.
|
|
21
|
+
* @default "Pagination"
|
|
22
|
+
*/
|
|
23
|
+
navigation?: string;
|
|
24
|
+
/** "Page X of Y" status text.
|
|
25
|
+
* @default (current, total) => `Page ${current} of ${total}`
|
|
26
|
+
*/
|
|
27
|
+
pageStatus?: (current: number, total: number) => string;
|
|
28
|
+
/** Visually-hidden label for the page size selector.
|
|
29
|
+
* @default "Items per page"
|
|
30
|
+
*/
|
|
31
|
+
itemsPerPage?: string;
|
|
32
|
+
/** Option text in the page size selector.
|
|
33
|
+
* @default (n) => `${n} / page`
|
|
34
|
+
*/
|
|
35
|
+
perPage?: (n: number) => string;
|
|
36
|
+
};
|
|
37
|
+
export declare const defaultPagerLabels: Required<PagerLabels>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Pager Types
|
|
3
|
+
// =============================================================================
|
|
4
|
+
export const defaultPagerLabels = {
|
|
5
|
+
previous: 'Previous page',
|
|
6
|
+
next: 'Next page',
|
|
7
|
+
page: (n) => `Page ${n}`,
|
|
8
|
+
navigation: 'Pagination',
|
|
9
|
+
pageStatus: (current, total) => `Page ${current} of ${total}`,
|
|
10
|
+
itemsPerPage: 'Items per page',
|
|
11
|
+
perPage: (n) => `${n} / page`,
|
|
12
|
+
};
|
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
import type { RadioProps } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Ref targets the inner `<button role="radio">`, not the outer wrapper `<div>`.
|
|
4
|
+
* This is intentional — roving tabindex and focus management operate on the button.
|
|
5
|
+
*/
|
|
2
6
|
export declare const Radio: import("react").ForwardRefExoticComponent<RadioProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { forwardRef, useCallback, useEffect, useRef } from 'react';
|
|
2
|
+
import { forwardRef, useCallback, useEffect, useId, useRef } from 'react';
|
|
3
3
|
import { cx } from '../../utils/cx.js';
|
|
4
4
|
import { composeRefs } from '../../utils/compose-refs.js';
|
|
5
5
|
import { toKey } from '../../utils/value-key.js';
|
|
@@ -14,8 +14,14 @@ import { useRadioGroupContext } from './RadioGroupContext.js';
|
|
|
14
14
|
// Arrow keys in the group move focus AND select.
|
|
15
15
|
//
|
|
16
16
|
// =============================================================================
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Ref targets the inner `<button role="radio">`, not the outer wrapper `<div>`.
|
|
19
|
+
* This is intentional — roving tabindex and focus management operate on the button.
|
|
20
|
+
*/
|
|
21
|
+
export const Radio = forwardRef(function Radio({ value, label, description, disabled = false, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, className, children, }, externalRef) {
|
|
18
22
|
const { selectedValue, focusableValue, rootDisabled, registerItem, unregisterItem, onSelect, } = useRadioGroupContext();
|
|
23
|
+
const id = useId();
|
|
24
|
+
const descriptionId = `${id}-desc`;
|
|
19
25
|
const isSelected = selectedValue !== undefined && toKey(selectedValue) === toKey(value);
|
|
20
26
|
const isDisabled = rootDisabled || disabled;
|
|
21
27
|
const isFocusable = focusableValue !== undefined && toKey(focusableValue) === toKey(value);
|
|
@@ -24,8 +30,8 @@ export const Radio = forwardRef(function Radio({ value, label, disabled = false,
|
|
|
24
30
|
useEffect(() => {
|
|
25
31
|
if (hasWarnedRef.current)
|
|
26
32
|
return;
|
|
27
|
-
if (isDev() && !label) {
|
|
28
|
-
console.warn('Radio: Missing accessible name. Provide a label prop.');
|
|
33
|
+
if (isDev() && !label && !ariaLabel && !ariaLabelledBy) {
|
|
34
|
+
console.warn('Radio: Missing accessible name. Provide a label, aria-label, or aria-labelledby prop.');
|
|
29
35
|
hasWarnedRef.current = true;
|
|
30
36
|
}
|
|
31
37
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -45,6 +51,10 @@ export const Radio = forwardRef(function Radio({ value, label, disabled = false,
|
|
|
45
51
|
return;
|
|
46
52
|
onSelect(value);
|
|
47
53
|
};
|
|
48
|
-
|
|
54
|
+
const hasExpandedContent = !!(description || children);
|
|
55
|
+
return (_jsxs("div", { className: cx('tui-radio', hasExpandedContent && 'has-content', className), children: [_jsxs("button", { ref: composeRefs(callbackRef, externalRef), type: "button", role: "radio", className: "tui-radio__control", "aria-checked": isSelected, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": description ? descriptionId : undefined,
|
|
56
|
+
// Item-level disabled: native disabled (removes from focus cycle).
|
|
57
|
+
// Group-level disabled: aria-disabled (preserves AT group context).
|
|
58
|
+
disabled: disabled || undefined, "aria-disabled": rootDisabled || undefined, tabIndex: isFocusable ? 0 : -1, onClick: handleClick, children: [_jsx("span", { className: "tui-radio__indicator", "aria-hidden": "true" }), label && _jsx("span", { className: "tui-radio__label", children: label })] }), hasExpandedContent && (_jsxs("div", { className: "tui-radio__body", children: [description && (_jsx("p", { id: descriptionId, className: "tui-radio__description", children: description })), children] }))] }));
|
|
49
59
|
});
|
|
50
60
|
Radio.displayName = 'Radio';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { RadioGroupProps } from './types';
|
|
2
|
-
export declare function RadioGroup({ value: controlledValue, defaultValue, onValueChange, disabled, orientation, loop, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, className, children, }: RadioGroupProps): import("react/jsx-runtime").JSX.Element;
|
|
2
|
+
export declare function RadioGroup({ value: controlledValue, defaultValue, onValueChange, disabled, orientation, loop, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, 'aria-invalid': ariaInvalid, 'aria-required': ariaRequired, className, children, }: RadioGroupProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -17,7 +17,7 @@ import { isDev } from '../../utils/is-dev.js';
|
|
|
17
17
|
// --tui-radio-accent Accent color for selected state
|
|
18
18
|
//
|
|
19
19
|
// =============================================================================
|
|
20
|
-
export function RadioGroup({ value: controlledValue, defaultValue, onValueChange, disabled = false, orientation = 'vertical', loop = true, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, className, children, }) {
|
|
20
|
+
export function RadioGroup({ value: controlledValue, defaultValue, onValueChange, disabled = false, orientation = 'vertical', loop = true, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, 'aria-invalid': ariaInvalid, 'aria-required': ariaRequired, className, children, }) {
|
|
21
21
|
const [selectedValue, setSelectedValue] = useControllableState({
|
|
22
22
|
value: controlledValue,
|
|
23
23
|
defaultValue,
|
|
@@ -50,5 +50,5 @@ export function RadioGroup({ value: controlledValue, defaultValue, onValueChange
|
|
|
50
50
|
unregisterItem,
|
|
51
51
|
onSelect,
|
|
52
52
|
}), [selectedValue, focusableValue, disabled, orientation, registerItem, unregisterItem, onSelect]);
|
|
53
|
-
return (_jsx(RadioGroupContext.Provider, { value: contextValue, children: _jsx("div", { role: "radiogroup", className: cx('tui-radio-group', orientation === 'horizontal' && 'is-horizontal', className), "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-
|
|
53
|
+
return (_jsx(RadioGroupContext.Provider, { value: contextValue, children: _jsx("div", { role: "radiogroup", className: cx('tui-radio-group', orientation === 'horizontal' && 'is-horizontal', className), "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, "aria-disabled": disabled || undefined, "aria-orientation": orientation, onKeyDown: handleKeyDown, children: children }) }));
|
|
54
54
|
}
|