@yewdiel21/motion-kit 0.0.0-stage → 0.9.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 (391) hide show
  1. package/NOTICE +38 -0
  2. package/README.md +80 -2
  3. package/bin/motion-kit.mjs +242 -0
  4. package/css.d.ts +2 -0
  5. package/dist/auto/index.d.ts +63 -0
  6. package/dist/auto/index.js +207 -0
  7. package/dist/auto/index.js.map +1 -0
  8. package/dist/chunk-2BQGFOA7.js +177 -0
  9. package/dist/chunk-2BQGFOA7.js.map +1 -0
  10. package/dist/chunk-34XYOXHG.js +2374 -0
  11. package/dist/chunk-34XYOXHG.js.map +1 -0
  12. package/dist/chunk-6OJAP6ZL.js +832 -0
  13. package/dist/chunk-6OJAP6ZL.js.map +1 -0
  14. package/dist/chunk-ABNRDDO7.js +28 -0
  15. package/dist/chunk-ABNRDDO7.js.map +1 -0
  16. package/dist/chunk-FS2HJVVY.js +3320 -0
  17. package/dist/chunk-FS2HJVVY.js.map +1 -0
  18. package/dist/chunk-J27KH2A2.js +157 -0
  19. package/dist/chunk-J27KH2A2.js.map +1 -0
  20. package/dist/chunk-KLY5BRRI.js +18 -0
  21. package/dist/chunk-KLY5BRRI.js.map +1 -0
  22. package/dist/chunk-Q4LVFNLN.js +1617 -0
  23. package/dist/chunk-Q4LVFNLN.js.map +1 -0
  24. package/dist/chunk-RFDIZYFX.js +304 -0
  25. package/dist/chunk-RFDIZYFX.js.map +1 -0
  26. package/dist/chunk-TGV27ZWM.js +17 -0
  27. package/dist/chunk-TGV27ZWM.js.map +1 -0
  28. package/dist/confetti-BSX1CLLV.d.ts +41 -0
  29. package/dist/css/components/chat-background.css +87 -0
  30. package/dist/css/components/chat.css +237 -0
  31. package/dist/css/components/cursor.css +178 -0
  32. package/dist/css/components/dialog.css +259 -0
  33. package/dist/css/components/island.css +301 -0
  34. package/dist/css/components/menu.css +105 -0
  35. package/dist/css/components/nav.css +1054 -0
  36. package/dist/css/components/steps.css +50 -0
  37. package/dist/css/components/theme-picker.css +115 -0
  38. package/dist/css/components/toast.css +115 -0
  39. package/dist/css/controls.css +950 -0
  40. package/dist/css/effects.css +587 -0
  41. package/dist/css/fonts.css +3 -0
  42. package/dist/css/themes/black-green.css +126 -0
  43. package/dist/css/themes/black.css +126 -0
  44. package/dist/css/themes/blue.css +126 -0
  45. package/dist/css/themes/brown.css +126 -0
  46. package/dist/css/themes/cold-blue.css +126 -0
  47. package/dist/css/themes/funny.css +126 -0
  48. package/dist/css/themes/gray.css +126 -0
  49. package/dist/css/themes/green.css +126 -0
  50. package/dist/css/themes/modern-1.css +126 -0
  51. package/dist/css/themes/neogreen.css +126 -0
  52. package/dist/css/themes/new.css +126 -0
  53. package/dist/css/themes/new2.css +126 -0
  54. package/dist/css/themes/oled.css +126 -0
  55. package/dist/css/themes/orange.css +126 -0
  56. package/dist/css/themes/pastel-pink.css +126 -0
  57. package/dist/css/themes/pink.css +126 -0
  58. package/dist/css/themes/purple.css +126 -0
  59. package/dist/css/themes/red.css +126 -0
  60. package/dist/css/themes/super-blue.css +126 -0
  61. package/dist/css/themes.css +2431 -0
  62. package/dist/css/tokens.css +452 -0
  63. package/dist/elements/index.d.ts +183 -0
  64. package/dist/elements/index.js +472 -0
  65. package/dist/elements/index.js.map +1 -0
  66. package/dist/engine-CUr9QijM.d.ts +46 -0
  67. package/dist/gl/index.d.ts +61 -0
  68. package/dist/gl/index.js +18 -0
  69. package/dist/gl/index.js.map +1 -0
  70. package/dist/index.css +5296 -0
  71. package/dist/index.d.ts +440 -0
  72. package/dist/index.js +390 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/react/index.d.ts +476 -0
  75. package/dist/react/index.js +969 -0
  76. package/dist/react/index.js.map +1 -0
  77. package/dist/reduced-BM4Ls0g5.d.ts +566 -0
  78. package/dist/reveal-WFUWDrei.d.ts +416 -0
  79. package/dist/store-Bqy_obnD.d.ts +195 -0
  80. package/dist/theme/generate.d.ts +18 -0
  81. package/dist/theme/generate.js +64 -0
  82. package/dist/theme/generate.js.map +1 -0
  83. package/dist/theme/index.d.ts +17 -0
  84. package/dist/theme/index.js +32 -0
  85. package/dist/theme/index.js.map +1 -0
  86. package/dist/theme-BFMoCzi6.d.ts +158 -0
  87. package/dist/types-BkS_raL8.d.ts +7 -0
  88. package/package.json +114 -3
  89. package/registry.json +469 -0
  90. package/skills/motion-kit/SKILL.md +189 -0
  91. package/skills/motion-kit/reference/components.md +86 -0
  92. package/skills/motion-kit/reference/design.md +92 -0
  93. package/skills/motion-kit/reference/recipes.md +172 -0
  94. package/src/auto/auto-init.ts +151 -0
  95. package/src/auto/index.ts +3 -0
  96. package/src/auto/parse-options.ts +68 -0
  97. package/src/components/chat/README.md +90 -0
  98. package/src/components/chat/chat.css +236 -0
  99. package/src/components/chat/element.ts +98 -0
  100. package/src/components/chat/engine.ts +295 -0
  101. package/src/components/chat/react.tsx +196 -0
  102. package/src/components/chat-background/README.md +50 -0
  103. package/src/components/chat-background/audio.ts +56 -0
  104. package/src/components/chat-background/chat-background.css +86 -0
  105. package/src/components/chat-background/element.ts +31 -0
  106. package/src/components/chat-background/engine.ts +108 -0
  107. package/src/components/chat-background/react.tsx +36 -0
  108. package/src/components/cursor/README.md +53 -0
  109. package/src/components/cursor/cursor.css +177 -0
  110. package/src/components/cursor/element.ts +23 -0
  111. package/src/components/cursor/engine.ts +254 -0
  112. package/src/components/cursor/react.tsx +26 -0
  113. package/src/components/dialog/README.md +106 -0
  114. package/src/components/dialog/dialog.css +258 -0
  115. package/src/components/dialog/element.ts +118 -0
  116. package/src/components/dialog/engine.ts +432 -0
  117. package/src/components/dialog/react.tsx +200 -0
  118. package/src/components/icon.tsx +10 -0
  119. package/src/components/icons.ts +24 -0
  120. package/src/components/island/README.md +91 -0
  121. package/src/components/island/element.ts +45 -0
  122. package/src/components/island/engine.ts +200 -0
  123. package/src/components/island/island.css +300 -0
  124. package/src/components/island/react.tsx +115 -0
  125. package/src/components/menu/README.md +67 -0
  126. package/src/components/menu/element.ts +55 -0
  127. package/src/components/menu/engine.ts +142 -0
  128. package/src/components/menu/menu.css +104 -0
  129. package/src/components/menu/react.tsx +82 -0
  130. package/src/components/nav/README.md +85 -0
  131. package/src/components/nav/element.ts +39 -0
  132. package/src/components/nav/engine.ts +164 -0
  133. package/src/components/nav/nav.css +1053 -0
  134. package/src/components/nav/react.tsx +90 -0
  135. package/src/components/steps/README.md +70 -0
  136. package/src/components/steps/element.ts +43 -0
  137. package/src/components/steps/engine.ts +185 -0
  138. package/src/components/steps/react.tsx +91 -0
  139. package/src/components/steps/steps.css +49 -0
  140. package/src/components/theme-picker/README.md +57 -0
  141. package/src/components/theme-picker/element.ts +20 -0
  142. package/src/components/theme-picker/engine.ts +91 -0
  143. package/src/components/theme-picker/react.tsx +31 -0
  144. package/src/components/theme-picker/theme-picker.css +114 -0
  145. package/src/components/toast/README.md +60 -0
  146. package/src/components/toast/element.ts +25 -0
  147. package/src/components/toast/engine.ts +483 -0
  148. package/src/components/toast/react.tsx +22 -0
  149. package/src/components/toast/store.ts +122 -0
  150. package/src/components/toast/toast.css +114 -0
  151. package/src/core/channels.ts +165 -0
  152. package/src/core/curves.ts +35 -0
  153. package/src/core/frame.ts +12 -0
  154. package/src/core/gsap.ts +65 -0
  155. package/src/core/loop.ts +44 -0
  156. package/src/core/math.ts +9 -0
  157. package/src/core/physics-spring.ts +59 -0
  158. package/src/core/pointer.ts +63 -0
  159. package/src/core/reduced.ts +21 -0
  160. package/src/core/scope.ts +70 -0
  161. package/src/core/scroll-track.ts +106 -0
  162. package/src/core/split.ts +68 -0
  163. package/src/core/spring.ts +22 -0
  164. package/src/core/styles.ts +31 -0
  165. package/src/core/timing.ts +204 -0
  166. package/src/core/types.ts +20 -0
  167. package/src/core/visible.ts +19 -0
  168. package/src/css/base.css +142 -0
  169. package/src/css/controls/README.md +122 -0
  170. package/src/css/controls/burger.css +67 -0
  171. package/src/css/controls/button.css +226 -0
  172. package/src/css/controls/checkbox.css +53 -0
  173. package/src/css/controls/chip.css +85 -0
  174. package/src/css/controls/field.css +124 -0
  175. package/src/css/controls/glass.css +220 -0
  176. package/src/css/controls/icon.css +26 -0
  177. package/src/css/controls/segmented.css +60 -0
  178. package/src/css/controls/switch.css +72 -0
  179. package/src/css/default-theme.css +123 -0
  180. package/src/css/effects/README.md +134 -0
  181. package/src/css/effects/anim.css +224 -0
  182. package/src/css/effects/fade.css +15 -0
  183. package/src/css/effects/float.css +64 -0
  184. package/src/css/effects/frost.css +62 -0
  185. package/src/css/effects/grain.css +58 -0
  186. package/src/css/effects/loading.css +60 -0
  187. package/src/css/effects/success.css +22 -0
  188. package/src/css/effects/text.css +67 -0
  189. package/src/css/fonts.css +3 -0
  190. package/src/css/states.css +51 -0
  191. package/src/css/themes/black-green.css +126 -0
  192. package/src/css/themes/black.css +126 -0
  193. package/src/css/themes/blue.css +126 -0
  194. package/src/css/themes/brown.css +126 -0
  195. package/src/css/themes/cold-blue.css +126 -0
  196. package/src/css/themes/funny.css +126 -0
  197. package/src/css/themes/gray.css +126 -0
  198. package/src/css/themes/green.css +126 -0
  199. package/src/css/themes/modern-1.css +126 -0
  200. package/src/css/themes/neogreen.css +126 -0
  201. package/src/css/themes/new.css +126 -0
  202. package/src/css/themes/new2.css +126 -0
  203. package/src/css/themes/oled.css +126 -0
  204. package/src/css/themes/orange.css +126 -0
  205. package/src/css/themes/pastel-pink.css +126 -0
  206. package/src/css/themes/pink.css +126 -0
  207. package/src/css/themes/purple.css +126 -0
  208. package/src/css/themes/red.css +126 -0
  209. package/src/css/themes/super-blue.css +126 -0
  210. package/src/css/tokens.css +182 -0
  211. package/src/elements/base.ts +76 -0
  212. package/src/elements/index.ts +48 -0
  213. package/src/gl/index.ts +15 -0
  214. package/src/gl/liquid-blend/README.md +23 -0
  215. package/src/gl/liquid-blend/create-liquid-blend.ts +7 -0
  216. package/src/gl/liquid-blend/shader.ts +9 -0
  217. package/src/gl/matcap/README.md +23 -0
  218. package/src/gl/matcap/create-matcap.ts +7 -0
  219. package/src/gl/matcap/shader.ts +16 -0
  220. package/src/gl/particle-hero/README.md +41 -0
  221. package/src/gl/particle-hero/create-particle-hero.ts +544 -0
  222. package/src/gl/particle-hero/particle-hero.css +19 -0
  223. package/src/gl/particle-hero/shaders.ts +84 -0
  224. package/src/gl/shader-blur/README.md +23 -0
  225. package/src/gl/shader-blur/create-shader-blur.ts +7 -0
  226. package/src/gl/shader-blur/shader.ts +11 -0
  227. package/src/gl/shader-grain/README.md +23 -0
  228. package/src/gl/shader-grain/create-shader-grain.ts +7 -0
  229. package/src/gl/shader-grain/shader.ts +6 -0
  230. package/src/gl/shared/create-shader-effect.ts +186 -0
  231. package/src/gl/shared/shader.css +3 -0
  232. package/src/gl/shared/shaders.ts +30 -0
  233. package/src/index.ts +177 -0
  234. package/src/motion/anchor.ts +45 -0
  235. package/src/motion/chat.ts +97 -0
  236. package/src/motion/confetti.ts +130 -0
  237. package/src/motion/count.ts +95 -0
  238. package/src/motion/details.ts +70 -0
  239. package/src/motion/drag-dismiss.ts +231 -0
  240. package/src/motion/effects.ts +81 -0
  241. package/src/motion/entrances.ts +27 -0
  242. package/src/motion/floating.ts +389 -0
  243. package/src/motion/flow.ts +115 -0
  244. package/src/motion/indicator.ts +44 -0
  245. package/src/motion/layout.ts +39 -0
  246. package/src/motion/liquid.ts +227 -0
  247. package/src/motion/morph.ts +540 -0
  248. package/src/motion/reveal.ts +112 -0
  249. package/src/motion/sheet.ts +65 -0
  250. package/src/motion/surface.ts +108 -0
  251. package/src/react/animated-number.tsx +30 -0
  252. package/src/react/flip-group.tsx +44 -0
  253. package/src/react/frost.tsx +33 -0
  254. package/src/react/index.ts +80 -0
  255. package/src/react/slot-text.tsx +44 -0
  256. package/src/react/theme-provider.tsx +42 -0
  257. package/src/react/use-card-dock.ts +9 -0
  258. package/src/react/use-cascade.ts +9 -0
  259. package/src/react/use-contrast.ts +9 -0
  260. package/src/react/use-counter.ts +8 -0
  261. package/src/react/use-curved-carousel.ts +10 -0
  262. package/src/react/use-depth-grid.ts +9 -0
  263. package/src/react/use-details.ts +7 -0
  264. package/src/react/use-dock-pill.ts +10 -0
  265. package/src/react/use-drag-ruler.ts +10 -0
  266. package/src/react/use-fill-text.ts +9 -0
  267. package/src/react/use-fit-text.ts +8 -0
  268. package/src/react/use-flow.ts +8 -0
  269. package/src/react/use-frame-zoom.ts +9 -0
  270. package/src/react/use-infinite-reel.ts +8 -0
  271. package/src/react/use-letters.ts +9 -0
  272. package/src/react/use-liquid-blend.ts +8 -0
  273. package/src/react/use-marquee.ts +9 -0
  274. package/src/react/use-mask-text.ts +9 -0
  275. package/src/react/use-matcap.ts +8 -0
  276. package/src/react/use-parallax.ts +9 -0
  277. package/src/react/use-particle-hero.ts +9 -0
  278. package/src/react/use-pile.ts +8 -0
  279. package/src/react/use-pop.ts +8 -0
  280. package/src/react/use-radial-menu.ts +10 -0
  281. package/src/react/use-recipe.ts +38 -0
  282. package/src/react/use-reflow.ts +9 -0
  283. package/src/react/use-repel-bento.ts +8 -0
  284. package/src/react/use-reveal.ts +8 -0
  285. package/src/react/use-rotator.ts +10 -0
  286. package/src/react/use-shader-blur.ts +8 -0
  287. package/src/react/use-shader-grain.ts +8 -0
  288. package/src/react/use-slot-text.ts +17 -0
  289. package/src/react/use-snap-strip.ts +9 -0
  290. package/src/react/use-sortable.ts +14 -0
  291. package/src/react/use-spring-cards.ts +8 -0
  292. package/src/react/use-stretch-footer.ts +20 -0
  293. package/src/react/use-tabs.ts +10 -0
  294. package/src/react/use-text-roll.ts +9 -0
  295. package/src/react/use-ticket-menu.ts +15 -0
  296. package/src/react/use-words.ts +8 -0
  297. package/src/recipes/card-dock/README.md +36 -0
  298. package/src/recipes/card-dock/card-dock.css +178 -0
  299. package/src/recipes/card-dock/create-card-dock.ts +97 -0
  300. package/src/recipes/cascade/README.md +33 -0
  301. package/src/recipes/cascade/create-cascade.ts +111 -0
  302. package/src/recipes/contrast/README.md +29 -0
  303. package/src/recipes/contrast/create-contrast.ts +68 -0
  304. package/src/recipes/counter/README.md +42 -0
  305. package/src/recipes/counter/create-counter.ts +84 -0
  306. package/src/recipes/curved-carousel/README.md +50 -0
  307. package/src/recipes/curved-carousel/carousel-math.ts +20 -0
  308. package/src/recipes/curved-carousel/create-curved-carousel.ts +257 -0
  309. package/src/recipes/curved-carousel/curved-carousel.css +50 -0
  310. package/src/recipes/depth-grid/README.md +30 -0
  311. package/src/recipes/depth-grid/create-depth-grid.ts +120 -0
  312. package/src/recipes/details/README.md +31 -0
  313. package/src/recipes/details/create-details.ts +27 -0
  314. package/src/recipes/dock-pill/README.md +23 -0
  315. package/src/recipes/dock-pill/create-dock-pill.ts +68 -0
  316. package/src/recipes/dock-pill/dock-pill.css +5 -0
  317. package/src/recipes/drag-ruler/README.md +21 -0
  318. package/src/recipes/drag-ruler/create-drag-ruler.ts +95 -0
  319. package/src/recipes/drag-ruler/drag-ruler.css +16 -0
  320. package/src/recipes/fill-text/README.md +27 -0
  321. package/src/recipes/fill-text/create-fill-text.ts +93 -0
  322. package/src/recipes/fit-text/README.md +37 -0
  323. package/src/recipes/fit-text/create-fit-text.ts +55 -0
  324. package/src/recipes/fit-text/fit-text.css +6 -0
  325. package/src/recipes/flow/README.md +46 -0
  326. package/src/recipes/flow/create-flow.ts +55 -0
  327. package/src/recipes/frame-zoom/README.md +29 -0
  328. package/src/recipes/frame-zoom/create-frame-zoom.ts +91 -0
  329. package/src/recipes/infinite-reel/README.md +23 -0
  330. package/src/recipes/infinite-reel/create-infinite-reel.ts +140 -0
  331. package/src/recipes/infinite-reel/infinite-reel.css +8 -0
  332. package/src/recipes/letters/README.md +30 -0
  333. package/src/recipes/letters/create-letters.ts +113 -0
  334. package/src/recipes/marquee/README.md +32 -0
  335. package/src/recipes/marquee/create-marquee.ts +74 -0
  336. package/src/recipes/marquee/marquee.css +41 -0
  337. package/src/recipes/mask-text/README.md +39 -0
  338. package/src/recipes/mask-text/create-mask-text.ts +134 -0
  339. package/src/recipes/parallax/README.md +31 -0
  340. package/src/recipes/parallax/create-parallax.ts +74 -0
  341. package/src/recipes/pile/README.md +48 -0
  342. package/src/recipes/pile/create-pile.ts +50 -0
  343. package/src/recipes/pile/pile-math.ts +27 -0
  344. package/src/recipes/pile/pile.css +63 -0
  345. package/src/recipes/pill/README.md +30 -0
  346. package/src/recipes/pill/pill.css +48 -0
  347. package/src/recipes/pop/README.md +36 -0
  348. package/src/recipes/pop/create-pop.ts +52 -0
  349. package/src/recipes/radial-menu/README.md +44 -0
  350. package/src/recipes/radial-menu/create-radial-menu.ts +96 -0
  351. package/src/recipes/radial-menu/radial-menu.css +45 -0
  352. package/src/recipes/reflow/README.md +47 -0
  353. package/src/recipes/reflow/create-reflow.ts +49 -0
  354. package/src/recipes/repel-bento/README.md +21 -0
  355. package/src/recipes/repel-bento/create-repel-bento.ts +65 -0
  356. package/src/recipes/repel-bento/repel-bento.css +3 -0
  357. package/src/recipes/reveal/README.md +36 -0
  358. package/src/recipes/reveal/create-reveal.ts +48 -0
  359. package/src/recipes/rotator/README.md +21 -0
  360. package/src/recipes/rotator/create-rotator.ts +66 -0
  361. package/src/recipes/rotator/rotator.css +3 -0
  362. package/src/recipes/slot-text/README.md +44 -0
  363. package/src/recipes/slot-text/create-slot-text.ts +244 -0
  364. package/src/recipes/slot-text/slot-text.css +42 -0
  365. package/src/recipes/snap-strip/README.md +31 -0
  366. package/src/recipes/snap-strip/create-snap-strip.ts +133 -0
  367. package/src/recipes/snap-strip/snap-strip.css +23 -0
  368. package/src/recipes/sortable/README.md +88 -0
  369. package/src/recipes/sortable/create-sortable.ts +863 -0
  370. package/src/recipes/sortable/sortable.css +40 -0
  371. package/src/recipes/spring-cards/README.md +48 -0
  372. package/src/recipes/spring-cards/create-spring-cards.ts +140 -0
  373. package/src/recipes/spring-cards/spring-cards.css +23 -0
  374. package/src/recipes/stretch-footer/README.md +44 -0
  375. package/src/recipes/stretch-footer/create-stretch-footer.ts +69 -0
  376. package/src/recipes/stretch-footer/stretch-footer.css +28 -0
  377. package/src/recipes/tabs/README.md +39 -0
  378. package/src/recipes/tabs/create-tabs.ts +91 -0
  379. package/src/recipes/tabs/tabs.css +19 -0
  380. package/src/recipes/text-roll/README.md +35 -0
  381. package/src/recipes/text-roll/create-text-roll.ts +137 -0
  382. package/src/recipes/text-roll/text-roll.css +21 -0
  383. package/src/recipes/ticket-menu/README.md +24 -0
  384. package/src/recipes/ticket-menu/create-ticket-menu.ts +6 -0
  385. package/src/recipes/words/README.md +43 -0
  386. package/src/recipes/words/create-words.ts +114 -0
  387. package/src/theme/css-format.ts +32 -0
  388. package/src/theme/generate.ts +72 -0
  389. package/src/theme/index.ts +15 -0
  390. package/src/theme/presets.ts +79 -0
  391. package/src/theme/theme.ts +119 -0
