@seed-design/lynx-react 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (241) hide show
  1. package/lib/components/Accordion/Accordion.jsx +2 -2
  2. package/lib/components/ActionButton/ActionButton.jsx +1 -1
  3. package/lib/components/AlertDialog/AlertDialog.d.ts +64 -0
  4. package/lib/components/AlertDialog/AlertDialog.d.ts.map +1 -0
  5. package/lib/components/AlertDialog/AlertDialog.jsx +138 -0
  6. package/lib/components/AlertDialog/AlertDialog.namespace.d.ts +2 -0
  7. package/lib/components/AlertDialog/AlertDialog.namespace.d.ts.map +1 -0
  8. package/lib/components/AlertDialog/AlertDialog.namespace.js +17 -0
  9. package/lib/components/AlertDialog/index.d.ts +3 -0
  10. package/lib/components/AlertDialog/index.d.ts.map +1 -0
  11. package/lib/components/AppBar/AppBar.d.ts +14 -1
  12. package/lib/components/AppBar/AppBar.d.ts.map +1 -1
  13. package/lib/components/AppBar/AppBar.jsx +39 -8
  14. package/lib/components/AppBar/AppBar.namespace.d.ts +1 -1
  15. package/lib/components/AppBar/AppBar.namespace.d.ts.map +1 -1
  16. package/lib/components/AppBar/index.d.ts +1 -1
  17. package/lib/components/AppBar/index.d.ts.map +1 -1
  18. package/lib/components/AttachmentDisplay/AttachmentDisplay.d.ts +182 -0
  19. package/lib/components/AttachmentDisplay/AttachmentDisplay.d.ts.map +1 -0
  20. package/lib/components/AttachmentDisplay/AttachmentDisplay.jsx +444 -0
  21. package/lib/components/AttachmentDisplay/index.d.ts +2 -0
  22. package/lib/components/AttachmentDisplay/index.d.ts.map +1 -0
  23. package/lib/components/AttachmentInput/AttachmentInput.d.ts +179 -0
  24. package/lib/components/AttachmentInput/AttachmentInput.d.ts.map +1 -0
  25. package/lib/components/AttachmentInput/AttachmentInput.jsx +520 -0
  26. package/lib/components/AttachmentInput/index.d.ts +2 -0
  27. package/lib/components/AttachmentInput/index.d.ts.map +1 -0
  28. package/lib/components/BottomSheet/BottomSheet.d.ts +1 -1
  29. package/lib/components/BottomSheet/BottomSheet.d.ts.map +1 -1
  30. package/lib/components/BottomSheet/BottomSheet.jsx +1 -7
  31. package/lib/components/Callout/Callout.jsx +2 -2
  32. package/lib/components/Checkbox/Checkbox.jsx +2 -2
  33. package/lib/components/Chip/Chip.jsx +2 -2
  34. package/lib/components/ChipTabs/ChipTabs.d.ts +41 -0
  35. package/lib/components/ChipTabs/ChipTabs.d.ts.map +1 -0
  36. package/lib/components/ChipTabs/ChipTabs.jsx +39 -0
  37. package/lib/components/ChipTabs/ChipTabs.namespace.d.ts +2 -0
  38. package/lib/components/ChipTabs/ChipTabs.namespace.d.ts.map +1 -0
  39. package/lib/components/ChipTabs/ChipTabs.namespace.js +13 -0
  40. package/lib/components/ChipTabs/index.d.ts +3 -0
  41. package/lib/components/ChipTabs/index.d.ts.map +1 -0
  42. package/lib/components/ContextualFloatingButton/ContextualFloatingButton.d.ts +18 -0
  43. package/lib/components/ContextualFloatingButton/ContextualFloatingButton.d.ts.map +1 -0
  44. package/lib/components/ContextualFloatingButton/ContextualFloatingButton.jsx +113 -0
  45. package/lib/components/ContextualFloatingButton/index.d.ts +2 -0
  46. package/lib/components/ContextualFloatingButton/index.d.ts.map +1 -0
  47. package/lib/components/Dialog/Dialog.d.ts +87 -0
  48. package/lib/components/Dialog/Dialog.d.ts.map +1 -0
  49. package/lib/components/Dialog/Dialog.jsx +147 -0
  50. package/lib/components/Dialog/Dialog.namespace.d.ts +2 -0
  51. package/lib/components/Dialog/Dialog.namespace.d.ts.map +1 -0
  52. package/lib/components/Dialog/Dialog.namespace.js +18 -0
  53. package/lib/components/Dialog/index.d.ts +3 -0
  54. package/lib/components/Dialog/index.d.ts.map +1 -0
  55. package/lib/components/FloatingActionButton/FloatingActionButton.d.ts +23 -0
  56. package/lib/components/FloatingActionButton/FloatingActionButton.d.ts.map +1 -0
  57. package/lib/components/FloatingActionButton/FloatingActionButton.jsx +76 -0
  58. package/lib/components/FloatingActionButton/FloatingActionButton.namespace.d.ts +2 -0
  59. package/lib/components/FloatingActionButton/FloatingActionButton.namespace.d.ts.map +1 -0
  60. package/lib/components/FloatingActionButton/FloatingActionButton.namespace.js +10 -0
  61. package/lib/components/FloatingActionButton/index.d.ts +3 -0
  62. package/lib/components/FloatingActionButton/index.d.ts.map +1 -0
  63. package/lib/components/HelpBubble/HelpBubble.d.ts +67 -0
  64. package/lib/components/HelpBubble/HelpBubble.d.ts.map +1 -0
  65. package/lib/components/HelpBubble/HelpBubble.jsx +600 -0
  66. package/lib/components/HelpBubble/HelpBubble.namespace.d.ts +2 -0
  67. package/lib/components/HelpBubble/HelpBubble.namespace.d.ts.map +1 -0
  68. package/lib/components/HelpBubble/HelpBubble.namespace.js +18 -0
  69. package/lib/components/HelpBubble/index.d.ts +3 -0
  70. package/lib/components/HelpBubble/index.d.ts.map +1 -0
  71. package/lib/components/Icon/Icon.d.ts +1 -0
  72. package/lib/components/Icon/Icon.d.ts.map +1 -1
  73. package/lib/components/Icon/Icon.jsx +2 -1
  74. package/lib/components/IdentityPlaceholder/IdentityPlaceholder.d.ts +28 -0
  75. package/lib/components/IdentityPlaceholder/IdentityPlaceholder.d.ts.map +1 -0
  76. package/lib/components/IdentityPlaceholder/IdentityPlaceholder.jsx +37 -0
  77. package/lib/components/IdentityPlaceholder/IdentityPlaceholder.namespace.d.ts +2 -0
  78. package/lib/components/IdentityPlaceholder/IdentityPlaceholder.namespace.d.ts.map +1 -0
  79. package/lib/components/IdentityPlaceholder/IdentityPlaceholder.namespace.js +9 -0
  80. package/lib/components/IdentityPlaceholder/identity-placeholder-business.js +4 -0
  81. package/lib/components/IdentityPlaceholder/identity-placeholder-person.js +4 -0
  82. package/lib/components/IdentityPlaceholder/index.d.ts +3 -0
  83. package/lib/components/IdentityPlaceholder/index.d.ts.map +1 -0
  84. package/lib/components/InputButton/InputButton.d.ts +2 -0
  85. package/lib/components/InputButton/InputButton.d.ts.map +1 -1
  86. package/lib/components/InputButton/InputButton.jsx +40 -8
  87. package/lib/components/List/List.d.ts +1 -1
  88. package/lib/components/List/List.d.ts.map +1 -1
  89. package/lib/components/List/List.jsx +26 -12
  90. package/lib/components/Menu/Menu.d.ts.map +1 -1
  91. package/lib/components/Menu/Menu.jsx +15 -5
  92. package/lib/components/PageBanner/PageBanner.d.ts +3 -0
  93. package/lib/components/PageBanner/PageBanner.d.ts.map +1 -1
  94. package/lib/components/PageBanner/PageBanner.jsx +49 -11
  95. package/lib/components/QuantityPicker/QuantityPicker.d.ts +53 -0
  96. package/lib/components/QuantityPicker/QuantityPicker.d.ts.map +1 -0
  97. package/lib/components/QuantityPicker/QuantityPicker.jsx +282 -0
  98. package/lib/components/QuantityPicker/QuantityPicker.namespace.d.ts +2 -0
  99. package/lib/components/QuantityPicker/QuantityPicker.namespace.d.ts.map +1 -0
  100. package/lib/components/QuantityPicker/QuantityPicker.namespace.js +11 -0
  101. package/lib/components/QuantityPicker/index.d.ts +3 -0
  102. package/lib/components/QuantityPicker/index.d.ts.map +1 -0
  103. package/lib/components/RadioGroup/RadioGroup.jsx +2 -2
  104. package/lib/components/ReactionButton/ReactionButton.d.ts +1 -1
  105. package/lib/components/ReactionButton/ReactionButton.d.ts.map +1 -1
  106. package/lib/components/ReactionButton/ReactionButton.jsx +22 -13
  107. package/lib/components/ScrollFog/ScrollFog.d.ts +41 -0
  108. package/lib/components/ScrollFog/ScrollFog.d.ts.map +1 -0
  109. package/lib/components/ScrollFog/ScrollFog.jsx +65 -0
  110. package/lib/components/ScrollFog/index.d.ts +2 -0
  111. package/lib/components/ScrollFog/index.d.ts.map +1 -0
  112. package/lib/components/SegmentedControl/SegmentedControl.jsx +2 -2
  113. package/lib/components/Select/Select.d.ts +112 -0
  114. package/lib/components/Select/Select.d.ts.map +1 -0
  115. package/lib/components/Select/Select.jsx +885 -0
  116. package/lib/components/Select/Select.namespace.d.ts +2 -0
  117. package/lib/components/Select/Select.namespace.d.ts.map +1 -0
  118. package/lib/components/Select/Select.namespace.js +23 -0
  119. package/lib/components/Select/index.d.ts +3 -0
  120. package/lib/components/Select/index.d.ts.map +1 -0
  121. package/lib/components/Skeleton/Skeleton.d.ts +15 -0
  122. package/lib/components/Skeleton/Skeleton.d.ts.map +1 -0
  123. package/lib/components/Skeleton/Skeleton.jsx +18 -0
  124. package/lib/components/Skeleton/index.d.ts +2 -0
  125. package/lib/components/Skeleton/index.d.ts.map +1 -0
  126. package/lib/components/Slider/Slider.d.ts +101 -0
  127. package/lib/components/Slider/Slider.d.ts.map +1 -0
  128. package/lib/components/Slider/Slider.jsx +573 -0
  129. package/lib/components/Slider/Slider.namespace.d.ts +2 -0
  130. package/lib/components/Slider/Slider.namespace.d.ts.map +1 -0
  131. package/lib/components/Slider/Slider.namespace.js +19 -0
  132. package/lib/components/Slider/Slider.utils.d.ts +19 -0
  133. package/lib/components/Slider/Slider.utils.d.ts.map +1 -0
  134. package/lib/components/Slider/Slider.utils.js +106 -0
  135. package/lib/components/Slider/index.d.ts +3 -0
  136. package/lib/components/Slider/index.d.ts.map +1 -0
  137. package/lib/components/SwipeableMenuSheet/SwipeableMenuSheet.d.ts.map +1 -1
  138. package/lib/components/SwipeableMenuSheet/SwipeableMenuSheet.jsx +22 -8
  139. package/lib/components/Switch/Switch.d.ts.map +1 -1
  140. package/lib/components/Switch/Switch.jsx +5 -3
  141. package/lib/components/Tabs/Tabs.d.ts +34 -2
  142. package/lib/components/Tabs/Tabs.d.ts.map +1 -1
  143. package/lib/components/Tabs/Tabs.jsx +186 -56
  144. package/lib/components/Tabs/Tabs.utils.d.ts +14 -1
  145. package/lib/components/Tabs/Tabs.utils.d.ts.map +1 -1
  146. package/lib/components/Tabs/Tabs.utils.js +24 -3
  147. package/lib/components/Tabs/index.d.ts +1 -1
  148. package/lib/components/Tabs/index.d.ts.map +1 -1
  149. package/lib/components/index.d.ts +14 -0
  150. package/lib/components/index.d.ts.map +1 -1
  151. package/lib/hooks/useControllableState.d.ts +1 -14
  152. package/lib/hooks/useControllableState.d.ts.map +1 -1
  153. package/lib/hooks/usePressTap.d.ts +1 -27
  154. package/lib/hooks/usePressTap.d.ts.map +1 -1
  155. package/lib/index.js +29 -6
  156. package/lib/{hooks/useControllableState.js → packages/lynx-react-headless/use-controllable-state/lib/index.js} +2 -3
  157. package/lib/{hooks/usePressTap.js → packages/lynx-react-headless/use-press-tap/lib/index.js} +13 -10
  158. package/lib/types.d.ts +1 -0
  159. package/lib/types.d.ts.map +1 -1
  160. package/package.json +9 -5
  161. package/src/components/AlertDialog/AlertDialog.namespace.ts +22 -0
  162. package/src/components/AlertDialog/AlertDialog.test.tsx +226 -0
  163. package/src/components/AlertDialog/AlertDialog.tsx +374 -0
  164. package/src/components/AlertDialog/index.ts +24 -0
  165. package/src/components/AppBar/AppBar.namespace.ts +1 -0
  166. package/src/components/AppBar/AppBar.test.tsx +231 -0
  167. package/src/components/AppBar/AppBar.tsx +105 -13
  168. package/src/components/AppBar/index.ts +1 -0
  169. package/src/components/AttachmentDisplay/AttachmentDisplay.tsx +886 -0
  170. package/src/components/AttachmentDisplay/index.ts +48 -0
  171. package/src/components/AttachmentInput/AttachmentInput.tsx +1001 -0
  172. package/src/components/AttachmentInput/index.ts +49 -0
  173. package/src/components/BottomSheet/BottomSheet.test.tsx +22 -7
  174. package/src/components/BottomSheet/BottomSheet.tsx +1 -19
  175. package/src/components/ChipTabs/ChipTabs.namespace.ts +14 -0
  176. package/src/components/ChipTabs/ChipTabs.tsx +119 -0
  177. package/src/components/ChipTabs/index.ts +16 -0
  178. package/src/components/ContextualFloatingButton/ContextualFloatingButton.test.tsx +63 -0
  179. package/src/components/ContextualFloatingButton/ContextualFloatingButton.tsx +232 -0
  180. package/src/components/ContextualFloatingButton/index.ts +4 -0
  181. package/src/components/Dialog/Dialog.namespace.ts +24 -0
  182. package/src/components/Dialog/Dialog.test.tsx +172 -0
  183. package/src/components/Dialog/Dialog.tsx +373 -0
  184. package/src/components/Dialog/index.ts +26 -0
  185. package/src/components/FloatingActionButton/FloatingActionButton.namespace.ts +8 -0
  186. package/src/components/FloatingActionButton/FloatingActionButton.test.tsx +59 -0
  187. package/src/components/FloatingActionButton/FloatingActionButton.tsx +207 -0
  188. package/src/components/FloatingActionButton/index.ts +10 -0
  189. package/src/components/HelpBubble/HelpBubble.namespace.ts +24 -0
  190. package/src/components/HelpBubble/HelpBubble.tsx +998 -0
  191. package/src/components/HelpBubble/index.ts +26 -0
  192. package/src/components/Icon/Icon.test.tsx +0 -3
  193. package/src/components/Icon/Icon.tsx +11 -1
  194. package/src/components/IdentityPlaceholder/IdentityPlaceholder.namespace.ts +6 -0
  195. package/src/components/IdentityPlaceholder/IdentityPlaceholder.tsx +103 -0
  196. package/src/components/IdentityPlaceholder/assets.d.ts +4 -0
  197. package/src/components/IdentityPlaceholder/identity-placeholder-business.webp +0 -0
  198. package/src/components/IdentityPlaceholder/identity-placeholder-person.webp +0 -0
  199. package/src/components/IdentityPlaceholder/index.ts +8 -0
  200. package/src/components/InputButton/InputButton.test.tsx +128 -1
  201. package/src/components/InputButton/InputButton.tsx +60 -9
  202. package/src/components/List/List.test.tsx +54 -2
  203. package/src/components/List/List.tsx +43 -10
  204. package/src/components/Menu/Menu.tsx +14 -4
  205. package/src/components/Menu/__tests__/Menu.test.tsx +34 -0
  206. package/src/components/PageBanner/PageBanner.test.tsx +110 -3
  207. package/src/components/PageBanner/PageBanner.tsx +83 -19
  208. package/src/components/QuantityPicker/QuantityPicker.namespace.ts +10 -0
  209. package/src/components/QuantityPicker/QuantityPicker.test.tsx +235 -0
  210. package/src/components/QuantityPicker/QuantityPicker.tsx +605 -0
  211. package/src/components/QuantityPicker/index.ts +14 -0
  212. package/src/components/ReactionButton/ReactionButton.feedback.test.tsx +50 -0
  213. package/src/components/ReactionButton/ReactionButton.test.tsx +5 -3
  214. package/src/components/ReactionButton/ReactionButton.tsx +20 -12
  215. package/src/components/ScrollFog/ScrollFog.test.tsx +222 -0
  216. package/src/components/ScrollFog/ScrollFog.tsx +172 -0
  217. package/src/components/ScrollFog/index.ts +1 -0
  218. package/src/components/Select/Select.namespace.ts +34 -0
  219. package/src/components/Select/Select.test.tsx +297 -0
  220. package/src/components/Select/Select.tsx +1483 -0
  221. package/src/components/Select/index.ts +39 -0
  222. package/src/components/Skeleton/Skeleton.test.tsx +64 -0
  223. package/src/components/Skeleton/Skeleton.tsx +45 -0
  224. package/src/components/Skeleton/index.ts +1 -0
  225. package/src/components/Slider/Slider.namespace.ts +26 -0
  226. package/src/components/Slider/Slider.test.tsx +260 -0
  227. package/src/components/Slider/Slider.tsx +897 -0
  228. package/src/components/Slider/Slider.utils.ts +167 -0
  229. package/src/components/Slider/index.ts +30 -0
  230. package/src/components/SwipeableMenuSheet/SwipeableMenuSheet.test.tsx +21 -1
  231. package/src/components/SwipeableMenuSheet/SwipeableMenuSheet.tsx +30 -7
  232. package/src/components/Switch/Switch.tsx +14 -13
  233. package/src/components/Tabs/Tabs.test.tsx +218 -1
  234. package/src/components/Tabs/Tabs.tsx +346 -75
  235. package/src/components/Tabs/Tabs.utils.ts +51 -1
  236. package/src/components/Tabs/index.ts +17 -1
  237. package/src/components/index.ts +14 -0
  238. package/src/hooks/useControllableState.ts +1 -43
  239. package/src/hooks/usePressTap.test.ts +23 -11
  240. package/src/hooks/usePressTap.ts +1 -60
  241. package/src/types.ts +1 -0
