@remix-run/ui 0.3.0 → 0.4.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 (308) hide show
  1. package/README.md +50 -164
  2. package/dist/accordion/index.d.ts +46 -0
  3. package/dist/accordion/index.js +142 -0
  4. package/dist/accordion/index.js.map +1 -0
  5. package/dist/accordion/primitives.d.ts +104 -0
  6. package/dist/accordion/primitives.js +312 -0
  7. package/dist/accordion/primitives.js.map +1 -0
  8. package/dist/{components/anchor/anchor.js → anchor/index.js} +1 -1
  9. package/dist/anchor/index.js.map +1 -0
  10. package/dist/animation/demos/reordering.js +9 -6
  11. package/dist/animation/demos/reordering.js.map +1 -1
  12. package/dist/{components/breadcrumbs/breadcrumbs.js → breadcrumbs/index.js} +19 -19
  13. package/dist/breadcrumbs/index.js.map +1 -0
  14. package/dist/button/index.d.ts +15 -0
  15. package/dist/button/index.js +128 -0
  16. package/dist/button/index.js.map +1 -0
  17. package/dist/checkbox/index.d.ts +14 -0
  18. package/dist/checkbox/index.js +125 -0
  19. package/dist/checkbox/index.js.map +1 -0
  20. package/dist/combobox/index.d.ts +21 -0
  21. package/dist/combobox/index.js +58 -0
  22. package/dist/combobox/index.js.map +1 -0
  23. package/dist/{components/combobox/combobox.d.ts → combobox/primitives.d.ts} +3 -7
  24. package/dist/{components/combobox/combobox.js → combobox/primitives.js} +8 -61
  25. package/dist/combobox/primitives.js.map +1 -0
  26. package/dist/input/index.d.ts +13 -0
  27. package/dist/input/index.js +121 -0
  28. package/dist/input/index.js.map +1 -0
  29. package/dist/listbox/flash-attribute.js.map +1 -0
  30. package/dist/{components/listbox/listbox.d.ts → listbox/index.d.ts} +2 -6
  31. package/dist/{components/listbox/listbox.js → listbox/index.js} +4 -82
  32. package/dist/listbox/index.js.map +1 -0
  33. package/dist/menu/hover-aim.js.map +1 -0
  34. package/dist/menu/index.d.ts +42 -0
  35. package/dist/menu/index.js +250 -0
  36. package/dist/menu/index.js.map +1 -0
  37. package/dist/{components/menu/menu.d.ts → menu/primitives.d.ts} +3 -32
  38. package/dist/{components/menu/menu.js → menu/primitives.js} +97 -314
  39. package/dist/menu/primitives.js.map +1 -0
  40. package/dist/{components/popover/popover.d.ts → popover/index.d.ts} +2 -4
  41. package/dist/{components/popover/popover.js → popover/index.js} +5 -48
  42. package/dist/popover/index.js.map +1 -0
  43. package/dist/{interactions/outside-click/outside-click-mixin.js → popover/outside-click.js} +1 -1
  44. package/dist/popover/outside-click.js.map +1 -0
  45. package/dist/popover/scroll-lock.d.ts +1 -0
  46. package/dist/{utils → popover}/scroll-lock.js +0 -18
  47. package/dist/popover/scroll-lock.js.map +1 -0
  48. package/dist/radio/index.d.ts +12 -0
  49. package/dist/radio/index.js +77 -0
  50. package/dist/radio/index.js.map +1 -0
  51. package/dist/runtime/reconcile.js +11 -5
  52. package/dist/runtime/reconcile.js.map +1 -1
  53. package/dist/runtime/vdom.js +3 -0
  54. package/dist/runtime/vdom.js.map +1 -1
  55. package/dist/select/index.d.ts +19 -0
  56. package/dist/select/index.js +95 -0
  57. package/dist/select/index.js.map +1 -0
  58. package/dist/select/keydown.js.map +1 -0
  59. package/dist/{components/select/select.d.ts → select/primitives.d.ts} +4 -6
  60. package/dist/{components/select/select.js → select/primitives.js} +11 -63
  61. package/dist/select/primitives.js.map +1 -0
  62. package/dist/server/stream.js +2 -2
  63. package/dist/server/stream.js.map +1 -1
  64. package/dist/shared/focus-styles.d.ts +1 -0
  65. package/dist/shared/focus-styles.js +2 -0
  66. package/dist/shared/focus-styles.js.map +1 -0
  67. package/dist/shared/icons.d.ts +9 -0
  68. package/dist/shared/icons.js +42 -0
  69. package/dist/shared/icons.js.map +1 -0
  70. package/dist/shared/listbox-popover-styles.d.ts +6 -0
  71. package/dist/shared/listbox-popover-styles.js +137 -0
  72. package/dist/shared/listbox-popover-styles.js.map +1 -0
  73. package/dist/shared/style-values.d.ts +78 -0
  74. package/dist/{theme/presets/rmx-01/index.js → shared/style-values.js} +3 -34
  75. package/dist/shared/style-values.js.map +1 -0
  76. package/dist/{interactions/typeahead/typeahead-mixin.js → shared/typeahead.js} +1 -1
  77. package/dist/shared/typeahead.js.map +1 -0
  78. package/dist/shared/wait-for-css-transition.js.map +1 -0
  79. package/dist/shared/wait.js.map +1 -0
  80. package/dist/tabs/index.d.ts +32 -0
  81. package/dist/tabs/index.js +157 -0
  82. package/dist/tabs/index.js.map +1 -0
  83. package/dist/tabs/primitives.d.ts +57 -0
  84. package/dist/tabs/primitives.js +238 -0
  85. package/dist/tabs/primitives.js.map +1 -0
  86. package/dist/toggle/index.d.ts +9 -0
  87. package/dist/toggle/index.js +108 -0
  88. package/dist/toggle/index.js.map +1 -0
  89. package/dist/toggle/primitives.d.ts +29 -0
  90. package/dist/toggle/primitives.js +127 -0
  91. package/dist/toggle/primitives.js.map +1 -0
  92. package/package.json +73 -44
  93. package/src/{components/accordion → accordion}/README.md +50 -14
  94. package/src/accordion/index.tsx +285 -0
  95. package/src/accordion/primitives.tsx +512 -0
  96. package/src/{components/anchor → anchor}/README.md +37 -2
  97. package/src/animation/demos/reordering.tsx +9 -6
  98. package/src/{components/breadcrumbs → breadcrumbs}/README.md +4 -4
  99. package/src/{components/breadcrumbs/breadcrumbs.tsx → breadcrumbs/index.tsx} +18 -18
  100. package/src/button/README.md +44 -0
  101. package/src/button/index.ts +168 -0
  102. package/src/checkbox/README.md +59 -0
  103. package/src/checkbox/index.ts +162 -0
  104. package/src/{components/combobox → combobox}/README.md +58 -9
  105. package/src/combobox/index.tsx +112 -0
  106. package/src/{components/combobox/combobox.tsx → combobox/primitives.tsx} +5 -91
  107. package/src/input/README.md +52 -0
  108. package/src/input/index.ts +147 -0
  109. package/src/{components/listbox → listbox}/README.md +9 -41
  110. package/src/{components/listbox/listbox.ts → listbox/index.ts} +2 -87
  111. package/src/{components/menu → menu}/README.md +55 -14
  112. package/src/menu/index.tsx +354 -0
  113. package/src/{components/menu/menu.tsx → menu/primitives.tsx} +99 -413
  114. package/src/{components/popover → popover}/README.md +20 -39
  115. package/src/{components/popover/popover.ts → popover/index.ts} +3 -57
  116. package/src/{utils → popover}/scroll-lock.ts +0 -28
  117. package/src/radio/README.md +53 -0
  118. package/src/radio/index.ts +100 -0
  119. package/src/runtime/demos/readme.demo.tsx +16 -14
  120. package/src/runtime/reconcile.ts +11 -5
  121. package/src/runtime/vdom.ts +3 -0
  122. package/src/{components/select → select}/README.md +29 -19
  123. package/src/select/index.tsx +159 -0
  124. package/src/{components/select/select.tsx → select/primitives.tsx} +10 -98
  125. package/src/server/stream.ts +3 -3
  126. package/src/shared/focus-styles.ts +2 -0
  127. package/src/shared/icons.tsx +62 -0
  128. package/src/shared/listbox-popover-styles.ts +146 -0
  129. package/src/shared/style-values.ts +165 -0
  130. package/src/tabs/README.md +141 -0
  131. package/src/tabs/index.tsx +244 -0
  132. package/src/tabs/primitives.ts +365 -0
  133. package/src/toggle/README.md +56 -0
  134. package/src/toggle/index.tsx +134 -0
  135. package/src/toggle/primitives.ts +192 -0
  136. package/dist/animation/demos/animation.demo.d.ts +0 -5
  137. package/dist/animation/demos/animation.demo.js +0 -92
  138. package/dist/animation/demos/animation.demo.js.map +0 -1
  139. package/dist/animation/demos/spring.demo.d.ts +0 -6
  140. package/dist/animation/demos/spring.demo.js +0 -304
  141. package/dist/animation/demos/spring.demo.js.map +0 -1
  142. package/dist/components/accordion/accordion.d.ts +0 -92
  143. package/dist/components/accordion/accordion.js +0 -335
  144. package/dist/components/accordion/accordion.js.map +0 -1
  145. package/dist/components/accordion/demos/card.demo.d.ts +0 -7
  146. package/dist/components/accordion/demos/card.demo.js +0 -59
  147. package/dist/components/accordion/demos/card.demo.js.map +0 -1
  148. package/dist/components/accordion/demos/multiple.demo.d.ts +0 -7
  149. package/dist/components/accordion/demos/multiple.demo.js +0 -20
  150. package/dist/components/accordion/demos/multiple.demo.js.map +0 -1
  151. package/dist/components/accordion/demos/overview.demo.d.ts +0 -7
  152. package/dist/components/accordion/demos/overview.demo.js +0 -20
  153. package/dist/components/accordion/demos/overview.demo.js.map +0 -1
  154. package/dist/components/anchor/anchor.js.map +0 -1
  155. package/dist/components/breadcrumbs/basic.demo.d.ts +0 -6
  156. package/dist/components/breadcrumbs/basic.demo.js +0 -15
  157. package/dist/components/breadcrumbs/basic.demo.js.map +0 -1
  158. package/dist/components/breadcrumbs/breadcrumbs.js.map +0 -1
  159. package/dist/components/breadcrumbs/separator.demo.d.ts +0 -6
  160. package/dist/components/breadcrumbs/separator.demo.js +0 -15
  161. package/dist/components/breadcrumbs/separator.demo.js.map +0 -1
  162. package/dist/components/button/button.d.ts +0 -105
  163. package/dist/components/button/button.js +0 -207
  164. package/dist/components/button/button.js.map +0 -1
  165. package/dist/components/button/demos/aliases.demo.d.ts +0 -7
  166. package/dist/components/button/demos/aliases.demo.js +0 -19
  167. package/dist/components/button/demos/aliases.demo.js.map +0 -1
  168. package/dist/components/button/demos/basic.demo.d.ts +0 -7
  169. package/dist/components/button/demos/basic.demo.js +0 -20
  170. package/dist/components/button/demos/basic.demo.js.map +0 -1
  171. package/dist/components/button/demos/states.demo.d.ts +0 -7
  172. package/dist/components/button/demos/states.demo.js +0 -30
  173. package/dist/components/button/demos/states.demo.js.map +0 -1
  174. package/dist/components/combobox/combobox.js.map +0 -1
  175. package/dist/components/combobox/overview.demo.d.ts +0 -7
  176. package/dist/components/combobox/overview.demo.js +0 -154
  177. package/dist/components/combobox/overview.demo.js.map +0 -1
  178. package/dist/components/glyph/glyph.d.ts +0 -14
  179. package/dist/components/glyph/glyph.js +0 -65
  180. package/dist/components/glyph/glyph.js.map +0 -1
  181. package/dist/components/listbox/listbox.js.map +0 -1
  182. package/dist/components/listbox/overview.demo.d.ts +0 -7
  183. package/dist/components/listbox/overview.demo.js +0 -65
  184. package/dist/components/listbox/overview.demo.js.map +0 -1
  185. package/dist/components/menu/demos/bubbling.demo.d.ts +0 -6
  186. package/dist/components/menu/demos/bubbling.demo.js +0 -16
  187. package/dist/components/menu/demos/bubbling.demo.js.map +0 -1
  188. package/dist/components/menu/demos/context-trigger.demo.d.ts +0 -7
  189. package/dist/components/menu/demos/context-trigger.demo.js +0 -87
  190. package/dist/components/menu/demos/context-trigger.demo.js.map +0 -1
  191. package/dist/components/menu/demos/overview.demo.d.ts +0 -7
  192. package/dist/components/menu/demos/overview.demo.js +0 -33
  193. package/dist/components/menu/demos/overview.demo.js.map +0 -1
  194. package/dist/components/menu/hover-aim.js.map +0 -1
  195. package/dist/components/menu/menu.js.map +0 -1
  196. package/dist/components/popover/overview.demo.d.ts +0 -7
  197. package/dist/components/popover/overview.demo.js +0 -65
  198. package/dist/components/popover/overview.demo.js.map +0 -1
  199. package/dist/components/popover/popover.js.map +0 -1
  200. package/dist/components/select/deconstructed.demo.d.ts +0 -7
  201. package/dist/components/select/deconstructed.demo.js +0 -54
  202. package/dist/components/select/deconstructed.demo.js.map +0 -1
  203. package/dist/components/select/overview.demo.d.ts +0 -6
  204. package/dist/components/select/overview.demo.js +0 -28
  205. package/dist/components/select/overview.demo.js.map +0 -1
  206. package/dist/components/select/select.js.map +0 -1
  207. package/dist/components/separator/separator.d.ts +0 -2
  208. package/dist/components/separator/separator.js +0 -15
  209. package/dist/components/separator/separator.js.map +0 -1
  210. package/dist/components/tabs/tabs.d.ts +0 -78
  211. package/dist/components/tabs/tabs.js +0 -339
  212. package/dist/components/tabs/tabs.js.map +0 -1
  213. package/dist/interactions/keydown/keydown.js.map +0 -1
  214. package/dist/interactions/outside-click/outside-click-mixin.js.map +0 -1
  215. package/dist/interactions/typeahead/typeahead-mixin.js.map +0 -1
  216. package/dist/runtime/demos/basic.demo.d.ts +0 -6
  217. package/dist/runtime/demos/basic.demo.js +0 -16
  218. package/dist/runtime/demos/basic.demo.js.map +0 -1
  219. package/dist/runtime/demos/controlled-uncontrolled-values.demo.d.ts +0 -6
  220. package/dist/runtime/demos/controlled-uncontrolled-values.demo.js +0 -77
  221. package/dist/runtime/demos/controlled-uncontrolled-values.demo.js.map +0 -1
  222. package/dist/runtime/demos/keyed-list.demo.d.ts +0 -6
  223. package/dist/runtime/demos/keyed-list.demo.js +0 -62
  224. package/dist/runtime/demos/keyed-list.demo.js.map +0 -1
  225. package/dist/runtime/demos/readme.demo.d.ts +0 -5
  226. package/dist/runtime/demos/readme.demo.js +0 -396
  227. package/dist/runtime/demos/readme.demo.js.map +0 -1
  228. package/dist/theme/contract.d.ts +0 -61
  229. package/dist/theme/contract.js +0 -131
  230. package/dist/theme/contract.js.map +0 -1
  231. package/dist/theme/glyph-contract.d.ts +0 -11
  232. package/dist/theme/glyph-contract.js +0 -34
  233. package/dist/theme/glyph-contract.js.map +0 -1
  234. package/dist/theme/presets/rmx-01/glyphs.d.ts +0 -2
  235. package/dist/theme/presets/rmx-01/glyphs.js +0 -252
  236. package/dist/theme/presets/rmx-01/glyphs.js.map +0 -1
  237. package/dist/theme/presets/rmx-01/index.d.ts +0 -4
  238. package/dist/theme/presets/rmx-01/index.js.map +0 -1
  239. package/dist/theme/runtime.d.ts +0 -2
  240. package/dist/theme/runtime.js +0 -84
  241. package/dist/theme/runtime.js.map +0 -1
  242. package/dist/theme/theme.d.ts +0 -6
  243. package/dist/theme/theme.js +0 -5
  244. package/dist/theme/theme.js.map +0 -1
  245. package/dist/utils/flash-attribute.js.map +0 -1
  246. package/dist/utils/scroll-lock.d.ts +0 -3
  247. package/dist/utils/scroll-lock.js.map +0 -1
  248. package/dist/utils/wait-for-css-transition.js.map +0 -1
  249. package/dist/utils/wait.js.map +0 -1
  250. package/src/animation/demos/animation.demo.tsx +0 -194
  251. package/src/animation/demos/spring.demo.tsx +0 -385
  252. package/src/components/accordion/accordion.tsx +0 -565
  253. package/src/components/accordion/demos/card.demo.tsx +0 -114
  254. package/src/components/accordion/demos/multiple.demo.tsx +0 -57
  255. package/src/components/accordion/demos/overview.demo.tsx +0 -57
  256. package/src/components/breadcrumbs/basic.demo.tsx +0 -18
  257. package/src/components/breadcrumbs/separator.demo.tsx +0 -19
  258. package/src/components/button/README.md +0 -44
  259. package/src/components/button/button.tsx +0 -286
  260. package/src/components/button/demos/aliases.demo.tsx +0 -28
  261. package/src/components/button/demos/basic.demo.tsx +0 -31
  262. package/src/components/button/demos/states.demo.tsx +0 -53
  263. package/src/components/combobox/overview.demo.tsx +0 -200
  264. package/src/components/glyph/README.md +0 -72
  265. package/src/components/glyph/glyph.tsx +0 -110
  266. package/src/components/listbox/overview.demo.tsx +0 -102
  267. package/src/components/menu/demos/bubbling.demo.tsx +0 -35
  268. package/src/components/menu/demos/context-trigger.demo.tsx +0 -133
  269. package/src/components/menu/demos/overview.demo.tsx +0 -77
  270. package/src/components/popover/overview.demo.tsx +0 -144
  271. package/src/components/select/deconstructed.demo.tsx +0 -96
  272. package/src/components/select/overview.demo.tsx +0 -52
  273. package/src/components/separator/separator.ts +0 -19
  274. package/src/components/tabs/README.md +0 -105
  275. package/src/components/tabs/tabs.tsx +0 -504
  276. package/src/runtime/demos/basic.demo.tsx +0 -21
  277. package/src/runtime/demos/controlled-uncontrolled-values.demo.tsx +0 -177
  278. package/src/runtime/demos/keyed-list.demo.tsx +0 -94
  279. package/src/theme/README.md +0 -103
  280. package/src/theme/contract.ts +0 -215
  281. package/src/theme/glyph-contract.ts +0 -59
  282. package/src/theme/presets/rmx-01/glyphs.tsx +0 -308
  283. package/src/theme/presets/rmx-01/index.ts +0 -123
  284. package/src/theme/runtime.ts +0 -119
  285. package/src/theme/theme.ts +0 -15
  286. package/src/utils/scroll-lock/README.md +0 -33
  287. /package/dist/{components/anchor/anchor.d.ts → anchor/index.d.ts} +0 -0
  288. /package/dist/{components/breadcrumbs/breadcrumbs.d.ts → breadcrumbs/index.d.ts} +0 -0
  289. /package/dist/{utils → listbox}/flash-attribute.d.ts +0 -0
  290. /package/dist/{utils → listbox}/flash-attribute.js +0 -0
  291. /package/dist/{components/menu → menu}/hover-aim.d.ts +0 -0
  292. /package/dist/{components/menu → menu}/hover-aim.js +0 -0
  293. /package/dist/{interactions/outside-click/outside-click-mixin.d.ts → popover/outside-click.d.ts} +0 -0
  294. /package/dist/{interactions/keydown → select}/keydown.d.ts +0 -0
  295. /package/dist/{interactions/keydown → select}/keydown.js +0 -0
  296. /package/dist/{interactions/typeahead/typeahead-mixin.d.ts → shared/typeahead.d.ts} +0 -0
  297. /package/dist/{utils → shared}/wait-for-css-transition.d.ts +0 -0
  298. /package/dist/{utils → shared}/wait-for-css-transition.js +0 -0
  299. /package/dist/{utils → shared}/wait.d.ts +0 -0
  300. /package/dist/{utils → shared}/wait.js +0 -0
  301. /package/src/{components/anchor/anchor.ts → anchor/index.ts} +0 -0
  302. /package/src/{utils → listbox}/flash-attribute.ts +0 -0
  303. /package/src/{components/menu → menu}/hover-aim.ts +0 -0
  304. /package/src/{interactions/outside-click/outside-click-mixin.ts → popover/outside-click.ts} +0 -0
  305. /package/src/{interactions/keydown → select}/keydown.ts +0 -0
  306. /package/src/{interactions/typeahead/typeahead-mixin.ts → shared/typeahead.ts} +0 -0
  307. /package/src/{utils → shared}/wait-for-css-transition.ts +0 -0
  308. /package/src/{utils → shared}/wait.ts +0 -0
