@duro-app/ui 4.4.0 → 5.0.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 (273) hide show
  1. package/dist/{Table-JTgcR_Fh.js → Table-BKN_d-mp.js} +506 -410
  2. package/dist/Table-BKN_d-mp.js.map +1 -0
  3. package/dist/components/ActionBar/ActionBar.stories.d.ts.map +1 -1
  4. package/dist/components/ActionBar/styles.css.d.ts +7 -7
  5. package/dist/components/ActionBar/styles.css.d.ts.map +1 -1
  6. package/dist/components/Alert/styles.css.d.ts +2 -2
  7. package/dist/components/Alert/styles.css.d.ts.map +1 -1
  8. package/dist/components/Badge/styles.css.d.ts +2 -2
  9. package/dist/components/Button/styles.css.d.ts +5 -5
  10. package/dist/components/Button/styles.css.d.ts.map +1 -1
  11. package/dist/components/Callout/styles.css.d.ts +1 -1
  12. package/dist/components/Callout/styles.css.d.ts.map +1 -1
  13. package/dist/components/Card/styles.css.d.ts +3 -3
  14. package/dist/components/Card/styles.css.d.ts.map +1 -1
  15. package/dist/components/Checkbox/Checkbox.d.ts.map +1 -1
  16. package/dist/components/Checkbox/styles.css.d.ts +7 -11
  17. package/dist/components/Checkbox/styles.css.d.ts.map +1 -1
  18. package/dist/components/Cluster/Cluster.stories.d.ts.map +1 -1
  19. package/dist/components/ColorInput/styles.css.d.ts +6 -6
  20. package/dist/components/ColorInput/styles.css.d.ts.map +1 -1
  21. package/dist/components/ColorMode/styles.css.d.ts +3 -3
  22. package/dist/components/ColorMode/styles.css.d.ts.map +1 -1
  23. package/dist/components/Combobox/Combobox.d.ts.map +1 -1
  24. package/dist/components/Combobox/Combobox.meta.d.ts.map +1 -1
  25. package/dist/components/Combobox/styles.css.d.ts +3 -3
  26. package/dist/components/Combobox/styles.css.d.ts.map +1 -1
  27. package/dist/components/DetailPanel/DetailPanel.d.ts.map +1 -1
  28. package/dist/components/DetailPanel/DetailPanel.stories.d.ts.map +1 -1
  29. package/dist/components/DetailPanel/styles.css.d.ts +19 -10
  30. package/dist/components/DetailPanel/styles.css.d.ts.map +1 -1
  31. package/dist/components/Dialog/Dialog.d.ts.map +1 -1
  32. package/dist/components/Dialog/Dialog.stories.d.ts.map +1 -1
  33. package/dist/components/Dialog/styles.css.d.ts +18 -9
  34. package/dist/components/Dialog/styles.css.d.ts.map +1 -1
  35. package/dist/components/DragDrop/DragDrop.d.ts.map +1 -1
  36. package/dist/components/DragDrop/DragDrop.stories.d.ts.map +1 -1
  37. package/dist/components/DragDrop/styles.css.d.ts +0 -6
  38. package/dist/components/DragDrop/styles.css.d.ts.map +1 -1
  39. package/dist/components/Drawer/Drawer.d.ts.map +1 -1
  40. package/dist/components/Drawer/DrawerScroll.stories.d.ts +13 -0
  41. package/dist/components/Drawer/DrawerScroll.stories.d.ts.map +1 -0
  42. package/dist/components/Drawer/styles.css.d.ts +19 -10
  43. package/dist/components/Drawer/styles.css.d.ts.map +1 -1
  44. package/dist/components/Drawer/useSwipeDismiss.d.ts +7 -2
  45. package/dist/components/Drawer/useSwipeDismiss.d.ts.map +1 -1
  46. package/dist/components/Field/Field.stories.d.ts.map +1 -1
  47. package/dist/components/Field/styles.css.d.ts +1 -1
  48. package/dist/components/Field/styles.css.d.ts.map +1 -1
  49. package/dist/components/Form/Form.stories.d.ts.map +1 -1
  50. package/dist/components/Grid/Grid.d.ts +6 -4
  51. package/dist/components/Grid/Grid.d.ts.map +1 -1
  52. package/dist/components/Grid/Grid.stories.d.ts.map +1 -1
  53. package/dist/components/Grid/styles.css.d.ts +3 -3
  54. package/dist/components/Input/Input.stories.d.ts.map +1 -1
  55. package/dist/components/Input/styles.css.d.ts +3 -3
  56. package/dist/components/Input/styles.css.d.ts.map +1 -1
  57. package/dist/components/InputGroup/InputGroup.d.ts +4 -2
  58. package/dist/components/InputGroup/InputGroup.d.ts.map +1 -1
  59. package/dist/components/InputGroup/InputGroup.stories.d.ts.map +1 -1
  60. package/dist/components/InputGroup/styles.css.d.ts +3 -3
  61. package/dist/components/InputGroup/styles.css.d.ts.map +1 -1
  62. package/dist/components/LinkButton/styles.css.d.ts +1 -1
  63. package/dist/components/LinkButton/styles.css.d.ts.map +1 -1
  64. package/dist/components/List/List.stories.d.ts.map +1 -1
  65. package/dist/components/List/styles.css.d.ts +3 -3
  66. package/dist/components/List/styles.css.d.ts.map +1 -1
  67. package/dist/components/Menu/styles.css.d.ts +4 -4
  68. package/dist/components/Menu/styles.css.d.ts.map +1 -1
  69. package/dist/components/Menu/useMenuRoot.d.ts.map +1 -1
  70. package/dist/components/PageShell/styles.css.d.ts +3 -3
  71. package/dist/components/PageShell/styles.css.d.ts.map +1 -1
  72. package/dist/components/Panel/styles.css.d.ts +3 -3
  73. package/dist/components/Panel/styles.css.d.ts.map +1 -1
  74. package/dist/components/Popover/Popover.d.ts.map +1 -1
  75. package/dist/components/Popover/Popover.meta.d.ts.map +1 -1
  76. package/dist/components/Popover/Popover.stories.d.ts.map +1 -1
  77. package/dist/components/Popover/PopoverLayerContext.d.ts +10 -0
  78. package/dist/components/Popover/PopoverLayerContext.d.ts.map +1 -0
  79. package/dist/components/Popover/PopoverLayers.stories.d.ts +13 -0
  80. package/dist/components/Popover/PopoverLayers.stories.d.ts.map +1 -0
  81. package/dist/components/Popover/styles.css.d.ts +2 -2
  82. package/dist/components/Popover/styles.css.d.ts.map +1 -1
  83. package/dist/components/RadioGroup/RadioGroup.d.ts.map +1 -1
  84. package/dist/components/RadioGroup/styles.css.d.ts +5 -9
  85. package/dist/components/RadioGroup/styles.css.d.ts.map +1 -1
  86. package/dist/components/ScrollArea/ScrollArea.d.ts +10 -3
  87. package/dist/components/ScrollArea/ScrollArea.d.ts.map +1 -1
  88. package/dist/components/ScrollArea/ScrollArea.stories.d.ts.map +1 -1
  89. package/dist/components/ScrollArea/styles.css.d.ts +8 -8
  90. package/dist/components/ScrollArea/styles.css.d.ts.map +1 -1
  91. package/dist/components/Select/Select.d.ts.map +1 -1
  92. package/dist/components/Select/Select.meta.d.ts.map +1 -1
  93. package/dist/components/Select/styles.css.d.ts +4 -4
  94. package/dist/components/Select/styles.css.d.ts.map +1 -1
  95. package/dist/components/Select/useSelectRoot.d.ts.map +1 -1
  96. package/dist/components/SideNav/styles.css.d.ts +7 -7
  97. package/dist/components/SideNav/styles.css.d.ts.map +1 -1
  98. package/dist/components/Spinner/Spinner.d.ts.map +1 -1
  99. package/dist/components/Spinner/styles.css.d.ts +10 -14
  100. package/dist/components/Spinner/styles.css.d.ts.map +1 -1
  101. package/dist/components/Switch/Switch.d.ts.map +1 -1
  102. package/dist/components/Switch/styles.css.d.ts +10 -14
  103. package/dist/components/Switch/styles.css.d.ts.map +1 -1
  104. package/dist/components/Table/ColumnFilter.d.ts.map +1 -1
  105. package/dist/components/Table/FromTanstack.d.ts +2 -1
  106. package/dist/components/Table/FromTanstack.d.ts.map +1 -1
  107. package/dist/components/Table/Pagination.d.ts.map +1 -1
  108. package/dist/components/Table/SortIndicator.d.ts.map +1 -1
  109. package/dist/components/Table/Table.d.ts +11 -9
  110. package/dist/components/Table/Table.d.ts.map +1 -1
  111. package/dist/components/Table/Table.stories.d.ts.map +1 -1
  112. package/dist/components/Table/styles.css.d.ts +6 -6
  113. package/dist/components/Table/styles.css.d.ts.map +1 -1
  114. package/dist/components/Tabs/styles.css.d.ts +8 -8
  115. package/dist/components/Tabs/styles.css.d.ts.map +1 -1
  116. package/dist/components/Tag/Tag.stories.d.ts.map +1 -1
  117. package/dist/components/Tag/styles.css.d.ts +4 -4
  118. package/dist/components/Tag/styles.css.d.ts.map +1 -1
  119. package/dist/components/TagGroup/TagGroup.d.ts.map +1 -1
  120. package/dist/components/TagGroup/styles.css.d.ts +4 -10
  121. package/dist/components/TagGroup/styles.css.d.ts.map +1 -1
  122. package/dist/components/Textarea/Textarea.stories.d.ts.map +1 -1
  123. package/dist/components/Textarea/styles.css.d.ts +3 -3
  124. package/dist/components/Textarea/styles.css.d.ts.map +1 -1
  125. package/dist/components/ThemeProvider/ThemeProvider.stories.d.ts.map +1 -1
  126. package/dist/components/Toast/Toast.stories.d.ts.map +1 -1
  127. package/dist/components/Toast/styles.css.d.ts +12 -12
  128. package/dist/components/Toast/styles.css.d.ts.map +1 -1
  129. package/dist/components/Toggle/Toggle.d.ts.map +1 -1
  130. package/dist/components/Toggle/styles.css.d.ts +14 -4
  131. package/dist/components/Toggle/styles.css.d.ts.map +1 -1
  132. package/dist/components/ToggleGroup/ToggleGroup.d.ts +7 -1
  133. package/dist/components/ToggleGroup/ToggleGroup.d.ts.map +1 -1
  134. package/dist/components/ToggleGroup/ToggleGroup.meta.d.ts.map +1 -1
  135. package/dist/components/ToggleGroup/ToggleGroup.stories.d.ts +4 -0
  136. package/dist/components/ToggleGroup/ToggleGroup.stories.d.ts.map +1 -1
  137. package/dist/components/ToggleGroup/ToggleGroupContext.d.ts +6 -0
  138. package/dist/components/ToggleGroup/ToggleGroupContext.d.ts.map +1 -1
  139. package/dist/components/ToggleGroup/styles.css.d.ts +20 -1
  140. package/dist/components/ToggleGroup/styles.css.d.ts.map +1 -1
  141. package/dist/components/ToggleGroup/useRovingFocus.d.ts +17 -0
  142. package/dist/components/ToggleGroup/useRovingFocus.d.ts.map +1 -0
  143. package/dist/components/Tooltip/styles.css.d.ts +1 -1
  144. package/dist/components/Tooltip/styles.css.d.ts.map +1 -1
  145. package/dist/components/Tree/styles.css.d.ts +4 -4
  146. package/dist/components/Tree/styles.css.d.ts.map +1 -1
  147. package/dist/components/VirtualTable/VirtualTable.d.ts +2 -1
  148. package/dist/components/VirtualTable/VirtualTable.d.ts.map +1 -1
  149. package/dist/components/VirtualTable/VirtualTable.stories.d.ts.map +1 -1
  150. package/dist/components/VirtualTable/styles.css.d.ts +7 -7
  151. package/dist/components/VirtualTable/styles.css.d.ts.map +1 -1
  152. package/dist/docs/Spacing.stories.d.ts.map +1 -1
  153. package/dist/docs/Typography.stories.d.ts.map +1 -1
  154. package/dist/docs/helpers.d.ts.map +1 -1
  155. package/dist/index.css +1 -1
  156. package/dist/index.js +3788 -3515
  157. package/dist/index.js.map +1 -1
  158. package/dist/shared/length.d.ts +24 -0
  159. package/dist/shared/length.d.ts.map +1 -0
  160. package/dist/shared/length.typecheck.d.ts +28 -0
  161. package/dist/shared/length.typecheck.d.ts.map +1 -0
  162. package/dist/styles/visually-hidden.css.d.ts +10 -0
  163. package/dist/styles/visually-hidden.css.d.ts.map +1 -0
  164. package/dist/table.js +166 -166
  165. package/dist/table.js.map +1 -1
  166. package/package.json +2 -2
  167. package/src/components/ActionBar/ActionBar.stories.tsx +4 -2
  168. package/src/components/ActionBar/styles.css.ts +11 -9
  169. package/src/components/Alert/styles.css.ts +4 -3
  170. package/src/components/Badge/styles.css.ts +3 -3
  171. package/src/components/Button/styles.css.ts +8 -7
  172. package/src/components/Callout/styles.css.ts +2 -1
  173. package/src/components/Card/styles.css.ts +4 -3
  174. package/src/components/Checkbox/Checkbox.tsx +2 -1
  175. package/src/components/Checkbox/styles.css.ts +10 -11
  176. package/src/components/Cluster/Cluster.stories.tsx +2 -1
  177. package/src/components/ColorInput/styles.css.ts +9 -7
  178. package/src/components/ColorMode/styles.css.ts +4 -3
  179. package/src/components/Combobox/Combobox.meta.ts +6 -0
  180. package/src/components/Combobox/Combobox.tsx +5 -0
  181. package/src/components/Combobox/styles.css.ts +5 -3
  182. package/src/components/DetailPanel/DetailPanel.stories.tsx +5 -3
  183. package/src/components/DetailPanel/DetailPanel.tsx +3 -0
  184. package/src/components/DetailPanel/styles.css.ts +17 -11
  185. package/src/components/Dialog/Dialog.stories.tsx +2 -1
  186. package/src/components/Dialog/Dialog.tsx +3 -0
  187. package/src/components/Dialog/styles.css.ts +14 -9
  188. package/src/components/DragDrop/DragDrop.stories.tsx +2 -1
  189. package/src/components/DragDrop/DragDrop.tsx +2 -1
  190. package/src/components/DragDrop/styles.css.ts +0 -7
  191. package/src/components/Drawer/Drawer.meta.ts +1 -1
  192. package/src/components/Drawer/Drawer.tsx +15 -2
  193. package/src/components/Drawer/DrawerScroll.stories.tsx +292 -0
  194. package/src/components/Drawer/styles.css.ts +15 -10
  195. package/src/components/Drawer/useSwipeDismiss.ts +53 -8
  196. package/src/components/Field/Field.stories.tsx +3 -2
  197. package/src/components/Field/styles.css.ts +2 -1
  198. package/src/components/Form/Form.stories.tsx +4 -3
  199. package/src/components/Grid/Grid.meta.ts +2 -2
  200. package/src/components/Grid/Grid.stories.tsx +3 -2
  201. package/src/components/Grid/Grid.tsx +8 -6
  202. package/src/components/Grid/styles.css.ts +2 -2
  203. package/src/components/Input/Input.stories.tsx +2 -1
  204. package/src/components/Input/styles.css.ts +4 -3
  205. package/src/components/InputGroup/InputGroup.stories.tsx +3 -2
  206. package/src/components/InputGroup/InputGroup.tsx +5 -3
  207. package/src/components/InputGroup/styles.css.ts +4 -3
  208. package/src/components/LinkButton/styles.css.ts +2 -1
  209. package/src/components/List/List.stories.tsx +4 -2
  210. package/src/components/List/styles.css.ts +6 -4
  211. package/src/components/Menu/styles.css.ts +6 -4
  212. package/src/components/Menu/useMenuRoot.ts +5 -1
  213. package/src/components/PageShell/styles.css.ts +4 -3
  214. package/src/components/Panel/styles.css.ts +4 -3
  215. package/src/components/Popover/Popover.meta.ts +7 -0
  216. package/src/components/Popover/Popover.stories.tsx +6 -4
  217. package/src/components/Popover/Popover.tsx +30 -24
  218. package/src/components/Popover/PopoverLayerContext.ts +20 -0
  219. package/src/components/Popover/PopoverLayers.stories.tsx +234 -0
  220. package/src/components/Popover/styles.css.ts +3 -2
  221. package/src/components/RadioGroup/RadioGroup.tsx +2 -1
  222. package/src/components/RadioGroup/styles.css.ts +8 -9
  223. package/src/components/ScrollArea/ScrollArea.meta.ts +2 -2
  224. package/src/components/ScrollArea/ScrollArea.stories.tsx +7 -5
  225. package/src/components/ScrollArea/ScrollArea.tsx +18 -7
  226. package/src/components/ScrollArea/styles.css.ts +10 -9
  227. package/src/components/Select/Select.meta.ts +6 -0
  228. package/src/components/Select/Select.tsx +8 -1
  229. package/src/components/Select/styles.css.ts +6 -4
  230. package/src/components/Select/useSelectRoot.ts +6 -2
  231. package/src/components/SideNav/styles.css.ts +10 -8
  232. package/src/components/Spinner/Spinner.tsx +2 -1
  233. package/src/components/Spinner/styles.css.ts +14 -14
  234. package/src/components/Switch/Switch.tsx +2 -1
  235. package/src/components/Switch/styles.css.ts +15 -15
  236. package/src/components/Table/ColumnFilter.tsx +5 -4
  237. package/src/components/Table/FromTanstack.tsx +2 -1
  238. package/src/components/Table/Pagination.tsx +6 -4
  239. package/src/components/Table/SortIndicator.tsx +4 -2
  240. package/src/components/Table/Table.stories.tsx +8 -8
  241. package/src/components/Table/Table.tsx +19 -15
  242. package/src/components/Table/styles.css.ts +9 -7
  243. package/src/components/Tabs/styles.css.ts +11 -9
  244. package/src/components/Tag/Tag.stories.tsx +4 -2
  245. package/src/components/Tag/styles.css.ts +6 -5
  246. package/src/components/TagGroup/TagGroup.tsx +2 -1
  247. package/src/components/TagGroup/styles.css.ts +6 -10
  248. package/src/components/Textarea/Textarea.stories.tsx +2 -1
  249. package/src/components/Textarea/styles.css.ts +4 -3
  250. package/src/components/ThemeProvider/ThemeProvider.stories.tsx +7 -5
  251. package/src/components/Toast/Toast.stories.tsx +2 -1
  252. package/src/components/Toast/styles.css.ts +16 -14
  253. package/src/components/Toggle/Toggle.meta.ts +1 -1
  254. package/src/components/Toggle/Toggle.tsx +19 -2
  255. package/src/components/Toggle/styles.css.ts +18 -4
  256. package/src/components/ToggleGroup/ToggleGroup.meta.ts +11 -1
  257. package/src/components/ToggleGroup/ToggleGroup.stories.tsx +157 -0
  258. package/src/components/ToggleGroup/ToggleGroup.tsx +87 -10
  259. package/src/components/ToggleGroup/ToggleGroupContext.ts +6 -0
  260. package/src/components/ToggleGroup/styles.css.ts +32 -2
  261. package/src/components/ToggleGroup/useRovingFocus.ts +121 -0
  262. package/src/components/Tooltip/styles.css.ts +2 -1
  263. package/src/components/Tree/styles.css.ts +8 -6
  264. package/src/components/VirtualTable/VirtualTable.stories.tsx +4 -3
  265. package/src/components/VirtualTable/VirtualTable.tsx +5 -3
  266. package/src/components/VirtualTable/styles.css.ts +9 -8
  267. package/src/docs/Spacing.stories.tsx +11 -10
  268. package/src/docs/Typography.stories.tsx +8 -6
  269. package/src/docs/helpers.tsx +3 -2
  270. package/src/shared/length.ts +51 -0
  271. package/src/shared/length.typecheck.tsx +47 -0
  272. package/src/styles/visually-hidden.css.ts +16 -0
  273. package/dist/Table-JTgcR_Fh.js.map +0 -1