@@ -0,0 +1,167 @@
1
+ export interface SliderGeometry {
2
+ left: number;
3
+ width: number;
4
+ }
5
+
6
+ export function clamp(value: number, min: number, max: number): number {
7
+ return Math.min(max, Math.max(min, value));
8
+ }
9
+
10
+ export function percentageForValue(value: number, min: number, max: number): number {
11
+ if (max === min) return 0;
12
+ return clamp(((value - min) / (max - min)) * 100, 0, 100);
13
+ }
14
+
15
+ export function valueForPercentage(
16
+ percent: number,
17
+ min: number,
18
+ max: number,
19
+ dir: "ltr" | "rtl",
20
+ ): number {
21
+ const ratio = clamp(percent, 0, 100) / 100;
22
+ return dir === "rtl" ? max - ratio * (max - min) : min + ratio * (max - min);
23
+ }
24
+
25
+ export function decimalCount(value: number): number {
26
+ const text = String(value);
27
+ const exponent = text.toLowerCase().split("e");
28
+ const fraction = exponent[0].split(".")[1]?.length ?? 0;
29
+ return fraction + (exponent[1] ? Math.max(0, -Number(exponent[1])) : 0);
30
+ }
31
+
32
+ export function roundValue(value: number, decimals: number): number {
33
+ const factor = 10 ** decimals;
34
+ return Math.round(value * factor) / factor;
35
+ }
36
+
37
+ export function closestAllowedValue(value: number, allowedValues: readonly number[]): number {
38
+ if (allowedValues.length === 0) return value;
39
+ let closest = allowedValues[0];
40
+ let distance = Math.abs(value - closest);
41
+ for (let index = 1; index < allowedValues.length; index += 1) {
42
+ const candidate = allowedValues[index];
43
+ const candidateDistance = Math.abs(value - candidate);
44
+ if (candidateDistance < distance) {
45
+ closest = candidate;
46
+ distance = candidateDistance;
47
+ }
48
+ }
49
+ return closest;
50
+ }
51
+
52
+ export function normalizeValue(
53
+ value: number,
54
+ min: number,
55
+ max: number,
56
+ step: number,
57
+ allowedValues?: readonly number[],
58
+ ): number {
59
+ const safeMin = Math.min(min, max);
60
+ const safeMax = Math.max(min, max);
61
+ const candidate = Number.isFinite(value) ? value : safeMin;
62
+ if (allowedValues && allowedValues.length > 0) {
63
+ return closestAllowedValue(candidate, allowedValues);
64
+ }
65
+ const safeStep = Number.isFinite(step) && step > 0 ? step : 1;
66
+ const decimals = Math.max(decimalCount(safeStep), decimalCount(safeMin));
67
+ return clamp(
68
+ roundValue(Math.round((candidate - safeMin) / safeStep) * safeStep + safeMin, decimals),
69
+ safeMin,
70
+ safeMax,
71
+ );
72
+ }
73
+
74
+ export function normalizeValues(
75
+ values: readonly number[] | undefined,
76
+ min: number,
77
+ max: number,
78
+ step: number,
79
+ allowedValues?: readonly number[],
80
+ ): number[] {
81
+ const source = values && values.length > 0 ? values : [(min + max) / 2];
82
+ return source
83
+ .map((value) => normalizeValue(value, min, max, step, allowedValues))
84
+ .sort((a, b) => a - b);
85
+ }
86
+
87
+ export function nearestValueIndex(values: readonly number[], value: number): number {
88
+ if (values.length < 2) return 0;
89
+ let index = 0;
90
+ let distance = Math.abs(values[0] - value);
91
+ for (let candidate = 1; candidate < values.length; candidate += 1) {
92
+ const candidateDistance = Math.abs(values[candidate] - value);
93
+ if (candidateDistance < distance) {
94
+ index = candidate;
95
+ distance = candidateDistance;
96
+ }
97
+ }
98
+ return index;
99
+ }
100
+
101
+ export function hasMinimumSteps(
102
+ values: readonly number[],
103
+ minimumSteps: number,
104
+ step: number,
105
+ ): boolean {
106
+ if (minimumSteps <= 0 || values.length < 2) return true;
107
+ const safeStep = Number.isFinite(step) && step > 0 ? step : 1;
108
+ const decimals = decimalCount(safeStep);
109
+ const required = roundValue(minimumSteps * safeStep, decimals);
110
+ for (let index = 1; index < values.length; index += 1) {
111
+ const difference = roundValue(values[index] - values[index - 1], decimals);
112
+ if (difference < required) return false;
113
+ }
114
+ return true;
115
+ }
116
+
117
+ export function getThumbInBoundsOffset(width: number, percent: number, direction: 1 | -1): number {
118
+ if (!width) return 0;
119
+ const halfWidth = width / 2;
120
+ const scaledOffset = (clamp(percent, 0, 100) / 50) * halfWidth;
121
+ return (halfWidth - scaledOffset * direction) * direction;
122
+ }
123
+
124
+ export function getStickyLabelOffset(
125
+ labelWidth: number,
126
+ percent: number,
127
+ thumbWidth: number,
128
+ trackWidth: number,
129
+ direction: 1 | -1,
130
+ ): number {
131
+ if (!labelWidth || !trackWidth) return 0;
132
+ const thumbOffset = getThumbInBoundsOffset(thumbWidth, percent, direction);
133
+ const naturalCenter = (percent / 100) * trackWidth + thumbOffset;
134
+ const halfLabel = labelWidth / 2;
135
+ const center = clamp(naturalCenter, halfLabel, trackWidth - halfLabel);
136
+ return (center - (percent / 100) * trackWidth) * direction;
137
+ }
138
+
139
+ export function eventPageX(event: unknown): number {
140
+ const target = event as {
141
+ touches?: Array<{ pageX?: unknown; clientX?: unknown }>;
142
+ changedTouches?: Array<{ pageX?: unknown; clientX?: unknown }>;
143
+ pageX?: unknown;
144
+ clientX?: unknown;
145
+ detail?: { x?: unknown };
146
+ } | null;
147
+ const touch = target?.touches?.[0] ?? target?.changedTouches?.[0];
148
+ const candidates = [
149
+ touch?.pageX,
150
+ touch?.clientX,
151
+ target?.pageX,
152
+ target?.clientX,
153
+ target?.detail?.x,
154
+ ];
155
+ for (const candidate of candidates) {
156
+ const number = Number(candidate);
157
+ if (Number.isFinite(number)) return number;
158
+ }
159
+ return Number.NaN;
160
+ }
161
+
162
+ export function geometryFromRect(value: unknown): SliderGeometry | null {
163
+ const rect = value as { left?: unknown; width?: unknown } | null | undefined;
164
+ const left = Number(rect?.left);
165
+ const width = Number(rect?.width);
166
+ return Number.isFinite(left) && Number.isFinite(width) && width > 0 ? { left, width } : null;
167
+ }
@@ -0,0 +1,30 @@
1
+ export {
2
+ SliderRoot,
3
+ SliderControl,
4
+ SliderTrack,
5
+ SliderRange,
6
+ SliderThumb,
7
+ SliderTick,
8
+ SliderMarkers,
9
+ SliderMarker,
10
+ SliderValueIndicatorRoot,
11
+ SliderValueIndicatorArrow,
12
+ SliderValueIndicatorArrowTip,
13
+ SliderValueIndicatorLabel,
14
+ type SliderClassNames,
15
+ type SliderValues,
16
+ type SliderRootProps,
17
+ type SliderControlProps,
18
+ type SliderTrackProps,
19
+ type SliderRangeProps,
20
+ type SliderThumbProps,
21
+ type SliderTickProps,
22
+ type SliderMarkersProps,
23
+ type SliderMarkerProps,
24
+ type SliderValueIndicatorRootProps,
25
+ type SliderValueIndicatorArrowProps,
26
+ type SliderValueIndicatorArrowTipProps,
27
+ type SliderValueIndicatorLabelProps,
28
+ } from "./Slider";
29
+
30
+ export * as Slider from "./Slider.namespace";
@@ -223,7 +223,9 @@ describe("SwipeableMenuSheet", () => {
223
223
  (getByText("Second item") as HTMLElement).parentElement?.parentElement?.className,
224
224
  ).toContain("labelAlign_center");
225
225
 
226
- const item = (getByText("First item") as HTMLElement).parentElement?.parentElement;
226
+ const item = (getByText("First item") as HTMLElement).closest<HTMLElement>(
227
+ ".seed-menu-sheet-item__root",
228
+ );
227
229
  if (!item) throw new Error("Expected menu sheet item.");
228
230
  fireEvent.tap(item);
229
231
 
@@ -255,4 +257,22 @@ describe("SwipeableMenuSheet", () => {
255
257
  exitAnimation: { type: "tween", duration: 0 },
256
258
  });
257
259
  });
