@remix-run/ui 0.3.0 → 0.5.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 (434) hide show
  1. package/README.md +116 -157
  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/animate-layout-mixin.js +1 -1
  11. package/dist/animation/animate-layout-mixin.js.map +1 -1
  12. package/dist/animation/animate-mixins.js +2 -2
  13. package/dist/animation/animate-mixins.js.map +1 -1
  14. package/dist/animation/demos/color-interpolation.js +1 -1
  15. package/dist/animation/demos/color-interpolation.js.map +1 -1
  16. package/dist/animation/demos/drag-release.d.ts +1 -1
  17. package/dist/animation/demos/hold-to-confirm.d.ts +3 -3
  18. package/dist/animation/demos/material-ripple.js +1 -3
  19. package/dist/animation/demos/material-ripple.js.map +1 -1
  20. package/dist/animation/demos/reordering.js +9 -6
  21. package/dist/animation/demos/reordering.js.map +1 -1
  22. package/dist/animation/index.js +4 -4
  23. package/dist/animation/spring.js.map +1 -1
  24. package/dist/{components/breadcrumbs/breadcrumbs.js → breadcrumbs/index.js} +19 -19
  25. package/dist/breadcrumbs/index.js.map +1 -0
  26. package/dist/button/index.d.ts +15 -0
  27. package/dist/button/index.js +128 -0
  28. package/dist/button/index.js.map +1 -0
  29. package/dist/checkbox/index.d.ts +14 -0
  30. package/dist/checkbox/index.js +127 -0
  31. package/dist/checkbox/index.js.map +1 -0
  32. package/dist/combobox/index.d.ts +21 -0
  33. package/dist/combobox/index.js +58 -0
  34. package/dist/combobox/index.js.map +1 -0
  35. package/dist/{components/combobox/combobox.d.ts → combobox/primitives.d.ts} +4 -8
  36. package/dist/{components/combobox/combobox.js → combobox/primitives.js} +8 -61
  37. package/dist/combobox/primitives.js.map +1 -0
  38. package/dist/dev/refresh.d.ts +1 -0
  39. package/dist/dev/refresh.js +2 -0
  40. package/dist/dev/refresh.js.map +1 -0
  41. package/dist/index.d.ts +1 -1
  42. package/dist/index.js +14 -14
  43. package/dist/input/index.d.ts +13 -0
  44. package/dist/input/index.js +121 -0
  45. package/dist/input/index.js.map +1 -0
  46. package/dist/jsx-runtime.js +2 -2
  47. package/dist/listbox/flash-attribute.js.map +1 -0
  48. package/dist/{components/listbox/listbox.d.ts → listbox/index.d.ts} +2 -6
  49. package/dist/{components/listbox/listbox.js → listbox/index.js} +4 -82
  50. package/dist/listbox/index.js.map +1 -0
  51. package/dist/menu/hover-aim.js.map +1 -0
  52. package/dist/menu/index.d.ts +42 -0
  53. package/dist/menu/index.js +250 -0
  54. package/dist/menu/index.js.map +1 -0
  55. package/dist/{components/menu/menu.d.ts → menu/primitives.d.ts} +4 -33
  56. package/dist/{components/menu/menu.js → menu/primitives.js} +98 -315
  57. package/dist/menu/primitives.js.map +1 -0
  58. package/dist/{components/popover/popover.d.ts → popover/index.d.ts} +2 -4
  59. package/dist/{components/popover/popover.js → popover/index.js} +5 -48
  60. package/dist/popover/index.js.map +1 -0
  61. package/dist/{interactions/outside-click/outside-click-mixin.js → popover/outside-click.js} +1 -1
  62. package/dist/popover/outside-click.js.map +1 -0
  63. package/dist/popover/scroll-lock.d.ts +1 -0
  64. package/dist/{utils → popover}/scroll-lock.js +0 -18
  65. package/dist/popover/scroll-lock.js.map +1 -0
  66. package/dist/radio/index.d.ts +12 -0
  67. package/dist/radio/index.js +77 -0
  68. package/dist/radio/index.js.map +1 -0
  69. package/dist/runtime/client-entry-boundary.d.ts +14 -0
  70. package/dist/runtime/client-entry-boundary.js +29 -0
  71. package/dist/runtime/client-entry-boundary.js.map +1 -0
  72. package/dist/runtime/component.d.ts +19 -4
  73. package/dist/runtime/component.js +39 -9
  74. package/dist/runtime/component.js.map +1 -1
  75. package/dist/runtime/core/attributes.js +36 -3
  76. package/dist/runtime/core/attributes.js.map +1 -1
  77. package/dist/runtime/core/children.d.ts +2 -0
  78. package/dist/runtime/core/children.js +26 -2
  79. package/dist/runtime/core/children.js.map +1 -1
  80. package/dist/runtime/core/props.js +2 -2
  81. package/dist/runtime/core/vnode.d.ts +3 -3
  82. package/dist/runtime/core/vnode.js +22 -1
  83. package/dist/runtime/core/vnode.js.map +1 -1
  84. package/dist/runtime/create-element.d.ts +2 -2
  85. package/dist/runtime/create-element.js +3 -2
  86. package/dist/runtime/create-element.js.map +1 -1
  87. package/dist/runtime/diff-dom.js +294 -219
  88. package/dist/runtime/diff-dom.js.map +1 -1
  89. package/dist/runtime/diff-props.js +3 -3
  90. package/dist/runtime/dom.d.ts +12 -0
  91. package/dist/runtime/element-function.d.ts +3 -0
  92. package/dist/runtime/element-function.js +2 -0
  93. package/dist/runtime/element-function.js.map +1 -0
  94. package/dist/runtime/form-navigation.d.ts +25 -0
  95. package/dist/runtime/form-navigation.js +132 -0
  96. package/dist/runtime/form-navigation.js.map +1 -0
  97. package/dist/runtime/frame-resolution.d.ts +5 -0
  98. package/dist/runtime/frame-resolution.js +10 -0
  99. package/dist/runtime/frame-resolution.js.map +1 -0
  100. package/dist/runtime/frame.d.ts +58 -15
  101. package/dist/runtime/frame.js +367 -114
  102. package/dist/runtime/frame.js.map +1 -1
  103. package/dist/runtime/jsx.d.ts +1 -1
  104. package/dist/runtime/jsx.js +1 -1
  105. package/dist/runtime/jsx.js.map +1 -1
  106. package/dist/runtime/key.d.ts +1 -0
  107. package/dist/runtime/key.js +2 -0
  108. package/dist/runtime/key.js.map +1 -0
  109. package/dist/runtime/mixins/attrs-mixin.js +2 -2
  110. package/dist/runtime/mixins/link-mixin.js +4 -3
  111. package/dist/runtime/mixins/link-mixin.js.map +1 -1
  112. package/dist/runtime/mixins/mixin.d.ts +15 -10
  113. package/dist/runtime/mixins/mixin.js +4 -4
  114. package/dist/runtime/mixins/mixin.js.map +1 -1
  115. package/dist/runtime/mixins/on-mixin.js +1 -1
  116. package/dist/runtime/mixins/ref-mixin.js +1 -1
  117. package/dist/runtime/navigation.d.ts +4 -1
  118. package/dist/runtime/navigation.js +157 -32
  119. package/dist/runtime/navigation.js.map +1 -1
  120. package/dist/runtime/reconcile.d.ts +11 -10
  121. package/dist/runtime/reconcile.js +557 -423
  122. package/dist/runtime/reconcile.js.map +1 -1
  123. package/dist/runtime/refresh.d.ts +28 -0
  124. package/dist/runtime/refresh.js +31 -0
  125. package/dist/runtime/refresh.js.map +1 -0
  126. package/dist/runtime/render.js +1 -1
  127. package/dist/runtime/render.js.map +1 -1
  128. package/dist/runtime/run.d.ts +3 -1
  129. package/dist/runtime/run.js +15 -7
  130. package/dist/runtime/run.js.map +1 -1
  131. package/dist/runtime/scheduler.js +8 -16
  132. package/dist/runtime/scheduler.js.map +1 -1
  133. package/dist/runtime/to-vnode.d.ts +2 -2
  134. package/dist/runtime/to-vnode.js +68 -20
  135. package/dist/runtime/to-vnode.js.map +1 -1
  136. package/dist/runtime/vdom.d.ts +4 -2
  137. package/dist/runtime/vdom.js +89 -41
  138. package/dist/runtime/vdom.js.map +1 -1
  139. package/dist/runtime/vnode.d.ts +138 -67
  140. package/dist/runtime/vnode.js +17 -12
  141. package/dist/runtime/vnode.js.map +1 -1
  142. package/dist/select/index.d.ts +19 -0
  143. package/dist/select/index.js +95 -0
  144. package/dist/select/index.js.map +1 -0
  145. package/dist/select/keydown.js.map +1 -0
  146. package/dist/{components/select/select.d.ts → select/primitives.d.ts} +5 -7
  147. package/dist/{components/select/select.js → select/primitives.js} +11 -63
  148. package/dist/select/primitives.js.map +1 -0
  149. package/dist/server/stream.js +22 -29
  150. package/dist/server/stream.js.map +1 -1
  151. package/dist/shared/focus-styles.d.ts +1 -0
  152. package/dist/shared/focus-styles.js +2 -0
  153. package/dist/shared/focus-styles.js.map +1 -0
  154. package/dist/shared/icons.d.ts +9 -0
  155. package/dist/shared/icons.js +42 -0
  156. package/dist/shared/icons.js.map +1 -0
  157. package/dist/shared/listbox-popover-styles.d.ts +6 -0
  158. package/dist/shared/listbox-popover-styles.js +137 -0
  159. package/dist/shared/listbox-popover-styles.js.map +1 -0
  160. package/dist/shared/style-values.d.ts +78 -0
  161. package/dist/shared/style-values.js +88 -0
  162. package/dist/shared/style-values.js.map +1 -0
  163. package/dist/{interactions/typeahead/typeahead-mixin.js → shared/typeahead.js} +1 -1
  164. package/dist/shared/typeahead.js.map +1 -0
  165. package/dist/shared/wait-for-css-transition.js.map +1 -0
  166. package/dist/shared/wait.js.map +1 -0
  167. package/dist/style/css-mixin.js +3 -3
  168. package/dist/style/index.js +2 -2
  169. package/dist/style/style.js +25 -6
  170. package/dist/style/style.js.map +1 -1
  171. package/dist/style/stylesheet.d.ts +0 -1
  172. package/dist/style/stylesheet.js +13 -54
  173. package/dist/style/stylesheet.js.map +1 -1
  174. package/dist/tabs/index.d.ts +32 -0
  175. package/dist/tabs/index.js +157 -0
  176. package/dist/tabs/index.js.map +1 -0
  177. package/dist/tabs/primitives.d.ts +57 -0
  178. package/dist/tabs/primitives.js +238 -0
  179. package/dist/tabs/primitives.js.map +1 -0
  180. package/dist/test.js +1 -1
  181. package/dist/toggle/index.d.ts +9 -0
  182. package/dist/toggle/index.js +108 -0
  183. package/dist/toggle/index.js.map +1 -0
  184. package/dist/toggle/primitives.d.ts +29 -0
  185. package/dist/toggle/primitives.js +127 -0
  186. package/dist/toggle/primitives.js.map +1 -0
  187. package/package.json +81 -49
  188. package/src/{components/accordion → accordion}/README.md +50 -14
  189. package/src/accordion/index.tsx +285 -0
  190. package/src/accordion/primitives.tsx +512 -0
  191. package/src/{components/anchor → anchor}/README.md +37 -2
  192. package/src/animation/animate-mixins.ts +3 -3
  193. package/src/animation/demos/color-interpolation.tsx +1 -1
  194. package/src/animation/demos/reordering.tsx +9 -6
  195. package/src/{components/breadcrumbs → breadcrumbs}/README.md +4 -4
  196. package/src/{components/breadcrumbs/breadcrumbs.tsx → breadcrumbs/index.tsx} +18 -18
  197. package/src/button/README.md +44 -0
  198. package/src/button/index.ts +168 -0
  199. package/src/checkbox/README.md +59 -0
  200. package/src/checkbox/index.ts +164 -0
  201. package/src/{components/combobox → combobox}/README.md +58 -9
  202. package/src/combobox/index.tsx +112 -0
  203. package/src/{components/combobox/combobox.tsx → combobox/primitives.tsx} +5 -91
  204. package/src/dev/refresh.ts +1 -0
  205. package/src/index.ts +1 -1
  206. package/src/input/README.md +52 -0
  207. package/src/input/index.ts +147 -0
  208. package/src/{components/listbox → listbox}/README.md +9 -41
  209. package/src/{components/listbox/listbox.ts → listbox/index.ts} +2 -87
  210. package/src/{components/menu → menu}/README.md +55 -14
  211. package/src/menu/index.tsx +354 -0
  212. package/src/{components/menu/menu.tsx → menu/primitives.tsx} +99 -413
  213. package/src/{components/popover → popover}/README.md +20 -39
  214. package/src/{components/popover/popover.ts → popover/index.ts} +3 -57
  215. package/src/{utils → popover}/scroll-lock.ts +0 -28
  216. package/src/radio/README.md +53 -0
  217. package/src/radio/index.ts +101 -0
  218. package/src/runtime/client-entry-boundary.ts +54 -0
  219. package/src/runtime/component.ts +65 -16
  220. package/src/runtime/core/attributes.ts +35 -1
  221. package/src/runtime/core/children.ts +24 -2
  222. package/src/runtime/core/vnode.ts +25 -4
  223. package/src/runtime/create-element.ts +6 -5
  224. package/src/runtime/demos/readme.demo.tsx +20 -18
  225. package/src/runtime/diff-dom.ts +358 -243
  226. package/src/runtime/dom.ts +331 -214
  227. package/src/runtime/element-function.ts +1 -0
  228. package/src/runtime/event-listeners.ts +1 -1
  229. package/src/runtime/form-navigation.ts +184 -0
  230. package/src/runtime/frame-resolution.ts +13 -0
  231. package/src/runtime/frame.ts +482 -124
  232. package/src/runtime/jsx.ts +3 -5
  233. package/src/runtime/key.ts +1 -0
  234. package/src/runtime/mixins/link-mixin.ts +1 -0
  235. package/src/runtime/mixins/mixin.ts +21 -18
  236. package/src/runtime/navigation.ts +204 -30
  237. package/src/runtime/reconcile.ts +769 -760
  238. package/src/runtime/refresh.ts +46 -0
  239. package/src/runtime/run.ts +12 -2
  240. package/src/runtime/scheduler.ts +9 -29
  241. package/src/runtime/to-vnode.ts +85 -23
  242. package/src/runtime/vdom.ts +91 -69
  243. package/src/runtime/vnode.ts +192 -88
  244. package/src/{components/select → select}/README.md +29 -19
  245. package/src/select/index.tsx +159 -0
  246. package/src/{components/select/select.tsx → select/primitives.tsx} +10 -98
  247. package/src/server/stream.ts +25 -31
  248. package/src/shared/focus-styles.ts +2 -0
  249. package/src/shared/icons.tsx +62 -0
  250. package/src/shared/listbox-popover-styles.ts +146 -0
  251. package/src/shared/style-values.ts +165 -0
  252. package/src/style/style.ts +25 -6
  253. package/src/style/stylesheet.ts +32 -57
  254. package/src/tabs/README.md +141 -0
  255. package/src/tabs/index.tsx +245 -0
  256. package/src/tabs/primitives.ts +365 -0
  257. package/src/test/README.md +151 -60
  258. package/src/toggle/README.md +56 -0
  259. package/src/toggle/index.tsx +136 -0
  260. package/src/toggle/primitives.ts +192 -0
  261. package/dist/animation/demos/animation.demo.d.ts +0 -5
  262. package/dist/animation/demos/animation.demo.js +0 -92
  263. package/dist/animation/demos/animation.demo.js.map +0 -1
  264. package/dist/animation/demos/spring.demo.d.ts +0 -6
  265. package/dist/animation/demos/spring.demo.js +0 -304
  266. package/dist/animation/demos/spring.demo.js.map +0 -1
  267. package/dist/components/accordion/accordion.d.ts +0 -92
  268. package/dist/components/accordion/accordion.js +0 -335
  269. package/dist/components/accordion/accordion.js.map +0 -1
  270. package/dist/components/accordion/demos/card.demo.d.ts +0 -7
  271. package/dist/components/accordion/demos/card.demo.js +0 -59
  272. package/dist/components/accordion/demos/card.demo.js.map +0 -1
  273. package/dist/components/accordion/demos/multiple.demo.d.ts +0 -7
  274. package/dist/components/accordion/demos/multiple.demo.js +0 -20
  275. package/dist/components/accordion/demos/multiple.demo.js.map +0 -1
  276. package/dist/components/accordion/demos/overview.demo.d.ts +0 -7
  277. package/dist/components/accordion/demos/overview.demo.js +0 -20
  278. package/dist/components/accordion/demos/overview.demo.js.map +0 -1
  279. package/dist/components/anchor/anchor.js.map +0 -1
  280. package/dist/components/breadcrumbs/basic.demo.d.ts +0 -6
  281. package/dist/components/breadcrumbs/basic.demo.js +0 -15
  282. package/dist/components/breadcrumbs/basic.demo.js.map +0 -1
  283. package/dist/components/breadcrumbs/breadcrumbs.js.map +0 -1
  284. package/dist/components/breadcrumbs/separator.demo.d.ts +0 -6
  285. package/dist/components/breadcrumbs/separator.demo.js +0 -15
  286. package/dist/components/breadcrumbs/separator.demo.js.map +0 -1
  287. package/dist/components/button/button.d.ts +0 -105
  288. package/dist/components/button/button.js +0 -207
  289. package/dist/components/button/button.js.map +0 -1
  290. package/dist/components/button/demos/aliases.demo.d.ts +0 -7
  291. package/dist/components/button/demos/aliases.demo.js +0 -19
  292. package/dist/components/button/demos/aliases.demo.js.map +0 -1
  293. package/dist/components/button/demos/basic.demo.d.ts +0 -7
  294. package/dist/components/button/demos/basic.demo.js +0 -20
  295. package/dist/components/button/demos/basic.demo.js.map +0 -1
  296. package/dist/components/button/demos/states.demo.d.ts +0 -7
  297. package/dist/components/button/demos/states.demo.js +0 -30
  298. package/dist/components/button/demos/states.demo.js.map +0 -1
  299. package/dist/components/combobox/combobox.js.map +0 -1
  300. package/dist/components/combobox/overview.demo.d.ts +0 -7
  301. package/dist/components/combobox/overview.demo.js +0 -154
  302. package/dist/components/combobox/overview.demo.js.map +0 -1
  303. package/dist/components/glyph/glyph.d.ts +0 -14
  304. package/dist/components/glyph/glyph.js +0 -65
  305. package/dist/components/glyph/glyph.js.map +0 -1
  306. package/dist/components/listbox/listbox.js.map +0 -1
  307. package/dist/components/listbox/overview.demo.d.ts +0 -7
  308. package/dist/components/listbox/overview.demo.js +0 -65
  309. package/dist/components/listbox/overview.demo.js.map +0 -1
  310. package/dist/components/menu/demos/bubbling.demo.d.ts +0 -6
  311. package/dist/components/menu/demos/bubbling.demo.js +0 -16
  312. package/dist/components/menu/demos/bubbling.demo.js.map +0 -1
  313. package/dist/components/menu/demos/context-trigger.demo.d.ts +0 -7
  314. package/dist/components/menu/demos/context-trigger.demo.js +0 -87
  315. package/dist/components/menu/demos/context-trigger.demo.js.map +0 -1
  316. package/dist/components/menu/demos/overview.demo.d.ts +0 -7
  317. package/dist/components/menu/demos/overview.demo.js +0 -33
  318. package/dist/components/menu/demos/overview.demo.js.map +0 -1
  319. package/dist/components/menu/hover-aim.js.map +0 -1
  320. package/dist/components/menu/menu.js.map +0 -1
  321. package/dist/components/popover/overview.demo.d.ts +0 -7
  322. package/dist/components/popover/overview.demo.js +0 -65
  323. package/dist/components/popover/overview.demo.js.map +0 -1
  324. package/dist/components/popover/popover.js.map +0 -1
  325. package/dist/components/select/deconstructed.demo.d.ts +0 -7
  326. package/dist/components/select/deconstructed.demo.js +0 -54
  327. package/dist/components/select/deconstructed.demo.js.map +0 -1
  328. package/dist/components/select/overview.demo.d.ts +0 -6
  329. package/dist/components/select/overview.demo.js +0 -28
  330. package/dist/components/select/overview.demo.js.map +0 -1
  331. package/dist/components/select/select.js.map +0 -1
  332. package/dist/components/separator/separator.d.ts +0 -2
  333. package/dist/components/separator/separator.js +0 -15
  334. package/dist/components/separator/separator.js.map +0 -1
  335. package/dist/components/tabs/tabs.d.ts +0 -78
  336. package/dist/components/tabs/tabs.js +0 -339
  337. package/dist/components/tabs/tabs.js.map +0 -1
  338. package/dist/interactions/keydown/keydown.js.map +0 -1
  339. package/dist/interactions/outside-click/outside-click-mixin.js.map +0 -1
  340. package/dist/interactions/typeahead/typeahead-mixin.js.map +0 -1
  341. package/dist/runtime/demos/basic.demo.d.ts +0 -6
  342. package/dist/runtime/demos/basic.demo.js +0 -16
  343. package/dist/runtime/demos/basic.demo.js.map +0 -1
  344. package/dist/runtime/demos/controlled-uncontrolled-values.demo.d.ts +0 -6
  345. package/dist/runtime/demos/controlled-uncontrolled-values.demo.js +0 -77
  346. package/dist/runtime/demos/controlled-uncontrolled-values.demo.js.map +0 -1
  347. package/dist/runtime/demos/keyed-list.demo.d.ts +0 -6
  348. package/dist/runtime/demos/keyed-list.demo.js +0 -62
  349. package/dist/runtime/demos/keyed-list.demo.js.map +0 -1
  350. package/dist/runtime/demos/readme.demo.d.ts +0 -5
  351. package/dist/runtime/demos/readme.demo.js +0 -396
  352. package/dist/runtime/demos/readme.demo.js.map +0 -1
  353. package/dist/theme/contract.d.ts +0 -61
  354. package/dist/theme/contract.js +0 -131
  355. package/dist/theme/contract.js.map +0 -1
  356. package/dist/theme/glyph-contract.d.ts +0 -11
  357. package/dist/theme/glyph-contract.js +0 -34
  358. package/dist/theme/glyph-contract.js.map +0 -1
  359. package/dist/theme/presets/rmx-01/glyphs.d.ts +0 -2
  360. package/dist/theme/presets/rmx-01/glyphs.js +0 -252
  361. package/dist/theme/presets/rmx-01/glyphs.js.map +0 -1
  362. package/dist/theme/presets/rmx-01/index.d.ts +0 -4
  363. package/dist/theme/presets/rmx-01/index.js +0 -119
  364. package/dist/theme/presets/rmx-01/index.js.map +0 -1
  365. package/dist/theme/runtime.d.ts +0 -2
  366. package/dist/theme/runtime.js +0 -84
  367. package/dist/theme/runtime.js.map +0 -1
  368. package/dist/theme/theme.d.ts +0 -6
  369. package/dist/theme/theme.js +0 -5
  370. package/dist/theme/theme.js.map +0 -1
  371. package/dist/utils/flash-attribute.js.map +0 -1
  372. package/dist/utils/scroll-lock.d.ts +0 -3
  373. package/dist/utils/scroll-lock.js.map +0 -1
  374. package/dist/utils/wait-for-css-transition.js.map +0 -1
  375. package/dist/utils/wait.js.map +0 -1
  376. package/src/animation/demos/animation.demo.tsx +0 -194
  377. package/src/animation/demos/spring.demo.tsx +0 -385
  378. package/src/components/accordion/accordion.tsx +0 -565
  379. package/src/components/accordion/demos/card.demo.tsx +0 -114
  380. package/src/components/accordion/demos/multiple.demo.tsx +0 -57
  381. package/src/components/accordion/demos/overview.demo.tsx +0 -57
  382. package/src/components/breadcrumbs/basic.demo.tsx +0 -18
  383. package/src/components/breadcrumbs/separator.demo.tsx +0 -19
  384. package/src/components/button/README.md +0 -44
  385. package/src/components/button/button.tsx +0 -286
  386. package/src/components/button/demos/aliases.demo.tsx +0 -28
  387. package/src/components/button/demos/basic.demo.tsx +0 -31
  388. package/src/components/button/demos/states.demo.tsx +0 -53
  389. package/src/components/combobox/overview.demo.tsx +0 -200
  390. package/src/components/glyph/README.md +0 -72
  391. package/src/components/glyph/glyph.tsx +0 -110
  392. package/src/components/listbox/overview.demo.tsx +0 -102
  393. package/src/components/menu/demos/bubbling.demo.tsx +0 -35
  394. package/src/components/menu/demos/context-trigger.demo.tsx +0 -133
  395. package/src/components/menu/demos/overview.demo.tsx +0 -77
  396. package/src/components/popover/overview.demo.tsx +0 -144
  397. package/src/components/select/deconstructed.demo.tsx +0 -96
  398. package/src/components/select/overview.demo.tsx +0 -52
  399. package/src/components/separator/separator.ts +0 -19
  400. package/src/components/tabs/README.md +0 -105
  401. package/src/components/tabs/tabs.tsx +0 -504
  402. package/src/runtime/demos/basic.demo.tsx +0 -21
  403. package/src/runtime/demos/controlled-uncontrolled-values.demo.tsx +0 -177
  404. package/src/runtime/demos/keyed-list.demo.tsx +0 -94
  405. package/src/theme/README.md +0 -103
  406. package/src/theme/contract.ts +0 -215
  407. package/src/theme/glyph-contract.ts +0 -59
  408. package/src/theme/presets/rmx-01/glyphs.tsx +0 -308
  409. package/src/theme/presets/rmx-01/index.ts +0 -123
  410. package/src/theme/runtime.ts +0 -119
  411. package/src/theme/theme.ts +0 -15
  412. package/src/utils/scroll-lock/README.md +0 -33
  413. /package/dist/{components/anchor/anchor.d.ts → anchor/index.d.ts} +0 -0
  414. /package/dist/{components/breadcrumbs/breadcrumbs.d.ts → breadcrumbs/index.d.ts} +0 -0
  415. /package/dist/{utils → listbox}/flash-attribute.d.ts +0 -0
  416. /package/dist/{utils → listbox}/flash-attribute.js +0 -0
  417. /package/dist/{components/menu → menu}/hover-aim.d.ts +0 -0
  418. /package/dist/{components/menu → menu}/hover-aim.js +0 -0
  419. /package/dist/{interactions/outside-click/outside-click-mixin.d.ts → popover/outside-click.d.ts} +0 -0
  420. /package/dist/{interactions/keydown → select}/keydown.d.ts +0 -0
  421. /package/dist/{interactions/keydown → select}/keydown.js +0 -0
  422. /package/dist/{interactions/typeahead/typeahead-mixin.d.ts → shared/typeahead.d.ts} +0 -0
  423. /package/dist/{utils → shared}/wait-for-css-transition.d.ts +0 -0
  424. /package/dist/{utils → shared}/wait-for-css-transition.js +0 -0
  425. /package/dist/{utils → shared}/wait.d.ts +0 -0
  426. /package/dist/{utils → shared}/wait.js +0 -0
  427. /package/src/{components/anchor/anchor.ts → anchor/index.ts} +0 -0
  428. /package/src/{utils → listbox}/flash-attribute.ts +0 -0
  429. /package/src/{components/menu → menu}/hover-aim.ts +0 -0
  430. /package/src/{interactions/outside-click/outside-click-mixin.ts → popover/outside-click.ts} +0 -0
  431. /package/src/{interactions/keydown → select}/keydown.ts +0 -0
  432. /package/src/{interactions/typeahead/typeahead-mixin.ts → shared/typeahead.ts} +0 -0
  433. /package/src/{utils → shared}/wait-for-css-transition.ts +0 -0
  434. /package/src/{utils → shared}/wait.ts +0 -0
