@tangible/ui 0.0.1 → 0.0.2

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 (129) hide show
  1. package/components/Card/Card.d.ts +1 -0
  2. package/components/Card/Card.js +17 -20
  3. package/components/Checkbox/Checkbox.d.ts +9 -0
  4. package/components/Checkbox/Checkbox.js +92 -0
  5. package/components/Checkbox/index.d.ts +2 -0
  6. package/components/Checkbox/index.js +1 -0
  7. package/components/Checkbox/types.d.ts +10 -0
  8. package/components/Checkbox/types.js +1 -0
  9. package/components/Chip/Chip.d.ts +4 -1
  10. package/components/Chip/Chip.js +32 -7
  11. package/components/ChipGroup/ChipGroup.d.ts +5 -0
  12. package/components/ChipGroup/ChipGroup.js +68 -0
  13. package/components/ChipGroup/ChipGroupContext.d.ts +3 -0
  14. package/components/ChipGroup/ChipGroupContext.js +5 -0
  15. package/components/ChipGroup/index.d.ts +3 -0
  16. package/components/ChipGroup/index.js +2 -0
  17. package/components/ChipGroup/types.d.ts +36 -0
  18. package/components/ChipGroup/types.js +1 -0
  19. package/components/Chips/Chips.d.ts +2 -0
  20. package/components/Chips/Chips.js +1 -1
  21. package/components/Combobox/Combobox.d.ts +33 -0
  22. package/components/Combobox/Combobox.js +466 -0
  23. package/components/Combobox/ComboboxContext.d.ts +8 -0
  24. package/components/Combobox/ComboboxContext.js +36 -0
  25. package/components/Combobox/index.d.ts +2 -0
  26. package/components/Combobox/index.js +1 -0
  27. package/components/Combobox/types.d.ts +204 -0
  28. package/components/Combobox/types.js +1 -0
  29. package/components/Dropdown/Dropdown.js +2 -1
  30. package/components/Field/Field.d.ts +39 -0
  31. package/components/Field/Field.js +92 -0
  32. package/components/Field/FieldContext.d.ts +16 -0
  33. package/components/Field/FieldContext.js +10 -0
  34. package/components/Field/index.d.ts +2 -0
  35. package/components/Field/index.js +1 -0
  36. package/components/Modal/Modal.d.ts +4 -4
  37. package/components/Modal/Modal.js +14 -12
  38. package/components/MultiSelect/MultiSelect.d.ts +39 -0
  39. package/components/MultiSelect/MultiSelect.js +623 -0
  40. package/components/MultiSelect/MultiSelectContext.d.ts +20 -0
  41. package/components/MultiSelect/MultiSelectContext.js +56 -0
  42. package/components/MultiSelect/index.d.ts +2 -0
  43. package/components/MultiSelect/index.js +1 -0
  44. package/components/MultiSelect/types.d.ts +218 -0
  45. package/components/MultiSelect/types.js +3 -0
  46. package/components/Notice/Notice.d.ts +1 -1
  47. package/components/Notice/Notice.js +1 -1
  48. package/components/Progress/Progress.js +1 -1
  49. package/components/Progress/types.d.ts +7 -7
  50. package/components/Radio/Radio.d.ts +2 -0
  51. package/components/Radio/Radio.js +50 -0
  52. package/components/Radio/RadioGroup.d.ts +2 -0
  53. package/components/Radio/RadioGroup.js +54 -0
  54. package/components/Radio/RadioGroupContext.d.ts +3 -0
  55. package/components/Radio/RadioGroupContext.js +9 -0
  56. package/components/Radio/index.d.ts +8 -0
  57. package/components/Radio/index.js +6 -0
  58. package/components/Radio/types.d.ts +32 -0
  59. package/components/Radio/types.js +1 -0
  60. package/components/Rating/Rating.d.ts +5 -5
  61. package/components/Rating/Rating.js +2 -2
  62. package/components/SegmentedControl/SegmentedControl.js +20 -104
  63. package/components/SegmentedControl/types.d.ts +4 -8
  64. package/components/Select/Select.d.ts +39 -0
  65. package/components/Select/Select.js +497 -0
  66. package/components/Select/SelectContext.d.ts +20 -0
  67. package/components/Select/SelectContext.js +56 -0
  68. package/components/Select/index.d.ts +3 -0
  69. package/components/Select/index.js +1 -0
  70. package/components/Select/types.d.ts +216 -0
  71. package/components/Select/types.js +11 -0
  72. package/components/Sidebar/Sidebar.js +12 -12
  73. package/components/Sidebar/types.d.ts +5 -5
  74. package/components/StepIndicator/StepIndicator.js +1 -1
  75. package/components/StepList/StepList.js +2 -2
  76. package/components/StepList/types.d.ts +4 -4
  77. package/components/Switch/Switch.d.ts +9 -0
  78. package/components/Switch/Switch.js +91 -0
  79. package/components/Switch/index.d.ts +2 -0
  80. package/components/Switch/index.js +1 -0
  81. package/components/Switch/types.d.ts +11 -0
  82. package/components/Switch/types.js +1 -0
  83. package/components/TextInput/TextInput.d.ts +8 -0
  84. package/components/TextInput/TextInput.js +25 -0
  85. package/components/TextInput/index.d.ts +2 -0
  86. package/components/TextInput/index.js +1 -0
  87. package/components/TextInput/types.d.ts +32 -0
  88. package/components/TextInput/types.js +1 -0
  89. package/components/Textarea/Textarea.d.ts +6 -0
  90. package/components/Textarea/Textarea.js +49 -0
  91. package/components/Textarea/index.d.ts +2 -0
  92. package/components/Textarea/index.js +1 -0
  93. package/components/Textarea/types.d.ts +25 -0
  94. package/components/Textarea/types.js +1 -0
  95. package/components/index.d.ts +20 -0
  96. package/components/index.js +10 -0
  97. package/icons/icons.svg +1 -0
  98. package/icons/manifest.json +8 -0
  99. package/icons/registry.d.ts +2 -0
  100. package/icons/registry.js +1 -0
  101. package/icons/system/index.d.ts +2 -0
  102. package/icons/system/index.js +11 -0
  103. package/package.json +1 -1
  104. package/styles/all.css +1 -1
  105. package/styles/all.expanded.css +1187 -96
  106. package/styles/all.expanded.unlayered.css +1187 -96
  107. package/styles/all.unlayered.css +1 -1
  108. package/styles/components/_bundle.scss +20 -0
  109. package/styles/components/input/index.scss +5 -20
  110. package/styles/index.scss +16 -0
  111. package/styles/system/_control.scss +34 -0
  112. package/styles/system/_tokens.scss +8 -0
  113. package/styles/system/index.scss +2 -1
  114. package/styles/utilities/_index.scss +50 -0
  115. package/tui-manifest.json +632 -61
  116. package/utils/compose-events.d.ts +15 -0
  117. package/utils/compose-events.js +27 -0
  118. package/utils/hash.d.ts +15 -0
  119. package/utils/hash.js +32 -0
  120. package/utils/index.d.ts +3 -0
  121. package/utils/index.js +6 -0
  122. package/utils/is-dev.d.ts +5 -0
  123. package/utils/is-dev.js +7 -0
  124. package/utils/use-controllable-state.d.ts +19 -0
  125. package/utils/use-controllable-state.js +59 -0
  126. package/utils/use-roving-group.d.ts +33 -0
  127. package/utils/use-roving-group.js +123 -0
  128. package/utils/value-key.d.ts +16 -0
  129. package/utils/value-key.js +14 -0
