@remix-run/ui 0.2.0 → 0.3.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 (232) hide show
  1. package/dist/animation/demos/animation.demo.d.ts +5 -0
  2. package/dist/animation/demos/animation.demo.js +92 -0
  3. package/dist/animation/demos/animation.demo.js.map +1 -0
  4. package/dist/animation/demos/aspect-ratio.d.ts +2 -0
  5. package/dist/animation/demos/aspect-ratio.js +69 -0
  6. package/dist/animation/demos/aspect-ratio.js.map +1 -0
  7. package/dist/animation/demos/bouncy-switch.d.ts +2 -0
  8. package/dist/animation/demos/bouncy-switch.js +36 -0
  9. package/dist/animation/demos/bouncy-switch.js.map +1 -0
  10. package/dist/animation/demos/color-interpolation.d.ts +1 -0
  11. package/dist/animation/demos/color-interpolation.js +53 -0
  12. package/dist/animation/demos/color-interpolation.js.map +1 -0
  13. package/dist/animation/demos/cube.d.ts +2 -0
  14. package/dist/animation/demos/cube.js +86 -0
  15. package/dist/animation/demos/cube.js.map +1 -0
  16. package/dist/animation/demos/default-animate.d.ts +2 -0
  17. package/dist/animation/demos/default-animate.js +89 -0
  18. package/dist/animation/demos/default-animate.js.map +1 -0
  19. package/dist/animation/demos/drag-release.d.ts +24 -0
  20. package/dist/animation/demos/drag-release.js +96 -0
  21. package/dist/animation/demos/drag-release.js.map +1 -0
  22. package/dist/animation/demos/enter.d.ts +1 -0
  23. package/dist/animation/demos/enter.js +19 -0
  24. package/dist/animation/demos/enter.js.map +1 -0
  25. package/dist/animation/demos/exit.d.ts +2 -0
  26. package/dist/animation/demos/exit.js +58 -0
  27. package/dist/animation/demos/exit.js.map +1 -0
  28. package/dist/animation/demos/flip-toggle.d.ts +2 -0
  29. package/dist/animation/demos/flip-toggle.js +37 -0
  30. package/dist/animation/demos/flip-toggle.js.map +1 -0
  31. package/dist/animation/demos/hold-to-confirm.d.ts +13 -0
  32. package/dist/animation/demos/hold-to-confirm.js +211 -0
  33. package/dist/animation/demos/hold-to-confirm.js.map +1 -0
  34. package/dist/animation/demos/html-content.d.ts +2 -0
  35. package/dist/animation/demos/html-content.js +30 -0
  36. package/dist/animation/demos/html-content.js.map +1 -0
  37. package/dist/animation/demos/interruptible-keyframes.d.ts +1 -0
  38. package/dist/animation/demos/interruptible-keyframes.js +48 -0
  39. package/dist/animation/demos/interruptible-keyframes.js.map +1 -0
  40. package/dist/animation/demos/keyframes.d.ts +1 -0
  41. package/dist/animation/demos/keyframes.js +24 -0
  42. package/dist/animation/demos/keyframes.js.map +1 -0
  43. package/dist/animation/demos/material-ripple.d.ts +2 -0
  44. package/dist/animation/demos/material-ripple.js +127 -0
  45. package/dist/animation/demos/material-ripple.js.map +1 -0
  46. package/dist/animation/demos/mixin-presence-list.d.ts +2 -0
  47. package/dist/animation/demos/mixin-presence-list.js +88 -0
  48. package/dist/animation/demos/mixin-presence-list.js.map +1 -0
  49. package/dist/animation/demos/mixin-reclaim.d.ts +2 -0
  50. package/dist/animation/demos/mixin-reclaim.js +90 -0
  51. package/dist/animation/demos/mixin-reclaim.js.map +1 -0
  52. package/dist/animation/demos/multi-state-badge.d.ts +2 -0
  53. package/dist/animation/demos/multi-state-badge.js +231 -0
  54. package/dist/animation/demos/multi-state-badge.js.map +1 -0
  55. package/dist/animation/demos/press.d.ts +2 -0
  56. package/dist/animation/demos/press.js +63 -0
  57. package/dist/animation/demos/press.js.map +1 -0
  58. package/dist/animation/demos/reordering.d.ts +2 -0
  59. package/dist/animation/demos/reordering.js +50 -0
  60. package/dist/animation/demos/reordering.js.map +1 -0
  61. package/dist/animation/demos/rolling-square.d.ts +2 -0
  62. package/dist/animation/demos/rolling-square.js +42 -0
  63. package/dist/animation/demos/rolling-square.js.map +1 -0
  64. package/dist/animation/demos/rotate.d.ts +1 -0
  65. package/dist/animation/demos/rotate.js +18 -0
  66. package/dist/animation/demos/rotate.js.map +1 -0
  67. package/dist/animation/demos/shared-layout.d.ts +2 -0
  68. package/dist/animation/demos/shared-layout.js +114 -0
  69. package/dist/animation/demos/shared-layout.js.map +1 -0
  70. package/dist/animation/demos/spring.demo.d.ts +6 -0
  71. package/dist/animation/demos/spring.demo.js +304 -0
  72. package/dist/animation/demos/spring.demo.js.map +1 -0
  73. package/dist/animation/demos/transition-options.d.ts +1 -0
  74. package/dist/animation/demos/transition-options.js +21 -0
  75. package/dist/animation/demos/transition-options.js.map +1 -0
  76. package/dist/animation/spring.d.ts +22 -0
  77. package/dist/animation/spring.js +22 -0
  78. package/dist/animation/spring.js.map +1 -1
  79. package/dist/components/accordion/demos/card.demo.d.ts +1 -0
  80. package/dist/components/accordion/demos/card.demo.js +1 -0
  81. package/dist/components/accordion/demos/card.demo.js.map +1 -1
  82. package/dist/components/accordion/demos/multiple.demo.d.ts +1 -0
  83. package/dist/components/accordion/demos/multiple.demo.js +1 -0
  84. package/dist/components/accordion/demos/multiple.demo.js.map +1 -1
  85. package/dist/components/accordion/demos/overview.demo.d.ts +1 -0
  86. package/dist/components/accordion/demos/overview.demo.js +1 -0
  87. package/dist/components/accordion/demos/overview.demo.js.map +1 -1
  88. package/dist/components/anchor/anchor.d.ts +8 -1
  89. package/dist/components/anchor/anchor.js +30 -11
  90. package/dist/components/anchor/anchor.js.map +1 -1
  91. package/dist/components/breadcrumbs/{demos/basic.demo.d.ts → basic.demo.d.ts} +1 -0
  92. package/dist/components/breadcrumbs/{demos/basic.demo.js → basic.demo.js} +1 -0
  93. package/dist/components/breadcrumbs/basic.demo.js.map +1 -0
  94. package/dist/components/breadcrumbs/{demos/separator.demo.d.ts → separator.demo.d.ts} +1 -0
  95. package/dist/components/breadcrumbs/{demos/separator.demo.js → separator.demo.js} +1 -0
  96. package/dist/components/breadcrumbs/separator.demo.js.map +1 -0
  97. package/dist/components/button/demos/aliases.demo.d.ts +1 -0
  98. package/dist/components/button/demos/aliases.demo.js +1 -0
  99. package/dist/components/button/demos/aliases.demo.js.map +1 -1
  100. package/dist/components/button/demos/basic.demo.d.ts +1 -0
  101. package/dist/components/button/demos/basic.demo.js +1 -0
  102. package/dist/components/button/demos/basic.demo.js.map +1 -1
  103. package/dist/components/button/demos/states.demo.d.ts +1 -0
  104. package/dist/components/button/demos/states.demo.js +1 -0
  105. package/dist/components/button/demos/states.demo.js.map +1 -1
  106. package/dist/components/combobox/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
  107. package/dist/components/combobox/{demos/overview.demo.js → overview.demo.js} +1 -0
  108. package/dist/components/combobox/overview.demo.js.map +1 -0
  109. package/dist/components/listbox/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
  110. package/dist/components/listbox/{demos/overview.demo.js → overview.demo.js} +1 -0
  111. package/dist/components/listbox/overview.demo.js.map +1 -0
  112. package/dist/components/menu/demos/bubbling.demo.d.ts +1 -0
  113. package/dist/components/menu/demos/bubbling.demo.js +1 -0
  114. package/dist/components/menu/demos/bubbling.demo.js.map +1 -1
  115. package/dist/components/menu/demos/context-trigger.demo.d.ts +7 -0
  116. package/dist/components/menu/demos/context-trigger.demo.js +87 -0
  117. package/dist/components/menu/demos/context-trigger.demo.js.map +1 -0
  118. package/dist/components/menu/demos/overview.demo.d.ts +1 -0
  119. package/dist/components/menu/demos/overview.demo.js +1 -0
  120. package/dist/components/menu/demos/overview.demo.js.map +1 -1
  121. package/dist/components/menu/menu.d.ts +3 -0
  122. package/dist/components/menu/menu.js +55 -0
  123. package/dist/components/menu/menu.js.map +1 -1
  124. package/dist/components/popover/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
  125. package/dist/components/popover/{demos/overview.demo.js → overview.demo.js} +1 -0
  126. package/dist/components/popover/overview.demo.js.map +1 -0
  127. package/dist/components/popover/popover.d.ts +2 -2
  128. package/dist/components/popover/popover.js +11 -4
  129. package/dist/components/popover/popover.js.map +1 -1
  130. package/dist/components/select/{demos/deconstructed.demo.d.ts → deconstructed.demo.d.ts} +1 -0
  131. package/dist/components/select/{demos/deconstructed.demo.js → deconstructed.demo.js} +1 -0
  132. package/dist/components/select/deconstructed.demo.js.map +1 -0
  133. package/dist/components/select/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
  134. package/dist/components/select/{demos/overview.demo.js → overview.demo.js} +1 -0
  135. package/dist/components/select/overview.demo.js.map +1 -0
  136. package/dist/components/select/select.js +1 -1
  137. package/dist/components/select/select.js.map +1 -1
  138. package/dist/runtime/client-entries.d.ts +1 -2
  139. package/dist/runtime/client-entries.js.map +1 -1
  140. package/dist/runtime/component.d.ts +2 -2
  141. package/dist/runtime/component.js +1 -1
  142. package/dist/runtime/component.js.map +1 -1
  143. package/dist/runtime/demos/basic.demo.d.ts +6 -0
  144. package/dist/runtime/demos/basic.demo.js +16 -0
  145. package/dist/runtime/demos/basic.demo.js.map +1 -0
  146. package/dist/runtime/demos/controlled-uncontrolled-values.demo.d.ts +6 -0
  147. package/dist/runtime/demos/controlled-uncontrolled-values.demo.js +77 -0
  148. package/dist/runtime/demos/controlled-uncontrolled-values.demo.js.map +1 -0
  149. package/dist/runtime/demos/keyed-list.demo.d.ts +6 -0
  150. package/dist/runtime/demos/keyed-list.demo.js +62 -0
  151. package/dist/runtime/demos/keyed-list.demo.js.map +1 -0
  152. package/dist/runtime/demos/readme.demo.d.ts +5 -0
  153. package/dist/runtime/demos/readme.demo.js +396 -0
  154. package/dist/runtime/demos/readme.demo.js.map +1 -0
  155. package/dist/runtime/diff-dom.js +194 -21
  156. package/dist/runtime/diff-dom.js.map +1 -1
  157. package/dist/runtime/frame.d.ts +7 -0
  158. package/dist/runtime/frame.js +176 -41
  159. package/dist/runtime/frame.js.map +1 -1
  160. package/dist/runtime/jsx.d.ts +2 -3
  161. package/dist/runtime/jsx.js.map +1 -1
  162. package/dist/server/stream.js +3 -2
  163. package/dist/server/stream.js.map +1 -1
  164. package/dist/style/style.js +27 -10
  165. package/dist/style/style.js.map +1 -1
  166. package/package.json +3 -3
  167. package/src/animation/demos/animation.demo.tsx +194 -0
  168. package/src/animation/demos/aspect-ratio.tsx +120 -0
  169. package/src/animation/demos/bouncy-switch.tsx +47 -0
  170. package/src/animation/demos/color-interpolation.tsx +73 -0
  171. package/src/animation/demos/cube.tsx +116 -0
  172. package/src/animation/demos/default-animate.tsx +129 -0
  173. package/src/animation/demos/drag-release.ts +118 -0
  174. package/src/animation/demos/enter.tsx +22 -0
  175. package/src/animation/demos/exit.tsx +79 -0
  176. package/src/animation/demos/flip-toggle.tsx +46 -0
  177. package/src/animation/demos/hold-to-confirm.tsx +317 -0
  178. package/src/animation/demos/html-content.tsx +41 -0
  179. package/src/animation/demos/interruptible-keyframes.tsx +62 -0
  180. package/src/animation/demos/keyframes.tsx +29 -0
  181. package/src/animation/demos/material-ripple.tsx +158 -0
  182. package/src/animation/demos/mixin-presence-list.tsx +121 -0
  183. package/src/animation/demos/mixin-reclaim.tsx +125 -0
  184. package/src/animation/demos/multi-state-badge.tsx +426 -0
  185. package/src/animation/demos/press.tsx +67 -0
  186. package/src/animation/demos/reordering.tsx +65 -0
  187. package/src/animation/demos/rolling-square.tsx +57 -0
  188. package/src/animation/demos/rotate.tsx +21 -0
  189. package/src/animation/demos/shared-layout.tsx +211 -0
  190. package/src/animation/demos/spring.demo.tsx +385 -0
  191. package/src/animation/demos/transition-options.tsx +24 -0
  192. package/src/animation/spring.ts +22 -0
  193. package/src/components/accordion/demos/card.demo.tsx +1 -0
  194. package/src/components/accordion/demos/multiple.demo.tsx +1 -0
  195. package/src/components/accordion/demos/overview.demo.tsx +1 -0
  196. package/src/components/anchor/README.md +11 -3
  197. package/src/components/anchor/anchor.ts +53 -12
  198. package/src/components/breadcrumbs/{demos/basic.demo.tsx → basic.demo.tsx} +1 -0
  199. package/src/components/breadcrumbs/{demos/separator.demo.tsx → separator.demo.tsx} +1 -0
  200. package/src/components/button/demos/aliases.demo.tsx +1 -0
  201. package/src/components/button/demos/basic.demo.tsx +1 -0
  202. package/src/components/button/demos/states.demo.tsx +1 -0
  203. package/src/components/combobox/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
  204. package/src/components/listbox/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
  205. package/src/components/menu/README.md +26 -2
  206. package/src/components/menu/demos/bubbling.demo.tsx +1 -0
  207. package/src/components/menu/demos/context-trigger.demo.tsx +133 -0
  208. package/src/components/menu/demos/overview.demo.tsx +1 -0
  209. package/src/components/menu/menu.tsx +73 -1
  210. package/src/components/popover/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
  211. package/src/components/popover/popover.ts +18 -9
  212. package/src/components/select/{demos/deconstructed.demo.tsx → deconstructed.demo.tsx} +1 -0
  213. package/src/components/select/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
  214. package/src/components/select/select.tsx +1 -1
  215. package/src/runtime/client-entries.ts +1 -5
  216. package/src/runtime/component.ts +3 -3
  217. package/src/runtime/demos/basic.demo.tsx +21 -0
  218. package/src/runtime/demos/controlled-uncontrolled-values.demo.tsx +177 -0
  219. package/src/runtime/demos/keyed-list.demo.tsx +94 -0
  220. package/src/runtime/demos/readme.demo.tsx +702 -0
  221. package/src/runtime/diff-dom.ts +270 -20
  222. package/src/runtime/frame.ts +218 -41
  223. package/src/runtime/jsx.ts +6 -10
  224. package/src/server/stream.ts +7 -2
  225. package/src/style/style.ts +29 -10
  226. package/dist/components/breadcrumbs/demos/basic.demo.js.map +0 -1
  227. package/dist/components/breadcrumbs/demos/separator.demo.js.map +0 -1
  228. package/dist/components/combobox/demos/overview.demo.js.map +0 -1
  229. package/dist/components/listbox/demos/overview.demo.js.map +0 -1
  230. package/dist/components/popover/demos/overview.demo.js.map +0 -1
  231. package/dist/components/select/demos/deconstructed.demo.js.map +0 -1
  232. package/dist/components/select/demos/overview.demo.js.map +0 -1
