@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
@@ -0,0 +1,512 @@
1
+ import {
2
+ attrs,
3
+ createMixin,
4
+ on,
5
+ ref,
6
+ type Dispatched,
7
+ type ElementProps,
8
+ type Handle,
9
+ type MixinHandle,
10
+ type Props,
11
+ type RemixNode,
12
+ } from '@remix-run/ui'
13
+
14
+ const ACCORDION_CHANGE_EVENT = 'rmx:accordion-change' as const
15
+
16
+ type AccordionChangeHandler<target extends HTMLElement> = (
17
+ event: Dispatched<AccordionChangeEvent, target>,
18
+ signal: AbortSignal,
19
+ ) => void | Promise<void>
20
+
21
+ declare global {
22
+ interface HTMLElementEventMap {
23
+ [ACCORDION_CHANGE_EVENT]: AccordionChangeEvent
24
+ }
25
+ }
26
+
27
+ export class AccordionChangeEvent extends Event {
28
+ readonly accordionType: AccordionType
29
+ readonly itemValue: string
30
+ readonly value: AccordionValue
31
+
32
+ constructor(
33
+ value: AccordionValue,
34
+ init: {
35
+ accordionType: AccordionType
36
+ itemValue: string
37
+ },
38
+ ) {
39
+ super(ACCORDION_CHANGE_EVENT, {
40
+ bubbles: true,
41
+ })
42
+ this.accordionType = init.accordionType
43
+ this.itemValue = init.itemValue
44
+ this.value = value
45
+ }
46
+ }
47
+
48
+ export type AccordionType = 'single' | 'multiple'
49
+ export type AccordionValue = string | null | string[]
50
+ export type AccordionSingleValue = string | null
51
+ export type AccordionMultipleValue = string[]
52
+ export type AccordionHeadingLevel = 1 | 2 | 3 | 4 | 5 | 6
53
+
54
+ type FocusDirection = 'first' | 'last' | 'next' | 'previous'
55
+
56
+ type RegisteredItem = {
57
+ disabled: boolean
58
+ getTriggerNode(): HTMLButtonElement | null
59
+ value: string
60
+ }
61
+
62
+ interface AccordionContextValue {
63
+ readonly collapsible: boolean
64
+ readonly disabled: boolean
65
+ readonly headingLevel: AccordionHeadingLevel
66
+ readonly type: AccordionType
67
+ focusItem(value: string, direction: FocusDirection): void
68
+ getPanelId(value: string): string
69
+ getTriggerId(value: string): string | undefined
70
+ isOpen(value: string): boolean
71
+ registerItem(item: RegisteredItem): void
72
+ registerRoot(node: HTMLElement): void
73
+ toggleItem(value: string): void
74
+ unregisterRoot(node: HTMLElement): void
75
+ }
76
+
77
+ interface AccordionItemContextValue {
78
+ readonly disabled: boolean
79
+ readonly headingLevel: AccordionHeadingLevel
80
+ readonly lockedOpen: boolean
81
+ readonly open: boolean
82
+ readonly panelId: string
83
+ readonly triggerId: string
84
+ readonly value: string
85
+ setTriggerNode(node: HTMLButtonElement | null): void
86
+ }
87
+
88
+ export interface AccordionBaseContextProps {
89
+ children?: RemixNode
90
+ disabled?: boolean
91
+ headingLevel?: AccordionHeadingLevel
92
+ }
93
+
94
+ export interface AccordionSingleContextProps extends AccordionBaseContextProps {
95
+ type?: 'single'
96
+ value?: AccordionSingleValue
97
+ defaultValue?: AccordionSingleValue
98
+ onValueChange?: (value: AccordionSingleValue) => void
99
+ collapsible?: boolean
100
+ }
101
+
102
+ export interface AccordionMultipleContextProps extends AccordionBaseContextProps {
103
+ type: 'multiple'
104
+ value?: AccordionMultipleValue
105
+ defaultValue?: AccordionMultipleValue
106
+ onValueChange?: (value: AccordionMultipleValue) => void
107
+ }
108
+
109
+ export type AccordionContextProps = AccordionSingleContextProps | AccordionMultipleContextProps
110
+
111
+ export interface AccordionRootOptions {}
112
+
113
+ export interface AccordionItemOptions {
114
+ disabled?: boolean
115
+ value: string
116
+ }
117
+
118
+ export interface AccordionTriggerOptions {
119
+ disabled?: boolean
120
+ }
121
+
122
+ export interface AccordionContentOptions {}
123
+
124
+ export type AccordionProps = Props<'div'> & AccordionContextProps
125
+
126
+ export type AccordionItemProps = Omit<Props<'div'>, 'children'> &
127
+ AccordionItemOptions & {
128
+ children?: RemixNode
129
+ }
130
+
131
+ export type AccordionTriggerProps = Omit<Props<'button'>, 'children' | 'type'> &
132
+ AccordionTriggerOptions & {
133
+ children?: RemixNode
134
+ type?: 'button' | 'submit' | 'reset'
135
+ }
136
+
137
+ export type AccordionContentProps = Omit<Props<'div'>, 'children'> & {
138
+ children?: RemixNode
139
+ }
140
+
141
+ function isMultipleProps(
142
+ props: AccordionContextProps | null,
143
+ ): props is AccordionMultipleContextProps {
144
+ return props?.type === 'multiple'
145
+ }
146
+
147
+ function getAccordionContext(handle: Handle<unknown, unknown> | MixinHandle) {
148
+ return handle.context.get(AccordionProvider)
149
+ }
150
+
151
+ function getAccordionItemContext(handle: Handle<unknown, unknown> | MixinHandle) {
152
+ return handle.context.get(AccordionItemProvider)
153
+ }
154
+
155
+ function AccordionProvider(
156
+ handle: Handle<AccordionContextProps, AccordionContextValue>,
157
+ ): () => RemixNode {
158
+ let rootNode: HTMLElement | null = null
159
+ let registeredItems: RegisteredItem[] = []
160
+ let uncontrolledSingleValue: string | null = null
161
+ let uncontrolledMultipleValue: string[] = []
162
+ let hasInitializedSingle = false
163
+ let hasInitializedMultiple = false
164
+
165
+ function getType() {
166
+ return isMultipleProps(handle.props) ? 'multiple' : 'single'
167
+ }
168
+
169
+ function getSingleValue() {
170
+ if (isMultipleProps(handle.props)) {
171
+ return null
172
+ }
173
+
174
+ if (handle.props.value !== undefined) {
175
+ return handle.props.value
176
+ }
177
+
178
+ if (!hasInitializedSingle) {
179
+ uncontrolledSingleValue = handle.props.defaultValue ?? null
180
+ hasInitializedSingle = true
181
+ }
182
+
183
+ return uncontrolledSingleValue
184
+ }
185
+
186
+ function getMultipleValue() {
187
+ if (!isMultipleProps(handle.props)) {
188
+ return []
189
+ }
190
+
191
+ if (handle.props.value !== undefined) {
192
+ return handle.props.value
193
+ }
194
+
195
+ if (!hasInitializedMultiple) {
196
+ uncontrolledMultipleValue = [...(handle.props.defaultValue ?? [])]
197
+ hasInitializedMultiple = true
198
+ }
199
+
200
+ return uncontrolledMultipleValue
201
+ }
202
+
203
+ function isOpen(value: string) {
204
+ if (getType() === 'multiple') {
205
+ return getMultipleValue().includes(value)
206
+ }
207
+
208
+ return getSingleValue() === value
209
+ }
210
+
211
+ function dispatchChange(itemValue: string, value: AccordionValue) {
212
+ rootNode?.dispatchEvent(
213
+ new AccordionChangeEvent(value, {
214
+ accordionType: getType(),
215
+ itemValue,
216
+ }),
217
+ )
218
+ }
219
+
220
+ function toggleItem(itemValue: string) {
221
+ if (handle.props.disabled) {
222
+ return
223
+ }
224
+
225
+ if (isMultipleProps(handle.props)) {
226
+ let currentValue = getMultipleValue()
227
+ let nextValue = currentValue.includes(itemValue)
228
+ ? currentValue.filter((value) => value !== itemValue)
229
+ : [...currentValue, itemValue]
230
+
231
+ if (handle.props.value === undefined) {
232
+ uncontrolledMultipleValue = nextValue
233
+ void handle.update()
234
+ }
235
+
236
+ handle.props.onValueChange?.(nextValue)
237
+ dispatchChange(itemValue, nextValue)
238
+ return
239
+ }
240
+
241
+ let isCurrentItemOpen = getSingleValue() === itemValue
242
+ if (isCurrentItemOpen && !(handle.props.collapsible ?? true)) {
243
+ return
244
+ }
245
+
246
+ let nextValue = isCurrentItemOpen ? null : itemValue
247
+
248
+ if (handle.props.value === undefined) {
249
+ uncontrolledSingleValue = nextValue
250
+ void handle.update()
251
+ }
252
+
253
+ handle.props.onValueChange?.(nextValue)
254
+ dispatchChange(itemValue, nextValue)
255
+ }
256
+
257
+ function focusItem(itemValue: string, direction: FocusDirection) {
258
+ let items = registeredItems.filter((item) => !item.disabled && item.getTriggerNode() !== null)
259
+ if (items.length === 0) {
260
+ return
261
+ }
262
+
263
+ let currentIndex = items.findIndex((item) => item.value === itemValue)
264
+ let targetIndex = 0
265
+
266
+ switch (direction) {
267
+ case 'first':
268
+ targetIndex = 0
269
+ break
270
+ case 'last':
271
+ targetIndex = items.length - 1
272
+ break
273
+ case 'previous':
274
+ targetIndex = currentIndex <= 0 ? items.length - 1 : currentIndex - 1
275
+ break
276
+ case 'next':
277
+ targetIndex =
278
+ currentIndex === -1 || currentIndex === items.length - 1 ? 0 : currentIndex + 1
279
+ break
280
+ }
281
+
282
+ items[targetIndex]?.getTriggerNode()?.focus()
283
+ }
284
+
285
+ function registerItem(item: RegisteredItem) {
286
+ registeredItems.push(item)
287
+ }
288
+
289
+ function getTriggerId(value: string) {
290
+ return registeredItems.find((item) => item.value === value)?.getTriggerNode()?.id
291
+ }
292
+
293
+ function getPanelId(value: string) {
294
+ return `${handle.id}-${value}-panel`
295
+ }
296
+
297
+ handle.context.set({
298
+ get collapsible() {
299
+ return getType() === 'multiple'
300
+ ? true
301
+ : ((handle.props as AccordionSingleContextProps).collapsible ?? true)
302
+ },
303
+ get disabled() {
304
+ return handle.props.disabled ?? false
305
+ },
306
+ focusItem,
307
+ getPanelId,
308
+ getTriggerId,
309
+ get headingLevel() {
310
+ return handle.props.headingLevel ?? 3
311
+ },
312
+ isOpen,
313
+ registerItem,
314
+ registerRoot(node) {
315
+ rootNode = node
316
+ },
317
+ toggleItem,
318
+ get type() {
319
+ return getType()
320
+ },
321
+ unregisterRoot(node) {
322
+ if (rootNode === node) {
323
+ rootNode = null
324
+ }
325
+ },
326
+ })
327
+
328
+ return () => {
329
+ registeredItems = []
330
+ return handle.props.children
331
+ }
332
+ }
333
+
334
+ function AccordionItemProvider(
335
+ handle: Handle<AccordionItemOptions & { children?: RemixNode }, AccordionItemContextValue>,
336
+ ): () => RemixNode {
337
+ let triggerNode: HTMLButtonElement | null = null
338
+ let triggerId = `${handle.id}-trigger`
339
+ let panelId = `${handle.id}-panel`
340
+ let disabled = false
341
+ let headingLevel: AccordionHeadingLevel = 3
342
+ let lockedOpen = false
343
+ let open = false
344
+ let value = handle.props.value
345
+
346
+ handle.context.set({
347
+ get disabled() {
348
+ return disabled
349
+ },
350
+ get headingLevel() {
351
+ return headingLevel
352
+ },
353
+ get lockedOpen() {
354
+ return lockedOpen
355
+ },
356
+ get open() {
357
+ return open
358
+ },
359
+ get panelId() {
360
+ return panelId
361
+ },
362
+ setTriggerNode(node) {
363
+ triggerNode = node
364
+ },
365
+ get triggerId() {
366
+ return triggerId
367
+ },
368
+ get value() {
369
+ return value
370
+ },
371
+ })
372
+
373
+ return () => {
374
+ let accordion = getAccordionContext(handle)
375
+ value = handle.props.value
376
+ disabled = accordion.disabled || handle.props.disabled === true
377
+ headingLevel = accordion.headingLevel
378
+ open = accordion.isOpen(handle.props.value)
379
+ lockedOpen = accordion.type === 'single' && !accordion.collapsible && open
380
+
381
+ accordion.registerItem({
382
+ disabled,
383
+ getTriggerNode: () => triggerNode,
384
+ value: handle.props.value,
385
+ })
386
+
387
+ return handle.props.children
388
+ }
389
+ }
390
+
391
+ const rootMixin = createMixin<HTMLElement, [options?: AccordionRootOptions], ElementProps>(
392
+ (handle) => {
393
+ let context = getAccordionContext(handle)
394
+ let rootNode: HTMLElement | null = null
395
+
396
+ handle.queueTask((node, signal) => {
397
+ rootNode = node
398
+ context.registerRoot(node)
399
+ signal.addEventListener('abort', () => {
400
+ if (rootNode === node) {
401
+ rootNode = null
402
+ }
403
+ context.unregisterRoot(node)
404
+ })
405
+ })
406
+
407
+ return () =>
408
+ attrs({
409
+ 'data-disabled': context.disabled ? '' : undefined,
410
+ 'data-type': context.type,
411
+ })
412
+ },
413
+ )
414
+
415
+ const itemMixin = createMixin<HTMLElement, [options?: AccordionItemOptions], ElementProps>(
416
+ (handle) => {
417
+ let item = getAccordionItemContext(handle)
418
+
419
+ return () =>
420
+ attrs({
421
+ 'data-disabled': item.disabled ? '' : undefined,
422
+ 'data-state': item.open ? 'open' : 'closed',
423
+ })
424
+ },
425
+ )
426
+
427
+ const triggerMixin = createMixin<
428
+ HTMLButtonElement,
429
+ [options?: AccordionTriggerOptions],
430
+ ElementProps
431
+ >((handle) => {
432
+ let accordion = getAccordionContext(handle)
433
+ let item = getAccordionItemContext(handle)
434
+
435
+ return (options, props = options as ElementProps) => {
436
+ options = props === options ? undefined : options
437
+ let disabled = item.disabled || options?.disabled === true || props.disabled === true
438
+ let toggleItem = () => {
439
+ if (disabled || item.lockedOpen) {
440
+ return
441
+ }
442
+
443
+ accordion.toggleItem(item.value)
444
+ }
445
+
446
+ return [
447
+ attrs({
448
+ 'aria-controls': item.panelId,
449
+ 'aria-disabled': item.lockedOpen ? true : undefined,
450
+ 'aria-expanded': item.open,
451
+ 'data-state': item.open ? 'open' : 'closed',
452
+ disabled: disabled ? true : undefined,
453
+ id: item.triggerId,
454
+ }),
455
+ ref((node: HTMLButtonElement) => {
456
+ item.setTriggerNode(node)
457
+ }),
458
+ on('click', toggleItem),
459
+ on('keydown', (event) => {
460
+ switch (event.key) {
461
+ case 'ArrowDown':
462
+ event.preventDefault()
463
+ accordion.focusItem(item.value, 'next')
464
+ break
465
+ case 'ArrowUp':
466
+ event.preventDefault()
467
+ accordion.focusItem(item.value, 'previous')
468
+ break
469
+ case 'Home':
470
+ event.preventDefault()
471
+ accordion.focusItem(item.value, 'first')
472
+ break
473
+ case 'End':
474
+ event.preventDefault()
475
+ accordion.focusItem(item.value, 'last')
476
+ break
477
+ }
478
+ }),
479
+ ]
480
+ }
481
+ })
482
+
483
+ const contentMixin = createMixin<HTMLElement, [options?: AccordionContentOptions], ElementProps>(
484
+ (handle) => {
485
+ let item = getAccordionItemContext(handle)
486
+
487
+ return () =>
488
+ attrs({
489
+ 'aria-hidden': item.open ? undefined : true,
490
+ 'aria-labelledby': item.triggerId,
491
+ 'data-state': item.open ? 'open' : 'closed',
492
+ id: item.panelId,
493
+ inert: item.open ? undefined : true,
494
+ })
495
+ },
496
+ )
497
+
498
+ export const Context = AccordionProvider
499
+ export const ItemContext: (
500
+ handle: Handle<AccordionItemOptions & { children?: RemixNode }, AccordionItemContextValue>,
501
+ ) => () => RemixNode = AccordionItemProvider
502
+ export const content = contentMixin
503
+ export const item = itemMixin
504
+ export const root = rootMixin
505
+ export const trigger = triggerMixin
506
+
507
+ export function onAccordionChange<target extends HTMLElement>(
508
+ handler: AccordionChangeHandler<target>,
509
+ captureBoolean?: boolean,
510
+ ): ReturnType<typeof on<target, typeof ACCORDION_CHANGE_EVENT>> {
511
+ return on(ACCORDION_CHANGE_EVENT, handler, captureBoolean)
512
+ }
@@ -2,7 +2,7 @@
2
2
 
