@tangible/ui 0.0.7 → 0.0.9

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.
Files changed (103) hide show
  1. package/components/Accordion/Accordion.js +11 -3
  2. package/components/Avatar/Avatar.d.ts +1 -1
  3. package/components/Avatar/Avatar.js +5 -4
  4. package/components/Avatar/AvatarGroup.js +7 -5
  5. package/components/Avatar/index.d.ts +2 -2
  6. package/components/Avatar/index.js +1 -1
  7. package/components/Avatar/types.d.ts +27 -0
  8. package/components/Avatar/types.js +8 -0
  9. package/components/Button/Button.js +4 -2
  10. package/components/Button/index.d.ts +2 -1
  11. package/components/Button/index.js +1 -0
  12. package/components/Button/types.d.ts +10 -0
  13. package/components/Button/types.js +3 -1
  14. package/components/Checkbox/Checkbox.js +46 -11
  15. package/components/Checkbox/types.d.ts +9 -0
  16. package/components/Combobox/Combobox.d.ts +1 -1
  17. package/components/Combobox/Combobox.js +50 -7
  18. package/components/Combobox/index.d.ts +2 -1
  19. package/components/Combobox/index.js +1 -0
  20. package/components/Combobox/types.d.ts +9 -0
  21. package/components/Combobox/types.js +3 -1
  22. package/components/Dropdown/Dropdown.d.ts +1 -1
  23. package/components/Dropdown/Dropdown.js +32 -12
  24. package/components/Field/Field.d.ts +4 -1
  25. package/components/Field/Field.js +35 -14
  26. package/components/Field/FieldContext.d.ts +16 -0
  27. package/components/Field/FieldContext.js +3 -0
  28. package/components/Field/index.d.ts +2 -1
  29. package/components/Field/index.js +1 -0
  30. package/components/Icon/Icon.d.ts +1 -1
  31. package/components/Icon/Icon.js +2 -2
  32. package/components/Modal/Modal.d.ts +5 -1
  33. package/components/Modal/Modal.js +2 -2
  34. package/components/MoveHandle/MoveHandle.d.ts +1 -1
  35. package/components/MoveHandle/MoveHandle.js +4 -4
  36. package/components/MoveHandle/types.d.ts +1 -1
  37. package/components/MultiSelect/MultiSelect.d.ts +1 -1
  38. package/components/MultiSelect/MultiSelect.js +58 -19
  39. package/components/MultiSelect/index.d.ts +2 -1
  40. package/components/MultiSelect/index.js +1 -0
  41. package/components/MultiSelect/types.d.ts +34 -0
  42. package/components/MultiSelect/types.js +10 -0
  43. package/components/Pager/Pager.d.ts +7 -1
  44. package/components/Pager/Pager.js +7 -5
  45. package/components/Pager/index.d.ts +2 -0
  46. package/components/Pager/index.js +1 -0
  47. package/components/Pager/types.d.ts +37 -0
  48. package/components/Pager/types.js +12 -0
  49. package/components/Progress/Progress.d.ts +2 -1
  50. package/components/Progress/Progress.js +3 -3
  51. package/components/Rating/Rating.d.ts +2 -32
  52. package/components/Rating/Rating.js +5 -3
  53. package/components/Rating/index.d.ts +2 -1
  54. package/components/Rating/index.js +1 -0
  55. package/components/Rating/types.d.ts +41 -0
  56. package/components/Rating/types.js +4 -0
  57. package/components/SegmentedControl/SegmentedControl.js +6 -5
  58. package/components/SegmentedControl/types.d.ts +17 -5
  59. package/components/Select/Select.d.ts +1 -0
  60. package/components/Select/Select.js +131 -77
  61. package/components/Select/SelectContext.d.ts +4 -16
  62. package/components/Select/SelectContext.js +5 -35
  63. package/components/Select/types.d.ts +19 -19
  64. package/components/Sidebar/Sidebar.js +25 -20
  65. package/components/StepIndicator/StepIndicator.d.ts +1 -1
  66. package/components/StepIndicator/StepIndicator.js +14 -10
  67. package/components/StepIndicator/index.d.ts +2 -1
  68. package/components/StepIndicator/index.js +1 -0
  69. package/components/StepIndicator/types.d.ts +18 -0
  70. package/components/StepIndicator/types.js +7 -1
  71. package/components/Table/BulkActionsBar.d.ts +4 -1
  72. package/components/Table/BulkActionsBar.js +5 -4
  73. package/components/Table/DataTable.d.ts +4 -1
  74. package/components/Table/DataTable.js +10 -8
  75. package/components/Table/index.d.ts +3 -0
  76. package/components/Table/index.js +2 -0
  77. package/components/Table/types.d.ts +20 -0
  78. package/components/Table/types.js +11 -0
  79. package/components/Tabs/Tabs.js +11 -4
  80. package/components/TextInput/TextInput.js +2 -1
  81. package/components/TextInput/types.d.ts +7 -1
  82. package/components/Textarea/Textarea.js +3 -2
  83. package/components/Textarea/types.d.ts +6 -1
  84. package/components/Tooltip/Tooltip.d.ts +1 -1
  85. package/components/Tooltip/Tooltip.js +16 -10
  86. package/icons/icons.svg +29 -15
  87. package/icons/lms/index.d.ts +8 -0
  88. package/icons/lms/index.js +48 -4
  89. package/icons/manifest.json +112 -0
  90. package/icons/player/index.js +9 -9
  91. package/icons/registry.d.ts +28 -0
  92. package/icons/registry.js +14 -0
  93. package/icons/system/index.d.ts +20 -0
  94. package/icons/system/index.js +112 -2
  95. package/package.json +1 -1
  96. package/styles/all.css +1 -1
  97. package/styles/all.expanded.css +266 -59
  98. package/styles/all.expanded.unlayered.css +266 -59
  99. package/styles/all.unlayered.css +1 -1
  100. package/styles/components/input/index.scss +29 -7
  101. package/styles/system/_constants.scss +1 -1
  102. package/styles/system/_tokens.scss +1 -0
  103. package/tui-manifest.json +78 -52