@@ -0,0 +1,90 @@
1
+ 'use client';
2
+
3
+ import { useRef, type HTMLAttributes, type MouseEvent, type ReactNode } from 'react';
4
+ import { useIsomorphicLayoutEffect } from '../../react/use-recipe';
5
+ import { revealShell, type NavStyle } from './engine';
6
+
7
+ export interface NavItem {
8
+ value: string;
9
+ label: ReactNode;
10
+ icon?: ReactNode;
11
+ /** Render a link instead of a button. */
12
+ href?: string;
13
+ /** Hidden in the phone bar (Stepbro's hide-on-mobile). */
14
+ hideOnMobile?: boolean;
15
+ }
16
+
17
+ export interface NavProps extends Omit<HTMLAttributes<HTMLElement>, 'onSelect'> {
18
+ items: NavItem[];
19
+ /** Value of the current page. */
20
+ active?: string;
21
+ onSelect?: (value: string, event: MouseEvent<HTMLElement>) => void;
22
+ navStyle?: NavStyle;
23
+ accent?: boolean;
24
+ /** Reveal the surrounding .mk-shell from the first button on mount. */
25
+ reveal?: boolean;
26
+ /** The top button (workspace, account…): give it className "mk-nav__button mk-nav__button--first". */
27
+ first?: ReactNode;
28
+ /** Buttons at the end (className "mk-nav__button mk-nav__button--action"). */
29
+ actions?: ReactNode;
30
+ }
31
+
32
+ /** Stepbro's navigation. Put it inside a <div className="mk-shell"> next to <main className="mk-shell__view">. */
33
+ export function Nav({
34
+ items,
35
+ active,
36
+ onSelect,
37
+ navStyle = 'bonita',
38
+ accent = false,
39
+ reveal = false,
40
+ first,
41
+ actions,
42
+ className,
43
+ ...props
44
+ }: NavProps) {
45
+ const ref = useRef<HTMLElement>(null);
46
+ useIsomorphicLayoutEffect(() => {
47
+ if (!reveal || !ref.current) return;
48
+ const handle = revealShell(ref.current);
49
+ return () => handle?.cancel();
50
+ // Only on mount, like Stepbro.
51
+ // eslint-disable-next-line react-hooks/exhaustive-deps
52
+ }, []);
53
+ const classes = ['mk-nav', `mk-nav--${navStyle}`, accent && 'mk-nav--accent', className].filter(Boolean).join(' ');
54
+ return (
55
+ <nav ref={ref} aria-label="Principal" {...props} className={classes}>
56
+ {first}
57
+ <div className="mk-nav__buttons">
58
+ {items.map((item) => {
59
+ const shared = {
60
+ className: 'mk-nav__button',
61
+ 'aria-current': item.value === active ? ('page' as const) : undefined,
62
+ 'data-mk-value': item.value,
63
+ 'data-mk-hide-mobile': item.hideOnMobile ? '' : undefined,
64
+ onClick: (event: MouseEvent<HTMLElement>) => onSelect?.(item.value, event),
65
+ };
66
+ const content = (
67
+ <>
68
+ {item.icon ? (
69
+ <span className="mk-nav__icon" aria-hidden="true">
70
+ {item.icon}
71
+ </span>
72
+ ) : null}
73
+ <span className="mk-nav__name">{item.label}</span>
74
+ </>
75
+ );
76
+ return item.href ? (
77
+ <a key={item.value} href={item.href} {...shared}>
78
+ {content}
79
+ </a>
80
+ ) : (
81
+ <button key={item.value} type="button" {...shared}>
82
+ {content}
83
+ </button>
84
+ );
85
+ })}
86
+ </div>
87
+ {actions ? <div className="mk-nav__actions">{actions}</div> : null}
88
+ </nav>
89
+ );
90
+ }
@@ -0,0 +1,70 @@
1
+ # steps
2
+
3
+ El onboarding de Stepbro: un paso a la vez. Al cambiar de paso, los elementos del actual se van hacia abajo con blur (0.5 s, 50 ms entre ellos, `flowSoft`), se cambia el paso, se espera un frame y los del siguiente caen desde arriba. Un contador rueda hasta el número del paso (0.9 s), el foco pasa al título del paso nuevo y, mientras un paso trabaja (`busy`), el fondo de chat de dentro acelera y crece (energía 1.6, escala 1.8 en 0.8 s; vuelta en 0.6 s).
4
+
5
+ Origen: Stepbro `OnboardingView` (`step-change`, `onboarding-busy-background`, kit Money). El fondo va al 20 % de opacidad, como en Stepbro.
6
+
7
+ ## HTML / Astro
8
+
9
+ ```html
10
+ <mk-steps class="mk-steps" style="height: 100dvh">
11
+ <mk-chat-background></mk-chat-background>
12
+ <p>Paso <span data-mk-counter>1</span> de 3</p>
13
+ <div class="mk-steps__track">
14
+ <section data-mk-step>
15
+ <h2 data-mk-flow-item>Hola</h2>
16
+ <p data-mk-flow-item>Vamos a preparar tu cuenta.</p>
17
+ <button class="mk-button" data-mk-flow-item data-mk-step-next>Empezar</button>
18
+ </section>
19
+ <section data-mk-step>
20
+ <h2 data-mk-step-title data-mk-flow-item>¿Cómo te llamas?</h2>
21
+ <button class="mk-button" data-mk-flow-item data-mk-step-prev>Atrás</button>
22
+ </section>
23
+ </div>
24
+ </mk-steps>
25
+
26
+ <script>
27
+ import { defineElements } from '@yewdiel21/motion-kit/elements';
28
+ defineElements(['mk-steps', 'mk-chat-background']);
29
+ const steps = document.querySelector('mk-steps');
30
+ steps.addEventListener('mk:step', (event) => console.log(event.detail.index));
31
+ // mientras guardas: steps.busy(true) … steps.busy(false); steps.next()
32
+ </script>
33
+ ```
34
+
35
+ Sin custom element: `createSteps(root, opciones)`.
36
+
37
+ Partes: `[data-mk-step]` (los pasos; uno visible), `[data-mk-flow-item]` (lo que fluye dentro de un paso; sin marcas fluyen sus hijos), `[data-mk-step-title]` (recibe el foco; si no, el primer encabezado), `[data-mk-counter]`, botones `[data-mk-step-next]`, `[data-mk-step-prev]`, `[data-mk-step-goto="2"]` (1-based).
38
+
39
+ ## React
40
+
41
+ ```tsx
42
+ const [step, setStep] = useState(0);
43
+ const [saving, setSaving] = useState(false);
44
+
45
+ <Steps className="mk-steps" index={step} onChange={setStep} busy={saving}>
46
+ <ChatBackground />
47
+ <p>Paso <Steps.Counter /> de 2</p>
48
+ <Steps.Step>
49
+ <h2 data-mk-flow-item>Hola</h2>
50
+ <button data-mk-flow-item onClick={() => setStep(1)}>Empezar</button>
51
+ </Steps.Step>
52
+ <Steps.Step>
53
+ <h2 data-mk-flow-item>¿Cómo te llamas?</h2>
54
+ </Steps.Step>
55
+ </Steps>
56
+ ```
57
+
58
+ `Steps.Step` debe ser hijo directo de `Steps`. React escribe el `hidden` inicial y el primer número del contador; después los lleva el motor.
59
+
60
+ ## API
61
+
62
+ `createSteps(root, { index, duration, animateFirst, target, onChange })` → `{ next(), prev(), goTo(i), busy(on), index, count, steps, destroy }`. `next`, `prev` y `goTo` devuelven una promesa que se cumple al terminar; si se piden varios cambios seguidos, termina en el último. Evento `mk:step` (`detail`: `index`, `previous`, `step`).
63
+
64
+ ## Variables CSS
65
+
66
+ `--mk-chat-background-opacity` (0.2 dentro de `.mk-steps`).
67
+
68
+ ## Accesibilidad
69
+
70
+ Los pasos ocultos llevan `hidden` (fuera del árbol de accesibilidad). Al cambiar, el foco va al título del paso nuevo (con `tabindex="-1"` si hace falta) sin mover el scroll. `busy(true)` pone `aria-busy="true"`. Con movimiento reducido el paso cambia sin fluir.
@@ -0,0 +1,43 @@
1
+ import { MkElement, defineElement, type OptionType } from '../../elements/base';
2
+ import { createSteps, type StepsInstance } from './engine';
3
+
4
+ /**
5
+ * <mk-steps index="0">
6
+ * <mk-chat-background></mk-chat-background>
7
+ * <section data-mk-step><h2 data-mk-flow-item>Hola</h2><button data-mk-flow-item data-mk-step-next>Seguir</button></section>
8
+ * <section data-mk-step>…</section>
9
+ * </mk-steps>
10
+ * Emits `mk:step` (detail: index, previous, step) after each change.
11
+ */
12
+ export class MkStepsElement extends MkElement<StepsInstance> {
13
+ static options: Record<string, OptionType> = { index: 'number', duration: 'number', animateFirst: 'boolean' };
14
+
15
+ protected create(options: Record<string, unknown>) {
16
+ return createSteps(this, options);
17
+ }
18
+
19
+ // A new index moves to that step; rebuilding would jump without the flow.
20
+ attributeChangedCallback(name: string, previous: string | null, next: string | null) {
21
+ if (previous === next || !this.instance) return;
22
+ if (name === 'index') void this.instance.goTo(Number(next) || 0);
23
+ else super.attributeChangedCallback(name, previous, next);
24
+ }
25
+
26
+ next() {
27
+ return this.instance?.next();
28
+ }
29
+
30
+ prev() {
31
+ return this.instance?.prev();
32
+ }
33
+
34
+ goTo(index: number) {
35
+ return this.instance?.goTo(index);
36
+ }
37
+
38
+ busy(on: boolean) {
39
+ this.instance?.busy(on);
40
+ }
41
+ }
42
+
43
+ export const defineSteps = () => defineElement('mk-steps', MkStepsElement);
@@ -0,0 +1,185 @@
1
+ import { gsap, registerMotion } from '../../core/gsap';
2
+ import { nextFrame } from '../../core/frame';
3
+ import { entrances, reduced } from '../../core/reduced';
4
+ import { createScope } from '../../core/scope';
5
+ import { clearStyles } from '../../core/styles';
6
+ import { TIMING } from '../../core/timing';
7
+ import { inert, warnOnce, type Instance } from '../../core/types';
8
+ import { revealCounter } from '../../motion/count';
9
+ import { flowIn, flowOut } from '../../motion/flow';
10
+ import { chatBackgroundOf } from '../chat-background/engine';
11
+
12
+ /*
13
+ * Stepbro's onboarding: one step at a time. Changing step flows the current
14
+ * step's items out (down into a 16 px blur, 0.5 s, 50 ms apart), swaps the step,
15
+ * waits a frame and flows the next one's items in from above. A counter rolls
16
+ * to the step number (0.9 s), focus moves to the new step's title, and while a
17
+ * step works (busy) the chat background inside speeds up and grows (energy 1.6,
18
+ * scale 1.8 in 0.8 s; back in 0.6 s).
19
+ * Parts: [data-mk-step] (items: [data-mk-flow-item], else its children),
20
+ * [data-mk-step-title], [data-mk-counter], [data-mk-step-next|prev|goto="n"].
21
+ */
22
+
23
+ export interface StepsOptions {
24
+ /** First step shown (0-based). Default: the one marked data-mk-current, else 0. */
25
+ index?: number;
26
+ duration?: number;
27
+ /** Flow the first step in on start (default, as Stepbro). */
28
+ animateFirst?: boolean;
29
+ /** Items of a step that flow (default [data-mk-flow-item], else the step's children). */
30
+ target?: string;
31
+ onChange?: (index: number, previous: number) => void;
32
+ }
33
+
34
+ export interface StepsInstance extends Instance {
35
+ next(): Promise<void>;
36
+ prev(): Promise<void>;
37
+ goTo(index: number): Promise<void>;
38
+ /** A step is working: drives the chat background inside (Stepbro's onBusy). */
39
+ busy(on: boolean): void;
40
+ readonly index: number;
41
+ readonly count: number;
42
+ readonly steps: HTMLElement[];
43
+ }
44
+
45
+ export interface StepDetail {
46
+ index: number;
47
+ previous: number;
48
+ step: HTMLElement;
49
+ }
50
+
51
+ const STEP = '[data-mk-step]';
52
+ const ITEM_STYLES = ['transform', 'translate', 'rotate', 'scale', 'filter', 'opacity', 'visibility'];
53
+
54
+ export function createSteps(root: HTMLElement | null, options: StepsOptions = {}): StepsInstance {
55
+ // Steps of this root only, not those of a nested one.
56
+ const steps = root
57
+ ? [...root.querySelectorAll<HTMLElement>(STEP)].filter((step) => {
58
+ const outer = step.parentElement?.closest(STEP);
59
+ return !outer || !root.contains(outer);
60
+ })
61
+ : [];
62
+ if (!root || !steps.length) {
63
+ warnOnce('steps: needs [data-mk-step] children');
64
+ return inert({ next: async () => {}, prev: async () => {}, goTo: async () => {}, busy() {}, index: -1, count: 0, steps: [] });
65
+ }
66
+ registerMotion();
67
+ const scope = createScope();
68
+ const { duration = TIMING.onboarding.step, animateFirst = true, target = '[data-mk-flow-item]' } = options;
69
+ const clamp = (value: number) => Math.min(steps.length - 1, Math.max(0, Math.round(value)));
70
+ const marked = steps.findIndex((step) => step.hasAttribute('data-mk-current'));
71
+ let index = clamp(options.index ?? (marked >= 0 ? marked : 0));
72
+ let wanted = index;
73
+ let running: Promise<void> | null = null;
74
+ let stopCounter = () => {};
75
+
76
+ const itemsOf = (step: HTMLElement) => {
77
+ const marked = [...step.querySelectorAll<HTMLElement>(target)];
78
+ return marked.length ? marked : ([...step.children] as HTMLElement[]);
79
+ };
80
+ const show = (current: number) => {
81
+ steps.forEach((step, i) => {
82
+ scope.attr(step, 'hidden', i === current ? null : '');
83
+ scope.attr(step, 'data-mk-current', i === current ? '' : null);
84
+ });
85
+ };
86
+ const counter = () => {
87
+ const el = root.querySelector<HTMLElement>('[data-mk-counter]');
88
+ if (!el) return;
89
+ stopCounter();
90
+ stopCounter = revealCounter(el, index + 1, { duration: TIMING.onboarding.counter });
91
+ };
92
+ const focusTitle = (step: HTMLElement) => {
93
+ const title = step.querySelector<HTMLElement>('[data-mk-step-title]') ?? step.querySelector<HTMLElement>('h1, h2, h3, h4, h5, h6');
94
+ if (!title) return;
95
+ if (!title.hasAttribute('tabindex') && title.tabIndex < 0) scope.attr(title, 'tabindex', '-1');
96
+ title.focus({ preventScroll: true });
97
+ };
98
+
99
+ // One loop at a time; it follows `wanted` until it settles. `running` is cleared
100
+ // in the same tick the loop ends, so a goTo right after starts a new one.
101
+ const change = async () => {
102
+ try {
103
+ while (wanted !== index && !scope.disposed) {
104
+ const previous = index;
105
+ const next = wanted;
106
+ const out = reduced() ? null : flowOut(itemsOf(steps[previous]!), { duration });
107
+ if (out) await out;
108
+ if (scope.disposed) return;
109
+ index = next;
110
+ show(index);
111
+ const step = steps[index]!;
112
+ await nextFrame();
113
+ if (scope.disposed) return;
114
+ // Opacity only: a visibility-hidden title could not take the focus.
115
+ const items = itemsOf(step);
116
+ clearStyles(items, ['visibility']);
117
+ flowIn(items, { duration, autoAlpha: false });
118
+ counter();
119
+ focusTitle(step);
120
+ root.dispatchEvent(new CustomEvent<StepDetail>('mk:step', { bubbles: true, detail: { index, previous, step } }));
121
+ options.onChange?.(index, previous);
122
+ }
123
+ } finally {
124
+ running = null;
125
+ }
126
+ };
127
+
128
+ const goTo = (to: number) => {
129
+ wanted = clamp(to);
130
+ if (wanted === index && !running) return Promise.resolve();
131
+ running ??= change();
132
+ return running;
133
+ };
134
+
135
+ show(index);
136
+ scope.add(() => {
137
+ stopCounter();
138
+ for (const step of steps) {
139
+ const items = itemsOf(step);
140
+ gsap.killTweensOf(items);
141
+ clearStyles(items, ITEM_STYLES);
142
+ }
143
+ });
144
+ scope.on(root, 'click', (event) => {
145
+ const trigger = (event.target as Element | null)?.closest<HTMLElement>('[data-mk-step-next], [data-mk-step-prev], [data-mk-step-goto]');
146
+ if (!trigger || !root.contains(trigger)) return;
147
+ if (trigger.hasAttribute('data-mk-step-next')) void goTo(wanted + 1);
148
+ else if (trigger.hasAttribute('data-mk-step-prev')) void goTo(wanted - 1);
149
+ else void goTo(Number(trigger.dataset.mkStepGoto) - 1);
150
+ });
151
+ const counterEl = root.querySelector<HTMLElement>('[data-mk-counter]');
152
+ if (counterEl) {
153
+ const text = counterEl.textContent;
154
+ counterEl.textContent = String(index + 1);
155
+ scope.add(() => {
156
+ counterEl.textContent = text;
157
+ });
158
+ }
159
+ if (animateFirst && entrances()) flowIn(itemsOf(steps[index]!), { duration });
160
+ root.dataset.mkReady = '';
161
+
162
+ return {
163
+ next: () => goTo(wanted + 1),
164
+ prev: () => goTo(wanted - 1),
165
+ goTo,
166
+ busy(on: boolean) {
167
+ scope.attr(root, 'aria-busy', on ? 'true' : null);
168
+ const background = root.querySelector('.mk-chat-background, mk-chat-background');
169
+ chatBackgroundOf(background)?.busy(on);
170
+ },
171
+ get index() {
172
+ return index;
173
+ },
174
+ get count() {
175
+ return steps.length;
176
+ },
177
+ get steps() {
178
+ return steps;
179
+ },
180
+ destroy() {
181
+ scope.dispose();
182
+ delete root.dataset.mkReady;
183
+ },
184
+ };
185
+ }
@@ -0,0 +1,91 @@
1
+ 'use client';
2
+
3
+ import {
4
+ Children,
5
+ createContext,
6
+ isValidElement,
7
+ useContext,
8
+ useRef,
9
+ type HTMLAttributes,
10
+ type ReactNode,
11
+ } from 'react';
12
+ import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
13
+ import { createSteps, type StepsInstance } from './engine';
14
+
15
+ interface StepsContextValue {
16
+ /** The step shown on the first render: React sets `hidden` once, then the engine owns it. */
17
+ initial: number;
18
+ }
19
+
20
+ const StepsContext = createContext<StepsContextValue>({ initial: 0 });
21
+ const StepIndex = createContext(-1);
22
+
23
+ export interface StepsProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {
24
+ /** Current step (0-based). Changing it flows to that step. */
25
+ index?: number;
26
+ onChange?: (index: number, previous: number) => void;
27
+ /** A step is working: the chat background inside speeds up (Stepbro's onBusy). */
28
+ busy?: boolean;
29
+ duration?: number;
30
+ children?: ReactNode;
31
+ }
32
+
33
+ function Root({ index = 0, onChange, busy = false, duration, className, children, ...props }: StepsProps) {
34
+ const ref = useRef<HTMLDivElement>(null);
35
+ const instance = useRef<StepsInstance | null>(null);
36
+ const initial = useRef(index);
37
+ const change = useLatest(onChange);
38
+ useIsomorphicLayoutEffect(() => {
39
+ const created = createSteps(ref.current, { index: initial.current, duration, onChange: (i, previous) => change.current?.(i, previous) });
40
+ instance.current = created;
41
+ return () => {
42
+ created.destroy();
43
+ instance.current = null;
44
+ };
45
+ // Created once; index and busy are pushed below.
46
+ // eslint-disable-next-line react-hooks/exhaustive-deps
47
+ }, []);
48
+ useIsomorphicLayoutEffect(() => {
49
+ void instance.current?.goTo(index);
50
+ }, [index]);
51
+ useIsomorphicLayoutEffect(() => {
52
+ instance.current?.busy(busy);
53
+ }, [busy]);
54
+ let count = 0;
55
+ const numbered = Children.map(children, (child) =>
56
+ isValidElement(child) && child.type === Step ? <StepIndex.Provider value={count++}>{child}</StepIndex.Provider> : child,
57
+ );
58
+ return (
59
+ <StepsContext.Provider value={{ initial: initial.current }}>
60
+ <div ref={ref} className={['mk-steps', className].filter(Boolean).join(' ')} {...props}>
61
+ {numbered}
62
+ </div>
63
+ </StepsContext.Provider>
64
+ );
65
+ }
66
+
67
+ function Step({ children, ...props }: HTMLAttributes<HTMLElement>) {
68
+ const { initial } = useContext(StepsContext);
69
+ const index = useContext(StepIndex);
70
+ return (
71
+ <section data-mk-step="" hidden={index !== initial} {...props}>
72
+ {children}
73
+ </section>
74
+ );
75
+ }
76
+
77
+ /** The step number; the engine rolls it (React only writes the first value). */
78
+ function Counter(props: HTMLAttributes<HTMLSpanElement>) {
79
+ const { initial } = useContext(StepsContext);
80
+ return (
81
+ <span data-mk-counter="" suppressHydrationWarning {...props}>
82
+ {initial + 1}
83
+ </span>
84
+ );
85
+ }
86
+
87
+ /**
88
+ * Stepbro's onboarding: <Steps index={i} busy={working}><ChatBackground /><Steps.Counter />
89
+ * <Steps.Step><h2 data-mk-flow-item>…</h2></Steps.Step>…</Steps>. Steps must be direct children.
90
+ */
91
+ export const Steps = Object.assign(Root, { Step, Counter });
@@ -0,0 +1,49 @@
1
+ /* steps — Stepbro's onboarding: one step at a time, centred in a column up to 600 px wide. */
2
+ .mk-steps,
3
+ mk-steps {
4
+ position: relative;
5
+ display: flex;
6
+ flex-direction: column;
7
+ align-items: center;
8
+ justify-content: space-around;
9
+ min-height: 0;
10
+ color: var(--md-sys-color-on-background);
11
+ font-family: var(--mk-font);
12
+ }
13
+
14
+ /* Stepbro's onboarding background runs at 20 %. */
15
+ :is(.mk-steps, mk-steps) > :is(.mk-chat-background, mk-chat-background) {
16
+ --mk-chat-background-opacity: 0.2;
17
+ }
18
+
19
+ .mk-steps__track {
20
+ position: relative;
21
+ z-index: 1;
22
+ display: flex;
23
+ flex-direction: column;
24
+ flex-grow: 1;
25
+ width: 100%;
26
+ max-width: 600px;
27
+ }
28
+
29
+ [data-mk-step] {
30
+ display: flex;
31
+ flex-direction: column;
32
+ flex-grow: 1;
33
+ align-items: center;
34
+ justify-content: space-around;
35
+ }
36
+
37
+ /* A step's own display never brings a hidden step back. */
38
+ [data-mk-step][hidden] {
39
+ display: none !important;
40
+ }
41
+
42
+ [data-mk-step-title]:focus,
43
+ [data-mk-step] :is(h1, h2, h3, h4, h5, h6)[tabindex='-1']:focus {
44
+ outline: none;
45
+ }
46
+
47
+ [data-mk-counter] {
48
+ font-variant-numeric: tabular-nums;
49
+ }
@@ -0,0 +1,57 @@
1
+ # theme-picker
2
+
3
+ Las bolas de color del selector de tema de Stepbro. Entran escalonadas (desde 50 %, blur 4 px, 10 ms entre bolas); al pasar el ratón la bola se redondea a 32 px y se satura, y la elegida queda como cuadrado redondeado de 16 px (0.5 s, `cubic-bezier(.38,.49,0,1.5)`), con dos luces difuminadas que se mueven dentro (3.5 s). Al elegir una se aplica el tema con `setTheme()`.
4
+
5
+ Origen: Stepbro — `theme-picker` / `theme-picker-enter` en el kit; CSS de las bolas portada de Kayro (`.accent-swatch`).
6
+
7
+ ## HTML / Astro
8
+
9
+ ```html
10
+ <mk-theme-picker></mk-theme-picker>
11
+ <mk-theme-picker show-hidden></mk-theme-picker>
12
+ <mk-theme-picker themes="neogreen,pink,oled"></mk-theme-picker>
13
+
14
+ <script>
15
+ import { defineElements } from '@yewdiel21/motion-kit/elements';
16
+ import { initTheme } from '@yewdiel21/motion-kit/theme';
17
+ initTheme();
18
+ defineElements(['mk-theme-picker']);
19
+ </script>
20
+ ```
21
+
22
+ Con tus propias bolas (no genera ninguna):
23
+
24
+ ```html
25
+ <div class="mk-theme-picker" id="picker">
26
+ <button class="mk-swatch" data-mk-swatch="neogreen" style="--mk-swatch:#00e563" aria-label="Neogreen"></button>
27
+ <button class="mk-swatch" data-mk-swatch="pink" style="--mk-swatch:#f7acff" aria-label="Pink"></button>
28
+ </div>
29
+ <script>createThemePicker(document.getElementById('picker'));</script>
30
+ ```
31
+
32
+ ## React
33
+
34
+ ```tsx
35
+ <ThemeProvider defaultTheme="neogreen">
36
+ <ThemePicker onChange={(id) => console.log(id)} />
37
+ </ThemeProvider>
38
+ ```
39
+
40
+ `useTheme()` dentro del provider da `{ id, mode, resolved, setTheme, setMode }`.
41
+
42
+ ## Opciones
43
+
44
+ | Opción | Atributo | Por defecto | Qué hace |
45
+ |---|---|---|---|
46
+ | `themes` | `themes="a,b"` | los 10 visibles de Stepbro | Ids y orden de las bolas |
47
+ | `showHidden` | `show-hidden` | `false` | Incluye los 9 temas que Stepbro oculta |
48
+ | `labels.group` | `aria-label` | `'Tema'` | Nombre accesible del grupo |
49
+ | `onChange` | evento `mk:theme` en `document` | — | Tema elegido |
50
+
51
+ ## Variables CSS
52
+
53
+ `--mk-swatch-size` (72px), `--mk-swatch-gap` (12px) y, por bola, `--mk-swatch` (su color).
54
+
55
+ ## Accesibilidad
56
+
57
+ Grupo con nombre; cada bola es un botón con `aria-label` y `aria-pressed`. Con movimiento reducido no hay entrada ni transiciones.
@@ -0,0 +1,20 @@
1
+ import { MkElement, defineElement, type OptionType } from '../../elements/base';
2
+ import { createThemePicker, type ThemePickerInstance } from './engine';
3
+
4
+ /**
5
+ * <mk-theme-picker show-hidden themes="neogreen,pink"></mk-theme-picker>
6
+ * Emits `mk:theme` on document when a theme is chosen (from setTheme).
7
+ */
8
+ export class MkThemePickerElement extends MkElement<ThemePickerInstance> {
9
+ static options: Record<string, OptionType> = { showHidden: 'boolean', themes: 'list' };
10
+
11
+ protected create(options: Record<string, unknown>) {
12
+ return createThemePicker(this, options);
13
+ }
14
+
15
+ select(id: string) {
16
+ this.instance?.select(id);
17
+ }
18
+ }
19
+
20
+ export const defineThemePicker = () => defineElement('mk-theme-picker', MkThemePickerElement);