@remix-run/ui 0.1.2 → 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 (318) hide show
  1. package/README.md +9 -7
  2. package/dist/animation/demos/animation.demo.d.ts +5 -0
  3. package/dist/animation/demos/animation.demo.js +92 -0
  4. package/dist/animation/demos/animation.demo.js.map +1 -0
  5. package/dist/animation/demos/aspect-ratio.d.ts +2 -0
  6. package/dist/animation/demos/aspect-ratio.js +69 -0
  7. package/dist/animation/demos/aspect-ratio.js.map +1 -0
  8. package/dist/animation/demos/bouncy-switch.d.ts +2 -0
  9. package/dist/animation/demos/bouncy-switch.js +36 -0
  10. package/dist/animation/demos/bouncy-switch.js.map +1 -0
  11. package/dist/animation/demos/color-interpolation.d.ts +1 -0
  12. package/dist/animation/demos/color-interpolation.js +53 -0
  13. package/dist/animation/demos/color-interpolation.js.map +1 -0
  14. package/dist/animation/demos/cube.d.ts +2 -0
  15. package/dist/animation/demos/cube.js +86 -0
  16. package/dist/animation/demos/cube.js.map +1 -0
  17. package/dist/animation/demos/default-animate.d.ts +2 -0
  18. package/dist/animation/demos/default-animate.js +89 -0
  19. package/dist/animation/demos/default-animate.js.map +1 -0
  20. package/dist/animation/demos/drag-release.d.ts +24 -0
  21. package/dist/animation/demos/drag-release.js +96 -0
  22. package/dist/animation/demos/drag-release.js.map +1 -0
  23. package/dist/animation/demos/enter.d.ts +1 -0
  24. package/dist/animation/demos/enter.js +19 -0
  25. package/dist/animation/demos/enter.js.map +1 -0
  26. package/dist/animation/demos/exit.d.ts +2 -0
  27. package/dist/animation/demos/exit.js +58 -0
  28. package/dist/animation/demos/exit.js.map +1 -0
  29. package/dist/animation/demos/flip-toggle.d.ts +2 -0
  30. package/dist/animation/demos/flip-toggle.js +37 -0
  31. package/dist/animation/demos/flip-toggle.js.map +1 -0
  32. package/dist/animation/demos/hold-to-confirm.d.ts +13 -0
  33. package/dist/animation/demos/hold-to-confirm.js +211 -0
  34. package/dist/animation/demos/hold-to-confirm.js.map +1 -0
  35. package/dist/animation/demos/html-content.d.ts +2 -0
  36. package/dist/animation/demos/html-content.js +30 -0
  37. package/dist/animation/demos/html-content.js.map +1 -0
  38. package/dist/animation/demos/interruptible-keyframes.d.ts +1 -0
  39. package/dist/animation/demos/interruptible-keyframes.js +48 -0
  40. package/dist/animation/demos/interruptible-keyframes.js.map +1 -0
  41. package/dist/animation/demos/keyframes.d.ts +1 -0
  42. package/dist/animation/demos/keyframes.js +24 -0
  43. package/dist/animation/demos/keyframes.js.map +1 -0
  44. package/dist/animation/demos/material-ripple.d.ts +2 -0
  45. package/dist/animation/demos/material-ripple.js +127 -0
  46. package/dist/animation/demos/material-ripple.js.map +1 -0
  47. package/dist/animation/demos/mixin-presence-list.d.ts +2 -0
  48. package/dist/animation/demos/mixin-presence-list.js +88 -0
  49. package/dist/animation/demos/mixin-presence-list.js.map +1 -0
  50. package/dist/animation/demos/mixin-reclaim.d.ts +2 -0
  51. package/dist/animation/demos/mixin-reclaim.js +90 -0
  52. package/dist/animation/demos/mixin-reclaim.js.map +1 -0
  53. package/dist/animation/demos/multi-state-badge.d.ts +2 -0
  54. package/dist/animation/demos/multi-state-badge.js +231 -0
  55. package/dist/animation/demos/multi-state-badge.js.map +1 -0
  56. package/dist/animation/demos/press.d.ts +2 -0
  57. package/dist/animation/demos/press.js +63 -0
  58. package/dist/animation/demos/press.js.map +1 -0
  59. package/dist/animation/demos/reordering.d.ts +2 -0
  60. package/dist/animation/demos/reordering.js +50 -0
  61. package/dist/animation/demos/reordering.js.map +1 -0
  62. package/dist/animation/demos/rolling-square.d.ts +2 -0
  63. package/dist/animation/demos/rolling-square.js +42 -0
  64. package/dist/animation/demos/rolling-square.js.map +1 -0
  65. package/dist/animation/demos/rotate.d.ts +1 -0
  66. package/dist/animation/demos/rotate.js +18 -0
  67. package/dist/animation/demos/rotate.js.map +1 -0
  68. package/dist/animation/demos/shared-layout.d.ts +2 -0
  69. package/dist/animation/demos/shared-layout.js +114 -0
  70. package/dist/animation/demos/shared-layout.js.map +1 -0
  71. package/dist/animation/demos/spring.demo.d.ts +6 -0
  72. package/dist/animation/demos/spring.demo.js +304 -0
  73. package/dist/animation/demos/spring.demo.js.map +1 -0
  74. package/dist/animation/demos/transition-options.d.ts +1 -0
  75. package/dist/animation/demos/transition-options.js +21 -0
  76. package/dist/animation/demos/transition-options.js.map +1 -0
  77. package/dist/animation/spring.d.ts +24 -31
  78. package/dist/animation/spring.js +31 -8
  79. package/dist/animation/spring.js.map +1 -1
  80. package/dist/components/accordion/accordion.d.ts +6 -6
  81. package/dist/components/accordion/accordion.js.map +1 -1
  82. package/dist/components/accordion/demos/card.demo.d.ts +1 -0
  83. package/dist/components/accordion/demos/card.demo.js +1 -0
  84. package/dist/components/accordion/demos/card.demo.js.map +1 -1
  85. package/dist/components/accordion/demos/multiple.demo.d.ts +1 -0
  86. package/dist/components/accordion/demos/multiple.demo.js +1 -0
  87. package/dist/components/accordion/demos/multiple.demo.js.map +1 -1
  88. package/dist/components/accordion/demos/overview.demo.d.ts +1 -0
  89. package/dist/components/accordion/demos/overview.demo.js +1 -0
  90. package/dist/components/accordion/demos/overview.demo.js.map +1 -1
  91. package/dist/components/anchor/anchor.d.ts +8 -1
  92. package/dist/components/anchor/anchor.js +30 -11
  93. package/dist/components/anchor/anchor.js.map +1 -1
  94. package/dist/components/breadcrumbs/{demos/basic.demo.d.ts → basic.demo.d.ts} +1 -0
  95. package/dist/components/breadcrumbs/{demos/basic.demo.js → basic.demo.js} +1 -0
  96. package/dist/components/breadcrumbs/basic.demo.js.map +1 -0
  97. package/dist/components/breadcrumbs/breadcrumbs.d.ts +1 -1
  98. package/dist/components/breadcrumbs/{demos/separator.demo.d.ts → separator.demo.d.ts} +1 -0
  99. package/dist/components/breadcrumbs/{demos/separator.demo.js → separator.demo.js} +1 -0
  100. package/dist/components/breadcrumbs/separator.demo.js.map +1 -0
  101. package/dist/components/button/button.d.ts +4 -4
  102. package/dist/components/button/button.js +3 -1
  103. package/dist/components/button/button.js.map +1 -1
  104. package/dist/components/button/demos/aliases.demo.d.ts +1 -0
  105. package/dist/components/button/demos/aliases.demo.js +1 -0
  106. package/dist/components/button/demos/aliases.demo.js.map +1 -1
  107. package/dist/components/button/demos/basic.demo.d.ts +1 -0
  108. package/dist/components/button/demos/basic.demo.js +1 -0
  109. package/dist/components/button/demos/basic.demo.js.map +1 -1
  110. package/dist/components/button/demos/states.demo.d.ts +1 -0
  111. package/dist/components/button/demos/states.demo.js +1 -0
  112. package/dist/components/button/demos/states.demo.js.map +1 -1
  113. package/dist/components/combobox/combobox.d.ts +10 -10
  114. package/dist/components/combobox/combobox.js.map +1 -1
  115. package/dist/components/combobox/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
  116. package/dist/components/combobox/{demos/overview.demo.js → overview.demo.js} +1 -0
  117. package/dist/components/combobox/overview.demo.js.map +1 -0
  118. package/dist/components/listbox/listbox.d.ts +3 -3
  119. package/dist/components/listbox/listbox.js.map +1 -1
  120. package/dist/components/listbox/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
  121. package/dist/components/listbox/{demos/overview.demo.js → overview.demo.js} +1 -0
  122. package/dist/components/listbox/overview.demo.js.map +1 -0
  123. package/dist/components/menu/demos/bubbling.demo.d.ts +1 -0
  124. package/dist/components/menu/demos/bubbling.demo.js +1 -0
  125. package/dist/components/menu/demos/bubbling.demo.js.map +1 -1
  126. package/dist/components/menu/demos/context-trigger.demo.d.ts +7 -0
  127. package/dist/components/menu/demos/context-trigger.demo.js +87 -0
  128. package/dist/components/menu/demos/context-trigger.demo.js.map +1 -0
  129. package/dist/components/menu/demos/overview.demo.d.ts +1 -0
  130. package/dist/components/menu/demos/overview.demo.js +1 -0
  131. package/dist/components/menu/demos/overview.demo.js.map +1 -1
  132. package/dist/components/menu/menu.d.ts +15 -12
  133. package/dist/components/menu/menu.js +55 -0
  134. package/dist/components/menu/menu.js.map +1 -1
  135. package/dist/components/popover/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
  136. package/dist/components/popover/{demos/overview.demo.js → overview.demo.js} +1 -0
  137. package/dist/components/popover/overview.demo.js.map +1 -0
  138. package/dist/components/popover/popover.d.ts +7 -7
  139. package/dist/components/popover/popover.js +11 -4
  140. package/dist/components/popover/popover.js.map +1 -1
  141. package/dist/components/select/{demos/deconstructed.demo.d.ts → deconstructed.demo.d.ts} +1 -0
  142. package/dist/components/select/{demos/deconstructed.demo.js → deconstructed.demo.js} +1 -0
  143. package/dist/components/select/deconstructed.demo.js.map +1 -0
  144. package/dist/components/select/{demos/overview.demo.d.ts → overview.demo.d.ts} +1 -0
  145. package/dist/components/select/{demos/overview.demo.js → overview.demo.js} +1 -0
  146. package/dist/components/select/overview.demo.js.map +1 -0
  147. package/dist/components/select/select.d.ts +10 -10
  148. package/dist/components/select/select.js +1 -1
  149. package/dist/components/select/select.js.map +1 -1
  150. package/dist/components/tabs/tabs.d.ts +3 -3
  151. package/dist/index.d.ts +1 -1
  152. package/dist/index.js.map +1 -1
  153. package/dist/interactions/keydown/keydown.d.ts +1 -1
  154. package/dist/interactions/outside-click/outside-click-mixin.d.ts +2 -1
  155. package/dist/interactions/typeahead/typeahead-mixin.d.ts +3 -1
  156. package/dist/runtime/client-entries.d.ts +1 -2
  157. package/dist/runtime/client-entries.js.map +1 -1
  158. package/dist/runtime/component.d.ts +20 -17
  159. package/dist/runtime/component.js +15 -6
  160. package/dist/runtime/component.js.map +1 -1
  161. package/dist/runtime/core/attributes.d.ts +3 -1
  162. package/dist/runtime/core/attributes.js +19 -1
  163. package/dist/runtime/core/attributes.js.map +1 -1
  164. package/dist/runtime/core/props.js +6 -5
  165. package/dist/runtime/core/props.js.map +1 -1
  166. package/dist/runtime/demos/basic.demo.d.ts +6 -0
  167. package/dist/runtime/demos/basic.demo.js +16 -0
  168. package/dist/runtime/demos/basic.demo.js.map +1 -0
  169. package/dist/runtime/demos/controlled-uncontrolled-values.demo.d.ts +6 -0
  170. package/dist/runtime/demos/controlled-uncontrolled-values.demo.js +77 -0
  171. package/dist/runtime/demos/controlled-uncontrolled-values.demo.js.map +1 -0
  172. package/dist/runtime/demos/keyed-list.demo.d.ts +6 -0
  173. package/dist/runtime/demos/keyed-list.demo.js +62 -0
  174. package/dist/runtime/demos/keyed-list.demo.js.map +1 -0
  175. package/dist/runtime/demos/readme.demo.d.ts +5 -0
  176. package/dist/runtime/demos/readme.demo.js +396 -0
  177. package/dist/runtime/demos/readme.demo.js.map +1 -0
  178. package/dist/runtime/diff-dom.js +194 -21
  179. package/dist/runtime/diff-dom.js.map +1 -1
  180. package/dist/runtime/dom.d.ts +16 -9
  181. package/dist/runtime/frame.d.ts +7 -0
  182. package/dist/runtime/frame.js +176 -41
  183. package/dist/runtime/frame.js.map +1 -1
  184. package/dist/runtime/jsx.d.ts +3 -4
  185. package/dist/runtime/jsx.js.map +1 -1
  186. package/dist/runtime/mixins/link-mixin.d.ts +5 -1
  187. package/dist/runtime/mixins/link-mixin.js.map +1 -1
  188. package/dist/runtime/mixins/mixin.d.ts +2 -1
  189. package/dist/runtime/mixins/mixin.js.map +1 -1
  190. package/dist/runtime/mixins/ref-mixin.d.ts +2 -1
  191. package/dist/runtime/mixins/ref-mixin.js.map +1 -1
  192. package/dist/runtime/reconcile.d.ts +1 -1
  193. package/dist/runtime/reconcile.js +19 -5
  194. package/dist/runtime/reconcile.js.map +1 -1
  195. package/dist/runtime/scheduler.d.ts +10 -10
  196. package/dist/runtime/scheduler.js +1 -1
  197. package/dist/runtime/scheduler.js.map +1 -1
  198. package/dist/runtime/svg-attributes.js +1 -0
  199. package/dist/runtime/svg-attributes.js.map +1 -1
  200. package/dist/runtime/to-vnode.js +2 -2
  201. package/dist/runtime/to-vnode.js.map +1 -1
  202. package/dist/runtime/vnode.d.ts +6 -1
  203. package/dist/runtime/vnode.js +4 -0
  204. package/dist/runtime/vnode.js.map +1 -1
  205. package/dist/server/stream.d.ts +2 -0
  206. package/dist/server/stream.js +82 -11
  207. package/dist/server/stream.js.map +1 -1
  208. package/dist/style/css-mixin.d.ts +2 -1
  209. package/dist/style/css-mixin.js.map +1 -1
  210. package/dist/style/index.d.ts +1 -1
  211. package/dist/style/style.js +27 -10
  212. package/dist/style/style.js.map +1 -1
  213. package/dist/style/stylesheet.d.ts +13 -13
  214. package/dist/style/stylesheet.js.map +1 -1
  215. package/dist/theme/contract.d.ts +24 -222
  216. package/dist/theme/contract.js +2 -2
  217. package/dist/theme/contract.js.map +1 -1
  218. package/dist/theme/glyph-contract.d.ts +1 -3
  219. package/dist/theme/glyph-contract.js.map +1 -1
  220. package/dist/theme/presets/rmx-01/index.d.ts +4 -2
  221. package/dist/theme/presets/rmx-01/index.js.map +1 -1
  222. package/dist/utils/scroll-lock.d.ts +3 -3
  223. package/dist/utils/scroll-lock.js.map +1 -1
  224. package/package.json +4 -4
  225. package/src/animation/README.md +7 -5
  226. package/src/animation/demos/animation.demo.tsx +194 -0
  227. package/src/animation/demos/aspect-ratio.tsx +120 -0
  228. package/src/animation/demos/bouncy-switch.tsx +47 -0
  229. package/src/animation/demos/color-interpolation.tsx +73 -0
  230. package/src/animation/demos/cube.tsx +116 -0
  231. package/src/animation/demos/default-animate.tsx +129 -0
  232. package/src/animation/demos/drag-release.ts +118 -0
  233. package/src/animation/demos/enter.tsx +22 -0
  234. package/src/animation/demos/exit.tsx +79 -0
  235. package/src/animation/demos/flip-toggle.tsx +46 -0
  236. package/src/animation/demos/hold-to-confirm.tsx +317 -0
  237. package/src/animation/demos/html-content.tsx +41 -0
  238. package/src/animation/demos/interruptible-keyframes.tsx +62 -0
  239. package/src/animation/demos/keyframes.tsx +29 -0
  240. package/src/animation/demos/material-ripple.tsx +158 -0
  241. package/src/animation/demos/mixin-presence-list.tsx +121 -0
  242. package/src/animation/demos/mixin-reclaim.tsx +125 -0
  243. package/src/animation/demos/multi-state-badge.tsx +426 -0
  244. package/src/animation/demos/press.tsx +67 -0
  245. package/src/animation/demos/reordering.tsx +65 -0
  246. package/src/animation/demos/rolling-square.tsx +57 -0
  247. package/src/animation/demos/rotate.tsx +21 -0
  248. package/src/animation/demos/shared-layout.tsx +211 -0
  249. package/src/animation/demos/spring.demo.tsx +385 -0
  250. package/src/animation/demos/transition-options.tsx +24 -0
  251. package/src/animation/spring.ts +43 -9
  252. package/src/components/accordion/accordion.tsx +7 -5
  253. package/src/components/accordion/demos/card.demo.tsx +1 -0
  254. package/src/components/accordion/demos/multiple.demo.tsx +1 -0
  255. package/src/components/accordion/demos/overview.demo.tsx +1 -0
  256. package/src/components/anchor/README.md +11 -3
  257. package/src/components/anchor/anchor.ts +54 -13
  258. package/src/components/breadcrumbs/{demos/basic.demo.tsx → basic.demo.tsx} +1 -0
  259. package/src/components/breadcrumbs/breadcrumbs.tsx +1 -1
  260. package/src/components/breadcrumbs/{demos/separator.demo.tsx → separator.demo.tsx} +1 -0
  261. package/src/components/button/button.tsx +30 -18
  262. package/src/components/button/demos/aliases.demo.tsx +1 -0
  263. package/src/components/button/demos/basic.demo.tsx +1 -0
  264. package/src/components/button/demos/states.demo.tsx +1 -0
  265. package/src/components/combobox/README.md +1 -1
  266. package/src/components/combobox/combobox.tsx +30 -12
  267. package/src/components/combobox/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
  268. package/src/components/glyph/glyph.tsx +2 -2
  269. package/src/components/listbox/listbox.ts +88 -83
  270. package/src/components/listbox/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
  271. package/src/components/menu/README.md +26 -2
  272. package/src/components/menu/demos/bubbling.demo.tsx +1 -0
  273. package/src/components/menu/demos/context-trigger.demo.tsx +133 -0
  274. package/src/components/menu/demos/overview.demo.tsx +1 -0
  275. package/src/components/menu/menu.tsx +100 -16
  276. package/src/components/popover/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
  277. package/src/components/popover/popover.ts +101 -81
  278. package/src/components/select/{demos/deconstructed.demo.tsx → deconstructed.demo.tsx} +1 -0
  279. package/src/components/select/{demos/overview.demo.tsx → overview.demo.tsx} +1 -0
  280. package/src/components/select/select.tsx +27 -10
  281. package/src/index.ts +1 -0
  282. package/src/runtime/client-entries.ts +1 -5
  283. package/src/runtime/component.ts +37 -15
  284. package/src/runtime/core/attributes.ts +21 -0
  285. package/src/runtime/core/props.ts +6 -4
  286. package/src/runtime/demos/basic.demo.tsx +21 -0
  287. package/src/runtime/demos/controlled-uncontrolled-values.demo.tsx +177 -0
  288. package/src/runtime/demos/keyed-list.demo.tsx +94 -0
  289. package/src/runtime/demos/readme.demo.tsx +702 -0
  290. package/src/runtime/diff-dom.ts +270 -20
  291. package/src/runtime/dom.ts +16 -9
  292. package/src/runtime/frame.ts +218 -41
  293. package/src/runtime/jsx.ts +7 -11
  294. package/src/runtime/mixins/link-mixin.ts +88 -86
  295. package/src/runtime/mixins/mixin.ts +9 -1
  296. package/src/runtime/mixins/ref-mixin.ts +4 -5
  297. package/src/runtime/reconcile.ts +23 -14
  298. package/src/runtime/scheduler.ts +20 -20
  299. package/src/runtime/svg-attributes.ts +1 -0
  300. package/src/runtime/to-vnode.ts +2 -2
  301. package/src/runtime/vnode.ts +10 -0
  302. package/src/server/README.md +4 -2
  303. package/src/server/stream.ts +84 -10
  304. package/src/style/css-mixin.ts +6 -1
  305. package/src/style/index.ts +1 -2
  306. package/src/style/style.ts +29 -10
  307. package/src/style/stylesheet.ts +14 -2
  308. package/src/theme/contract.ts +37 -5
  309. package/src/theme/glyph-contract.ts +3 -1
  310. package/src/theme/presets/rmx-01/index.ts +4 -2
  311. package/src/utils/scroll-lock.ts +7 -3
  312. package/dist/components/breadcrumbs/demos/basic.demo.js.map +0 -1
  313. package/dist/components/breadcrumbs/demos/separator.demo.js.map +0 -1
  314. package/dist/components/combobox/demos/overview.demo.js.map +0 -1
  315. package/dist/components/listbox/demos/overview.demo.js.map +0 -1
  316. package/dist/components/popover/demos/overview.demo.js.map +0 -1
  317. package/dist/components/select/demos/deconstructed.demo.js.map +0 -1
  318. package/dist/components/select/demos/overview.demo.js.map +0 -1