3
3
  `anchor` positions a floating element against an anchor element or viewport coordinates and keeps it constrained to the viewport. Use it for custom floating surfaces that need placement, flipping, offsets, and optional relative alignment.
4
4
 
5
- ## Usage
5
+ ## Primitive Usage
6
6
 
7
7
  ```tsx
8
8
  import { anchor } from 'remix/ui/anchor'
@@ -55,7 +55,42 @@ Anchor to coordinates when the surface should open at a pointer location.
55
55
  let cleanup = anchor(popover, { x: event.clientX, y: event.clientY }, { placement: 'bottom-start' })
56
56
  ```
57
57
 
58
- ## `anchor.*`
58
+ Keep presentation app-owned when the anchored element is rendered by your component:
59
+
60
+ ```tsx
61
+ import { anchor } from 'remix/ui/anchor'
62
+ import { on, ref, type Handle } from 'remix/ui'
63
+ import { panelStyle } from './floating.styles'
64
+
65
+ export function AnchoredPanel(handle: Handle) {
66
+ let cleanup = () => {}
67
+ let trigger: HTMLElement | null = null
68
+ let panel: HTMLElement | null = null
69
+
70
+ function position() {
71
+ cleanup()
72
+ if (trigger && panel) {
73
+ cleanup = anchor(panel, trigger, { placement: 'bottom-start', offset: 8 })
74
+ }
75
+ }
76
+
77
+ return () => (
78
+ <>
79
+ <button
80
+ mix={[ref((node) => (trigger = node as HTMLElement)), on('click', position)]}
81
+ type="button"
82
+ >
83
+ Open
84
+ </button>
85
+ <div data-panel mix={[panelStyle, ref((node) => (panel = node as HTMLElement))]}>
86
+ Panel
87
+ </div>
88
+ </>
89
+ )
90
+ }
91
+ ```
92
+
93
+ ## `remix/ui/anchor`
59
94
 