@@ -1,11 +1,8 @@
1
1
  import {
2
2
  attrs,
3
- createElement,
4
3
  createMixin,
5
- css,
6
4
  on,
7
5
  ref,
8
- type CSSMixinDescriptor,
9
6
  type Dispatched,
10
7
  type ElementProps,
11
8
  type Handle,
@@ -14,19 +11,16 @@ import {
14
11
  type Props,
15
12
  type RemixNode,
16
13
  } from '@remix-run/ui'
17
- import * as button from '../button/button.tsx'
18
- import { Glyph } from '../glyph/glyph.tsx'
19
- import * as listbox from '../listbox/listbox.ts'
20
- import * as popover from '../popover/popover.ts'
21
- import { theme } from '../../theme/theme.ts'
22
- import { hiddenTypeahead } from '../../interactions/typeahead/typeahead-mixin.ts'
23
- import { onKeyDown } from '../../interactions/keydown/keydown.ts'
24
- import { waitForCssTransition } from '../../utils/wait-for-css-transition.ts'
25
- import { wait } from '../../utils/wait.ts'
26
- import type { AnchorOptions } from '../anchor/anchor.ts'
14
+ import * as listbox from '../listbox/index.ts'
15
+ import * as popover from '../popover/index.ts'
16
+ import { hiddenTypeahead } from '../shared/typeahead.ts'
17
+ import { waitForCssTransition } from '../shared/wait-for-css-transition.ts'
18
+ import { wait } from '../shared/wait.ts'
19
+ import { onKeyDown } from './keydown.ts'
20
+ import type { AnchorOptions } from '../anchor/index.ts'
27
21
 
28
22
  const SELECT_CHANGE_EVENT = 'rmx:select-change' as const
29
- const LABEL_SWAP_DELAY_MS = 75
23
+ const DEFAULT_LABEL_SWAP_DELAY_MS = 75
30
24
 
31
25
  type SelectChangeHandler<target extends HTMLElement> = (
32
26
  event: Dispatched<SelectChangeEvent, target>,
@@ -65,6 +59,7 @@ export interface SelectContextProps {
65
59
  defaultLabel: string
66
60
  defaultValue?: string | null
67
61
  disabled?: boolean
62
+ labelSwapDelayMs?: number
68
63
  name?: string
69
64
  }
70
65
 
@@ -236,7 +231,7 @@ function SelectProvider(handle: Handle<SelectContextProps, SelectContextValue>):
236
231
  }
237
232
 
238
233
  await Promise.all([handle.update(), waitForCssTransition(surfaceRef, handle.signal)])
239
- await wait(LABEL_SWAP_DELAY_MS) // UX delay label swap for clear value change
234
+ await wait(handle.props.labelSwapDelayMs ?? DEFAULT_LABEL_SWAP_DELAY_MS)
240
235
  if (handle.signal.aborted) return
241
236
  displayedLabel = selectedLabel
242
237
  state = 'closed'
@@ -495,41 +490,6 @@ const hiddenInputMixin: MixinFactory<HTMLInputElement, [], ElementProps> = creat
495
490
  })