@@ -0,0 +1,365 @@
1
+ import {
2
+ attrs,
3
+ createMixin,
4
+ on,
5
+ ref,
6
+ type Dispatched,
7
+ type ElementProps,
8
+ type Handle,
9
+ type MixinHandle,
10
+ type MixinFactory,
11
+ type RemixNode,
12
+ } from '@remix-run/ui'
13
+
14
+ const TABS_CHANGE_EVENT = 'rmx:tabs-change' as const
15
+
16
+ type TabsChangeHandler<target extends HTMLElement> = (
17
+ event: Dispatched<TabsChangeEvent, target>,
18
+ signal: AbortSignal,
19
+ ) => void | Promise<void>
20
+
21
+ export type TabsActivationDirection = 'first' | 'last' | 'next' | 'previous'
22
+
23
+ export interface TabsRegisteredTab {
24
+ disabled: boolean
25
+ getTabNode(): HTMLButtonElement | null
26
+ name: string
27
+ }
28
+
29
+ export interface TabsContextValue {
30
+ readonly activeTab: string | null
31
+ readonly disabled: boolean
32
+ activateTab(name: string): void
33
+ activateTabInDirection(name: string, direction: TabsActivationDirection): void
34
+ getPanelId(name: string): string
35
+ getTabId(name: string): string
36
+ isActiveTab(name: string): boolean
37
+ registerRoot(node: HTMLElement): void
38
+ registerTab(tab: TabsRegisteredTab): void
39
+ unregisterRoot(node: HTMLElement): void
40
+ }
41
+
42
+ export interface TabsContextProps {
43
+ activeTab?: string
44
+ children?: RemixNode
45
+ defaultActiveTab?: string
46
+ disabled?: boolean
47
+ onActiveTabChange?: (activeTab: string) => void
48
+ }
49
+
50
+ export interface TabsRootOptions {}
51
+
52
+ export interface TabListOptions {}
53
+
54
+ export interface TabOptions {
55
+ disabled?: boolean
56
+ name: string
57
+ }
58
+
59
+ export interface TabPanelOptions {
60
+ name: string
61
+ }
62
+
63
+ export class TabsChangeEvent extends Event {
64
+ readonly activeTab: string
65
+ readonly previousActiveTab: string | null
66
+
67
+ constructor(activeTab: string, previousActiveTab: string | null) {
68
+ super(TABS_CHANGE_EVENT, {
69
+ bubbles: true,
70
+ })
71
+ this.activeTab = activeTab
72
+ this.previousActiveTab = previousActiveTab
73
+ }
74
+ }
75
+
76
+ declare global {
77
+ interface HTMLElementEventMap {
78
+ [TABS_CHANGE_EVENT]: TabsChangeEvent
79
+ }
80
+ }
81
+
82
+ function encodeIdPart(value: string): string {
83
+ return encodeURIComponent(value).replace(/%/g, '-')
84
+ }
85
+
86
+ function getTabState(active: boolean): 'active' | 'inactive' {
87
+ return active ? 'active' : 'inactive'
88
+ }
89
+
90
+ function getTabsContext(handle: Handle<unknown, unknown> | MixinHandle) {
91
+ return handle.context.get(TabsProvider)
92
+ }
93
+
94
+ function TabsProvider(handle: Handle<TabsContextProps, TabsContextValue>): () => RemixNode {
95
+ let rootNode: HTMLElement | null = null
96
+ let renderedTabs: TabsRegisteredTab[] = []
97
+ let uncontrolledActiveTab: string | null = null
98
+ let hasInitialized = false
99
+
100
+ function getActiveTab() {
101
+ if (handle.props.activeTab !== undefined) {
102
+ return handle.props.activeTab
103
+ }
104
+
105
+ if (!hasInitialized) {
106
+ uncontrolledActiveTab = handle.props.defaultActiveTab ?? null
107
+ hasInitialized = true
108
+ }
109
+
110
+ return uncontrolledActiveTab
111
+ }
112
+
113
+ function getRegisteredTab(name: string) {
114
+ return renderedTabs.find((tab) => tab.name === name)
115
+ }
116
+
117
+ function dispatchChange(activeTab: string, previousActiveTab: string | null) {
118
+ rootNode?.dispatchEvent(new TabsChangeEvent(activeTab, previousActiveTab))
119
+ }
120
+
121
+ function activateTab(name: string) {
122
+ if (handle.props.disabled) {
123
+ return
124
+ }
125
+
126
+ let tab = getRegisteredTab(name)
127
+ if (tab?.disabled) {
128
+ return
129
+ }
130
+
131
+ let previousActiveTab = getActiveTab()
132
+ if (previousActiveTab === name) {
133
+ return
134
+ }
135
+
136
+ if (handle.props.activeTab === undefined) {
137
+ uncontrolledActiveTab = name
138
+ void handle.update()
139
+ }
140
+
141
+ handle.props.onActiveTabChange?.(name)
142
+ dispatchChange(name, previousActiveTab)
143
+ }
144
+
145
+ function getFocusableTabs() {
146
+ return renderedTabs.filter((tab) => !tab.disabled && tab.getTabNode() !== null)
147
+ }
148
+
149
+ function activateTabInDirection(name: string, direction: TabsActivationDirection) {
150
+ let tabs = getFocusableTabs()
151
+ if (tabs.length === 0) {
152
+ return
153
+ }
154
+
155
+ let currentIndex = tabs.findIndex((tab) => tab.name === name)
156
+ let targetIndex = 0
157
+
158
+ switch (direction) {
159
+ case 'first':
160
+ targetIndex = 0
161
+ break
162
+ case 'last':
163
+ targetIndex = tabs.length - 1
164
+ break
165
+ case 'previous':
166
+ targetIndex = currentIndex <= 0 ? tabs.length - 1 : currentIndex - 1
167
+ break
168
+ case 'next':
169
+ targetIndex = currentIndex === -1 || currentIndex === tabs.length - 1 ? 0 : currentIndex + 1
170
+ break
171
+ }
172
+
173
+ let targetTab = tabs[targetIndex]
174
+ targetTab?.getTabNode()?.focus()
175
+ if (targetTab) {
176
+ activateTab(targetTab.name)
177
+ }
178
+ }
179
+
180
+ function getTabId(name: string) {
181
+ return `${handle.id}-${encodeIdPart(name)}-tab`
182
+ }
183
+
184
+ function getPanelId(name: string) {
185
+ return `${handle.id}-${encodeIdPart(name)}-panel`
186
+ }
187
+
188
+ function registerTab(tab: TabsRegisteredTab) {
189
+ renderedTabs.push(tab)
190
+
191
+ if (handle.props.activeTab === undefined && getActiveTab() === null && !tab.disabled) {
192
+ uncontrolledActiveTab = tab.name
193
+ }
194
+ }
195
+
196
+ handle.context.set({
197
+ get activeTab() {
198
+ return getActiveTab()
199
+ },
200
+ get disabled() {
201
+ return handle.props.disabled ?? false
202
+ },
203
+ activateTab,
204
+ activateTabInDirection,
205
+ getPanelId,
206
+ getTabId,
207
+ isActiveTab(name) {
208
+ return getActiveTab() === name
209
+ },
210
+ registerRoot(node) {
211
+ rootNode = node
212
+ },
213
+ registerTab,
214
+ unregisterRoot(node) {
215
+ if (rootNode === node) {
216
+ rootNode = null
217
+ }
218
+ },
219
+ })
220
+
221
+ return () => {
222
+ renderedTabs = []
223
+ return handle.props.children
224
+ }
225
+ }
226
+
227
+ const rootMixin: MixinFactory<HTMLElement, [options?: TabsRootOptions], ElementProps> = createMixin<
228
+ HTMLElement,
229
+ [options?: TabsRootOptions],
230
+ ElementProps
231
+ >((handle) => {
232
+ let context = getTabsContext(handle)
233
+
234
+ handle.queueTask((node, signal) => {
235
+ context.registerRoot(node)
236
+ signal.addEventListener('abort', () => {
237
+ context.unregisterRoot(node)
238
+ })
239
+ })
240
+
241
+ return () =>
242
+ attrs({
243
+ 'data-disabled': context.disabled ? '' : undefined,
244
+ })
245
+ })
246
+
247
+ const listMixin: MixinFactory<HTMLElement, [options?: TabListOptions], ElementProps> = createMixin<
248
+ HTMLElement,
249
+ [options?: TabListOptions],
250
+ ElementProps
251
+ >((handle) => {
252
+ let context = getTabsContext(handle)
253
+
254
+ return () =>
255
+ attrs({
256
+ 'aria-disabled': context.disabled ? true : undefined,
257
+ role: 'tablist',
258
+ })
259
+ })
260
+
261
+ const tabMixin: MixinFactory<HTMLButtonElement, [options: TabOptions], ElementProps> = createMixin<
262
+ HTMLButtonElement,
263
+ [options: TabOptions],
264
+ ElementProps
265
+ >((handle, hostType) => {
266
+ let context = getTabsContext(handle)
267
+ let tabNode: HTMLButtonElement | null = null
268
+
269
+ return (options, props) => {
270
+ let disabled = context.disabled || options.disabled === true || props.disabled === true
271
+
272
+ context.registerTab({
273
+ disabled,
274
+ getTabNode: () => tabNode,
275
+ name: options.name,
276
+ })
277
+
278
+ let active = context.isActiveTab(options.name)
279
+
280
+ return [
281
+ attrs({
282
+ 'aria-controls': context.getPanelId(options.name),
283
+ 'aria-selected': active ? 'true' : 'false',
284
+ 'data-state': getTabState(active),
285
+ disabled: disabled ? true : undefined,
286
+ id: context.getTabId(options.name),
287
+ role: 'tab',
288
+ tabIndex: active && !disabled ? 0 : -1,
289
+ type: hostType === 'button' ? 'button' : undefined,
290
+ }),
291
+ ref((node: HTMLButtonElement, signal) => {
292
+ tabNode = node
293
+ signal.addEventListener('abort', () => {
294
+ if (tabNode === node) {
295
+ tabNode = null
296
+ }
297
+ })
298
+ }),
299
+ on('click', () => {
300
+ context.activateTab(options.name)
301
+ }),
302
+ on('keydown', (event) => {
303
+ switch (event.key) {
304
+ case 'ArrowDown':
305
+ case 'ArrowRight':
306
+ event.preventDefault()
307
+ context.activateTabInDirection(options.name, 'next')
308
+ break
309
+ case 'ArrowUp':
310
+ case 'ArrowLeft':
311
+ event.preventDefault()
312
+ context.activateTabInDirection(options.name, 'previous')
313
+ break
314
+ case 'Home':
315
+ event.preventDefault()
316
+ context.activateTabInDirection(options.name, 'first')
317
+ break
318
+ case 'End':
319
+ event.preventDefault()
320
+ context.activateTabInDirection(options.name, 'last')
321
+ break
322
+ case 'Enter':
323
+ case ' ':
324
+ event.preventDefault()
325
+ context.activateTab(options.name)
326
+ break
327
+ }
328
+ }),
329
+ ]
330
+ }
331
+ })
332
+
333
+ const panelMixin: MixinFactory<HTMLElement, [options: TabPanelOptions], ElementProps> = createMixin<
334
+ HTMLElement,
335
+ [options: TabPanelOptions],
336
+ ElementProps
337
+ >((handle) => {
338
+ let context = getTabsContext(handle)
339
+
340
+ return (options) => {
341
+ let active = context.isActiveTab(options.name)
342
+
343
+ return attrs({
344
+ 'aria-labelledby': context.getTabId(options.name),
345
+ 'data-state': getTabState(active),
346
+ hidden: active ? undefined : true,
347
+ id: context.getPanelId(options.name),
348
+ inert: active ? undefined : true,
349
+ role: 'tabpanel',
350
+ })
351
+ }
352
+ })
353
+
354
+ export const Context = TabsProvider
355
+ export const list = listMixin
356
+ export const panel = panelMixin
357
+ export const root = rootMixin
358
+ export const tab = tabMixin
359
+
360
+ export function onTabsChange<target extends HTMLElement>(
361
+ handler: TabsChangeHandler<target>,
362
+ captureBoolean?: boolean,
363
+ ): ReturnType<typeof on<target, typeof TABS_CHANGE_EVENT>> {
364
+ return on(TABS_CHANGE_EVENT, handler, captureBoolean)
365
+ }
@@ -1,104 +1,195 @@
1
1
  # Test