260
+ it("keeps item backgrounds outside the scale target and scales the whole close button", () => {
261
+ const { getByText } = render(
262
+ <SwipeableMenuSheet.Root>
263
+ <SwipeableMenuSheet.Item>
264
+ <SwipeableMenuSheet.ItemLabel>Choice</SwipeableMenuSheet.ItemLabel>
265
+ </SwipeableMenuSheet.Item>
266
+ <SwipeableMenuSheet.CloseButton>Close feedback</SwipeableMenuSheet.CloseButton>
267
+ </SwipeableMenuSheet.Root>,
268
+ );
269
+ const target = (getByText("Choice") as HTMLElement).parentElement!;
270
+ expect(target.classList.contains("seed-menu-sheet-item__scaleContent")).toBe(true);
271
+ expect(target.getAttribute("flatten")).toBe("false");
272
+ expect(target.parentElement!.classList.contains("seed-menu-sheet-item__root")).toBe(true);
273
+ expect(target.parentElement!.hasAttribute("flatten")).toBe(false);
274
+ const close = (getByText("Close feedback") as HTMLElement).parentElement!;
275
+ expect(close.classList.contains("seed-menu-sheet__closeButton")).toBe(true);
276
+ expect(close.getAttribute("flatten")).toBe("false");
277
+ });
258
278
  });