496
491
  })
497
492
 
498
- const selectTriggerCss: CSSMixinDescriptor = css({
499
- minHeight: theme.control.height.sm,
500
- width: '100%',
501
- paddingInline: theme.space.md,
502
- paddingInlineEnd: theme.space.sm,
503
- display: 'grid',
504
- gridTemplateColumns: 'minmax(0, 1fr) auto',
505
- alignItems: 'center',
506
- gap: theme.space.sm,
507
- borderRadius: theme.radius.md,
508
- backgroundImage: 'none',
509
- border: '0.5px solid transparent',
510
- boxShadow: 'none',
511
- fontSize: theme.fontSize.xs,
512
- textAlign: 'left',
513
- backgroundColor: theme.surface.lvl3,
514
- color: theme.colors.text.secondary,
515
- '&:hover, &:focus-visible, &[aria-expanded="true"], &[aria-expanded="true"]:hover, &[aria-expanded="true"]:focus-visible':
516
- {
517
- backgroundColor: theme.surface.lvl4,
518
- color: theme.colors.text.primary,
519
- },
520
- '&:active': {
521
- backgroundColor: theme.surface.lvl3,
522
- },
523
- '&:focus-visible': {
524
- outline: `2px solid ${theme.colors.focus.ring}`,
525
- outlineOffset: '2px',
526
- },
527
- '&:disabled': {
528
- opacity: 0.6,
529
- },
530
- })
531
-
532
- export const triggerStyle = selectTriggerCss
533
493
  export const Context = SelectProvider