2
2
 
3
- When writing tests, use `root.flush()` to synchronously execute all pending updates and tasks. This ensures the DOM and component state are fully synchronized before making assertions.
3
+ Use `render` from `remix/ui/test` as the primary way to test components in a live browser through a `remix/test` "browser" test (`*.test.browser.tsx`). It creates a DOM container, renders and flushes the initial component tree, and returns helpers for querying, interacting with, and cleaning up the rendered output.
4
4
 
5
- ## Basic Testing Pattern
6
-
7
- The main use case is flushing after events that call `handle.update()`. Since updates are asynchronous, you need to flush to ensure the DOM reflects the changes:
5
+ ## Rendering Components
8
6
 
9
7
  ```tsx
8
+ import { expect } from 'remix/assert'
9
+ import { describe, it } from 'remix/test'
10
+ import { on, type Handle } from 'remix/ui'
11
+ import { render } from 'remix/ui/test'
12
+
10
13
  function Counter(handle: Handle) {
11
14
  let count = 0
12
15
 
13
16
  return () => (
14
- <button
15
- mix={[
16
- on('click', () => {
17
- count++
18
- handle.update()
19
- }),
20
- ]}
21
- >
22
- Count: {count}
23
- </button>
17
+ <div>
18
+ <button
19
+ data-action="increment"
20
+ mix={[
21
+ on('click', () => {
22
+ count++
23
+ handle.update()
24
+ }),
25
+ ]}
26
+ >
27
+ Increment
28
+ </button>
29
+ <output data-testid="count">{count}</output>
30
+ </div>
24
31
  )
25
32
  }
26
33
 
27
- // In your test
28
- let container = document.createElement('div')
29
- let root = createRoot(container)
34
+ describe('Counter', () => {
35
+ it('increments the count', async (t) => {
36
+ let { $, act, cleanup } = render(<Counter />)
37
+ t.after(cleanup)
30
38
 
31
- root.render(<Counter />)
32
- root.flush() // Ensure initial render completes
39
+ expect($('[data-testid="count"]')?.textContent).toBe('0')
33
40
 
34
- let button = container.querySelector('button')
35
- button.click() // Triggers handle.update()
36
- root.flush() // Flush to apply the update
41
+ await act(() => $('[data-action="increment"]')?.click())
37
42
 
38
- expect(container.textContent).toBe('Count: 1')
43
+ expect($('[data-testid="count"]')?.textContent).toBe('1')
44
+ })
45
+ })
39
46
  ```