@@ -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": "Pagination", children: items.map((it, i) => {
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 ? 'Previous page' : 'Next page';
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": `Page ${it.page}`, onClick: () => go(it.page), className: "tui-pager__item", children: it.page }, it.page));
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: [_jsxs("span", { children: ["Page ", _jsx("strong", { children: current }), " of ", total] }), showPageSizeSelector && (_jsxs("label", { className: "tui-pager__page-size-label", children: [_jsx("span", { className: "tui-visually-hidden", children: "Items per page" }), _jsx("select", { className: "tui-input", value: pageSize, onChange: (e) => onPageSizeChange(Number(e.target.value)), children: pageSizeOptions.map((s) => (_jsxs("option", { value: s, children: [s, " / page"] }, s))) })] }))] })] }));
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
  }
@@ -1,2 +1,4 @@
1
1
  export { Pager } from './Pager';
2
2
  export type { PagerProps, PagerMode } from './Pager';
3
+ export { defaultPagerLabels } from './types';
4
+ export type { PagerLabels } from './types';
@@ -1 +1,2 @@
1
1
  export { Pager } from './Pager.js';
2
+ export { defaultPagerLabels } from './types.js';
@@ -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,3 @@
1
+ import React from 'react';
1
2
  import type { ProgressProps } from './types';
2
- export declare function Progress(props: ProgressProps): import("react/jsx-runtime").JSX.Element;
3
+ export declare const Progress: React.NamedExoticComponent<ProgressProps>;
@@ -1,12 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import React from 'react';
2
+ import React, { memo } from 'react';
3
3
  import { useProgressSegments } from './useProgressSegments.js';
4
4
  import { cx } from '../../utils/cx.js';
5
5
  import { isDev } from '../../utils/is-dev.js';
6
6
  // =============================================================================
7
7
  // COMPONENT
8
8
  // =============================================================================