@@ -7,6 +7,8 @@ import { forwardRef, isValidElement } from "@lynx-js/react";
7
7
  import type { ForwardRefExoticComponent, PropsWithoutRef, RefAttributes } from "@lynx-js/react";
8
8
  import clsx from "clsx";
9
9
 
10
+ import { useScaleFeedback } from "../../hooks/useScaleFeedback";
11
+ import { mergeProps } from "../../utils/merge-props";
10
12
  import { usePressTap } from "../../hooks/usePressTap";
11
13
  import { useSafeArea } from "../../hooks/useSafeArea";
12
14
  import type {
@@ -54,6 +56,7 @@ interface SwipeableMenuSheetClassNames {
54
56
 
55
57
  interface SwipeableMenuSheetItemClassNames {
56
58
  root: string;
59
+ scaleContent: string;
57
60
  content: string;
58
61
  label: string;
59
62
  description: string;
@@ -457,6 +460,7 @@ export const SwipeableMenuSheetCloseButton: LynxForwardRefComponent<
457
460
  "accessibility-actions": accessibilityActions,
458
461
  "accessibility-exclusive-focus": accessibilityExclusiveFocus,
459
462
  "ios-platform-accessibility-id": iosPlatformAccessibilityId,
463
+ ...nativeProps
460
464
  } = props;
461
465
  const { rootRef, setPendingReason, skipAnimation } = useSwipeableMenuSheetContext();
462
466
  const handleTap = React.useCallback<NonNullable<LynxPressableProps["bindtap"]>>(
@@ -467,16 +471,26 @@ export const SwipeableMenuSheetCloseButton: LynxForwardRefComponent<
467
471
  },
468
472
  [rootRef, setPendingReason, skipAnimation, userBindtap],
469
473
  );
470
- const { pressed, ...pressHandlers } = usePressTap({
474
+ const { pressed, bindtouchstart, bindtouchend, bindtouchcancel, ...pressHandlers } = usePressTap({
471
475
  onTap: handleTap,
472
476
  mainThreadOnTap: userMainThreadBindtap,
473
477
  });
478
+ const { scaleFeedbackTriggerProps, scaleFeedbackTargetProps } = useScaleFeedback({
479
+ onTouchStart: bindtouchstart,
480
+ onTouchEnd: bindtouchend,
481
+ onTouchCancel: bindtouchcancel,
482
+ });
474
483
  const classNames = menuSheet({ skipAnimation, closeButtonPressed: pressed });
475
484
 
476
485
  return (
477
486
  <view
478
487
  {...(ref ? ({ ref: ref as LynxViewRef } as Record<string, unknown>) : {})}
479
- {...pressHandlers}
488
+ {...mergeProps(
489
+ scaleFeedbackTriggerProps,
490
+ scaleFeedbackTargetProps,
491
+ pressHandlers,
492
+ nativeProps,
493
+ )}
480
494
  className={clsx(classNames.closeButton, className)}
481
495
  style={style as never}
482
496
  accessibility-element={accessibilityElement}
@@ -541,14 +555,21 @@ export const SwipeableMenuSheetItem: LynxForwardRefComponent<unknown, SwipeableM
541
555
  "accessibility-actions": accessibilityActions,
542
556
  "accessibility-exclusive-focus": accessibilityExclusiveFocus,
543
557
  "ios-platform-accessibility-id": iosPlatformAccessibilityId,
558
+ ...nativeProps
544
559
  } = props;
545
560
  const groupLabelAlign = React.useContext(GroupLabelAlignContext);
546
561
  const contentLabelAlign = React.useContext(ContentLabelAlignContext);
547
562
  const { isLast } = React.useContext(SwipeableMenuSheetItemPositionContext);
548
563
  const resolvedLabelAlign = labelAlign ?? groupLabelAlign ?? contentLabelAlign ?? "left";
549
- const { pressed, ...pressHandlers } = usePressTap({
550
- onTap: userBindtap,
551
- mainThreadOnTap: userMainThreadBindtap,
564
+ const { pressed, bindtouchstart, bindtouchend, bindtouchcancel, ...pressHandlers } =
565
+ usePressTap({
566
+ onTap: userBindtap,
567
+ mainThreadOnTap: userMainThreadBindtap,
568
+ });
569
+ const { scaleFeedbackTriggerProps, scaleFeedbackTargetProps } = useScaleFeedback({
570
+ onTouchStart: bindtouchstart,
571
+ onTouchEnd: bindtouchend,
572
+ onTouchCancel: bindtouchcancel,
552
573
  });
553
574
  const classNames = menuSheetItem({ tone, labelAlign: resolvedLabelAlign, pressed });
554
575
  const itemContext = React.useMemo<SwipeableMenuSheetItemContextValue>(
@@ -566,7 +587,7 @@ export const SwipeableMenuSheetItem: LynxForwardRefComponent<unknown, SwipeableM
566
587
  >
567
588
  <view
568
589
  {...(ref ? ({ ref: ref as LynxViewRef } as Record<string, unknown>) : {})}
569
- {...pressHandlers}
590
+ {...mergeProps(scaleFeedbackTriggerProps, pressHandlers, nativeProps)}
570
591
  className={clsx(classNames.root, className)}
571
592
  style={style as never}
572
593
  accessibility-element={accessibilityElement}
@@ -580,7 +601,9 @@ export const SwipeableMenuSheetItem: LynxForwardRefComponent<unknown, SwipeableM
580
601
  accessibility-exclusive-focus={accessibilityExclusiveFocus}
581
602
  ios-platform-accessibility-id={iosPlatformAccessibilityId}
582
603
  >
583
- {children}
604
+ <view className={classNames.scaleContent} {...scaleFeedbackTargetProps}>
605
+ {children}
606
+ </view>
584
607
  </view>
585
608
  {!isLast ? (
586
609
  <view className={classNames.divider} accessibility-elements-hidden={true} />
@@ -15,11 +15,9 @@ import type {
15
15
  LynxTextRef,
16
16
  LynxViewRef,
17
17
  } from "../../types";
18
- import {
19
- useScaleFeedback,
20
- type ScaleFeedbackTargetProps,
21
- } from "../../hooks/useScaleFeedback";
18
+ import { useScaleFeedback, type ScaleFeedbackTargetProps } from "../../hooks/useScaleFeedback";
22
19
  import { mergeProps } from "../../utils/merge-props";
20
+ import { ScaleFeedbackContentContext } from "../../contexts";
23
21
 
24
22
  /**
25
23
  * @platform Lynx
@@ -110,13 +108,7 @@ export const SwitchRoot = React.forwardRef<unknown, SwitchRootProps>((props, ref
110
108
 
111
109
  const toggle = React.useCallback(() => setChecked(!checked), [checked, setChecked]);
112
110
 
113
- const {
114
- pressed,
115
- bindtouchstart,
116
- bindtouchend,
117
- bindtouchcancel,
118
- ...pressHandlers
119
- } = usePressTap({
111
+ const { pressed, bindtouchstart, bindtouchend, bindtouchcancel, ...pressHandlers } = usePressTap({
120
112
  disabled,
121
113
  onTap: toggle,
122
114
  });
@@ -139,7 +131,15 @@ export const SwitchRoot = React.forwardRef<unknown, SwitchRootProps>((props, ref
139
131
  toggle,
140
132
  scaleFeedbackTargetProps,
141
133
  }),
142
- [checked, disabled, pressed, switchVariantProps, switchmarkVariantProps, toggle, scaleFeedbackTargetProps],
134
+ [
135
+ checked,
136
+ disabled,
137
+ pressed,
138
+ switchVariantProps,
139
+ switchmarkVariantProps,
140
+ toggle,
141
+ scaleFeedbackTargetProps,
142
+ ],
143
143
  );
144
144
 
145
145
  return (
@@ -175,6 +175,7 @@ export const SwitchControl = React.forwardRef<unknown, SwitchControlProps>((prop
175
175
  const [variantProps, restProps] = switchmark.splitVariantProps(props);
176
176
  const { children, className, ...nativeProps } = restProps;
177
177
  const context = useSwitchContext("SwitchControl");
178
+ const hasScaledContent = React.useContext(ScaleFeedbackContentContext);
178
179
  const switchmarkVariantProps: SwitchmarkVariantProps = {
179
180
  ...context.switchmarkVariantProps,
180
181
  ...variantProps,
@@ -190,7 +191,7 @@ export const SwitchControl = React.forwardRef<unknown, SwitchControlProps>((prop
190
191
  <view
191
192
  {...mergeProps(
192
193
  ref ? { ref: ref as LynxViewRef } : {},
193
- context.scaleFeedbackTargetProps,
194
+ !hasScaledContent ? context.scaleFeedbackTargetProps : {},
194
195
  nativeProps,
195
196
  )}
196
197
  className={clsx(classes.root, className)}
@@ -1,15 +1,29 @@
1
1
  import "@testing-library/jest-dom";
2
- import { fireEvent, render } from "@lynx-js/react/testing-library";
2
+ import * as React from "@lynx-js/react";
3
+ import { createEvent, fireEvent, render } from "@lynx-js/react/testing-library";
3
4
  import { describe, expect, it, vi } from "vitest";
4
5
 
5
6
  import * as Tabs from "./Tabs.namespace";
7
+ import * as ChipTabs from "../ChipTabs/ChipTabs.namespace";
6
8
  import {
7
9
  areTabsTransitionsEnabled,
8
10
  getTabsLayoutWidth,
9
11
  getTabsOrderedItems,
12
+ getTabsScrollOffset,
10
13
  getTabsTriggerRects,
11
14
  } from "./Tabs.utils";
12
15
 
16
+ function fireViewPagerEvent(
17
+ pager: Element,
18
+ eventName: "change" | "willchange",
19
+ detail: { index: number; isDragged: boolean },
20
+ ) {
21
+ const init = { eventType: "bindEvent", eventName, detail };
22
+ const event = createEvent(`bindEvent:${eventName}`, pager, init);
23
+ Object.assign(event, init);
24
+ fireEvent(pager, event);
25
+ }
26
+
13
27
  function BasicTabs(props: {
14
28
  value?: string;
15
29
  defaultValue?: string;
@@ -46,6 +60,22 @@ describe("Tabs", () => {
46
60
  expect(listContent?.querySelectorAll(".seed-tabs__trigger")).toHaveLength(2);
47
61
  });
48
62
 
63
+ it("passes native fading edges to the ChipTabs scroll-view", () => {
64
+ const { container } = render(
65
+ <ChipTabs.Root defaultValue="one">
66
+ <ChipTabs.List fading-edge-length="20px">
67
+ <ChipTabs.Trigger value="one">첫 번째</ChipTabs.Trigger>
68
+ <ChipTabs.Trigger value="two">두 번째</ChipTabs.Trigger>
69
+ </ChipTabs.List>
70
+ </ChipTabs.Root>,
71
+ );
72
+
73
+ expect(container.querySelector("scroll-view")).toHaveAttribute(
74
+ "fading-edge-length",
75
+ "20px",
76
+ );
77
+ });
78
+
49
79
  it("disables label and indicator transitions during the initial render", () => {
50
80
  const { container } = render(<BasicTabs defaultValue="two" />);
51
81
  const labels = container.querySelectorAll<HTMLElement>(".seed-tabs__triggerLabel");
@@ -140,6 +170,121 @@ describe("Tabs", () => {
140
170
  expect(rects["three"]?.left).toBe(0);
141
171
  });
142
172
 
173
+ it("calculates tab scroll offsets from content insets and measured widths", () => {
174
+ const geometry = {
175
+ viewportWidth: 360,
176
+ contentWidth: 792,
177
+ contentInsetStart: 16,
178
+ contentInsetEnd: 16,
179
+ triggerRect: { left: 320, width: 56 },
180
+ };
181
+
182
+ expect(getTabsScrollOffset({ ...geometry, scrollAlign: "start", currentOffset: 0 })).toBe(320);
183
+ expect(getTabsScrollOffset({ ...geometry, scrollAlign: "center", currentOffset: 0 })).toBe(184);
184
+ expect(getTabsScrollOffset({ ...geometry, scrollAlign: "end", currentOffset: 0 })).toBe(48);
185
+ expect(getTabsScrollOffset({ ...geometry, scrollAlign: "nearest", currentOffset: 0 })).toBe(48);
186
+ expect(getTabsScrollOffset({ ...geometry, scrollAlign: "nearest", currentOffset: 160 })).toBe(
187
+ 160,
188
+ );
189
+ // A trigger can sit inside the desired 16px padding while remaining fully
190
+ // visible in the actual scroll viewport; nearest must not move it.
191
+ expect(
192
+ getTabsScrollOffset({
193
+ ...geometry,
194
+ scrollAlign: "nearest",
195
+ currentOffset: 200,
196
+ triggerRect: { left: 192, width: 56 },
197
+ }),
198
+ ).toBe(200);
199
+ expect(
200
+ getTabsScrollOffset({
201
+ ...geometry,
202
+ scrollAlign: "nearest",
203
+ currentOffset: 240,
204
+ triggerRect: { left: 192, width: 56 },
205
+ }),
206
+ ).toBe(192);
207
+ });
208
+
209
+ it("clamps tab scrolling at both content edges without moving a non-scrollable list", () => {
210
+ const geometry = {
211
+ viewportWidth: 360,
212
+ contentWidth: 792,
213
+ contentInsetStart: 16,
214
+ contentInsetEnd: 16,
215
+ };
216
+
217
+ expect(
218
+ getTabsScrollOffset({
219
+ ...geometry,
220
+ scrollAlign: "end",
221
+ currentOffset: 0,
222
+ triggerRect: { left: 0, width: 56 },
223
+ }),
224
+ ).toBe(0);
225
+ expect(
226
+ getTabsScrollOffset({
227
+ ...geometry,
228
+ scrollAlign: "start",
229
+ currentOffset: 0,
230
+ triggerRect: { left: 704, width: 56 },
231
+ }),
232
+ ).toBe(432);
233
+ expect(
234
+ getTabsScrollOffset({
235
+ ...geometry,
236
+ contentWidth: 360,
237
+ scrollAlign: "center",
238
+ currentOffset: 144,
239
+ triggerRect: { left: 320, width: 56 },
240
+ }),
241
+ ).toBe(0);
242
+ });
243
+
244
+ it("updates large ChipTabs selection and content without activating disabled triggers", () => {
245
+ const onValueChange = vi.fn();
246
+ const { container } = render(
247
+ <ChipTabs.Root size="large" defaultValue="one" onValueChange={onValueChange}>
248
+ <ChipTabs.List>
249
+ <ChipTabs.Trigger value="one" notification={<text>새 알림</text>}>
250
+ 첫 번째
251
+ </ChipTabs.Trigger>
252
+ <ChipTabs.Trigger value="two">두 번째</ChipTabs.Trigger>
253
+ <ChipTabs.Trigger value="three" disabled>
254
+ 세 번째
255
+ </ChipTabs.Trigger>
256
+ </ChipTabs.List>
257
+ <ChipTabs.Content value="one">첫 번째 콘텐츠</ChipTabs.Content>
258
+ <ChipTabs.Content value="two">두 번째 콘텐츠</ChipTabs.Content>
259
+ <ChipTabs.Content value="three">세 번째 콘텐츠</ChipTabs.Content>
260
+ </ChipTabs.Root>,
261
+ );
262
+ const triggers = container.querySelectorAll<HTMLElement>(
263
+ '[accessibility-role-description="tab"]',
264
+ );
265
+ const contents = container.querySelectorAll<HTMLElement>(
266
+ '[accessibility-role-description="tabpanel"]',
267
+ );
268
+
269
+ expect(triggers[0]).toHaveAttribute("accessibility-value", "selected");
270
+ expect(contents[0]).toHaveAttribute("accessibility-elements-hidden", "false");
271
+ expect(contents[0]).toHaveTextContent("첫 번째 콘텐츠");
272
+ expect(container).toHaveTextContent("새 알림");
273
+
274
+ fireEvent.tap(triggers[1]);
275
+
276
+ expect(onValueChange).toHaveBeenCalledWith("two");
277
+ expect(triggers[1]).toHaveAttribute("accessibility-value", "selected");
278
+ expect(contents[1]).toHaveAttribute("accessibility-elements-hidden", "false");
279
+ expect(contents[1]).toHaveTextContent("두 번째 콘텐츠");
280
+
281
+ fireEvent.tap(triggers[2]);
282
+
283
+ expect(onValueChange).toHaveBeenCalledTimes(1);
284
+ expect(triggers[1]).toHaveAttribute("accessibility-value", "selected");
285
+ expect(contents[1]).toHaveAttribute("accessibility-elements-hidden", "false");
286
+ });
287
+
143
288
  it("does not select a disabled trigger", () => {
144
289
  const onValueChange = vi.fn();
145
290
  const { container } = render(
@@ -190,6 +335,78 @@ describe("Tabs", () => {
190
335
  expect(container.querySelector(".seed-tabs__content")).not.toBeNull();
191
336
  });
192
337
 
338
+ it("starts only for native drags and closes non-changing or cancelled swipes", () => {
339
+ function SwipeLifecycleTabs() {
340
+ const [counts, setCounts] = React.useState({ starts: 0, ends: 0, settles: 0 });
341
+ const isSwiping = counts.starts > counts.ends;
342
+
343
+ return (
344
+ <Tabs.Root defaultValue="one">
345
+ <Tabs.List>
346
+ <Tabs.Trigger value="one">첫 번째</Tabs.Trigger>
347
+ <Tabs.Trigger value="two">두 번째</Tabs.Trigger>
348
+ </Tabs.List>
349
+ <Tabs.Carousel
350
+ swipeable
351
+ onSwipeStart={() =>
352
+ setCounts((current) => ({ ...current, starts: current.starts + 1 }))
353
+ }
354
+ onSwipeEnd={() => setCounts((current) => ({ ...current, ends: current.ends + 1 }))}
355
+ onSettle={() => setCounts((current) => ({ ...current, settles: current.settles + 1 }))}
356
+ >
357
+ <Tabs.CarouselCamera>
358
+ <Tabs.Content value="one">첫 번째 콘텐츠</Tabs.Content>
359
+ <Tabs.Content value="two">두 번째 콘텐츠</Tabs.Content>
360
+ </Tabs.CarouselCamera>
361
+ </Tabs.Carousel>
362
+ <text data-testid="swipe-state">{isSwiping ? "swiping" : "idle"}</text>
363
+ <text data-testid="swipe-counts">
364
+ {`${counts.starts}/${counts.ends}/${counts.settles}`}
365
+ </text>
366
+ </Tabs.Root>
367
+ );
368
+ }
369
+
370
+ const { container, getByTestId } = render(<SwipeLifecycleTabs />);
371
+ const pager = container.querySelector("viewpager")!;
372
+
373
+ fireEvent.touchstart(pager, {});
374
+ expect(getByTestId("swipe-state")).toHaveTextContent("idle");
375
+ fireEvent.touchend(pager, {});
376
+ expect(getByTestId("swipe-counts")).toHaveTextContent("0/0/0");
377
+
378
+ fireEvent.touchstart(pager, {});
379
+ fireViewPagerEvent(pager, "willchange", { index: 1, isDragged: true });
380
+ expect(getByTestId("swipe-state")).toHaveTextContent("swiping");
381
+ fireViewPagerEvent(pager, "change", { index: 1, isDragged: true });
382
+ fireEvent.touchend(pager, {});
383
+
384
+ expect(getByTestId("swipe-state")).toHaveTextContent("idle");
385
+ expect(getByTestId("swipe-counts")).toHaveTextContent("1/1/1");
386
+
387
+ fireEvent.touchstart(pager, {});
388
+ fireViewPagerEvent(pager, "willchange", { index: 1, isDragged: true });
389
+ fireEvent.touchend(pager, {});
390
+
391
+ expect(getByTestId("swipe-state")).toHaveTextContent("idle");
392
+ expect(getByTestId("swipe-counts")).toHaveTextContent("2/2/1");
393
+
394
+ fireEvent.touchstart(pager, {});
395
+ fireViewPagerEvent(pager, "willchange", { index: 0, isDragged: true });
396
+ fireViewPagerEvent(pager, "change", { index: 0, isDragged: true });
397
+ fireEvent.touchend(pager, {});
398
+
399
+ expect(getByTestId("swipe-state")).toHaveTextContent("idle");
400
+ expect(getByTestId("swipe-counts")).toHaveTextContent("3/3/2");
401
+
402
+ fireEvent.touchstart(pager, {});
403
+ fireViewPagerEvent(pager, "willchange", { index: 0, isDragged: true });
404
+ fireEvent.touchcancel(pager, {});
405
+ fireEvent.touchend(pager, {});
406
+ expect(getByTestId("swipe-state")).toHaveTextContent("idle");
407
+ expect(getByTestId("swipe-counts")).toHaveTextContent("4/4/2");
408
+ });
409
+
193
410
  it("renders carousel camera contents as native viewpager items", () => {
194
411
  const { container } = render(
195
412
  <Tabs.Root defaultValue="one">