@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.
Files changed (99) hide show
  1. package/components/Accordion/Accordion.js +11 -3
  2. package/components/Avatar/Avatar.js +4 -3
  3. package/components/Avatar/AvatarGroup.js +7 -5
  4. package/components/Avatar/index.d.ts +2 -2
  5. package/components/Avatar/index.js +1 -1
  6. package/components/Avatar/types.d.ts +27 -0
  7. package/components/Avatar/types.js +8 -0
  8. package/components/Button/Button.js +4 -2
  9. package/components/Button/index.d.ts +2 -1
  10. package/components/Button/index.js +1 -0
  11. package/components/Button/types.d.ts +10 -0
  12. package/components/Button/types.js +3 -1
  13. package/components/Checkbox/Checkbox.js +46 -11
  14. package/components/Checkbox/types.d.ts +9 -0
  15. package/components/Combobox/Combobox.d.ts +1 -1
  16. package/components/Combobox/Combobox.js +28 -7
  17. package/components/Combobox/index.d.ts +2 -1
  18. package/components/Combobox/index.js +1 -0
  19. package/components/Combobox/types.d.ts +14 -0
  20. package/components/Combobox/types.js +3 -1
  21. package/components/Dropdown/Dropdown.js +16 -4
  22. package/components/Field/Field.d.ts +4 -1
  23. package/components/Field/Field.js +38 -7
  24. package/components/Field/FieldContext.d.ts +18 -0
  25. package/components/Field/FieldContext.js +3 -0
  26. package/components/Field/index.d.ts +2 -1
  27. package/components/Field/index.js +1 -0
  28. package/components/MoveHandle/MoveHandle.js +1 -1
  29. package/components/MoveHandle/types.d.ts +1 -1
  30. package/components/MultiSelect/MultiSelect.d.ts +1 -1
  31. package/components/MultiSelect/MultiSelect.js +37 -19
  32. package/components/MultiSelect/index.d.ts +2 -1
  33. package/components/MultiSelect/index.js +1 -0
  34. package/components/MultiSelect/types.d.ts +34 -0
  35. package/components/MultiSelect/types.js +10 -0
  36. package/components/Pager/Pager.d.ts +7 -1
  37. package/components/Pager/Pager.js +7 -5
  38. package/components/Pager/index.d.ts +2 -0
  39. package/components/Pager/index.js +1 -0
  40. package/components/Pager/types.d.ts +37 -0
  41. package/components/Pager/types.js +12 -0
  42. package/components/Radio/Radio.d.ts +4 -0
  43. package/components/Radio/Radio.js +15 -5
  44. package/components/Radio/RadioGroup.d.ts +1 -1
  45. package/components/Radio/RadioGroup.js +2 -2
  46. package/components/Radio/types.d.ts +10 -0
  47. package/components/Rating/Rating.d.ts +2 -32
  48. package/components/Rating/Rating.js +5 -3
  49. package/components/Rating/index.d.ts +2 -1
  50. package/components/Rating/index.js +1 -0
  51. package/components/Rating/types.d.ts +41 -0
  52. package/components/Rating/types.js +4 -0
  53. package/components/SegmentedControl/SegmentedControl.js +6 -5
  54. package/components/SegmentedControl/types.d.ts +17 -5
  55. package/components/Select/Select.d.ts +1 -0
  56. package/components/Select/Select.js +109 -77
  57. package/components/Select/SelectContext.d.ts +4 -16
  58. package/components/Select/SelectContext.js +5 -35
  59. package/components/Select/types.d.ts +19 -19
  60. package/components/Sidebar/Sidebar.js +25 -20
  61. package/components/StepIndicator/StepIndicator.js +11 -8
  62. package/components/StepIndicator/index.d.ts +2 -1
  63. package/components/StepIndicator/index.js +1 -0
  64. package/components/StepIndicator/types.d.ts +18 -0
  65. package/components/StepIndicator/types.js +7 -1
  66. package/components/Switch/Switch.js +28 -14
  67. package/components/Table/BulkActionsBar.d.ts +4 -1
  68. package/components/Table/BulkActionsBar.js +5 -4
  69. package/components/Table/DataTable.d.ts +4 -1
  70. package/components/Table/DataTable.js +10 -8
  71. package/components/Table/index.d.ts +3 -0
  72. package/components/Table/index.js +2 -0
  73. package/components/Table/types.d.ts +20 -0
  74. package/components/Table/types.js +11 -0
  75. package/components/Tabs/Tabs.js +11 -4
  76. package/components/TextInput/TextInput.js +2 -1
  77. package/components/TextInput/types.d.ts +7 -1
  78. package/components/Textarea/Textarea.js +3 -2
  79. package/components/Textarea/types.d.ts +6 -1
  80. package/icons/icons.svg +29 -15
  81. package/icons/lms/index.d.ts +8 -0
  82. package/icons/lms/index.js +48 -4
  83. package/icons/manifest.json +112 -0
  84. package/icons/player/index.js +9 -9
  85. package/icons/registry.d.ts +28 -0
  86. package/icons/registry.js +14 -0
  87. package/icons/system/index.d.ts +20 -0
  88. package/icons/system/index.js +112 -2
  89. package/package.json +1 -1
  90. package/styles/all.css +1 -1
  91. package/styles/all.expanded.css +426 -136
  92. package/styles/all.expanded.unlayered.css +426 -136
  93. package/styles/all.unlayered.css +1 -1
  94. package/styles/components/input/index.scss +29 -7
  95. package/styles/system/_constants.scss +1 -1
  96. package/styles/system/_tokens.scss +1 -0
  97. package/styles/utilities/_index.scss +14 -4
  98. package/tui-manifest.json +102 -46
  99. package/utils/use-roving-group.js +9 -6
@@ -10,14 +10,24 @@ export type RadioGroupProps = {
10
10
  loop?: boolean;
11
11
  'aria-label'?: string;
12
12
  'aria-labelledby'?: string;
13
+ 'aria-describedby'?: string;
14
+ 'aria-invalid'?: boolean | 'true' | 'false';
15
+ 'aria-required'?: boolean | 'true' | 'false';
13
16
  className?: string;
14
17
  children: ReactNode;
15
18
  };
16
19
  export type RadioProps = {
17
20
  value: OptionValue;
18
21
  label?: ReactNode;
22
+ /**
23
+ * Description text displayed below the label.
24
+ */
25
+ description?: string;
19
26
  disabled?: boolean;
27
+ 'aria-label'?: string;
28
+ 'aria-labelledby'?: string;
20
29
  className?: string;
30
+ children?: ReactNode;
21
31
  };
22
32
  export type RadioItemRecord = RovingItemRecord;
23
33
  export type RadioGroupContextValue = {
@@ -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';