534
494
  export const hiddenInput = hiddenInputMixin
535
495
  export const list = listMixin
@@ -552,51 +512,3 @@ export function onSelectChange<target extends HTMLElement>(
552
512
  ): ReturnType<typeof on<target, typeof SELECT_CHANGE_EVENT>> {
553
513
  return on(SELECT_CHANGE_EVENT, handler, captureBoolean)
554
514
  }
555
-
556
- function SelectLabel(handle: Handle): () => RemixNode {
557
- let context = getSelectContext(handle)
558
-
559
- return () => <span mix={button.labelStyle}>{context.displayedLabel}</span>
560
- }
561
-
562
- export function Select(handle: Handle<SelectProps>): () => RemixNode {
563
- return () => {
564
- let { children, defaultLabel, defaultValue, disabled, name, mix, ...buttonProps } = handle.props
565
-
566
- return (
567
- <select.Context
568
- defaultLabel={defaultLabel}
569
- defaultValue={defaultValue}
570
- disabled={disabled}
571
- name={name}
572
- >
573
- <button {...buttonProps} mix={[button.baseStyle, triggerStyle, select.trigger(), mix]}>
574
- <SelectLabel />
575
- <Glyph mix={button.iconStyle} name="chevronVertical" />
576
- </button>
577
- <popover.Context>
578
- <div mix={[popover.surfaceStyle, select.popover()]}>
579
- <div mix={[popover.contentStyle, listbox.listStyle, select.list()]}>{children}</div>
580
- </div>
581
- </popover.Context>
582
- {name && <input mix={select.hiddenInput()} />}
583
- </select.Context>
584
- )
585
- }
586
- }
587
-
588
- export function Option(handle: Handle<SelectOptionProps>): () => RemixNode {
589
- return () => {
590
- let { label, value, disabled, textValue, children, mix, ...divProps } = handle.props
591
-
592
- return (
593
- <div
594
- {...divProps}
595
- mix={[listbox.optionStyle, select.option({ value, label, disabled, textValue }), mix]}
596
- >
597
- <Glyph mix={listbox.glyphStyle} name="check" />
598
- <span mix={listbox.labelStyle}>{children ?? handle.props.label}</span>
599
- </div>
600
- )
601
- }
602
- }
@@ -1052,17 +1052,17 @@ async function resolveClientEntries(
1052
1052
  ): Promise<void> {
1053
1053
  if (context.unresolvedHydrationData.size === 0) return
1054
1054
 
1055
- let resolvedEntries = new Map<string, ResolvedClientEntry>()
1055
+ let resolvedEntries = new Map<EntryComponent, ResolvedClientEntry>()
1056
1056
 
1057
1057
  for (let [hydrationId, unresolvedHydrationData] of context.unresolvedHydrationData) {
1058
1058
  let { entryId, component, props } = unresolvedHydrationData
1059
- let resolvedEntry = resolvedEntries.get(entryId)
1059
+ let resolvedEntry = resolvedEntries.get(component)
1060
1060
  if (!resolvedEntry) {
1061
1061
  resolvedEntry = resolveClientEntry
1062
1062
  ? await Promise.resolve(resolveClientEntry(entryId, component))
1063
1063
  : resolveDefaultClientEntry(entryId, component)
1064
1064
  validateResolvedClientEntry(entryId, resolvedEntry)
1065
- resolvedEntries.set(entryId, resolvedEntry)
1065
+ resolvedEntries.set(component, resolvedEntry)
1066
1066
  }
1067
1067
 
1068
1068
  context.hydrationData.set(hydrationId, {
@@ -0,0 +1,2 @@
1
+ export const controlFocusShadow =
2
+ '0 2px 3px -1px rgba(0, 0, 0, 0.04), 0 3px 4px -1.5px rgba(0, 0, 0, 0.04), 0 4px 5px -2px rgba(0, 0, 0, 0.04), 0 0 0 1px #3573F6, 0 0 0 4px rgba(53, 115, 246, 0.1), 0 6px 32px 4px rgba(53, 115, 246, 0.08), inset 0 0 8px 1px rgba(53, 115, 246, 0.05)'
@@ -0,0 +1,62 @@
1
+ import { createElement } from '@remix-run/ui'
2
+ import type { Handle, Props, RemixElement } from '@remix-run/ui'
3
+
4
+ type IconProps = Omit<Props<'svg'>, 'children'>
5
+
6
+ function icon(
7
+ handle: Handle<IconProps>,
8
+ children: RemixElement | readonly RemixElement[],
9
+ ): RemixElement {
10
+ let hiddenByDefault =
11
+ handle.props['aria-hidden'] === undefined &&
12
+ handle.props['aria-label'] === undefined &&
13
+ handle.props['aria-labelledby'] === undefined
14
+
15
+ return createElement(
16
+ 'svg',
17
+ {
18
+ ...handle.props,
19
+ 'aria-hidden': hiddenByDefault ? true : handle.props['aria-hidden'],
20
+ fill: handle.props.fill ?? 'none',
21
+ viewBox: handle.props.viewBox ?? '0 0 16 16',
22
+ xmlns: 'http://www.w3.org/2000/svg',
23
+ },
24
+ children,
25
+ )
26
+ }
27
+
28
+ function strokedPath(d: string): RemixElement {
29
+ return createElement('path', {
30
+ d,
31
+ fill: 'none',
32
+ stroke: 'currentColor',
33
+ strokeLinecap: 'round',
34
+ strokeLinejoin: 'round',
35
+ strokeWidth: '1.5',
36
+ })
37
+ }
38
+
39
+ export function AddIcon(handle: Handle<IconProps>): () => RemixElement {
40
+ return () => icon(handle, strokedPath('M8 3.25v9.5M3.25 8h9.5'))
41
+ }
42
+
43
+ export function CheckIcon(handle: Handle<IconProps>): () => RemixElement {
44
+ return () => icon(handle, strokedPath('m3.5 8.25 2.75 2.75L12.5 4.75'))
45
+ }
46
+
47
+ export function ChevronDownIcon(handle: Handle<IconProps>): () => RemixElement {
48
+ return () => icon(handle, strokedPath('m3.75 6.25 4.25 4 4.25-4'))
49
+ }
50
+
51
+ export function ChevronRightIcon(handle: Handle<IconProps>): () => RemixElement {
52
+ return () => icon(handle, strokedPath('m6 4 4 4-4 4'))
53
+ }
54
+
55
+ export function ChevronVerticalIcon(handle: Handle<IconProps>): () => RemixElement {
56
+ return () =>
57
+ icon(handle, [strokedPath('m3.75 6.5 4.25-4 4.25 4'), strokedPath('m3.75 9.5 4.25 4 4.25-4')])
58
+ }
59
+
60
+ export function SearchIcon(handle: Handle<IconProps>): () => RemixElement {
61
+ return () => icon(handle, strokedPath('M7.25 12.25a5 5 0 1 1 0-10 5 5 0 0 1 0 10Zm3.54-1.46 3 3'))
62
+ }
@@ -0,0 +1,146 @@
1
+ import { css, type CSSMixinDescriptor } from '@remix-run/ui'
2
+
3
+ import { componentStyleValues as styles } from './style-values.ts'
4
+
5
+ const popupViewportClampMaxHeight = '50dvh'
6
+
7
+ const popoverSurfaceTransitionCss: CSSMixinDescriptor = css({
8
+ opacity: 0,
9
+ '&:popover-open': {
10
+ opacity: 1,
11
+ },
12
+ '&:not(:popover-open)': {
13
+ pointerEvents: 'none',
14
+ transition: 'opacity 180ms ease-in, overlay 180ms ease-in, display 180ms ease-in',
15
+ transitionBehavior: 'allow-discrete',
16
+ },
17
+ })
18
+
19
+ const popoverSurfaceCss: CSSMixinDescriptor = css({
20
+ position: 'fixed',
21
+ inset: 'auto',
22
+ margin: 0,
23
+ padding: styles.space.none,
24
+ display: 'flex',
25
+ flexDirection: 'column',
26
+ minHeight: 0,
27
+ minWidth: '12rem',
28
+ maxWidth: `min(24rem, calc(100vw - (${styles.space.lg} * 2)))`,
29
+ maxHeight: popupViewportClampMaxHeight,
30
+ border: `1px solid ${styles.colors.border.subtle}`,
31
+ borderRadius: styles.radius.lg,
32
+ backgroundColor: styles.surface.lvl0,
33
+ color: styles.colors.text.primary,
34
+ overflow: 'hidden',
35
+ boxShadow: `${styles.shadow.xs}, ${styles.shadow.md}`,
36
+ '&::backdrop': {
37
+ background: 'transparent',
38
+ },
39
+ })
40
+
41
+ const listCss: CSSMixinDescriptor = css({
42
+ display: 'flex',
43
+ flexDirection: 'column',
44
+ flex: '1 1 auto',
45
+ minHeight: 0,
46
+ paddingBlock: styles.space.xs,
47
+ paddingInline: styles.space.none,
48
+ overflow: 'auto',
49
+ overscrollBehavior: 'contain',
50
+ outline: 'none',
51
+ userSelect: 'none',
52
+ WebkitUserSelect: 'none',
53
+ '--rmx-ui-item-inset': `calc(${styles.space.sm} + ${styles.space.xs})`,
54
+ '--rmx-ui-item-indicator-gap': styles.space.xs,
55
+ '--rmx-ui-item-indicator-width': styles.fontSize.md,
56
+ })
57
+
58
+ const itemCss: CSSMixinDescriptor = css({
59
+ display: 'flex',
60
+ alignItems: 'center',
61
+ width: '100%',
62
+ minWidth: 0,
63
+ minHeight: styles.control.height.md,
64
+ boxSizing: 'border-box',
65
+ position: 'relative',
66
+ isolation: 'isolate',
67
+ paddingInline: `calc(${styles.space.sm} + ${styles.space.xs})`,
68
+ color: styles.colors.text.primary,
69
+ fontFamily: styles.fontFamily.sans,
70
+ fontSize: styles.fontSize.sm,
71
+ fontWeight: styles.fontWeight.normal,
72
+ lineHeight: styles.lineHeight.normal,
73
+ textAlign: 'left',
74
+ userSelect: 'none',
75
+ WebkitUserSelect: 'none',
76
+ '&::before': {
77
+ content: '""',
78
+ position: 'absolute',
79
+ insetBlock: 0,
80
+ insetInline: styles.space.xs,
81
+ borderRadius: styles.radius.md,
82
+ backgroundColor: 'transparent',
83
+ pointerEvents: 'none',
84
+ zIndex: -1,
85
+ },
86
+ '&:focus': {
87
+ outline: 'none',
88
+ },
89
+ '&[data-highlighted="true"]': {
90
+ color: styles.colors.action.primary.foreground,
91
+ },
92
+ '&[data-highlighted="true"]::before': {
93
+ backgroundColor: styles.colors.action.primary.background,
94
+ },
95
+ '&[aria-disabled="true"]': {
96
+ opacity: 0.5,
97
+ },
98
+ scrollMarginBlock: styles.space.xs,
99
+ '--rmx-listbox-option-indicator-opacity': '0',
100
+ '&[hidden]': {
101
+ display: 'none',
102
+ },
103
+ '&[data-listbox-flash="true"], &[data-select-flash="true"], &[data-combobox-flash="true"]': {
104
+ color: styles.colors.text.primary,
105
+ },
106
+ '&[data-listbox-flash="true"]::before, &[data-select-flash="true"]::before, &[data-combobox-flash="true"]::before':
107
+ {
108
+ backgroundColor: 'transparent',
109
+ },
110
+ '&[aria-selected="true"]': {
111
+ '--rmx-listbox-option-indicator-opacity': '1',
112
+ },
113
+ })
114
+
115
+ const itemIndicatorCss: CSSMixinDescriptor = css({
116
+ display: 'inline-flex',
117
+ alignItems: 'center',
118
+ justifyContent: 'center',
119
+ flex: '0 0 var(--rmx-ui-item-indicator-width)',
120
+ width: 'var(--rmx-ui-item-indicator-width)',
121
+ minWidth: 0,
122
+ height: 'var(--rmx-ui-item-indicator-width)',
123
+ marginInlineEnd: 'var(--rmx-ui-item-indicator-gap)',
124
+ overflow: 'hidden',
125
+ color: 'currentColor',
126
+ opacity: 'var(--rmx-listbox-option-indicator-opacity)',
127
+ '& > svg': {
128
+ display: 'block',
129
+ width: '100%',
130
+ height: '100%',
131
+ },
132
+ })
133
+
134
+ const itemLabelCss: CSSMixinDescriptor = css({
135
+ display: 'inline-flex',
136
+ alignItems: 'center',
137
+ flex: '1 1 auto',
138
+ minWidth: 0,
139
+ WebkitUserSelect: 'none',
140
+ })
141
+
142
+ export const popoverSurfaceStyle = [popoverSurfaceCss, popoverSurfaceTransitionCss] as const
143
+ export const listboxIndicatorStyle = itemIndicatorCss
144
+ export const listboxLabelStyle = itemLabelCss
145
+ export const listboxListStyle = listCss
146
+ export const listboxOptionStyle = itemCss
@@ -0,0 +1,165 @@
1
+ type ComponentActionColors = {
2
+ readonly background: string
3
+ readonly backgroundHover: string
4
+ readonly backgroundActive: string
5
+ readonly foreground: string
6
+ readonly border: string
7
+ }
8
+
9
+ type ComponentStyleValues = {
10
+ readonly space: {
11
+ readonly none: string
12
+ readonly xs: string
13
+ readonly sm: string
14
+ readonly md: string
15
+ readonly lg: string
16
+ }
17
+ readonly radius: {
18
+ readonly md: string
19
+ readonly lg: string
20
+ readonly xl: string
21
+ readonly full: string
22
+ }
23
+ readonly fontFamily: {
24
+ readonly sans: string
25
+ }
26
+ readonly fontSize: {
27
+ readonly xs: string
28
+ readonly sm: string
29
+ readonly md: string
30
+ }
31
+ readonly lineHeight: {
32
+ readonly normal: string
33
+ readonly relaxed: string
34
+ }
35
+ readonly fontWeight: {
36
+ readonly normal: string
37
+ readonly medium: string
38
+ }
39
+ readonly control: {
40
+ readonly height: {
41
+ readonly sm: string
42
+ readonly md: string
43
+ readonly lg: string
44
+ }
45
+ }
46
+ readonly surface: {
47
+ readonly lvl0: string
48
+ readonly lvl1: string
49
+ readonly lvl2: string
50
+ readonly lvl3: string
51
+ readonly lvl4: string
52
+ }
53
+ readonly shadow: {
54
+ readonly xs: string
55
+ readonly sm: string
56
+ readonly md: string
57
+ }
58
+ readonly colors: {
59
+ readonly text: {
60
+ readonly primary: string
61
+ readonly secondary: string
62
+ readonly muted: string
63
+ }
64
+ readonly border: {
65
+ readonly subtle: string
66
+ readonly default: string
67
+ }
68
+ readonly focus: {
69
+ readonly ring: string
70
+ }
71
+ readonly action: {
72
+ readonly primary: ComponentActionColors
73
+ readonly secondary: ComponentActionColors
74
+ readonly danger: ComponentActionColors
75
+ }
76
+ }
77
+ }
78
+
79
+ export const componentStyleValues: ComponentStyleValues = {
80
+ space: {
81
+ none: '0px',
82
+ xs: '4px',
83
+ sm: '8px',
84
+ md: '12px',
85
+ lg: '16px',
86
+ },
87
+ radius: {
88
+ md: '8px',
89
+ lg: '12px',
90
+ xl: '16px',
91
+ full: '9999px',
92
+ },
93
+ fontSize: {
94
+ xs: '12px',
95
+ sm: '13px',
96
+ md: '14px',
97
+ },
98
+ fontFamily: {
99
+ sans: '"Inter", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
100
+ },
101
+ lineHeight: {
102
+ normal: '1.45',
103
+ relaxed: '1.65',
104
+ },
105
+ fontWeight: {
106
+ normal: '400',
107
+ medium: '500',
108
+ },
109
+ control: {
110
+ height: {
111
+ sm: '28px',
112
+ md: '32px',
113
+ lg: '36px',
114
+ },
115
+ },
116
+ surface: {
117
+ lvl0: '#ffffff',
118
+ lvl1: '#f8f8f8',
119
+ lvl2: '#f5f5f5',
120
+ lvl3: '#f3f3f3',
121
+ lvl4: '#efefef',
122
+ },
123
+ shadow: {
124
+ xs: '0 1px 1px rgb(0 0 0 / 0.05)',
125
+ sm: '0 1px 2px rgb(0 0 0 / 0.07)',
126
+ md: '0 6px 18px rgb(0 0 0 / 0.08)',
127
+ },
128
+ colors: {
129
+ text: {
130
+ primary: '#151515',
131
+ secondary: '#4f4f4f',
132
+ muted: '#6d6d6d',
133
+ },
134
+ border: {
135
+ subtle: '#e7e7e7',
136
+ default: '#d1d1d1',
137
+ },
138
+ focus: {
139
+ ring: '#1A72FF',
140
+ },
141
+ action: {
142
+ primary: {
143
+ background: '#1A72FF',
144
+ backgroundHover: '#1463e0',
145
+ backgroundActive: '#0f55c9',
146
+ foreground: 'rgb(255 255 255 / 0.92)',
147
+ border: '#1A72FF',
148
+ },
149
+ secondary: {
150
+ background: '#ffffff',
151
+ backgroundHover: '#fbfbfb',
152
+ backgroundActive: '#f3f3f3',
153
+ foreground: '#202020',
154
+ border: '#d1d1d1',
155
+ },
156
+ danger: {
157
+ background: '#FF3000',
158
+ backgroundHover: '#e12b00',
159
+ backgroundActive: '#c52600',
160
+ foreground: 'rgb(255 255 255 / 0.92)',
161
+ border: '#FF3000',
162
+ },
163
+ },
164
+ },
165
+ }
@@ -0,0 +1,141 @@
1
+ # tabs
2
+
3
+ `Tabs` renders a tab control with one active tab and matching panels. Use it when related views share the same page space and the owning component may either control the active tab or let the tabs context manage it.
4
+
5
+ ## Component Usage
6
+
7
+ ```tsx
8
+ import { Tabs, TabList, Tab, TabPanel } from 'remix/ui/tabs'
9
+
10
+ export function ProjectTabs() {
11
+ return (
12
+ <Tabs defaultActiveTab="overview">
13
+ <TabList aria-label="Project sections">
14
+ <Tab name="overview">Overview</Tab>
15
+ <Tab name="activity">Activity</Tab>
16
+ <Tab name="settings">Settings</Tab>
17
+ </TabList>
18
+
19
+ <TabPanel name="overview">Project summary.</TabPanel>
20
+ <TabPanel name="activity">Recent changes.</TabPanel>
21
+ <TabPanel name="settings">Project settings.</TabPanel>
22
+ </Tabs>
23
+ )
24
+ }
25
+ ```
26
+
27
+ Control the active tab when state should live in the owning component:
28
+
29
+ ```tsx
30
+ import type { Handle } from 'remix/ui'
31
+ import { Tabs, TabList, Tab, TabPanel } from 'remix/ui/tabs'
32
+
33
+ export function ControlledTabs(handle: Handle) {
34
+ let activeTab = 'overview'
35
+
36
+ return () => (
37
+ <Tabs
38
+ activeTab={activeTab}
39
+ onActiveTabChange={(nextActiveTab) => {
40
+ activeTab = nextActiveTab
41
+ void handle.update()
42
+ }}
43
+ >
44
+ <TabList aria-label="Project sections">
45
+ <Tab name="overview">Overview</Tab>
46
+ <Tab name="activity">Activity</Tab>
47
+ </TabList>
48
+
49
+ <TabPanel name="overview">Project summary.</TabPanel>
50
+ <TabPanel name="activity">Recent changes.</TabPanel>
51
+ </Tabs>
52
+ )
53
+ }
54
+ ```
55
+
56
+ Listen for bubbling `TabsChangeEvent` events with `onTabsChange` from `remix/ui/tabs/primitives`.
57
+
58
+ ```tsx
59
+ import { onTabsChange } from 'remix/ui/tabs/primitives'
60
+ import { Tabs, TabList, Tab, TabPanel } from 'remix/ui/tabs'
61
+
62
+ export function TrackedTabs() {
63
+ return (
64
+ <div
65
+ mix={[
66
+ onTabsChange((event) => {
67
+ console.log(event.previousActiveTab, event.activeTab)
68
+ }),
69
+ ]}
70
+ >
71
+ <Tabs defaultActiveTab="overview">
72
+ <TabList aria-label="Project sections">
73
+ <Tab name="overview">Overview</Tab>
74
+ <Tab name="activity">Activity</Tab>
75
+ </TabList>
76
+ <TabPanel name="overview">Project summary.</TabPanel>
77
+ <TabPanel name="activity">Recent changes.</TabPanel>
78
+ </Tabs>
79
+ </div>
80
+ )
81
+ }
82
+ ```
83
+
84
+ ## Primitive Usage
85
+
86
+ Use the lower-level primitives when app code owns the markup and styles:
87
+
88
+ ```tsx
89
+ import * as tabs from 'remix/ui/tabs/primitives'
90
+ import { listStyle, panelStyle, rootStyle, tabStyle } from './tabs.styles'
91
+
92
+ export function PrimitiveTabs() {
93
+ return (
94
+ <tabs.Context defaultActiveTab="overview">
95
+ <div mix={[rootStyle, tabs.root()]}>
96
+ <div mix={[listStyle, tabs.list()]} aria-label="Project sections">
97
+ <button mix={[tabStyle, tabs.tab({ name: 'overview' })]} type="button">
98
+ Overview
99
+ </button>
100
+ <button mix={[tabStyle, tabs.tab({ name: 'activity' })]} type="button">
101
+ Activity
102
+ </button>
103
+ </div>
104
+
105
+ <div mix={[panelStyle, tabs.panel({ name: 'overview' })]}>Project summary.</div>
106
+ <div mix={[panelStyle, tabs.panel({ name: 'activity' })]}>Recent changes.</div>
107
+ </div>
108
+ </tabs.Context>
109
+ )
110
+ }
111
+ ```
112
+
113
+ ## `remix/ui/tabs`
114
+
115
+ - `Tabs`: root component. Supports controlled `activeTab`, uncontrolled `defaultActiveTab`, `onActiveTabChange`, `disabled`, and `size: 'md' | 'lg'`.
116
+ - `TabList`: tablist wrapper that lays out the tabs in a track and applies the tablist primitive.
117
+ - `Tab`: button for one tab `name`. The active tab uses the toggle slider treatment and button-sized text. Pass `disabled` to prevent activation.
118
+ - `TabPanel`: panel for one tab `name`.
119
+ - `rootStyle`, `listStyle`, `tabStyle`, and `panelStyle`: flat style mixins used by the component markup.
120
+ - `onTabsChange` and `TabsChangeEvent`: re-exported primitive event helpers.
121
+ - `TabsProps`, `TabListProps`, `TabProps`, `TabPanelProps`, and `TabsSize`: public TypeScript types for the composed APIs.
122
+
123
+ ## `remix/ui/tabs/primitives`
124
+
125
+ - `Context`: lower-level provider for custom tabs composition.
126
+ - `root()`: wires the root element and bubbling change events.
127
+ - `list()`: wires the tablist role and disabled state.
128
+ - `tab({ name, disabled })`: wires a tab button with selected state, ids, keyboard activation, and pointer activation.
129
+ - `panel({ name })`: wires the matching tab panel id, hidden state, inert state, and label relationship.
130
+ - `onTabsChange(...)`: event mixin for the bubbling `TabsChangeEvent`.
131
+ - `TabsChangeEvent`: bubbling event with `activeTab` and `previousActiveTab`.
132
+ - `TabsContextProps`, `TabsContextValue`, `TabsRegisteredTab`, `TabsActivationDirection`, `TabsRootOptions`, `TabListOptions`, `TabOptions`, and `TabPanelOptions`: primitive prop and option types for custom composition.
133
+
134
+ ## Behavior Notes
135
+
136
+ - If neither `activeTab` nor `defaultActiveTab` is provided, the first enabled tab becomes active.
137
+ - Arrow keys activate the next or previous enabled tab. `Home` and `End` activate the first and last enabled tabs.
138
+ - `Enter`, `Space`, and pointer clicks activate the focused tab.
139
+ - Root `disabled` disables every tab. Tab `disabled` only disables that tab.
140
+ - Tabs and panels are linked with generated ids through `aria-controls`, `aria-labelledby`, and `aria-selected`.
141
+ - Inactive panels receive `hidden`, `inert`, and `data-state="inactive"`.