40
47
 
41
- ## Why Flush After Initial Render?
48
+ The initial render is already flushed, so the DOM and event listeners are ready when `render`
49
+ returns. Wrap interactions that may update component state in `act` and always await it. `act`
50
+ waits for the callback and then flushes pending component updates before the next assertion.
51
+
52
+ ### Testing Async Operations
42
53
 
43
- You should also flush after the initial `root.render()` to ensure event listeners are attached and the DOM is ready for interaction:
54
+ When an interaction starts an async operation, await that operation inside `act` so updates queued
55
+ after it resolves are flushed before making assertions:
44
56
 
45
57
  ```tsx
46
- let root = createRoot(container)
47
- root.render(<MyComponent />)
48
- root.flush() // Event listeners now attached
58
+ function UserLoader(handle: Handle<{ loadUser(): Promise<{ name: string }> }>) {
59
+ let name = 'No user loaded'
49
60
 
50
- // Safe to interact
51
- container.querySelector('button').click()
61
+ return () => (
62
+ <div>
63
+ <button
64
+ mix={[
65
+ on('click', async () => {
66
+ let user = await handle.props.loadUser()
67
+ name = user.name
68
+ handle.update()
69
+ }),
70
+ ]}
71
+ >
72
+ Load user
73
+ </button>
74
+ <output>{name}</output>
75
+ </div>
76
+ )
77
+ }
78
+
79
+ it('loads a user', async (t) => {
80
+ let userPromise = Promise.resolve({ name: 'Ada' })
81
+ let { $, act, cleanup } = render(<UserLoader loadUser={() => userPromise} />)
82
+ t.after(cleanup)
83
+
84
+ await act(async () => {
85
+ $('button')?.click()
86
+ await userPromise
87
+ })
88
+
89
+ expect($('output')?.textContent).toBe('Ada')
90
+ })
52
91
  ```