@@ -1,42 +1,17 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
3
3
  import { cx } from '../../utils/cx.js';
4
+ import { isDev } from '../../utils/is-dev.js';
5
+ import { toKey } from '../../utils/value-key.js';
6
+ import { useRovingGroup } from '../../utils/use-roving-group.js';
4
7
  import { SegmentedControlContext, useSegmentedControlContext } from './SegmentedControlContext.js';
5
8
  // =============================================================================
6
9
  // SegmentedControl Root
7
10
  // =============================================================================
8
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, }) {
9
12
  const [internalValue, setInternalValue] = useState(defaultValue);
10
- const [, setRegistryVersion] = useState(0);
11
- const itemsRef = useRef(new Map());
12
- const mountCounterRef = useRef(0);
13
13
  const isControlled = controlledValue !== undefined;
14
14
  const selectedValue = isControlled ? controlledValue : internalValue;
15
- // Item registration
16
- const registerItem = useCallback((record) => {
17
- const existing = itemsRef.current.get(record.value);
18
- itemsRef.current.set(record.value, {
19
- ...record,
20
- mountIndex: existing?.mountIndex ?? mountCounterRef.current++,
21
- });
22
- setRegistryVersion((v) => v + 1);
23
- }, []);
24
- const unregisterItem = useCallback((value) => {
25
- itemsRef.current.delete(value);
26
- setRegistryVersion((v) => v + 1);
27
- }, []);
28
- // Get items sorted by DOM order
29
- const getOrderedItems = useCallback(() => {
30
- const items = Array.from(itemsRef.current.values());
31
- return items.sort((a, b) => {
32
- const position = a.element.compareDocumentPosition(b.element);
33
- if (position & Node.DOCUMENT_POSITION_FOLLOWING)
34
- return -1;
35
- if (position & Node.DOCUMENT_POSITION_PRECEDING)
36
- return 1;
37
- return a.mountIndex - b.mountIndex;
38
- });
39
- }, []);
40
15
  // Selection handler
41
16
  const onSelect = useCallback((newValue) => {
42
17
  if (isControlled) {
@@ -47,82 +22,42 @@ function SegmentedControlRoot({ value: controlledValue, defaultValue, onValueCha
47
22
  onValueChange?.(newValue);
48
23
  }
49
24
  }, [isControlled, onValueChange]);
25
+ const { registerItem, unregisterItem, focusableValue, handleKeyDown, } = useRovingGroup({
26
+ selectedValue,
27
+ onSelect,
28
+ disabled,
29
+ loop,
30
+ orientation,
31
+ orientationKeyboard: true,
32
+ });
50
33
  // Dev-only: Warn if missing accessible name
51
34
  useEffect(() => {
52
35
  // Safe check for dev mode - import.meta.env may not exist in all bundler contexts
53
- const isDev = typeof import.meta !== 'undefined' && import.meta.env?.DEV;
54
- if (isDev && !ariaLabel && !ariaLabelledBy) {
36
+ if (isDev() && !ariaLabel && !ariaLabelledBy) {
55
37
  console.warn('SegmentedControl: Missing accessible name. Provide aria-label or aria-labelledby.');
56
38
  }
57
39
  }, [ariaLabel, ariaLabelledBy]);
58
- // Keyboard navigation
59
- const handleKeyDown = (event) => {
60
- const items = getOrderedItems().filter((item) => !item.disabled && !disabled);
61
- if (items.length === 0)
62
- return;
63
- let currentIndex = items.findIndex((item) => item.value === selectedValue);
64
- if (currentIndex === -1)
65
- currentIndex = 0;
66
- const prevKey = orientation === 'horizontal' ? 'ArrowLeft' : 'ArrowUp';
67
- const nextKey = orientation === 'horizontal' ? 'ArrowRight' : 'ArrowDown';
68
- let targetIndex = null;
69
- switch (event.key) {
70
- case nextKey:
71
- event.preventDefault();
72
- if (loop) {
73
- targetIndex = (currentIndex + 1) % items.length;
74
- }
75
- else {
76
- targetIndex = Math.min(currentIndex + 1, items.length - 1);
77
- }
78
- break;
79
- case prevKey:
80
- event.preventDefault();
81
- if (loop) {
82
- targetIndex = (currentIndex - 1 + items.length) % items.length;
83
- }
84
- else {
85
- targetIndex = Math.max(currentIndex - 1, 0);
86
- }
87
- break;
88
- case 'Home':
89
- event.preventDefault();
90
- targetIndex = 0;
91
- break;
92
- case 'End':
93
- event.preventDefault();
94
- targetIndex = items.length - 1;
95
- break;
96
- }
97
- if (targetIndex !== null && targetIndex !== currentIndex) {
98
- const targetItem = items[targetIndex];
99
- targetItem.element.focus();
100
- // Radiogroup: arrow keys move focus AND select
101
- onSelect(targetItem.value);
102
- }
103
- };
104
40
  const contextValue = useMemo(() => ({
105
41
  variant,
106
42
  size,
107
43
  orientation,
108
44
  loop,
109
45
  selectedValue,
46
+ focusableValue,
110
47
  rootDisabled: disabled,
111
48
  registerItem,
112
49
  unregisterItem,
113
- getOrderedItems,
114
50
  onSelect,
115
- itemsRef,
116
51
  }), [
117
52
  variant,
118
53
  size,
119
54
  orientation,
120
55
  loop,
121
56
  selectedValue,
57
+ focusableValue,
122
58
  disabled,
123
59
  registerItem,
124
60
  unregisterItem,
125
- getOrderedItems,
126
61
  onSelect,
127
62
  ]);
128
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 }) }));
@@ -131,31 +66,12 @@ function SegmentedControlRoot({ value: controlledValue, defaultValue, onValueCha
131
66
  // SegmentedControl.Item
132
67
  // =============================================================================
133
68
  function Item({ value, disabled = false, icon, 'aria-label': ariaLabel, className, children, }) {
134
- const { selectedValue, rootDisabled, registerItem, unregisterItem, onSelect, getOrderedItems, } = useSegmentedControlContext();
135
- const isSelected = selectedValue === value;
69
+ const { selectedValue, focusableValue, rootDisabled, registerItem, unregisterItem, onSelect, } = useSegmentedControlContext();
70
+ const isSelected = selectedValue !== undefined && toKey(selectedValue) === toKey(value);
136
71
  const isDisabled = rootDisabled || disabled;
137
- // Determine which item gets tabIndex={0}
138
- const getIsFocusable = () => {
139
- if (isDisabled)
140
- return false;
141
- if (isSelected)
142
- return true;
143
- // If no selection, first enabled item is focusable
144
- const items = getOrderedItems();
145
- const enabledItems = items.filter((item) => !item.disabled && !rootDisabled);
146
- if (enabledItems.length === 0)
147
- return false;
148
- if (selectedValue === undefined) {
149
- return enabledItems[0].value === value;
150
- }
151
- // If selected item is disabled, first enabled gets focus
152
- const selectedItem = items.find((item) => item.value === selectedValue);
153
- if (selectedItem?.disabled || rootDisabled) {
154
- return enabledItems[0].value === value;
155
- }
156
- return false;
157
- };
158
- const isFocusable = getIsFocusable();
72
+ const isFocusable = !isDisabled &&
73
+ focusableValue !== undefined &&
74
+ toKey(focusableValue) === toKey(value);
159
75
  // Callback ref for registration
160
76
  const callbackRef = useCallback((node) => {
161
77
  if (node) {
@@ -1,4 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
+ import type { RovingItemRecord } from '../../utils/use-roving-group';
2
3
  export type SegmentedControlValue = string | number;
3
4
  export type SegmentedControlVariant = 'pill' | 'outline' | 'underline';
4
5
  export type SegmentedControlSize = 'sm' | 'md';
@@ -42,22 +43,17 @@ export type SegmentedControlItemProps = {
42
43
  /** Label content */
43
44
  children?: ReactNode;
44
45
  };
45
- export type ItemRecord = {
46
- value: SegmentedControlValue;
47
- element: HTMLButtonElement;
48
- disabled: boolean;
49
- mountIndex: number;
50
- };
46
+ export type ItemRecord = RovingItemRecord;
51
47
  export type SegmentedControlContextValue = {
52
48
  variant: SegmentedControlVariant;
53
49
  size: SegmentedControlSize;
54
50
  orientation: SegmentedControlOrientation;
55
51
  loop: boolean;
56
52
  selectedValue: SegmentedControlValue | undefined;
53
+ /** The value of the item that should receive tabIndex={0} */
54
+ focusableValue: SegmentedControlValue | undefined;
57
55
  rootDisabled: boolean;
58
56
  registerItem: (record: Omit<ItemRecord, 'mountIndex'>) => void;
59
57
  unregisterItem: (value: SegmentedControlValue) => void;
60
- getOrderedItems: () => ItemRecord[];
61
58
  onSelect: (value: SegmentedControlValue) => void;
62
- itemsRef: React.RefObject<Map<SegmentedControlValue, ItemRecord>>;
63
59
  };
@@ -0,0 +1,39 @@
1
+ import { type SelectProps, type SelectTriggerProps, type SelectContentProps, type SelectOptionProps, type SelectGroupProps, type SelectLabelProps } from './types';
2
+ declare function SelectRoot({ id: triggerIdProp, value: controlledValue, defaultValue, onValueChange, open: controlledOpen, defaultOpen, onOpenChange, disabled, placeholder, size, side, align, sideOffset, clearable, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, children, }: SelectProps): import("react/jsx-runtime").JSX.Element;
3
+ declare namespace SelectRoot {
4
+ var displayName: string;
5
+ }
6
+ declare function SelectTriggerComponent({ asChild, className, children, }: SelectTriggerProps): import("react/jsx-runtime").JSX.Element;
7
+ declare namespace SelectTriggerComponent {
8
+ var displayName: string;
9
+ }
10
+ declare function SelectContentComponent({ className, children, }: SelectContentProps): import("react/jsx-runtime").JSX.Element;
11
+ declare namespace SelectContentComponent {
12
+ var displayName: string;
13
+ }
14
+ declare function SelectOptionComponent({ value: optionValue, disabled, textValue: explicitTextValue, className, children, }: SelectOptionProps): import("react/jsx-runtime").JSX.Element;
15
+ declare namespace SelectOptionComponent {
16
+ var displayName: string;
17
+ }
18
+ declare function SelectGroupComponent({ className, children }: SelectGroupProps): import("react/jsx-runtime").JSX.Element;
19
+ declare namespace SelectGroupComponent {
20
+ var displayName: string;
21
+ }
22
+ declare function SelectLabelComponent({ className, children }: SelectLabelProps): import("react/jsx-runtime").JSX.Element;
23
+ declare namespace SelectLabelComponent {
24
+ var displayName: string;
25
+ }
26
+ type SelectCompound = typeof SelectRoot & {
27
+ Trigger: typeof SelectTriggerComponent;
28
+ Content: typeof SelectContentComponent;
29
+ Option: typeof SelectOptionComponent;
30
+ Group: typeof SelectGroupComponent;
31
+ Label: typeof SelectLabelComponent;
32
+ };
33
+ export declare const Select: SelectCompound;
34
+ export declare const SelectTrigger: typeof SelectTriggerComponent;
35
+ export declare const SelectContent: typeof SelectContentComponent;
36
+ export declare const SelectOption: typeof SelectOptionComponent;
37
+ export declare const SelectGroup: typeof SelectGroupComponent;
38
+ export declare const SelectLabel: typeof SelectLabelComponent;
39
+ export { useSelectContext as useSelect } from './SelectContext';