@@ -3,6 +3,7 @@ import {expect, fn} from 'storybook/test'
3
3
  import {css, html} from 'react-strict-dom'
4
4
  import {Textarea} from './Textarea'
5
5
  import {spacing} from '@duro-app/tokens/tokens/spacing.css'
6
+ import {sizes} from '@duro-app/tokens/tokens/sizes.css'
6
7
 
7
8
  const meta: Meta<typeof Textarea> = {
8
9
  title: 'Components/Textarea',
@@ -76,7 +77,7 @@ export const CustomRows: Story = {
76
77
  }
77
78
 
78
79
  const stackStyles = css.create({
79
- stack: {display: 'flex', flexDirection: 'column', gap: spacing.ms, maxWidth: 320},
80
+ stack: {display: 'flex', flexDirection: 'column', gap: spacing.ms, maxWidth: sizes.panelSm},
80
81
  })
81
82
 
82
83
  export const AllVariants: Story = {
@@ -3,6 +3,7 @@ import {colors} from '@duro-app/tokens/tokens/colors.css'
3
3
  import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
4
4
  import {typography} from '@duro-app/tokens/tokens/typography.css'
5
5
  import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
6
+ import {borders} from '@duro-app/tokens/tokens/borders.css'
6
7
 
7
8
  export const styles = css.create({
8
9
  base: {
@@ -16,7 +17,7 @@ export const styles = css.create({
16
17
  lineHeight: typography.lineHeight,
17
18
  color: colors.text,
18
19
  backgroundColor: colors.bg,
19
- borderWidth: 1,
20
+ borderWidth: borders.hairline,
20
21
  borderStyle: 'solid',
21
22
  borderRadius: radii.sm,
22
23
  resize: 'vertical' as const,
@@ -25,7 +26,7 @@ export const styles = css.create({
25
26
  transitionTimingFunction: easing.standard,
26
27
  outlineWidth: {
27
28
  default: 0,
28
- ':focus-visible': 2,
29
+ ':focus-visible': borders.focusRing,
29
30
  },
30
31
  outlineStyle: {
31
32
  default: 'none',
@@ -37,7 +38,7 @@ export const styles = css.create({
37
38
  },
38
39
  outlineOffset: {
39
40
  default: 0,
40
- ':focus-visible': 1,
41
+ ':focus-visible': borders.focusOffsetSm,
41
42
  },
42
43
  },
43
44
  default: {
@@ -7,6 +7,8 @@ import {colors} from '@duro-app/tokens/tokens/colors.css'
7
7
  import {radii, spacing} from '@duro-app/tokens/tokens/spacing.css'
8
8
  import {typography} from '@duro-app/tokens/tokens/typography.css'
9
9
  import {shadows} from '@duro-app/tokens/tokens/shadows.css'
10
+ import {borders} from '@duro-app/tokens/tokens/borders.css'
11
+ import {sizes} from '@duro-app/tokens/tokens/sizes.css'
10
12
 
11
13
  const meta: Meta<typeof ThemeProvider> = {
12
14
  title: 'Theme/ThemeProvider',
@@ -27,7 +29,7 @@ const sampleStyles = css.create({
27
29
  card: {
28
30
  padding: spacing.md,
29
31
  backgroundColor: colors.bgCard,
30
- borderWidth: 1,
32
+ borderWidth: borders.hairline,
31
33
  borderStyle: 'solid',
32
34
  borderColor: colors.border,
33
35
  borderRadius: radii.md,
@@ -53,10 +55,10 @@ const sampleStyles = css.create({
53
55
  flexWrap: 'wrap',
54
56
  },
55
57
  swatch: {
56
- width: 48,
57
- height: 48,
58
+ width: sizes.iconXxl,
59
+ height: sizes.iconXxl,
58
60
  borderRadius: radii.sm,
59
- borderWidth: 1,
61
+ borderWidth: borders.hairline,
60
62
  borderStyle: 'solid',
61
63
  borderColor: colors.border,
62
64
  },
@@ -131,7 +133,7 @@ const sideBySideStyles = css.create({
131
133
  },
132
134
  column: {
133
135
  flex: 1,
134
- minWidth: 280,
136
+ minWidth: sizes.gridColMd,
135
137
  },
136
138
  label: {
137
139
  fontSize: typography.fontSizeSm,
@@ -3,13 +3,14 @@ import {expect, userEvent, waitFor} from 'storybook/test'
3
3
  import {css, html} from 'react-strict-dom'
4
4
  import {ToastProvider, useToast} from './ToastProvider'
5
5
  import {radii, spacing} from '@duro-app/tokens/tokens/spacing.css'
6
+ import {borders} from '@duro-app/tokens/tokens/borders.css'
6
7
 
7
8
  const s = css.create({
8
9
  row: {display: 'flex', flexDirection: 'row', gap: spacing.sm, flexWrap: 'wrap'},
9
10
  btn: {
10
11
  padding: spacing.sm,
11
12
  borderRadius: radii.sm,
12
- borderWidth: 1,
13
+ borderWidth: borders.hairline,
13
14
  borderStyle: 'solid',
14
15
  cursor: 'pointer',
15
16
  },
@@ -1,9 +1,11 @@
1
1
  import {css} from 'react-strict-dom'
2
2
  import {colors} from '@duro-app/tokens/tokens/colors.css'
3
- import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
3
+ import {microSpacing, radii, spacing} from '@duro-app/tokens/tokens/spacing.css'
4
4
  import {typography} from '@duro-app/tokens/tokens/typography.css'
5
5
  import {shadows} from '@duro-app/tokens/tokens/shadows.css'
6
- import {easing} from '@duro-app/tokens/tokens/motion.css'
6
+ import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
7
+ import {sizes} from '@duro-app/tokens/tokens/sizes.css'
8
+ import {borders} from '@duro-app/tokens/tokens/borders.css'
7
9
 
8
10
  const enter = css.keyframes({
9
11
  from: {opacity: 0, transform: 'translateY(10px)'},
@@ -31,17 +33,17 @@ export const styles = css.create({
31
33
  pointerEvents: 'auto',
32
34
  boxSizing: 'border-box',
33
35
  width: '100%',
34
- maxWidth: 440,
36
+ maxWidth: sizes.toastMaxW,
35
37
  display: 'flex',
36
38
  flexDirection: 'row',
37
39
  alignItems: 'flex-start',
38
40
  gap: spacing.sm,
39
41
  padding: spacing.md,
40
42
  borderRadius: radii.sm,
41
- borderWidth: 1,
43
+ borderWidth: borders.hairline,
42
44
  borderStyle: 'solid',
43
45
  borderColor: colors.border,
44
- borderLeftWidth: 3,
46
+ borderLeftWidth: borders.accent,
45
47
  backgroundColor: colors.bgCard,
46
48
  boxShadow: shadows.lg,
47
49
  color: colors.text,
@@ -51,7 +53,7 @@ export const styles = css.create({
51
53
  default: enter,
52
54
  '@media (prefers-reduced-motion: reduce)': 'none',
53
55
  },
54
- animationDuration: '160ms',
56
+ animationDuration: duration.brisk,
55
57
  animationTimingFunction: easing.easeOut,
56
58
  },
57
59
  success: {borderLeftColor: colors.successBorder},
@@ -63,7 +65,7 @@ export const styles = css.create({
63
65
  display: 'flex',
64
66
  alignItems: 'center',
65
67
  justifyContent: 'center',
66
- paddingTop: 1,
68
+ paddingTop: microSpacing.px1,
67
69
  },
68
70
  iconSuccess: {color: colors.successText},
69
71
  iconError: {color: colors.errorText},
@@ -74,14 +76,14 @@ export const styles = css.create({
74
76
  minWidth: 0,
75
77
  display: 'flex',
76
78
  flexDirection: 'column',
77
- gap: 2,
79
+ gap: microSpacing.px2,
78
80
  },
79
81
  message: {
80
82
  color: colors.text,
81
83
  },
82
84
  action: {
83
85
  alignSelf: 'flex-start',
84
- marginTop: 2,
86
+ marginTop: microSpacing.px2,
85
87
  padding: 0,
86
88
  borderWidth: 0,
87
89
  backgroundColor: 'transparent',
@@ -91,9 +93,9 @@ export const styles = css.create({
91
93
  textDecorationLine: 'underline',
92
94
  cursor: 'pointer',
93
95
  outlineStyle: {default: 'none', ':focus-visible': 'solid'},
94
- outlineWidth: 2,
96
+ outlineWidth: borders.focusRing,
95
97
  outlineColor: colors.accent,
96
- outlineOffset: 2,
98
+ outlineOffset: borders.focusOffset,
97
99
  borderRadius: radii.sm,
98
100
  },
99
101
  closeBtn: {
@@ -101,7 +103,7 @@ export const styles = css.create({
101
103
  display: 'flex',
102
104
  alignItems: 'center',
103
105
  justifyContent: 'center',
104
- padding: 2,
106
+ padding: microSpacing.px2,
105
107
  marginTop: -1,
106
108
  borderWidth: 0,
107
109
  backgroundColor: 'transparent',
@@ -109,8 +111,8 @@ export const styles = css.create({
109
111
  cursor: 'pointer',
110
112
  borderRadius: radii.sm,
111
113
  outlineStyle: {default: 'none', ':focus-visible': 'solid'},
112
- outlineWidth: 2,
114
+ outlineWidth: borders.focusRing,
113
115
  outlineColor: colors.accent,
114
- outlineOffset: 1,
116
+ outlineOffset: borders.focusOffsetSm,
115
117
  },
116
118
  })
@@ -2,7 +2,7 @@ import type {ComponentMeta} from '../component-meta'
2
2
 
3
3
  export const meta: ComponentMeta = {
4
4
  description:
5
- 'Toggle button with pressed/unpressed state. Works standalone or within ToggleGroup for multi/single selection.',
5
+ 'Toggle button with pressed/unpressed state. Works standalone or within ToggleGroup for multi/single selection. Under a coarse pointer (touch) every Toggle is at least sizes.touchTarget (44px) square; in a wrapping ToggleGroup each keeps its own border and a fixed row height.',
6
6
  whenToUse: [
7
7
  'Toolbar buttons that toggle on/off (bold, italic, grid view)',
8
8
  'Within ToggleGroup for selecting from visible options',
@@ -1,4 +1,4 @@
1
- import {type ReactNode, useCallback} from 'react'
1
+ import {type ReactNode, useCallback, useLayoutEffect, useRef} from 'react'
2
2
  import {html} from 'react-strict-dom'
3
3
  import {useControllableValue} from '../../hooks/useControllableValue'
4
4
  import {useToggleGroup} from '../ToggleGroup/ToggleGroupContext'
@@ -23,6 +23,11 @@ interface ToggleProps {
23
23
  children: ReactNode
24
24
  }
25
25
 
26
+ const wrappedSizeMap = {
27
+ default: styles.wrappedDefault,
28
+ small: styles.wrappedSmall,
29
+ } as const
30
+
26
31
  const sizeMap = {
27
32
  default: styles.sizeDefault,
28
33
  small: styles.sizeSmall,
@@ -46,6 +51,14 @@ export function Toggle({
46
51
  const disabled = disabledProp || (group?.disabled ?? false)
47
52
  const size = group?.size ?? sizeProp
48
53
 
54
+ const wrap = group?.wrap ?? false
55
+ const ref = useRef<HTMLButtonElement>(null)
56
+ const register = group?.register
57
+ useLayoutEffect(() => {
58
+ if (!wrap || !register || value === undefined || !ref.current) return
59
+ return register(value, ref.current)
60
+ }, [wrap, register, value])
61
+
49
62
  const [standalonePressed, setStandalonePressed] = useControllableValue(
50
63
  controlledPressed,
51
64
  defaultPressed,
@@ -65,7 +78,10 @@ export function Toggle({
65
78
 
66
79
  return (
67
80
  <html.button
81
+ ref={ref}
68
82
  type="button"
83
+ tabIndex={wrap && value !== undefined ? (group?.tabStopValue === value ? 0 : -1) : undefined}
84
+ onFocus={wrap && value !== undefined ? () => group?.onItemFocus(value) : undefined}
69
85
  aria-pressed={pressed}
70
86
  aria-label={ariaLabel}
71
87
  disabled={disabled}
@@ -75,7 +91,8 @@ export function Toggle({
75
91
  styles.base,
76
92
  sizeMap[size],
77
93
  pressed ? styles.pressed : styles.unpressed,
78
- isGrouped && styles.grouped,
94
+ wrap && wrappedSizeMap[size],
95
+ isGrouped && !wrap && styles.grouped,
79
96
  disabled && styles.disabled,
80
97
  ]}
81
98
  >
@@ -3,13 +3,15 @@ import {colors} from '@duro-app/tokens/tokens/colors.css'
3
3
  import {spacing, radii} from '@duro-app/tokens/tokens/spacing.css'
4
4
  import {typography} from '@duro-app/tokens/tokens/typography.css'
5
5
  import {duration, easing} from '@duro-app/tokens/tokens/motion.css'
6
+ import {sizes} from '@duro-app/tokens/tokens/sizes.css'
7
+ import {borders} from '@duro-app/tokens/tokens/borders.css'
6
8
 
7
9
  export const styles = css.create({
8
10
  base: {
9
11
  display: 'inline-flex',
10
12
  alignItems: 'center',
11
13
  justifyContent: 'center',
12
- borderWidth: 1,
14
+ borderWidth: borders.hairline,
13
15
  borderStyle: 'solid',
14
16
  cursor: 'pointer',
15
17
  fontFamily: typography.fontFamily,
@@ -17,10 +19,12 @@ export const styles = css.create({
17
19
  transitionProperty: 'background-color, border-color, color, opacity',
18
20
  transitionDuration: duration.fast,
19
21
  transitionTimingFunction: easing.standard,
20
- outlineWidth: {default: 0, ':focus-visible': 2},
22
+ outlineWidth: {default: 0, ':focus-visible': borders.focusRing},
21
23
  outlineStyle: 'solid',
22
24
  outlineColor: colors.accent,
23
- outlineOffset: 2,
25
+ outlineOffset: borders.focusOffset,
26
+ minWidth: {default: null, '@media (pointer: coarse)': sizes.touchTarget},
27
+ minHeight: {default: null, '@media (pointer: coarse)': sizes.touchTarget},
24
28
  },
25
29
  sizeDefault: {
26
30
  padding: `${spacing.sm} ${spacing.md}`,
@@ -34,6 +38,16 @@ export const styles = css.create({
34
38
  borderRadius: radii.sm,
35
39
  gap: spacing.xs,
36
40
  },
41
+ // wrapped toggles: a fixed block size per size, the touch target under a
42
+ // coarse pointer. ToggleGroup's maxRows arithmetic reads the same tokens.
43
+ wrappedDefault: {
44
+ boxSizing: 'border-box',
45
+ height: {default: sizes.controlMd, '@media (pointer: coarse)': sizes.touchTarget},
46
+ },
47
+ wrappedSmall: {
48
+ boxSizing: 'border-box',
49
+ height: {default: sizes.controlSm, '@media (pointer: coarse)': sizes.touchTarget},
50
+ },
37
51
  unpressed: {
38
52
  backgroundColor: {
39
53
  default: 'transparent',
@@ -56,7 +70,7 @@ export const styles = css.create({
56
70
  grouped: {
57
71
  borderWidth: 0,
58
72
  borderRadius: 0,
59
- borderRightWidth: 1,
73
+ borderRightWidth: borders.hairline,
60
74
  borderRightStyle: 'solid',
61
75
  // Override any borderColor set by pressed state — dividers always use border token
62
76
  borderColor: colors.border,
@@ -6,10 +6,11 @@ export const meta: ComponentMeta = {
6
6
  whenToUse: [
7
7
  'Visible option set where user picks one or more (view mode, filter categories)',
8
8
  'Segmented control pattern',
9
+ 'A visible grid of icon choices: wrap + maxRows (rows scroll past maxRows, one tab stop with arrow keys, sizes.touchTarget (44px) touch targets)',
9
10
  ],
10
11
  whenNotToUse: [
11
12
  'Dropdown selection — use Select',
12
- 'Many options (5+) — use Select for space efficiency',
13
+ 'Many text options (5+) — use Select for space efficiency; does not apply to icon-only choices, which keep options visible and open no layer',
13
14
  'Form checkboxes — use Checkbox group in Fieldset',
14
15
  ],
15
16
  relatedTo: [
@@ -30,5 +31,14 @@ export const meta: ComponentMeta = {
30
31
  <Toggle value="list">List view</Toggle>
31
32
  <Toggle value="grid">Grid view</Toggle>
32
33
  <Toggle value="board">Board view</Toggle>
34
+ </ToggleGroup>
35
+
36
+ // Icon choices: wraps onto rows, scrolls past three, one tab stop
37
+ <ToggleGroup wrap maxRows={3} size="small" value={[icon]} onValueChange={(v) => v[0] && setIcon(v[0])} aria-label="Icon">
38
+ {names.map((name) => (
39
+ <Toggle key={name} value={name} aria-label={name}>
40
+ <Icon name={name} size="sm" />
41
+ </Toggle>
42
+ ))}
33
43
  </ToggleGroup>`,
34
44
  }
@@ -3,7 +3,10 @@ import {expect, fn} from 'storybook/test'
3
3
  import {css, html} from 'react-strict-dom'
4
4
  import {ToggleGroup} from './ToggleGroup'
5
5
  import {Toggle} from '../Toggle/Toggle'
6
+ import {Icon, type IconName} from '../Icon'
7
+ import {SIZES_PX} from '@duro-app/tokens/keys'
6
8
  import {spacing} from '@duro-app/tokens/tokens/spacing.css'
9
+ import {sizes} from '@duro-app/tokens/tokens/sizes.css'
7
10
 
8
11
  const meta: Meta<typeof ToggleGroup> = {
9
12
  title: 'Components/ToggleGroup',
@@ -158,3 +161,157 @@ export const AllVariants: Story = {
158
161
  await expect(groups.length).toBe(4)
159
162
  },
160
163
  }
164
+
165
+ const ICON_NAMES: IconName[] = [
166
+ 'x-circle',
167
+ 'check-circle',
168
+ 'check-done',
169
+ 'clock',
170
+ 'forbidden',
171
+ 'info-circle',
172
+ 'alert-triangle',
173
+ 'shield',
174
+ 'lock',
175
+ 'key',
176
+ 'map',
177
+ 'layers',
178
+ 'repeat',
179
+ 'database',
180
+ 'shield-check',
181
+ 'route',
182
+ 'git-branch',
183
+ 'menu',
184
+ 'pin',
185
+ 'server',
186
+ 'hard-drive',
187
+ 'box',
188
+ 'image',
189
+ 'tag',
190
+ 'pie-chart',
191
+ 'users',
192
+ 'user-plus',
193
+ 'mail',
194
+ 'file-text',
195
+ 'plug',
196
+ 'search',
197
+ 'mic',
198
+ 'sun',
199
+ 'moon',
200
+ 'monitor',
201
+ 'contrast',
202
+ 'info-circle-filled',
203
+ 'alert-triangle-filled',
204
+ 'check-circle-filled',
205
+ 'x-circle-filled',
206
+ 'shield-filled',
207
+ 'lock-filled',
208
+ ]
209
+
210
+ const iconStyles = css.create({
211
+ frame: {width: sizes.panelSm},
212
+ })
213
+
214
+ function IconChoices({
215
+ names,
216
+ pressed,
217
+ size = 'small',
218
+ }: {
219
+ names: IconName[]
220
+ pressed: string
221
+ size?: 'small' | 'default'
222
+ }) {
223
+ return (
224
+ <html.div style={iconStyles.frame}>
225
+ <ToggleGroup wrap maxRows={3} size={size} defaultValue={[pressed]} aria-label="Icon">
226
+ {names.map((name) => (
227
+ <Toggle key={name} value={name} aria-label={name}>
228
+ <Icon name={name} size="sm" />
229
+ </Toggle>
230
+ ))}
231
+ </ToggleGroup>
232
+ </html.div>
233
+ )
234
+ }
235
+
236
+ export const IconChoicesStory: Story = {
237
+ name: 'Icon choices',
238
+ render: () => <IconChoices names={ICON_NAMES} pressed={ICON_NAMES[39]} />,
239
+ play: async ({canvas, canvasElement}) => {
240
+ const scrollTopBefore = document.scrollingElement?.scrollTop
241
+ await expect(ICON_NAMES.length).toBe(42)
242
+ const viewport = canvasElement.querySelector<HTMLElement>('[data-duro-scroll]')!
243
+ await expect(Math.abs(viewport.clientHeight - 118)).toBeLessThanOrEqual(2)
244
+
245
+ const pressed = canvas.getByRole('button', {name: ICON_NAMES[39]})
246
+ const other = canvas.getByRole('button', {name: ICON_NAMES[0]})
247
+ await expect(pressed).toHaveAttribute('aria-pressed', 'true')
248
+ for (const el of [pressed, other]) {
249
+ const cs = getComputedStyle(el)
250
+ await expect(cs.borderTopWidth).toBe('1px')
251
+ await expect(cs.borderRightWidth).toBe('1px')
252
+ await expect(cs.borderBottomWidth).toBe('1px')
253
+ await expect(cs.borderLeftWidth).toBe('1px')
254
+ // radii.sm
255
+ await expect(cs.borderTopLeftRadius).toBe('8px')
256
+ }
257
+
258
+ // the pressed toggle is visible inside the viewport
259
+ const v = viewport.getBoundingClientRect()
260
+ const p = pressed.getBoundingClientRect()
261
+ await expect(p.top).toBeGreaterThanOrEqual(v.top - 1)
262
+ await expect(p.bottom).toBeLessThanOrEqual(v.bottom + 1)
263
+ await expect(document.scrollingElement?.scrollTop).toBe(scrollTopBefore)
264
+ },
265
+ }
266
+
267
+ export const FiveIcons: Story = {
268
+ name: 'Five icons',
269
+ render: () => <IconChoices names={ICON_NAMES.slice(0, 5)} pressed={ICON_NAMES[0]} />,
270
+ play: async ({canvas}) => {
271
+ const group = canvas.getByRole('toolbar')
272
+ await expect(group.getBoundingClientRect().height).toBe(SIZES_PX.controlSm)
273
+ // exact: Toggle's styles read the same size tokens, and a drift must fail here
274
+ const toggle = canvas.getAllByRole('button')[0].getBoundingClientRect()
275
+ await expect(Math.abs(toggle.height - SIZES_PX.controlSm)).toBeLessThanOrEqual(0.5)
276
+ },
277
+ }
278
+
279
+ export const KeyboardRoving: Story = {
280
+ render: () => <IconChoices names={ICON_NAMES} pressed={ICON_NAMES[3]} />,
281
+ play: async ({canvas, userEvent}) => {
282
+ const btn = (i: number) => canvas.getByRole('button', {name: ICON_NAMES[i]})
283
+ await userEvent.tab()
284
+ await expect(btn(3)).toHaveFocus()
285
+ await userEvent.keyboard('{ArrowRight}')
286
+ await expect(btn(4)).toHaveFocus()
287
+ await userEvent.keyboard('{ArrowLeft}{ArrowLeft}')
288
+ await expect(btn(2)).toHaveFocus()
289
+ // Down moves to the next row, Up back
290
+ const top = btn(2).getBoundingClientRect().top
291
+ await userEvent.keyboard('{ArrowDown}')
292
+ const active = document.activeElement as HTMLElement
293
+ await expect(active.getBoundingClientRect().top).toBeGreaterThan(top + 20)
294
+ await userEvent.keyboard('{ArrowUp}')
295
+ await expect(btn(2)).toHaveFocus()
296
+ await userEvent.keyboard('{End}')
297
+ await expect(btn(41)).toHaveFocus()
298
+ await userEvent.keyboard('{Home}')
299
+ await expect(btn(0)).toHaveFocus()
300
+ await userEvent.keyboard('{Enter}')
301
+ await expect(btn(0)).toHaveAttribute('aria-pressed', 'true')
302
+ // a second tab leaves the group
303
+ await userEvent.tab()
304
+ await expect(canvas.getAllByRole('button')).not.toContain(document.activeElement)
305
+ },
306
+ }
307
+
308
+ export const DefaultSizeRows: Story = {
309
+ name: 'Default size rows',
310
+ render: () => (
311
+ <IconChoices names={ICON_NAMES.slice(0, 5)} pressed={ICON_NAMES[0]} size="default" />
312
+ ),
313
+ play: async ({canvas}) => {
314
+ const toggle = canvas.getAllByRole('button')[0].getBoundingClientRect()
315
+ await expect(Math.abs(toggle.height - SIZES_PX.controlMd)).toBeLessThanOrEqual(0.5)
316
+ },
317
+ }
@@ -1,9 +1,11 @@
1
- import {type ReactNode, useCallback} from 'react'
1
+ import {type ReactNode, useCallback, useLayoutEffect, useRef} from 'react'
2
2
  import {html} from 'react-strict-dom'
3
3
  import {useControllableValue} from '../../hooks/useControllableValue'
4
4
  import type {ToggleSize} from '../Toggle/Toggle'
5
5
  import {ToggleGroupContext, type Orientation} from './ToggleGroupContext'
6
6
  import {useFieldGroupLabelling} from '../Field/FieldContext'
7
+ import {ScrollArea} from '../ScrollArea/ScrollArea'
8
+ import {useRovingFocus} from './useRovingFocus'
7
9
  import {styles} from './styles.css'
8
10
 
9
11
  interface ToggleGroupProps {
@@ -21,6 +23,16 @@ interface ToggleGroupProps {
21
23
  orientation?: Orientation
22
24
  /** Size applied to all child toggles. */
23
25
  size?: ToggleSize
26
+ /** Items wrap onto rows, each with its own border (default false). Enables
27
+ * roving focus: one tab stop, arrow keys move between items. */
28
+ wrap?: boolean
29
+ // holds-until: the registry generator (packages/cli/scripts) reads a
30
+ // discriminated union. `{wrap?: false; maxRows?: never} | {wrap: true;
31
+ // maxRows?: number}` is the right type, but today it drops both props from
32
+ // the registry the CLI and MCP serve.
33
+ /** Caps a wrapping group at this many rows (plus half a row, hinting more)
34
+ * and scrolls the rest. Only with `wrap`; ignored without it. */
35
+ maxRows?: number
24
36
  /** Accessible name when the group is not inside a Field.Root (inside one,
25
37
  * the Field.Label names it). */
26
38
  'aria-label'?: string
@@ -37,6 +49,8 @@ export function ToggleGroup({
37
49
  disabled = false,
38
50
  orientation = 'horizontal',
39
51
  size = 'default',
52
+ wrap = false,
53
+ maxRows,
40
54
  children,
41
55
  ...labelling
42
56
  }: ToggleGroupProps) {
@@ -57,16 +71,79 @@ export function ToggleGroup({
57
71
  [value, multiple, setValue],
58
72
  )
59
73
 
74
+ const rootRef = useRef<HTMLDivElement>(null)
75
+ const roving = useRovingFocus({enabled: wrap, pressed: value, rootRef})
76
+ const scrolls = wrap && maxRows !== undefined
77
+ const pressedValue = value[0]
78
+
79
+ // bring the pressed toggle into view by moving the viewport itself, never
80
+ // scrollIntoView, which would scroll the page too
81
+ const {items} = roving
82
+ useLayoutEffect(() => {
83
+ if (!scrolls || pressedValue === undefined) return
84
+ const viewport = rootRef.current?.closest<HTMLElement>('[data-duro-scroll]')
85
+ if (!viewport) return
86
+ // Measured against the viewport's own box, not offsetTop: a positioned
87
+ // ancestor (a Drawer panel) would otherwise be the reference.
88
+ const centre = () => {
89
+ const el = items.current.get(pressedValue)
90
+ if (!el || viewport.clientHeight === 0) return false
91
+ const top =
92
+ el.getBoundingClientRect().top - viewport.getBoundingClientRect().top + viewport.scrollTop
93
+ const centered = top - (viewport.clientHeight - el.offsetHeight) / 2
94
+ const max = viewport.scrollHeight - viewport.clientHeight
95
+ viewport.scrollTop = Math.min(Math.max(centered, 0), Math.max(max, 0))
96
+ return true
97
+ }
98
+ if (centre()) return
99
+ // Not laid out yet (a container still opening): centre once it has a size.
100
+ const observer = new ResizeObserver(() => {
101
+ if (centre()) observer.disconnect()
102
+ })
103
+ observer.observe(viewport)
104
+ return () => observer.disconnect()
105
+ }, [scrolls, pressedValue, items])
106
+
107
+ const group = (
108
+ <html.div
109
+ ref={rootRef}
110
+ role="toolbar"
111
+ aria-orientation={orientation}
112
+ {...a11y}
113
+ style={[styles.root, orientation === 'vertical' && styles.vertical, wrap && styles.wrap]}
114
+ >
115
+ {children}
116
+ </html.div>
117
+ )
118
+
60
119
  return (
61
- <ToggleGroupContext.Provider value={{value, toggle, disabled, orientation, size}}>
62
- <html.div
63
- role="toolbar"
64
- aria-orientation={orientation}
65
- {...a11y}
66
- style={[styles.root, orientation === 'vertical' && styles.vertical]}
67
- >
68
- {children}
69
- </html.div>
120
+ <ToggleGroupContext.Provider
121
+ value={{
122
+ value,
123
+ toggle,
124
+ disabled,
125
+ orientation,
126
+ size,
127
+ wrap,
128
+ tabStopValue: roving.tabStopValue,
129
+ register: roving.register,
130
+ onItemFocus: roving.onItemFocus,
131
+ }}
132
+ >
133
+ {scrolls ? (
134
+ <ScrollArea.Root>
135
+ <ScrollArea.Viewport
136
+ rovingOwner
137
+ style={size === 'small' ? styles.maxRowsSmall(maxRows) : styles.maxRowsDefault(maxRows)}
138
+ >
139
+ <ScrollArea.Content>
140
+ <html.div style={styles.scrollContent}>{group}</html.div>
141
+ </ScrollArea.Content>
142
+ </ScrollArea.Viewport>
143
+ </ScrollArea.Root>
144
+ ) : (
145
+ group
146
+ )}
70
147
  </ToggleGroupContext.Provider>
71
148
  )
72
149
  }
@@ -10,6 +10,12 @@ export interface ToggleGroupContextValue {
10
10
  disabled: boolean
11
11
  orientation: Orientation
12
12
  size: ToggleSize
13
+ /** Items wrap onto rows with their own borders (and roving focus). */
14
+ wrap: boolean
15
+ /** The item holding the group's single tab stop (wrap only). */
16
+ tabStopValue: string | null
17
+ register: (itemValue: string, el: HTMLElement) => () => void
18
+ onItemFocus: (itemValue: string) => void
13
19
  }
14
20
 
15
21
  export const ToggleGroupContext = createContext<ToggleGroupContextValue | null>(null)