60
95
  - `anchor(floatingElement, anchorTarget, options)`: positions `floatingElement` against an element or coordinate target, starts animation-frame polling for geometry changes, and returns a cleanup function.
61
96
  - `AnchorOptions`: placement, inset, relative alignment, and offset options.
@@ -15,16 +15,19 @@ function shuffle<T>(array: T[]): T[] {
15
15
  export function Reordering(handle: Handle) {
16
16
  let order = initialOrder
17
17
 
18
- function scheduleNextShuffle() {
19
- setTimeout(() => {
20
- if (handle.signal.aborted) return
18
+ function scheduleNextShuffle(signal: AbortSignal) {
19
+ let timeoutId = setTimeout(async () => {
20
+ if (signal.aborted) return
21
21
  order = shuffle(order)
22
- handle.update()
23
- scheduleNextShuffle()
22
+ let nextSignal = await handle.update()
23
+ if (nextSignal.aborted) return
24
+ scheduleNextShuffle(nextSignal)
24
25
  }, 1000)
26
+
27
+ signal.addEventListener('abort', () => clearTimeout(timeoutId), { once: true })
25
28
  }
26
29
 
27
- scheduleNextShuffle()
30
+ handle.queueTask(scheduleNextShuffle)
28
31
 
29
32
  return () => (
30
33
  <ul
@@ -2,7 +2,7 @@
2
2
 
3
3
  `Breadcrumbs` renders semantic breadcrumb navigation from a list of items. Use it when the page needs a compact path back through parent sections.
4
4
 
5
- ## Usage
5
+ ## Component Usage
6
6
 
7
7
  ```tsx
8
8
  import { Breadcrumbs } from 'remix/ui/breadcrumbs'
@@ -32,13 +32,13 @@ Mark an earlier item as current when the page belongs to a parent section but th
32
32
  />
33
33
  ```
34
34
 
35
- Pass `separator` to replace the default chevron glyph.
35
+ Pass `separator` to replace the default chevron icon.
36
36
 
37
37
  ```tsx
38
38
  <Breadcrumbs items={[{ href: '/', label: 'Home' }, { label: 'Breadcrumbs' }]} separator="/" />
39
39
  ```
40
40
 
41
- ## `breadcrumbs.*`
41
+ ## `remix/ui/breadcrumbs`
42
42
 
43
43
  - `Breadcrumbs`: component that renders a `<nav>` with an ordered list of breadcrumb items.
44
44
  - `BreadcrumbItem`: item shape with `label`, optional `href`, and optional `current`.
@@ -52,4 +52,4 @@ Pass `separator` to replace the default chevron glyph.
52
52
  - An explicit current item wins over the last-item default.
53
53
  - Current items render as text with `aria-current="page"`, even when they include `href`.
54
54
  - Non-current items with `href` render as links; non-current items without `href` render as text.
55
- - The default separator is the `chevronRight` glyph. Separators render between items and are hidden from assistive technology.
55
+ - The default separator is a chevron icon. Separators render between items and are hidden from assistive technology.
@@ -1,8 +1,8 @@
1
1
  import { css, createElement } from '@remix-run/ui'
2
2
  import type { Handle, Props, RemixNode } from '@remix-run/ui'
3
3
 
4
- import { Glyph } from '../glyph/glyph.tsx'
5
- import { theme } from '../../theme/theme.ts'
4
+ import { ChevronRightIcon } from '../shared/icons.tsx'
5
+ import { componentStyleValues as styles } from '../shared/style-values.ts'
6
6
 
7
7
  export type BreadcrumbItem = {
8
8
  current?: boolean
@@ -23,7 +23,7 @@ const listCss = css({
23
23
  display: 'flex',
24
24
  flexWrap: 'wrap',
25
25
  alignItems: 'center',
26
- gap: `${theme.space.xs} ${theme.space.sm}`,
26
+ gap: `${styles.space.xs} ${styles.space.sm}`,
27
27
  minWidth: 0,
28
28
  margin: 0,
29
29
  padding: 0,
@@ -41,34 +41,34 @@ const separatorCss = css({
41
41
  alignItems: 'center',
42
42
  justifyContent: 'center',
43
43
  flexShrink: 0,
44
- width: theme.fontSize.sm,
45
- height: theme.fontSize.sm,
46
- color: theme.colors.text.muted,
44
+ width: styles.fontSize.sm,
45
+ height: styles.fontSize.sm,
46
+ color: styles.colors.text.muted,
47
47
  })
48
48
 
49
49
  const linkCss = css({
50
- color: theme.colors.text.secondary,
51
- fontSize: theme.fontSize.sm,
52
- lineHeight: theme.lineHeight.normal,
50
+ color: styles.colors.text.secondary,
51
+ fontSize: styles.fontSize.sm,
52
+ lineHeight: styles.lineHeight.normal,
53
53
  textDecoration: 'none',
54
54
  whiteSpace: 'nowrap',
55
55
  '&:hover': {
56
- color: theme.colors.text.primary,
56
+ color: styles.colors.text.primary,
57
57
  },
58
58
  })
59
59
 
60
60
  const currentCss = css({
61
- color: theme.colors.text.primary,
62
- fontSize: theme.fontSize.sm,
63
- lineHeight: theme.lineHeight.normal,
64
- fontWeight: theme.fontWeight.medium,
61
+ color: styles.colors.text.primary,
62
+ fontSize: styles.fontSize.sm,
63
+ lineHeight: styles.lineHeight.normal,
64
+ fontWeight: styles.fontWeight.medium,
65
65
  whiteSpace: 'nowrap',
66
66
  })
67
67
 
68
68
  const textCss = css({
69
- color: theme.colors.text.secondary,
70
- fontSize: theme.fontSize.sm,
71
- lineHeight: theme.lineHeight.normal,
69
+ color: styles.colors.text.secondary,
70
+ fontSize: styles.fontSize.sm,
71
+ lineHeight: styles.lineHeight.normal,
72
72
  whiteSpace: 'nowrap',
73
73
  })
74
74
 
@@ -80,7 +80,7 @@ export function Breadcrumbs(handle: Handle<BreadcrumbsProps>): () => RemixNode {
80
80
  currentIndex = Math.max(0, items.length - 1)
81
81
  }
82
82
 
83
- let separatorContent = separator ?? <Glyph name="chevronRight" />
83
+ let separatorContent = separator ?? <ChevronRightIcon />
84
84
 
85
85
  return (
86
86
  <nav aria-label={ariaLabel ?? 'Breadcrumb'} {...navProps} mix={[rootCss, mix]}>