@@ -0,0 +1,426 @@
1
+ import type { Handle } from '@remix-run/ui'
2
+ import { css, on, ref } from '@remix-run/ui'
3
+
4
+ import { animateEntrance, animateExit } from '@remix-run/ui/animation'
5
+ import { spring } from '@remix-run/ui/animation'
6
+
7
+ const STATES = {
8
+ idle: 'Start',
9
+ processing: 'Processing',
10
+ success: 'Done',
11
+ error: 'Something went wrong',
12
+ } as const
13
+
14
+ type State = keyof typeof STATES
15
+
16
+ function getNextState(state: State): State {
17
+ let states = Object.keys(STATES) as State[]
18
+ let nextIndex = (states.indexOf(state) + 1) % states.length
19
+ return states[nextIndex]
20
+ }
21
+
22
+ const ICON_SIZE = 20
23
+ const STROKE_WIDTH = 1.5
24
+ const VIEW_BOX_SIZE = 24
25
+
26
+ const iconEnterAnimation = {
27
+ transform: 'translateY(-40px) scale(0.5)',
28
+ filter: 'blur(6px)',
29
+ duration: 150,
30
+ easing: 'ease-out',
31
+ }
32
+
33
+ const iconExitAnimation = {
34
+ transform: 'translateY(40px) scale(0.5)',
35
+ filter: 'blur(6px)',
36
+ duration: 150,
37
+ easing: 'ease-in',
38
+ }
39
+
40
+ export function MultiStateBadge(handle: Handle) {
41
+ let state: State = 'idle'
42
+
43
+ return () => (
44
+ <div
45
+ mix={[
46
+ css({
47
+ display: 'flex',
48
+ flexDirection: 'column',
49
+ justifyContent: 'space-between',
50
+ alignItems: 'center',
51
+ padding: 16,
52
+ minHeight: 80,
53
+ }),
54
+ ]}
55
+ >
56
+ <button
57
+ mix={[
58
+ css({
59
+ background: 'none',
60
+ border: 'none',
61
+ cursor: 'pointer',
62
+ padding: 0,
63
+ }),
64
+ on('click', () => {
65
+ state = getNextState(state)
66
+ handle.update()
67
+ }),
68
+ ]}
69
+ >
70
+ <Badge state={state} />
71
+ </button>
72
+ </div>
73
+ )
74
+ }
75
+
76
+ function Badge(handle: Handle<{ state: State }>) {
77
+ let badgeEl: HTMLDivElement
78
+ let prevState: State | null = null
79
+
80
+ return () => {
81
+ let { state } = handle.props
82
+
83
+ // Trigger shake/scale animations on state change
84
+ if (prevState !== null && prevState !== state) {
85
+ handle.queueTask(() => {
86
+ if (state === 'error') {
87
+ badgeEl.animate(
88
+ {
89
+ transform: [
90
+ 'translateX(0)',
91
+ 'translateX(-6px)',
92
+ 'translateX(6px)',
93
+ 'translateX(-6px)',
94
+ 'translateX(0)',
95
+ ],
96
+ },
97
+ { duration: 300, easing: 'ease-in-out', delay: 100 },
98
+ )
99
+ } else if (state === 'success') {
100
+ badgeEl.animate(
101
+ { transform: ['scale(1)', 'scale(1.2)', 'scale(1)'] },
102
+ { duration: 300, easing: 'ease-in-out' },
103
+ )
104
+ }
105
+ })
106
+ }
107
+ prevState = state
108
+
109
+ return (
110
+ <div
111
+ mix={[
112
+ ref((node) => (badgeEl = node)),
113
+ css({
114
+ backgroundColor: '#e2e8f0',
115
+ color: '#0f1115',
116
+ display: 'flex',
117
+ overflow: 'hidden',
118
+ alignItems: 'center',
119
+ justifyContent: 'center',
120
+ padding: '12px 20px',
121
+ fontSize: 16,
122
+ borderRadius: 999,
123
+ willChange: 'transform, filter',
124
+ transition: `gap ${spring('snappy')}`,
125
+ }),
126
+ ]}
127
+ style={{ gap: state === 'idle' ? '0px' : '8px' }}
128
+ >
129
+ <Icon state={state} />
130
+ <Label state={state} />
131
+ </div>
132
+ )
133
+ }
134
+ }
135
+
136
+ function Icon(handle: Handle<{ state: State }>) {
137
+ return () => (
138
+ <span
139
+ mix={[
140
+ css({
141
+ height: ICON_SIZE,
142
+ position: 'relative',
143
+ display: 'flex',
144
+ alignItems: 'center',
145
+ justifyContent: 'center',
146
+ overflow: 'hidden',
147
+ transition: `width ${spring({ duration: 200, bounce: 0.2 })}`,
148
+ }),
149
+ ]}
150
+ style={{ width: handle.props.state === 'idle' ? 0 : ICON_SIZE }}
151
+ >
152
+ {handle.props.state === 'processing' && (
153
+ <span
154
+ key="loader"
155
+ mix={[
156
+ css({ position: 'absolute', left: 0, top: 0 }),
157
+ animateEntrance(iconEnterAnimation),
158
+ animateExit(iconExitAnimation),
159
+ ]}
160
+ >
161
+ <Loader />
162
+ </span>
163
+ )}
164
+ {handle.props.state === 'success' && (
165
+ <span
166
+ key="check"
167
+ mix={[
168
+ css({ position: 'absolute', left: 0, top: 0 }),
169
+ animateEntrance(iconEnterAnimation),
170
+ animateExit(iconExitAnimation),
171
+ ]}
172
+ >
173
+ <Check />
174
+ </span>
175
+ )}
176
+ {handle.props.state === 'error' && (
177
+ <span
178
+ key="x"
179
+ mix={[
180
+ css({ position: 'absolute', left: 0, top: 0 }),
181
+ animateEntrance(iconEnterAnimation),
182
+ animateExit(iconExitAnimation),
183
+ ]}
184
+ >
185
+ <X />
186
+ </span>
187
+ )}
188
+ </span>
189
+ )
190
+ }
191
+
192
+ function Loader() {
193
+ return () => (
194
+ <div
195
+ mix={[
196
+ ref((node) => {
197
+ node.animate(
198
+ { transform: ['rotate(0deg)', 'rotate(360deg)'] },
199
+ { duration: 1000, iterations: Infinity },
200
+ )
201
+ }),
202
+ css({
203
+ display: 'flex',
204
+ alignItems: 'center',
205
+ justifyContent: 'center',
206
+ width: ICON_SIZE,
207
+ height: ICON_SIZE,
208
+ }),
209
+ ]}
210
+ >
211
+ <svg
212
+ width={ICON_SIZE}
213
+ height={ICON_SIZE}
214
+ viewBox={`0 0 ${VIEW_BOX_SIZE} ${VIEW_BOX_SIZE}`}
215
+ fill="none"
216
+ stroke="currentColor"
217
+ strokeWidth={STROKE_WIDTH}
218
+ strokeLinecap="round"
219
+ strokeLinejoin="round"
220
+ >
221
+ <path d="M21 12a9 9 0 1 1-6.219-8.56" />
222
+ </svg>
223
+ </div>
224
+ )
225
+ }
226
+
227
+ function Check() {
228
+ return () => (
229
+ <svg
230
+ width={ICON_SIZE}
231
+ height={ICON_SIZE}
232
+ viewBox={`0 0 ${VIEW_BOX_SIZE} ${VIEW_BOX_SIZE}`}
233
+ fill="none"
234
+ stroke="currentColor"
235
+ strokeWidth={STROKE_WIDTH}
236
+ strokeLinecap="round"
237
+ strokeLinejoin="round"
238
+ >
239
+ <polyline
240
+ points="4 12 9 17 20 6"
241
+ mix={[
242
+ ref((node) => {
243
+ let length = node.getTotalLength()
244
+ node.style.strokeDasharray = `${length}`
245
+ node.style.strokeDashoffset = `${length}`
246
+ node.animate(
247
+ { strokeDashoffset: [length, 0] },
248
+ { ...spring({ duration: 300, bounce: 0.1 }), fill: 'forwards' },
249
+ )
250
+ }),
251
+ ]}
252
+ />
253
+ </svg>
254
+ )
255
+ }
256
+
257
+ function X() {
258
+ return () => (
259
+ <svg
260
+ width={ICON_SIZE}
261
+ height={ICON_SIZE}
262
+ viewBox={`0 0 ${VIEW_BOX_SIZE} ${VIEW_BOX_SIZE}`}
263
+ fill="none"
264
+ stroke="currentColor"
265
+ strokeWidth={STROKE_WIDTH}
266
+ strokeLinecap="round"
267
+ strokeLinejoin="round"
268
+ >
269
+ <line
270
+ x1="6"
271
+ y1="6"
272
+ x2="18"
273
+ y2="18"
274
+ mix={[
275
+ ref((node) => {
276
+ let length = node.getTotalLength()
277
+ node.style.strokeDasharray = `${length}`
278
+ node.style.strokeDashoffset = `${length}`
279
+ node.animate(
280
+ { strokeDashoffset: [length, 0] },
281
+ { ...spring({ duration: 300, bounce: 0.1 }), fill: 'forwards' },
282
+ )
283
+ }),
284
+ ]}
285
+ />
286
+ <line
287
+ x1="18"
288
+ y1="6"
289
+ x2="6"
290
+ y2="18"
291
+ mix={[
292
+ ref((node) => {
293
+ let length = node.getTotalLength()
294
+ node.style.strokeDasharray = `${length}`
295
+ node.style.strokeDashoffset = `${length}`
296
+ node.animate(
297
+ { strokeDashoffset: [length, 0] },
298
+ { ...spring({ duration: 300, bounce: 0.1 }), delay: 100, fill: 'forwards' },
299
+ )
300
+ }),
301
+ ]}
302
+ />
303
+ </svg>
304
+ )
305
+ }
306
+
307
+ function Label(handle: Handle<{ state: State }>) {
308
+ let measureEl: HTMLSpanElement
309
+ let labelWidth = 0
310
+ let labelHeight = 0
311
+
312
+ // Don't animate the label on initial render
313
+ let isFirstRender = true
314
+ handle.queueTask(() => {
315
+ isFirstRender = false
316
+ })
317
+
318
+ return () => {
319
+ let { state } = handle.props
320
+
321
+ // Measure label dimensions after render
322
+ handle.queueTask(() => {
323
+ if (measureEl) {
324
+ let rect = measureEl.getBoundingClientRect()
325
+ if (rect.width !== labelWidth || rect.height !== labelHeight) {
326
+ labelWidth = rect.width
327
+ labelHeight = rect.height
328
+ handle.update()
329
+ }
330
+ }
331
+ })
332
+
333
+ let labelMix = [
334
+ animateExit({
335
+ transform: 'translateY(20px)',
336
+ opacity: 0,
337
+ filter: 'blur(10px)',
338
+ duration: 200,
339
+ easing: 'ease-in-out',
340
+ }),
341
+ ]
342
+
343
+ if (!isFirstRender) {
344
+ labelMix.unshift(
345
+ animateEntrance({
346
+ transform: 'translateY(-20px)',
347
+ opacity: 0,
348
+ filter: 'blur(10px)',
349
+ duration: 200,
350
+ easing: 'ease-in-out',
351
+ }),
352
+ )
353
+ }
354
+
355
+ return (
356
+ <span
357
+ mix={[
358
+ css({
359
+ position: 'relative',
360
+ display: 'inline-block',
361
+ transition: `width ${spring({ duration: 200, bounce: 0.1 })}`,
362
+ }),
363
+ ]}
364
+ style={{
365
+ width: labelWidth || 'auto',
366
+ height: labelHeight || 'auto',
367
+ }}
368
+ >
369
+ {/* Hidden measurement element */}
370
+ <span
371
+ mix={[
372
+ ref((node) => (measureEl = node)),
373
+ css({ position: 'absolute', visibility: 'hidden', whiteSpace: 'nowrap' }),
374
+ ]}
375
+ >
376
+ {STATES[state]}
377
+ </span>
378
+
379
+ {state === 'idle' && (
380
+ <span
381
+ key="idle"
382
+ mix={[
383
+ css({ whiteSpace: 'nowrap', position: 'absolute', left: 0, top: 0 }),
384
+ ...labelMix,
385
+ ]}
386
+ >
387
+ {STATES.idle}
388
+ </span>
389
+ )}
390
+ {state === 'processing' && (
391
+ <span
392
+ key="processing"
393
+ mix={[
394
+ css({ whiteSpace: 'nowrap', position: 'absolute', left: 0, top: 0 }),
395
+ ...labelMix,
396
+ ]}
397
+ >
398
+ {STATES.processing}
399
+ </span>
400
+ )}
401
+ {state === 'success' && (
402
+ <span
403
+ key="success"
404
+ mix={[
405
+ css({ whiteSpace: 'nowrap', position: 'absolute', left: 0, top: 0 }),
406
+ ...labelMix,
407
+ ]}
408
+ >
409
+ {STATES.success}
410
+ </span>
411
+ )}
412
+ {state === 'error' && (
413
+ <span
414
+ key="error"
415
+ mix={[
416
+ css({ whiteSpace: 'nowrap', position: 'absolute', left: 0, top: 0 }),
417
+ ...labelMix,
418
+ ]}
419
+ >
420
+ {STATES.error}
421
+ </span>
422
+ )}
423
+ </span>
424
+ )
425
+ }
426
+ }
@@ -0,0 +1,67 @@
1
+ import { createMixin, css, on, type Handle } from '@remix-run/ui'
2
+ import { spring } from '@remix-run/ui/animation'
3
+
4
+ export function Press(handle: Handle) {
5
+ let pressed = false
6
+
7
+ function setPressed(nextPressed: boolean) {
8
+ if (pressed === nextPressed) return
9
+ pressed = nextPressed
10
+ handle.update()
11
+ }
12
+
13
+ return () => (
14
+ <div
15
+ tabIndex={0}
16
+ mix={[
17
+ css({
18
+ width: 100,
19
+ height: 100,
20
+ backgroundColor: '#9911ff',
21
+ borderRadius: 5,
22
+ transition: `transform ${spring()}`,
23
+ '&:focus': {
24
+ outline: '4px solid rgba(0,120,255,0.7)',
25
+ outlineOffset: 1,
26
+ },
27
+ '&:hover, &:focus': {
28
+ transform: pressed ? 'scale(0.8)' : 'scale(1.2)',
29
+ },
30
+ // or use default browser :active but lose keyboard "down" press states
31
+ // '&:active': {
32
+ // transform: 'scale(0.8)',
33
+ // },
34
+ }),
35
+ onPressDown(() => {
36
+ setPressed(true)
37
+ }),
38
+ onPressUp(() => {
39
+ setPressed(false)
40
+ }),
41
+ ]}
42
+ />
43
+ )
44
+ }
45
+
46
+ const onPressDown = createMixin<HTMLElement, [handler: () => void]>(() => (handler) => [
47
+ on('pointerdown', (event) => {
48
+ if (event.isPrimary === false) return
49
+ handler()
50
+ }),
51
+ on('keydown', (event) => {
52
+ if (!(event.key === 'Enter' || event.key === ' ') || event.repeat) return
53
+ event.preventDefault()
54
+ handler()
55
+ }),
56
+ ])
57
+
58
+ const onPressUp = createMixin<HTMLElement, [handler: () => void]>(() => (handler) => [
59
+ on('pointerup', handler),
60
+ on('pointerleave', handler),
61
+ on('keyup', (event) => {
62
+ if (!(event.key === 'Enter' || event.key === ' ')) return
63
+ event.preventDefault()
64
+ handler()
65
+ }),
66
+ on('blur', handler),
67
+ ])
@@ -0,0 +1,65 @@
1
+ import { css, type Handle } from '@remix-run/ui'
2
+ import { animateLayout, spring } from '@remix-run/ui/animation'
3
+
4
+ const initialOrder = ['#ff0088', '#dd00ee', '#9911ff', '#0d63f8']
5
+
6
+ function shuffle<T>(array: T[]): T[] {
7
+ let result = [...array]
8
+ for (let i = result.length - 1; i > 0; i--) {
9
+ let j = Math.floor(Math.random() * (i + 1))
10
+ ;[result[i], result[j]] = [result[j], result[i]]
11
+ }
12
+ return result
13
+ }
14
+
15
+ export function Reordering(handle: Handle) {
16
+ let order = initialOrder
17
+
18
+ function scheduleNextShuffle() {
19
+ setTimeout(() => {
20
+ if (handle.signal.aborted) return
21
+ order = shuffle(order)
22
+ handle.update()
23
+ scheduleNextShuffle()
24
+ }, 1000)
25
+ }
26
+
27
+ scheduleNextShuffle()
28
+
29
+ return () => (
30
+ <ul
31
+ mix={[
32
+ css({
33
+ listStyle: 'none',
34
+ padding: 0,
35
+ margin: 0,
36
+ position: 'relative',
37
+ display: 'flex',
38
+ flexWrap: 'wrap',
39
+ gap: 10,
40
+ width: 220,
41
+ flexDirection: 'row',
42
+ justifyContent: 'center',
43
+ alignItems: 'center',
44
+ }),
45
+ ]}
46
+ >
47
+ {order.map((backgroundColor) => (
48
+ <li
49
+ key={backgroundColor}
50
+ mix={[
51
+ css({
52
+ width: 100,
53
+ height: 100,
54
+ borderRadius: 10,
55
+ }),
56
+ animateLayout({
57
+ ...spring({ duration: 600, bounce: 0.2 }),
58
+ }),
59
+ ]}
60
+ style={{ backgroundColor }}
61
+ />
62
+ ))}
63
+ </ul>
64
+ )
65
+ }
@@ -0,0 +1,57 @@
1
+ import { type Handle } from '@remix-run/ui'
2
+ import { css, on } from '@remix-run/ui'
3
+
4
+ import { spring } from '@remix-run/ui/animation'
5
+
6
+ export function RollingSquare(handle: Handle) {
7
+ let toggled = false
8
+
9
+ return () => (
10
+ <div
11
+ mix={[
12
+ css({
13
+ display: 'flex',
14
+ flexDirection: 'column',
15
+ alignItems: 'center',
16
+ justifyContent: 'center',
17
+ gap: '20px',
18
+ minWidth: '300px',
19
+ }),
20
+ ]}
21
+ >
22
+ <div
23
+ mix={[
24
+ css({
25
+ width: '80px',
26
+ height: '80px',
27
+ backgroundColor: '#8df0cc',
28
+ borderRadius: '10px',
29
+ transition: `transform ${spring({ duration: 500, bounce: 0.5 })}`,
30
+ }),
31
+ ]}
32
+ style={{
33
+ transform: toggled ? 'translateX(100%) rotate(180deg)' : 'translateX(-100%)',
34
+ }}
35
+ />
36
+ <button
37
+ mix={[
38
+ css({
39
+ backgroundColor: '#8df0cc',
40
+ color: '#0f1115',
41
+ borderRadius: '5px',
42
+ padding: '10px',
43
+ margin: '10px',
44
+ border: 'none',
45
+ cursor: 'pointer',
46
+ }),
47
+ on('click', () => {
48
+ toggled = !toggled
49
+ handle.update()
50
+ }),
51
+ ]}
52
+ >
53
+ Toggle position
54
+ </button>
55
+ </div>
56
+ )
57
+ }
@@ -0,0 +1,21 @@
1
+ import { css } from '@remix-run/ui'
2
+
3
+ export function Rotate() {
4
+ return () => (
5
+ <div
6
+ mix={[
7
+ css({
8
+ width: 100,
9
+ height: 100,
10
+ backgroundColor: '#ff0088',
11
+ borderRadius: 5,
12
+ '@keyframes rotate-demo': {
13
+ '0%': { transform: 'rotate(0deg)' },
14
+ '100%': { transform: 'rotate(360deg)' },
15
+ },
16
+ animation: `rotate-demo 1s ease-in-out 1`,
17
+ }),
18
+ ]}
19
+ />
20
+ )
21
+ }