53
92
 
54
- ## Testing Async Operations
93
+ ### Querying Rendered Output
55
94
 
56
- For components with async operations in `queueTask`, flush after each step:
95
+ The returned query helpers are scoped to the rendered container so you don't have to worry about false positives outside of the rendered component:
57
96
 
58
97
  ```tsx
59
- function AsyncLoader(handle: Handle) {
60
- let data: string | null = null
61
-
62
- handle.queueTask(async (signal) => {
63
- let response = await fetch('/api/data', { signal })
64
- let json = await response.json()
65
- if (signal.aborted) return
66
- data = json.value
67
- handle.update()
68
- })
98
+ let { $, $$, container, cleanup } = render(
99
+ <nav>
100
+ <a href="/one">One</a>
101
+ <a href="/two">Two</a>
102
+ </nav>,
103
+ )
69
104
 
70
- return () => <div>{data ?? 'Loading...'}</div>
71
- }
105
+ let nav = $('nav') // container.querySelector('nav')
106
+ let links = $$('a') // container.querySelectorAll('a')
72
107
 
73
- // In your test (with mocked fetch)
74
- let root = createRoot(container)
75
- root.render(<AsyncLoader />)
76
- root.flush()
108
+ expect(nav).toBeTruthy()
109
+ expect(links).toHaveLength(2)
110
+ expect(container.textContent).toContain('One')
77
111
 
78
- expect(container.textContent).toBe('Loading...')
112
+ cleanup()
113
+ ```
79
114
 