@@ -0,0 +1,702 @@
1
+ import {
2
+ addEventListeners,
3
+ css,
4
+ on,
5
+ ref,
6
+ TypedEventTarget,
7
+ type Handle,
8
+ type RemixNode,
9
+ } from '@remix-run/ui'
10
+
11
+ // ============================================================================
12
+ // Getting Started - Basic App Example
13
+ // ============================================================================
14
+ function App(handle: Handle) {
15
+ let count = 0
16
+ return () => (
17
+ <button
18
+ mix={[
19
+ on('click', () => {
20
+ count++
21
+ handle.update()
22
+ }),
23
+ ]}
24
+ >
25
+ Count: {count}
26
+ </button>
27
+ )
28
+ }
29
+
30
+ // ============================================================================
31
+ // Component State and Updates - Counter
32
+ // ============================================================================
33
+ function Counter(handle: Handle) {
34
+ let count = 0
35
+
36
+ return () => (
37
+ <div>
38
+ <span>Count: {count}</span>
39
+ <button
40
+ mix={[
41
+ on('click', () => {
42
+ count++
43
+ handle.update()
44
+ }),
45
+ ]}
46
+ >
47
+ Increment
48
+ </button>
49
+ </div>
50
+ )
51
+ }
52
+
53
+ // ============================================================================
54
+ // Components - Greeting
55
+ // ============================================================================
56
+ function Greeting(handle: Handle<{ name: string }>) {
57
+ return () => <h1>Hello, {handle.props.name}!</h1>
58
+ }
59
+
60
+ // ============================================================================
61
+ // Stateful Components - CounterWithSetup
62
+ // ============================================================================
63
+ function CounterWithSetup(handle: Handle<{ initialCount: number; label?: string }>) {
64
+ // Component function: runs once
65
+ let count = handle.props.initialCount
66
+
67
+ // Return render function: runs on every update
68
+ return () => (
69
+ <div>
70
+ {handle.props.label || 'Count'}: {count}
71
+ <button
72
+ mix={[
73
+ on('click', () => {
74
+ count++
75
+ handle.update()
76
+ }),
77
+ ]}
78
+ >
79
+ Increment
80
+ </button>
81
+ </div>
82
+ )
83
+ }
84
+
85
+ // ============================================================================
86
+ // Setup Prop vs Props - CounterWithLabel
87
+ // ============================================================================
88
+ function CounterWithLabel(handle: Handle<{ initialCount: number; label?: string }>) {
89
+ let count = handle.props.initialCount
90
+
91
+ return () => (
92
+ <div>
93
+ {handle.props.label}: {count}
94
+ <button
95
+ mix={[
96
+ on('click', () => {
97
+ count++
98
+ handle.update()
99
+ }),
100
+ ]}
101
+ >
102
+ +
103
+ </button>
104
+ </div>
105
+ )
106
+ }
107
+
108
+ // ============================================================================
109
+ // Events - SearchInput
110
+ // ============================================================================
111
+ function SearchInput(handle: Handle) {
112
+ let query = ''
113
+ let results: string[] = []
114
+ let loading = false
115
+
116
+ return () => (
117
+ <div>
118
+ <input
119
+ type="text"
120
+ value={query}
121
+ placeholder="Type to search..."
122
+ mix={[
123
+ on('input', (event, signal) => {
124
+ query = event.currentTarget.value
125
+ loading = true
126
+ handle.update()
127
+
128
+ // Simulated search with timeout
129
+ setTimeout(() => {
130
+ if (signal.aborted) return
131
+ results = query ? [`Result for "${query}" 1`, `Result for "${query}" 2`] : []
132
+ loading = false
133
+ handle.update()
134
+ }, 300)
135
+ }),
136
+ ]}
137
+ />
138
+ {loading && <div>Loading...</div>}
139
+ {!loading && results.length > 0 && (
140
+ <ul>
141
+ {results.map((r) => (
142
+ <li>{r}</li>
143
+ ))}
144
+ </ul>
145
+ )}
146
+ </div>
147
+ )
148
+ }
149
+
150
+ // ============================================================================
151
+ // Controlled Input - Slug Form
152
+ // ============================================================================
153
+ function SlugForm(handle: Handle) {
154
+ let slug = ''
155
+ let generatedSlug = ''
156
+
157
+ return () => (
158
+ <form>
159
+ <label mix={[css({ display: 'flex', alignItems: 'center', gap: '8px' })]}>
160
+ <input
161
+ type="checkbox"
162
+ mix={[
163
+ on('change', (event) => {
164
+ if (event.currentTarget.checked) {
165
+ generatedSlug = crypto.randomUUID().slice(0, 8)
166
+ } else {
167
+ generatedSlug = ''
168
+ }
169
+ handle.update()
170
+ }),
171
+ ]}
172
+ />
173
+ Auto-generate slug
174
+ </label>
175
+ <label mix={[css({ display: 'flex', flexDirection: 'column', gap: '4px' })]}>
176
+ Slug
177
+ <input
178
+ type="text"
179
+ value={generatedSlug || slug}
180
+ disabled={!!generatedSlug}
181
+ mix={[
182
+ on('input', (event) => {
183
+ slug = event.currentTarget.value
184
+ handle.update()
185
+ }),
186
+ ]}
187
+ />
188
+ </label>
189
+ </form>
190
+ )
191
+ }
192
+
193
+ // ============================================================================
194
+ // Global Events - KeyboardTracker
195
+ // ============================================================================
196
+ function KeyboardTracker(handle: Handle) {
197
+ let keys: string[] = []
198
+
199
+ addEventListeners(document, handle.signal, {
200
+ keydown: (event) => {
201
+ keys.push(event.key)
202
+ if (keys.length > 10) keys.shift()
203
+ handle.update()
204
+ },
205
+ })
206
+
207
+ return () => <div>Keys: {keys.join(', ') || '(press some keys)'}</div>
208
+ }
209
+
210
+ // ============================================================================
211
+ // CSS Prop - Button (Basic)
212
+ // ============================================================================
213
+ function ButtonBasic() {
214
+ return () => (
215
+ <button
216
+ mix={[
217
+ css({
218
+ color: 'white',
219
+ backgroundColor: 'rgb(54, 113, 246)',
220
+ border: 'none',
221
+ padding: '8px 16px',
222
+ borderRadius: '4px',
223
+ cursor: 'pointer',
224
+ '&:hover': {
225
+ backgroundColor: 'rgb(37, 90, 210)',
226
+ },
227
+ '&:active': {
228
+ transform: 'scale(0.98)',
229
+ },
230
+ }),
231
+ ]}
232
+ >
233
+ Click me
234
+ </button>
235
+ )
236
+ }
237
+
238
+ // ============================================================================
239
+ // CSS Prop - Button (Advanced with nested rules)
240
+ // ============================================================================
241
+ function ButtonAdvanced() {
242
+ return () => (
243
+ <button
244
+ mix={[
245
+ css({
246
+ color: 'white',
247
+ backgroundColor: 'rgb(54, 113, 246)',
248
+ border: 'none',
249
+ padding: '10px 20px',
250
+ borderRadius: '6px',
251
+ cursor: 'pointer',
252
+ position: 'relative',
253
+ display: 'inline-flex',
254
+ alignItems: 'center',
255
+ gap: '8px',
256
+ '&:hover': {
257
+ backgroundColor: 'rgb(37, 90, 210)',
258
+ },
259
+ '&::before': {
260
+ content: '""',
261
+ position: 'absolute',
262
+ inset: '-2px',
263
+ borderRadius: '8px',
264
+ background: 'linear-gradient(45deg, rgb(54, 113, 246), rgb(99, 179, 255))',
265
+ zIndex: -1,
266
+ opacity: 0,
267
+ transition: 'opacity 0.2s',
268
+ },
269
+ '&:hover::before': {
270
+ opacity: 1,
271
+ },
272
+ '.icon': {
273
+ width: '16px',
274
+ height: '16px',
275
+ },
276
+ }),
277
+ ]}
278
+ >
279
+ <span className="icon">★</span>
280
+ Click me
281
+ </button>
282
+ )
283
+ }
284
+
285
+ // ============================================================================
286
+ // Ref Mixin - Form (Basic)
287
+ // ============================================================================
288
+ function FormBasic() {
289
+ let inputRef: HTMLInputElement
290
+
291
+ return () => (
292
+ <div>
293
+ <input
294
+ type="text"
295
+ placeholder="Click the button to select this"
296
+ // capture the input node
297
+ mix={[ref((node) => (inputRef = node)), css({ marginRight: '8px', padding: '4px 8px' })]}
298
+ />
299
+ <button
300
+ mix={[
301
+ css({ padding: '4px 12px' }),
302
+ on('click', () => {
303
+ // Select it from other parts of the form
304
+ inputRef.select()
305
+ }),
306
+ ]}
307
+ >
308
+ Select Input
309
+ </button>
310
+ </div>
311
+ )
312
+ }
313
+
314
+ // ============================================================================
315
+ // Ref Mixin with AbortSignal - ResizeObserver Component
316
+ // ============================================================================
317
+ function ResizeComponent(handle: Handle) {
318
+ let dimensions = { width: 0, height: 0 }
319
+
320
+ return () => (
321
+ <div
322
+ mix={[
323
+ ref((node, signal) => {
324
+ // Set up something that needs cleanup
325
+ let observer = new ResizeObserver((entries) => {
326
+ let entry = entries[0]
327
+ if (entry) {
328
+ dimensions.width = Math.round(entry.contentRect.width)
329
+ dimensions.height = Math.round(entry.contentRect.height)
330
+ handle.update()
331
+ }
332
+ })
333
+ observer.observe(node)
334
+
335
+ // Clean up when element is removed
336
+ signal.addEventListener('abort', () => {
337
+ observer.disconnect()
338
+ })
339
+ }),
340
+ css({
341
+ padding: '20px',
342
+ backgroundColor: 'rgba(255, 255, 255, 0.1)',
343
+ borderRadius: '8px',
344
+ resize: 'both',
345
+ overflow: 'auto',
346
+ minWidth: '100px',
347
+ minHeight: '60px',
348
+ border: '1px solid rgb(209, 213, 219)',
349
+ }),
350
+ ]}
351
+ >
352
+ Resize me! ({dimensions.width} × {dimensions.height})
353
+ </div>
354
+ )
355
+ }
356
+
357
+ // ============================================================================
358
+ // handle.update() - Player
359
+ // ============================================================================
360
+ function Player(handle: Handle) {
361
+ let isPlaying = false
362
+ let playButton: HTMLButtonElement
363
+ let stopButton: HTMLButtonElement
364
+
365
+ return () => (
366
+ <div mix={[css({ display: 'flex', gap: '8px' })]}>
367
+ <button
368
+ disabled={isPlaying}
369
+ mix={[
370
+ ref((node) => (playButton = node)),
371
+ css({
372
+ padding: '8px 16px',
373
+ opacity: isPlaying ? 0.5 : 1,
374
+ }),
375
+ on('click', async () => {
376
+ isPlaying = true
377
+ await handle.update()
378
+ // Focus the enabled button after update completes
379
+ stopButton.focus()
380
+ }),
381
+ ]}
382
+ >
383
+ ▶ Play
384
+ </button>
385
+ <button
386
+ disabled={!isPlaying}
387
+ mix={[
388
+ ref((node) => (stopButton = node)),
389
+ css({
390
+ padding: '8px 16px',
391
+ opacity: !isPlaying ? 0.5 : 1,
392
+ }),
393
+ on('click', async () => {
394
+ isPlaying = false
395
+ await handle.update()
396
+ // Focus the enabled button after update completes
397
+ playButton.focus()
398
+ }),
399
+ ]}
400
+ >
401
+ ⏹ Stop
402
+ </button>
403
+ </div>
404
+ )
405
+ }
406
+
407
+ // ============================================================================
408
+ // handle.queueTask - Form with scroll
409
+ // ============================================================================
410
+ function FormWithScroll(handle: Handle) {
411
+ let showDetails = false
412
+ let detailsSection: HTMLElement
413
+
414
+ return () => (
415
+ <div>
416
+ <label mix={[css({ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' })]}>
417
+ <input
418
+ type="checkbox"
419
+ checked={showDetails}
420
+ mix={[
421
+ on('change', (event) => {
422
+ showDetails = event.currentTarget.checked
423
+ handle.update()
424
+ if (showDetails) {
425
+ // Scroll to the expanded section after it renders
426
+ handle.queueTask(() => {
427
+ detailsSection.scrollIntoView({ behavior: 'smooth', block: 'start' })
428
+ })
429
+ }
430
+ }),
431
+ ]}
432
+ />
433
+ Show additional details
434
+ </label>
435
+ {showDetails && (
436
+ <section
437
+ mix={[
438
+ ref((node) => (detailsSection = node)),
439
+ css({
440
+ marginTop: '1rem',
441
+ padding: '1rem',
442
+ border: '1px solid rgba(255, 255, 255, 0.2)',
443
+ borderRadius: '8px',
444
+ backgroundColor: 'rgba(255, 255, 255, 0.05)',
445
+ }),
446
+ ]}
447
+ >
448
+ <h3 mix={[css({ margin: '0 0 0.5rem 0' })]}>Additional Details</h3>
449
+ <p mix={[css({ margin: 0 })]}>This section appears when the checkbox is checked.</p>
450
+ </section>
451
+ )}
452
+ </div>
453
+ )
454
+ }
455
+
456
+ // ============================================================================
457
+ // handle.signal - Clock
458
+ // ============================================================================
459
+ function Clock(handle: Handle) {
460
+ let interval = setInterval(() => {
461
+ // clear the interval when the component is disconnected
462
+ if (handle.signal.aborted) {
463
+ clearInterval(interval)
464
+ return
465
+ }
466
+ handle.update()
467
+ }, 1000)
468
+ return () => <span>{new Date().toLocaleTimeString()}</span>
469
+ }
470
+
471
+ // ============================================================================
472
+ // handle.id - LabeledInput
473
+ // ============================================================================
474
+ function LabeledInput(handle: Handle) {
475
+ return () => (
476
+ <div mix={[css({ display: 'flex', flexDirection: 'column', gap: '4px' })]}>
477
+ <label htmlFor={handle.id}>Name</label>
478
+ <input
479
+ id={handle.id}
480
+ type="text"
481
+ mix={[
482
+ css({
483
+ padding: '4px 8px',
484
+ borderRadius: '4px',
485
+ border: '1px solid rgba(255,255,255,0.3)',
486
+ }),
487
+ ]}
488
+ />
489
+ </div>
490
+ )
491
+ }
492
+
493
+ // ============================================================================
494
+ // Context API - Theme Provider and Consumer
495
+ // ============================================================================
496
+ function ThemeProvider(handle: Handle<{}, { theme: string }>) {
497
+ handle.context.set({ theme: 'dark' })
498
+
499
+ return () => (
500
+ <div mix={[css({ display: 'flex', flexDirection: 'column', gap: '8px' })]}>
501
+ <ThemedHeader />
502
+ </div>
503
+ )
504
+ }
505
+
506
+ function ThemedHeader(handle: Handle) {
507
+ // Consume context from ThemeProvider
508
+ let { theme } = handle.context.get(ThemeProvider)
509
+
510
+ return () => (
511
+ <header
512
+ mix={[
513
+ css({
514
+ backgroundColor: theme === 'dark' ? '#000' : '#fff',
515
+ color: theme === 'dark' ? '#fff' : '#000',
516
+ }),
517
+ ]}
518
+ >
519
+ Header
520
+ </header>
521
+ )
522
+ }
523
+
524
+ // ============================================================================
525
+ // Context API with EventTarget - Advanced Theme
526
+ // ============================================================================
527
+ class Theme extends TypedEventTarget<{ change: Event }> {
528
+ #value: 'light' | 'dark' = 'light'
529
+
530
+ get value() {
531
+ return this.#value
532
+ }
533
+
534
+ setValue(value: 'light' | 'dark') {
535
+ this.#value = value
536
+ this.dispatchEvent(new Event('change'))
537
+ }
538
+ }
539
+
540
+ function ThemeProviderAdvanced(handle: Handle<{}, Theme>) {
541
+ let theme = new Theme()
542
+ handle.context.set(theme)
543
+
544
+ return () => (
545
+ <div mix={[css({ display: 'flex', flexDirection: 'column', gap: '8px' })]}>
546
+ <button
547
+ mix={[
548
+ css({ padding: '8px 16px', alignSelf: 'flex-start' }),
549
+ on('click', () => {
550
+ // no updates in the parent component
551
+ theme.setValue(theme.value === 'light' ? 'dark' : 'light')
552
+ }),
553
+ ]}
554
+ >
555
+ Toggle Theme (EventTarget)
556
+ </button>
557
+ <ThemedContent />
558
+ </div>
559
+ )
560
+ }
561
+
562
+ function ThemedContent(handle: Handle) {
563
+ let theme = handle.context.get(ThemeProviderAdvanced)
564
+
565
+ // Subscribe to theme changes and update when it changes
566
+ addEventListeners(theme, handle.signal, {
567
+ change() {
568
+ handle.update()
569
+ },
570
+ })
571
+
572
+ return () => (
573
+ <div
574
+ mix={[
575
+ css({
576
+ padding: '12px',
577
+ borderRadius: '6px',
578
+ backgroundColor: theme.value === 'dark' ? '#1a1a1a' : '#f0f0f0',
579
+ color: theme.value === 'dark' ? '#fff' : '#000',
580
+ }),
581
+ ]}
582
+ >
583
+ Current theme: {theme.value}
584
+ </div>
585
+ )
586
+ }
587
+
588
+ // ============================================================================
589
+ // Fragments - List
590
+ // ============================================================================
591
+ function ListWithFragment() {
592
+ return () => (
593
+ <ul mix={[css({ margin: 0, paddingLeft: '20px' })]}>
594
+ <>
595
+ <li>Item 1</li>
596
+ <li>Item 2</li>
597
+ <li>Item 3</li>
598
+ </>
599
+ </ul>
600
+ )
601
+ }
602
+
603
+ // ============================================================================
604
+ // Example Container Component
605
+ // ============================================================================
606
+ function Example(handle: Handle<{ title: string; children: RemixNode }>) {
607
+ return () => (
608
+ <div className="example">
609
+ <h2>{handle.props.title}</h2>
610
+ <div className="example-content">{handle.props.children}</div>
611
+ </div>
612
+ )
613
+ }
614
+
615
+ // ============================================================================
616
+ // Main Demo App
617
+ // ============================================================================
618
+ /**
619
+ * @name README Examples
620
+ * @description A gallery of small examples that mirror the package README snippets.
621
+ */
622
+ export default function DemoApp() {
623
+ return () => (
624
+ <div className="examples-grid">
625
+ <Example title="Getting Started - Counter">
626
+ <App />
627
+ </Example>
628
+
629
+ <Example title="Component State - Counter">
630
+ <Counter />
631
+ </Example>
632
+
633
+ <Example title="Greeting">
634
+ <Greeting name="World" />
635
+ </Example>
636
+
637
+ <Example title="Counter with Setup">
638
+ <CounterWithSetup initialCount={10} label="Total" />
639
+ </Example>
640
+
641
+ <Example title="Setup vs Props">
642
+ <CounterWithLabel initialCount={5} label="Score" />
643
+ </Example>
644
+
645
+ <Example title="Events - Search Input">
646
+ <SearchInput />
647
+ </Example>
648
+
649
+ <Example title="Controlled Input - Slug Form">
650
+ <SlugForm />
651
+ </Example>
652
+
653
+ <Example title="Global Events - Keyboard Tracker">
654
+ <KeyboardTracker />
655
+ </Example>
656
+
657
+ <Example title="CSS Prop - Basic Button">
658
+ <ButtonBasic />
659
+ </Example>
660
+
661
+ <Example title="CSS Prop - Advanced Button">
662
+ <ButtonAdvanced />
663
+ </Example>
664
+
665
+ <Example title="Ref Mixin - Form">
666
+ <FormBasic />
667
+ </Example>
668
+
669
+ <Example title="Ref with AbortSignal - Resize Observer">
670
+ <ResizeComponent />
671
+ </Example>
672
+
673
+ <Example title="handle.update() - Player">
674
+ <Player />
675
+ </Example>
676
+
677
+ <Example title="handle.queueTask - Scroll to Section">
678
+ <FormWithScroll />
679
+ </Example>
680
+
681
+ <Example title="handle.signal - Clock">
682
+ <Clock />
683
+ </Example>
684
+
685
+ <Example title="handle.id - Labeled Input">
686
+ <LabeledInput />
687
+ </Example>
688
+
689
+ <Example title="Context API - Theme Provider">
690
+ <ThemeProvider />
691
+ </Example>
692
+
693
+ <Example title="Context with EventTarget">
694
+ <ThemeProviderAdvanced />
695
+ </Example>
696
+
697
+ <Example title="Fragments - List">
698
+ <ListWithFragment />
699
+ </Example>
700
+ </div>
701
+ )
702
+ }