9
- export function Progress(props) {
9
+ export const Progress = memo(function Progress(props) {
10
10
  const { children, mode = 'line', size = 'md', max = 100, showLabels = true, 'aria-labelledby': labelledBy, 'aria-label': ariaLabel, defaultLabel, className, } = props;
11
11
  // Determine mode
12
12
  const isSegmented = 'segments' in props && Array.isArray(props.segments);
@@ -98,4 +98,4 @@ export function Progress(props) {
98
98
  // Above/below/inline positions: render labels outside the track
99
99
  const labelRow = (_jsxs("div", { className: "tui-progress__labels", children: [labelStart && _jsx("span", { className: "tui-progress__label is-start", children: labelStart }), labelEnd && _jsx("span", { className: "tui-progress__label is-end", children: labelEnd })] }));
100
100
  return (_jsxs("div", { ...rootProps, children: [labelPosition === 'above' && labelRow, labelPosition === 'inline' ? (_jsxs("div", { className: "tui-progress__inline", children: [labelStart && _jsx("span", { className: "tui-progress__label is-start", children: labelStart }), trackContent, labelEnd && _jsx("span", { className: "tui-progress__label is-end", children: labelEnd })] })) : (trackContent), labelPosition === 'below' && labelRow, !labelledBy && !ariaLabel && defaultLabel && (_jsx("span", { className: "visually-hidden", children: defaultLabel }))] }));
101
- }
101
+ });
@@ -1,32 +1,2 @@
1
- import type { SizeStandard, Theme as ThemeFull } from '../../types';
2
- type Size = SizeStandard;
3
- type Theme = ThemeFull;
4
- export type RatingProps = {
5
- /** Controlled value (1..max). Use with onValueChange */
6
- value?: number;
7
- /** Uncontrolled initial value */
8
- defaultValue?: number;
9
- /** Maximum icons shown */
10
- max?: number;
11
- /** Disable interaction (keeps semantics) */
12
- disabled?: boolean;
13
- /** Presentational readOnly (no form semantics) */
14
- readOnly?: boolean;
15
- /** Name for the radio group (if you care about form posts) */
16
- name?: string;
17
- /** Size maps to icon + spacing */
18
- size?: Size;
19
- /** Theme feeds foreground color tokens */
20
- theme?: Theme;
21
- /** Called when the value changes */
22
- onValueChange?: (value: number) => void;
23
- /** Allow clicking the current selection to clear back to 0 */
24
- allowClear?: boolean;
25
- className?: string;
26
- /** Gap override (e.g. '0.25rem') – otherwise uses density utilities */
27
- gap?: string;
28
- /** Accessible label for the rating group. Defaults to "Rating: X of Y" */
29
- 'aria-label'?: string;
30
- };
31
- export declare function Rating({ value, defaultValue, max, disabled, readOnly, name, size, theme, onValueChange, allowClear, className, gap, 'aria-label': ariaLabel, }: RatingProps): import("react/jsx-runtime").JSX.Element;
32
- export {};
1
+ import type { RatingProps } from './types';
2
+ export declare function Rating({ value, defaultValue, max, disabled, readOnly, name, size, theme, onValueChange, allowClear, className, gap, 'aria-label': ariaLabel, labels: labelsProp, }: RatingProps): import("react/jsx-runtime").JSX.Element;
@@ -2,10 +2,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React from 'react';
3
3
  import { cx } from '../../utils/cx.js';
4
4
  import { Icon } from '../Icon/index.js';
5
- export function Rating({ value, defaultValue = 0, max = 5, disabled, readOnly, name, size = 'lg', theme = 'secondary', onValueChange, allowClear, className, gap, 'aria-label': ariaLabel, }) {
5
+ import { defaultRatingLabels } from './types.js';
6
+ export function Rating({ value, defaultValue = 0, max = 5, disabled, readOnly, name, size = 'lg', theme = 'secondary', onValueChange, allowClear, className, gap, 'aria-label': ariaLabel, labels: labelsProp, }) {
6
7
  const isControlled = value != null;
7
8
  const [internal, setInternal] = React.useState(defaultValue);
8
9
  const current = isControlled ? value : internal;
10
+ const labels = { ...defaultRatingLabels, ...labelsProp };
9
11
  const generatedId = React.useId();
10
12
  const groupName = name ?? generatedId;
11
13
  const setValue = (v) => {
@@ -62,13 +64,13 @@ export function Rating({ value, defaultValue = 0, max = 5, disabled, readOnly, n
62
64
  return;
63
65
  }
64
66
  };
65
- const defaultAriaLabel = `Rating: ${current} of ${max}`;
67
+ const defaultAriaLabel = labels.rating(current, max);
66
68
  // Roving tabindex: only the selected radio (or first if none) is in tab order
67
69
  const focusableIndex = current > 0 ? current : 1;
68
70
  return (_jsx("div", { className: cx('tui-rating', `is-size-${size}`, `is-theme-${theme}`, disabled && 'is-disabled', className), style: { gap }, role: readOnly ? 'img' : 'radiogroup', "aria-label": ariaLabel ?? defaultAriaLabel, onKeyDown: readOnly ? undefined : onKeyDown, children: Array.from({ length: max }).map((_, i) => {
69
71
  const n = i + 1;
70
72
  const checked = current >= n;
71
73
  const id = `${groupName}__star-${n}`;
72
- return (_jsxs("div", { className: "tui-rating__item", children: [!readOnly && (_jsx("input", { className: "tui-visually-hidden", type: "radio", id: id, name: groupName, value: n, checked: current === n, "aria-checked": current === n, tabIndex: n === focusableIndex ? 0 : -1, onChange: () => handleSelect(n), disabled: disabled })), readOnly ? (_jsx("span", { className: cx('tui-rating__star', checked && 'is-active'), children: _jsx(Icon, { name: checked ? 'system/star-fill' : 'system/star-outline' }) })) : (_jsxs("label", { className: cx('tui-rating__star', checked && 'is-active'), htmlFor: id, tabIndex: -1, children: [_jsx(Icon, { name: checked ? 'system/star-fill' : 'system/star-outline' }), _jsx("span", { className: "tui-visually-hidden", children: `${n} of ${max}` })] }))] }, n));
74
+ return (_jsxs("div", { className: "tui-rating__item", children: [!readOnly && (_jsx("input", { className: "tui-visually-hidden", type: "radio", id: id, name: groupName, value: n, checked: current === n, "aria-checked": current === n, tabIndex: n === focusableIndex ? 0 : -1, onChange: () => handleSelect(n), disabled: disabled })), readOnly ? (_jsx("span", { className: cx('tui-rating__star', checked && 'is-active'), children: _jsx(Icon, { name: checked ? 'system/star-fill' : 'system/star-outline' }) })) : (_jsxs("label", { className: cx('tui-rating__star', checked && 'is-active'), htmlFor: id, tabIndex: -1, children: [_jsx(Icon, { name: checked ? 'system/star-fill' : 'system/star-outline' }), _jsx("span", { className: "tui-visually-hidden", children: labels.value(n, max) })] }))] }, n));
73
75
  }) }));
74
76
  }
@@ -1,2 +1,3 @@
1
1
  export { Rating } from './Rating';
2
- export type { RatingProps } from './Rating';
2
+ export type { RatingProps, RatingLabels } from './types';
3
+ export { defaultRatingLabels } from './types';
@@ -1 +1,2 @@
1
1
  export { Rating } from './Rating.js';
2
+ export { defaultRatingLabels } from './types.js';
@@ -0,0 +1,41 @@
1
+ import type { SizeStandard, Theme as ThemeFull } from '../../types';
2
+ export type Size = SizeStandard;
3
+ export type Theme = ThemeFull;
4
+ export type RatingLabels = {
5
+ /** Group label for the radiogroup/img. Receives current value and max. */
6
+ rating?: (value: number, max: number) => string;
7
+ /** Visually-hidden label for each star. Receives star number and max. */
8
+ value?: (n: number, max: number) => string;
9
+ };
10
+ export declare const defaultRatingLabels: Required<RatingLabels>;
11
+ export type RatingProps = {
12
+ /** Controlled value (1..max). Use with onValueChange */
13
+ value?: number;
14
+ /** Uncontrolled initial value */
15
+ defaultValue?: number;
16
+ /** Maximum icons shown */
17
+ max?: number;
18
+ /** Disable interaction (keeps semantics) */
19
+ disabled?: boolean;
20
+ /** Presentational readOnly (no form semantics) */
21
+ readOnly?: boolean;
22
+ /** Name for the radio group (if you care about form posts) */
23
+ name?: string;
24
+ /** Size maps to icon + spacing */
25
+ size?: Size;
26
+ /** Theme feeds foreground color tokens */
27
+ theme?: Theme;
28
+ /** Called when the value changes */
29
+ onValueChange?: (value: number) => void;
30
+ /** Allow clicking the current selection to clear back to 0 */
31
+ allowClear?: boolean;
32
+ className?: string;
33
+ /** Gap override (e.g. '0.25rem') – otherwise uses density utilities */
34
+ gap?: string;
35
+ /** Accessible label for the rating group. Defaults to "Rating: X of Y" */
36
+ 'aria-label'?: string;
37
+ /**
38
+ * Override default English strings for i18n.
39
+ */
40
+ labels?: RatingLabels;
41
+ };
@@ -0,0 +1,4 @@
1
+ export const defaultRatingLabels = {
2
+ rating: (value, max) => `Rating: ${value} of ${max}`,
3
+ value: (n, max) => `${n} of ${max}`,
4
+ };
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { cx } from '../../utils/cx.js';
4
4
  import { isDev } from '../../utils/is-dev.js';
5
5
  import { toKey } from '../../utils/value-key.js';
@@ -8,9 +8,10 @@ import { SegmentedControlContext, useSegmentedControlContext } from './Segmented
8
8
  // =============================================================================
9
9
  // SegmentedControl Root
10
10
  // =============================================================================
11
- function SegmentedControlRoot({ value: controlledValue, defaultValue, onValueChange, variant = 'pill', size = 'md', orientation = 'horizontal', loop = true, disabled = false, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, className, children, }) {
11
+ function SegmentedControlRoot({ value: controlledValue, defaultValue, onValueChange, variant = 'pill', size = 'md', orientation = 'horizontal', loop = true, wrap = false, disabled = false, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, className, children, }) {
12
12
  const [internalValue, setInternalValue] = useState(defaultValue);
13
- const isControlled = controlledValue !== undefined;
13
+ // Lock controlled/uncontrolled decision at mount to prevent mode switching
14
+ const isControlled = useRef(controlledValue !== undefined).current;
14
15
  const selectedValue = isControlled ? controlledValue : internalValue;
15
16
  // Selection handler
16
17
  const onSelect = useCallback((newValue) => {
@@ -28,7 +29,7 @@ function SegmentedControlRoot({ value: controlledValue, defaultValue, onValueCha
28
29
  disabled,
29
30
  loop,
30
31
  orientation,
31
- orientationKeyboard: true,
32
+ orientationKeyboard: false,
32
33
  });
33
34
  // Dev-only: Warn if missing accessible name
34
35
  useEffect(() => {
@@ -60,7 +61,7 @@ function SegmentedControlRoot({ value: controlledValue, defaultValue, onValueCha
60
61
  unregisterItem,
61
62
  onSelect,
62
63
  ]);
63
- return (_jsx(SegmentedControlContext.Provider, { value: contextValue, children: _jsx("div", { role: "radiogroup", className: cx('tui-segmented', `is-variant-${variant}`, `is-size-${size}`, orientation === 'vertical' && 'is-vertical', className), "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-disabled": disabled || undefined, "aria-orientation": orientation, onKeyDown: handleKeyDown, children: children }) }));
64
+ return (_jsx(SegmentedControlContext.Provider, { value: contextValue, children: _jsx("div", { role: "radiogroup", className: cx('tui-segmented', `is-variant-${variant}`, `is-size-${size}`, orientation === 'vertical' && 'is-vertical', wrap && 'is-wrap', className), "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-disabled": disabled || undefined, "aria-orientation": orientation, onKeyDown: handleKeyDown, children: children }) }));
64
65
  }
65
66
  // =============================================================================
66
67
  // SegmentedControl.Item
@@ -1,8 +1,9 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type { RovingItemRecord } from '../../utils/use-roving-group';
3
+ import type { SizeStandard } from '../../types/sizes';
3
4
  export type SegmentedControlValue = string | number;
4
5
  export type SegmentedControlVariant = 'pill' | 'outline' | 'underline';
5
- export type SegmentedControlSize = 'sm' | 'md';
6
+ export type SegmentedControlSize = SizeStandard;
6
7
  export type SegmentedControlOrientation = 'horizontal' | 'vertical';
7
8
  export type SegmentedControlProps = {
8
9
  /** Controlled selected value */
@@ -19,11 +20,19 @@ export type SegmentedControlProps = {
19
20
  orientation?: SegmentedControlOrientation;
20
21
  /** Whether arrow keys wrap around */
21
22
  loop?: boolean;
23
+ /** Allow items to wrap to multiple lines (default: false) */
24
+ wrap?: boolean;
22
25
  /** Disable all items */
23
26
  disabled?: boolean;
24
- /** Accessible label */
27
+ /**
28
+ * Accessible label for the radiogroup.
29
+ * At least one of `aria-label` or `aria-labelledby` is required.
30
+ */
25
31
  'aria-label'?: string;
26
- /** ID of element that labels this control */
32
+ /**
33
+ * ID of element that labels this control.
34
+ * At least one of `aria-label` or `aria-labelledby` is required.
35
+ */
27
36
  'aria-labelledby'?: string;
28
37
  /** Additional classes */
29
38
  className?: string;
@@ -34,9 +43,12 @@ export type SegmentedControlItemProps = {
34
43
  value: SegmentedControlValue;
35
44
  /** Disable this item */
36
45
  disabled?: boolean;
37
- /** Icon element */
46
+ /**
47
+ * Icon element rendered before the label.
48
+ * @remarks Icon-only items (no `children`) must provide `aria-label`.
49
+ */
38
50
  icon?: ReactNode;
39
- /** Accessible label for icon-only items */
51
+ /** Accessible label — required for icon-only items */
40
52
  'aria-label'?: string;
41
53
  /** Additional classes */
42
54
  className?: string;
@@ -37,3 +37,4 @@ export declare const SelectOption: typeof SelectOptionComponent;
37
37
  export declare const SelectGroup: typeof SelectGroupComponent;
38
38
  export declare const SelectLabel: typeof SelectLabelComponent;
39
39
  export { useSelectContext as useSelect } from './SelectContext';
40
+ export type { SelectContextValue } from './types';