80
- // After fetch resolves
81
- await waitForFetch()
82
- root.flush()
115
+ Use `$` for the first matching element, `$$` for all matching elements, and `container` when a
116
+ DOM assertion does not fit a selector.
83
117
 
84
- expect(container.textContent).toBe('Expected data')
118
+ ### Cleaning Up
119
+
120
+ Register `cleanup` with the test context so it always runs, including when an assertion fails:
121
+
122
+ ```tsx
123
+ it('renders a dialog', (t) => {
124
+ let { $, cleanup } = render(<Dialog />)
125
+ t.after(cleanup)
126
+
127
+ expect($('[role="dialog"]')).toBeTruthy()
128
+ })
85
129
  ```
86
130
 
87
- ## Testing Component Removal
131
+ `cleanup` disposes the root and removes its container from the document. You can call it directly
132
+ when a test needs to assert cleanup behavior.
133
+
134
+ ### Rendering Into an Existing Container
88
135
 
89
- Use `root.dispose()` to clean up and verify cleanup behavior:
136
+ Pass a container when the test needs a specific DOM context:
90
137
 
91
138
  ```tsx
139
+ let container = document.createElement('section')
140
+ document.body.appendChild(container)
141
+
142
+ let result = render(<MyComponent />, { container })
143
+
144
+ expect(result.container).toBe(container)
145
+ result.cleanup()
146
+ ```
147
+
148
+ ## Advanced: Flushing Manually
149
+
150
+ Most component tests should use `render` and `act`. For lower-level runtime tests that need direct
151
+ control over rendering or scheduling, use the returned `root` and call `root.flush()` after an
152
+ operation that queues work:
153
+
154
+ ```tsx
155
+ let { root, container, cleanup } = render(<MyComponent value="first" />)
156
+
157
+ root.render(<MyComponent value="second" />)
158
+ root.flush()
159
+
160
+ expect(container.textContent).toContain('second')
161
+ cleanup()
162
+ ```
163
+
164
+ You can also create and manage a root directly:
165
+
166
+ ```tsx
167
+ import { createRoot } from 'remix/ui'
168
+
169
+ let container = document.createElement('div')
92
170
  let root = createRoot(container)
171
+
93
172
  root.render(<MyComponent />)
94
- root.flush()
173
+ root.flush() // Complete the initial render and attach event listeners
95
174
 
96
- // Verify setup behavior
97
- expect(container.querySelector('.content')).toBeTruthy()
175
+ container.querySelector('button')?.click()
176
+ root.flush() // Apply updates queued by the interaction
98
177
 
99
- // Remove and verify cleanup
100
178
  root.dispose()
101
- expect(container.innerHTML).toBe('')
179
+ ```
180
+
181
+ Manual flushing is also useful after an async operation resolves outside an `act` callback:
182
+
183
+ ```tsx
184
+ root.render(<AsyncLoader />)
185
+ root.flush()
186
+
187
+ expect(container.textContent).toBe('Loading...')
188
+
189
+ await waitForFetch()
190
+ root.flush()
191
+
192
+ expect(container.textContent).toBe('Expected data')
102
193
  ```
103
194
 
104
195
  ## See Also
@@ -0,0 +1,56 @@
1
+ # toggle
2
+
3
+ `toggle` is a style mixin for checkbox inputs rendered as switches. Use native input props for normal form behavior, and compose it with `remix/ui/toggle/primitives` when you need the headless toggle state helpers or custom hosts.
4
+
5
+ ## Usage
6
+
7
+ ```tsx
8
+ import toggle from 'remix/ui/toggle'
9
+
10
+ function NotificationSetting() {
11
+ return () => (
12
+ <label>
13
+ <input mix={toggle({ size: 'lg' })} defaultChecked />
14
+ Email notifications
15
+ </label>
16
+ )
17
+ }
18
+ ```
19
+
20
+ ## Primitive Usage
21
+
22
+ Use the lower-level primitive when app code needs normalized checked state, toggle change events, or a non-input host:
23
+
24
+ ```tsx
25
+ import toggle from 'remix/ui/toggle'
26
+ import * as togglePrimitive from 'remix/ui/toggle/primitives'
27
+
28
+ function CustomToggle() {
29
+ return () => (
30
+ <button
31
+ aria-label="Notifications"
32
+ mix={[...toggle(), togglePrimitive.control({ defaultChecked: true })]}
33
+ />
34
+ )
35
+ }
36
+ ```
37
+
38
+ ## `remix/ui/toggle`
39
+
40
+ - `toggle(options)`: styles a native checkbox input as a switch and supplies `type="checkbox"` plus `role="switch"` when the host is an `<input>` without an explicit non-checkbox `type`. Supports `size: 'md' | 'lg'` and defaults to `'md'`.
41
+ - `onToggleChange(handler)`: re-exported primitive event mixin.
42
+ - `ToggleChangeEvent`: re-exported primitive event class.
43
+ - `ToggleSize` and `ToggleOptions`: public TypeScript types for the style mixin.
44
+
45
+ ## `remix/ui/toggle/primitives`
46
+
47
+ - `control(options)`: wires a boolean switch control with checked state, ARIA, keyboard, and native input behavior.
48
+ - `onToggleChange(handler)`: event mixin for bubbling toggle changes.
49
+ - `ToggleChangeEvent`: event class dispatched when a toggle changes.
50
+ - `ToggleControlOptions`: primitive options for controlled and uncontrolled state.
51
+
52
+ ## Behavior Notes
53
+
54
+ - Checked styles apply through `:checked`, `[aria-checked="true"]`, or `[data-state="checked"]`.
55
+ - Native checkbox switches use the native `checked` attribute for state. Custom hosts use `role="switch"` and `aria-checked="true"` or `aria-checked="false"`.
56
+ - Every switch needs an accessible name, usually from visible label text, `aria-label`, or `aria-labelledby`.