@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,115 @@
1
+ 'use client';
2
+
3
+ import { useRef, type HTMLAttributes, type ReactNode } from 'react';
4
+ import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
5
+ import { createIsland, type IslandInstance, type IslandOptions } from './engine';
6
+
7
+ export interface IslandPanel {
8
+ name: string;
9
+ /** Accessible name of its button. */
10
+ label: string;
11
+ /** Button content; the menu's nine dots by default. */
12
+ icon?: ReactNode;
13
+ /** Light button (iyo's cart). */
14
+ light?: boolean;
15
+ content: ReactNode;
16
+ }
17
+
18
+ export interface IslandProps extends Omit<HTMLAttributes<HTMLElement>, 'onChange'>, Omit<IslandOptions, 'onChange'> {
19
+ brand?: ReactNode;
20
+ /** The call to action that fades out while a panel is open. */
21
+ cta?: { href: string; label: ReactNode };
22
+ panels: IslandPanel[];
23
+ /** Keep it (and its backdrop) inside a positioned parent instead of fixed over the page. */
24
+ contained?: boolean;
25
+ onChange?: (active: string | null) => void;
26
+ /** Receives the live instance (open, close, toggle, ready). */
27
+ instanceRef?: { current: IslandInstance | null };
28
+ }
29
+
30
+ /** The nine static dots of iyo's menu button. */
31
+ export function IslandDots() {
32
+ return (
33
+ <span className="mk-island__dots" aria-hidden="true">
34
+ {Array.from({ length: 9 }, (_, i) => (
35
+ <i key={i} />
36
+ ))}
37
+ </span>
38
+ );
39
+ }
40
+
41
+ /** iyo.ai's floating navbar. */
42
+ export function Island({
43
+ brand,
44
+ cta,
45
+ panels,
46
+ contained = false,
47
+ entrance,
48
+ closeOnScroll,
49
+ closeOnNavigate,
50
+ focusOnOpen,
51
+ onChange,
52
+ instanceRef,
53
+ className,
54
+ ...props
55
+ }: IslandProps) {
56
+ const ref = useRef<HTMLElement>(null);
57
+ const change = useLatest(onChange);
58
+ useIsomorphicLayoutEffect(() => {
59
+ const instance = createIsland(ref.current, {
60
+ entrance,
61
+ closeOnScroll,
62
+ closeOnNavigate,
63
+ focusOnOpen,
64
+ onChange: (active) => change.current?.(active),
65
+ });
66
+ if (instanceRef) instanceRef.current = instance;
67
+ return () => {
68
+ instance.destroy();
69
+ if (instanceRef) instanceRef.current = null;
70
+ };
71
+ // The panels' names define the markup the engine reads.
72
+ // eslint-disable-next-line react-hooks/exhaustive-deps
73
+ }, [panels.map((panel) => panel.name).join(), entrance, closeOnScroll, closeOnNavigate, focusOnOpen]);
74
+ const classes = ['mk-island', contained && 'mk-island--contained', className].filter(Boolean).join(' ');
75
+ return (
76
+ <nav ref={ref} aria-label="Principal" {...props} className={classes}>
77
+ <button type="button" className="mk-island__backdrop" data-mk-island-close tabIndex={-1} aria-label="Cerrar" />
78
+ <div className="mk-island__shell">
79
+ <div className="mk-island__glass" aria-hidden="true" />
80
+ <div className="mk-island__header">
81
+ {brand ? <span className="mk-island__brand">{brand}</span> : <span />}
82
+ <div className="mk-island__actions">
83
+ {cta ? (
84
+ <a className="mk-island__cta" href={cta.href}>
85
+ {cta.label}
86
+ </a>
87
+ ) : null}
88
+ {panels.map((panel, i) => (
89
+ <span key={panel.name} style={{ display: 'contents' }}>
90
+ {i > 0 ? <span className="mk-island__separator" aria-hidden="true" /> : null}
91
+ <button
92
+ type="button"
93
+ className={panel.light ? 'mk-island__button mk-island__button--light' : 'mk-island__button'}
94
+ data-mk-island-toggle={panel.name}
95
+ aria-label={panel.label}
96
+ >
97
+ {panel.icon ?? <IslandDots />}
98
+ </button>
99
+ </span>
100
+ ))}
101
+ </div>
102
+ </div>
103
+ <div className="mk-island__content">
104
+ <div>
105
+ {panels.map((panel) => (
106
+ <section key={panel.name} className="mk-island__panel" data-mk-island-panel={panel.name} aria-label={panel.label}>
107
+ {panel.content}
108
+ </section>
109
+ ))}
110
+ </div>
111
+ </div>
112
+ </div>
113
+ </nav>
114
+ );
115
+ }
@@ -0,0 +1,67 @@
1
+ # menu
2
+
3
+ El menú "Opciones" de Stepbro: un panel translúcido que crece desde su botón (el mismo motor del diálogo, anclado y sin velo) y cuyas opciones entran escalonadas desde 90 % con blur 4 px, de la última a la primera, 0.35 s, 40 ms entre opciones, tras 0.1 s. Un clic fuera o Escape lo cierra; flechas, Inicio y Fin mueven el foco.
4
+
5
+ Origen: Stepbro NavMenu (abre con ModalHost `absolute`, sin backdrop, superficie translúcida; `nav-menu-enter` en el kit). Su código usa el mismo plegado de 0.5 s que los diálogos; el screencast en vivo de Stepbro midió menos (abre 175 → 364 ms, cierra 66 → 182 ms) que `pnpm timings` aquí (≈ 585 / 510 ms); lo más probable es que su comparación de frames no registrara la cascada pequeña de las opciones ni la cola del plegado.
6
+
7
+ ## HTML / Astro
8
+
9
+ ```html
10
+ <button class="mk-button mk-button--icon" data-mk-open="opciones" aria-label="Opciones">
11
+ <span class="mk-icon">more_vert</span>
12
+ </button>
13
+
14
+ <mk-menu id="opciones" label="Opciones">
15
+ <button data-mk-item data-mk-value="apariencia"><span class="mk-icon">palette</span>Apariencia</button>
16
+ <button data-mk-item data-mk-value="ajustes"><span class="mk-icon">settings</span>Ajustes</button>
17
+ <hr data-mk-separator />
18
+ <button data-mk-item data-mk-value="salir"><span class="mk-icon">logout</span>Cerrar sesión</button>
19
+ </mk-menu>
20
+
21
+ <script>
22
+ import { defineElements } from '@yewdiel21/motion-kit/elements';
23
+ defineElements(['mk-menu']);
24
+ document.addEventListener('mk:select', (event) => console.log(event.detail.value));
25
+ </script>
26
+ ```
27
+
28
+ Sobre tu propio `<dialog class="mk-dialog mk-menu">`: `createMenu(dialog, opciones)`.
29
+
30
+ ## React
31
+
32
+ ```tsx
33
+ <Menu.Root onSelect={(value) => run(value)}>
34
+ <Menu.Trigger className="mk-button mk-button--icon" aria-label="Opciones">⋮</Menu.Trigger>
35
+ <Menu.Content aria-label="Opciones">
36
+ <Menu.Item value="apariencia">Apariencia</Menu.Item>
37
+ <Menu.Separator />
38
+ <Menu.Item value="salir">Cerrar sesión</Menu.Item>
39
+ </Menu.Content>
40
+ </Menu.Root>
41
+ ```
42
+
43
+ ## Opciones
44
+
45
+ | Opción | Atributo | Por defecto | Qué hace |
46
+ |---|---|---|---|
47
+ | `closeOnSelect` | `keep-open` | `true` | Cerrar al elegir |
48
+ | `onSelect` | evento `mk:select` | — | `(value, item)`; `value` = `data-mk-value` o el texto |
49
+ | `variant` | `variant` | `'morph'` | `'liquid'` |
50
+ | `labels`, `triggers`, `onOpen`, `onClose` | | | Como en `dialog` |
51
+
52
+ ## Partes
53
+
54
+ `[data-mk-item]` (opción), `[data-mk-separator]` (línea), `[data-mk-label]` (título de grupo).
55
+
56
+ ## Variables CSS
57
+
58
+ `--mk-menu-bg` (surface-3 al 78 %), `--mk-menu-radius` (28px), `--mk-menu-item-radius` (20px).
59
+
60
+ ## Accesibilidad
61
+
62
+ `role="menu"` y `role="menuitem"`; foco en la primera opción al abrir y de vuelta al botón al cerrar. Con movimiento reducido aparece sin animar.
63
+
64
+ ## Presentación boleto (v0.8)
65
+
66
+ `createMenu(dialog, { ticket: true })`, `createTicketMenu(dialog)`, `<mk-menu ticket>` o `<Menu.Root ticket>` aplican la presentación de Siena. El motor conserva foco, teclado, Escape y selección. Apertura recortada .9 s; contenido desde 150%, 1.1 s con stagger .02. El kit adapta los paneles globales con grid rows a un diálogo local. La receta `ticket-menu` tiene su propia página en el playground.
67
+
@@ -0,0 +1,55 @@
1
+ import { MkElement, defineElement, type OptionType } from '../../elements/base';
2
+ import type { Instance } from '../../core/types';
3
+ import type { DialogVariant } from '../dialog/engine';
4
+ import { createMenu, type MenuInstance } from './engine';
5
+
6
+ /**
7
+ * <mk-menu id="opciones" [variant="liquid"] [keep-open]>
8
+ * <button data-mk-item data-mk-value="ajustes">Ajustes</button> …
9
+ * </mk-menu>
10
+ * Any [data-mk-open="opciones"] opens it from that button; choosing an option
11
+ * emits `mk:select` (detail.value, detail.item).
12
+ */
13
+ export class MkMenuElement extends MkElement<Instance> {
14
+ static options: Record<string, OptionType> = { variant: 'string', keepOpen: 'boolean', label: 'string', ticket: 'boolean' };
15
+
16
+ menu: MenuInstance | null = null;
17
+
18
+ protected create(options: Record<string, unknown>): Instance {
19
+ const original = [...this.childNodes];
20
+ const dialog = document.createElement('dialog');
21
+ dialog.className = 'mk-dialog mk-menu';
22
+ if (options.label) dialog.setAttribute('aria-label', options.label as string);
23
+ dialog.append(...original);
24
+ this.append(dialog);
25
+ const menu = createMenu(dialog, {
26
+ ticket: Boolean(options.ticket),
27
+ variant: (options.variant as DialogVariant) ?? 'morph',
28
+ closeOnSelect: !options.keepOpen,
29
+ triggers: this.id || false,
30
+ });
31
+ this.menu = menu;
32
+ return {
33
+ destroy: () => {
34
+ menu.destroy();
35
+ this.append(...original);
36
+ dialog.remove();
37
+ this.menu = null;
38
+ },
39
+ };
40
+ }
41
+
42
+ open(origin?: HTMLElement | null) {
43
+ this.menu?.open(origin);
44
+ }
45
+
46
+ close() {
47
+ this.menu?.close();
48
+ }
49
+
50
+ get isOpen() {
51
+ return this.menu?.isOpen ?? false;
52
+ }
53
+ }
54
+
55
+ export const defineMenu = () => defineElement('mk-menu', MkMenuElement);
@@ -0,0 +1,142 @@
1
+ import { createScope } from '../../core/scope';
2
+ import { gsap } from '../../core/gsap';
3
+ import { reduced } from '../../core/reduced';
4
+ import { TIMING } from '../../core/timing';
5
+ import { inert, warnOnce } from '../../core/types';
6
+ import { flowInMenu } from '../../motion/entrances';
7
+ import { createDialog, type DialogInstance, type DialogOptions } from '../dialog/engine';
8
+
9
+ /*
10
+ * Stepbro's NavMenu "Opciones": the same ModalHost as a dialog, anchored to its
11
+ * button, without a tinted backdrop, on a translucent surface that grows out of
12
+ * the button; its options flow in from 90 % out of a 4 px blur, last first,
13
+ * 40 ms apart, after 0.1 s. Arrow keys, Home and End move between options.
14
+ */
15
+
16
+ export interface MenuOptions extends Omit<DialogOptions, 'placement' | 'backdrop' | 'drag' | 'autoResize' | 'animateContent'> {
17
+ ticket?: boolean;
18
+ /** Close after an option is chosen (default). */
19
+ closeOnSelect?: boolean;
20
+ onSelect?: (value: string, item: HTMLElement) => void;
21
+ }
22
+
23
+ export interface MenuInstance extends DialogInstance {
24
+ readonly items: HTMLElement[];
25
+ }
26
+
27
+ const ITEM = '[data-mk-item]';
28
+
29
+ export function createMenu(dialog: HTMLElement | null, options: MenuOptions = {}): MenuInstance {
30
+ if (!dialog) {
31
+ warnOnce('menu: missing <dialog> element');
32
+ return inert({
33
+ open() {},
34
+ close() {},
35
+ toggle() {},
36
+ float() {},
37
+ dock() {},
38
+ isOpen: false,
39
+ isFloating: false,
40
+ element: null as unknown as HTMLElement,
41
+ items: [],
42
+ });
43
+ }
44
+ const { closeOnSelect = true } = options;
45
+ const scope = createScope();
46
+ let ticketAnimation: gsap.core.Timeline | undefined;
47
+ if (options.ticket) {
48
+ scope.attr(dialog, 'data-mk-ticket', '');
49
+ scope.attr(dialog, 'style', dialog.getAttribute('style'));
50
+ for (const item of dialog.querySelectorAll<HTMLElement>(ITEM)) scope.attr(item, 'style', item.getAttribute('style'));
51
+ scope.add(() => ticketAnimation?.kill());
52
+ }
53
+ const items = () => [...dialog.querySelectorAll<HTMLElement>(ITEM)].filter((item) => !item.hasAttribute('disabled') && item.getAttribute('aria-disabled') !== 'true');
54
+
55
+ scope.attr(dialog, 'role', 'menu');
56
+ // Options can come and go (frameworks): roles are applied now and on every open.
57
+ const markItems = () => {
58
+ for (const item of dialog.querySelectorAll<HTMLElement>(ITEM)) {
59
+ scope.attr(item, 'role', 'menuitem');
60
+ scope.attr(item, 'tabindex', '-1');
61
+ }
62
+ };
63
+ markItems();
64
+
65
+ const surface = createDialog(dialog, {
66
+ ...options,
67
+ placement: 'anchor',
68
+ backdrop: false,
69
+ drag: false,
70
+ autoResize: false,
71
+ animateContent: false,
72
+ onOpen: () => {
73
+ markItems();
74
+ const list = items();
75
+ if (options.ticket && !reduced()) {
76
+ ticketAnimation?.kill();
77
+ ticketAnimation = gsap.timeline()
78
+ .fromTo(dialog, { clipPath: 'inset(0 0 100% 0)' }, { clipPath: 'inset(0 0 0% 0)', duration: TIMING.ticketMenu.open, ease: 'expo.out' })
79
+ .fromTo(list, { yPercent: TIMING.ticketMenu.from }, { yPercent: 0, duration: TIMING.ticketMenu.content, stagger: TIMING.ticketMenu.stagger, ease: 'expo.out' }, 0);
80
+ } else flowInMenu(list);
81
+ list[0]?.focus({ preventScroll: true });
82
+ options.onOpen?.();
83
+ },
84
+ });
85
+
86
+ const select = (item: HTMLElement) => {
87
+ const value = item.dataset.mkValue ?? item.textContent?.trim() ?? '';
88
+ dialog.dispatchEvent(new CustomEvent('mk:select', { bubbles: true, detail: { value, item } }));
89
+ options.onSelect?.(value, item);
90
+ if (closeOnSelect) surface.close();
91
+ };
92
+
93
+ scope.on(dialog, 'click', (event) => {
94
+ const item = (event.target as Element | null)?.closest<HTMLElement>(ITEM);
95
+ if (item && dialog.contains(item) && items().includes(item)) select(item);
96
+ });
97
+ scope.on(dialog, 'keydown', (event) => {
98
+ const key = (event as KeyboardEvent).key;
99
+ const list = items();
100
+ if (!list.length) return;
101
+ const at = list.indexOf(document.activeElement as HTMLElement);
102
+ const moves: Record<string, number> = {
103
+ ArrowDown: at < 0 ? 0 : (at + 1) % list.length,
104
+ ArrowUp: at < 0 ? list.length - 1 : (at - 1 + list.length) % list.length,
105
+ Home: 0,
106
+ End: list.length - 1,
107
+ };
108
+ if (key in moves) {
109
+ event.preventDefault();
110
+ list[moves[key]]?.focus();
111
+ return;
112
+ }
113
+ // Buttons and links activate themselves; other items need Enter and Space.
114
+ const target = event.target as HTMLElement;
115
+ if ((key === 'Enter' || key === ' ') && list.includes(target) && !target.matches('button, a[href]')) {
116
+ event.preventDefault();
117
+ select(target);
118
+ }
119
+ });
120
+
121
+ return {
122
+ open: surface.open,
123
+ close: surface.close,
124
+ toggle: surface.toggle,
125
+ float: surface.float,
126
+ dock: surface.dock,
127
+ get isOpen() {
128
+ return surface.isOpen;
129
+ },
130
+ get isFloating() {
131
+ return surface.isFloating;
132
+ },
133
+ get items() {
134
+ return items();
135
+ },
136
+ element: dialog,
137
+ destroy() {
138
+ surface.destroy();
139
+ scope.dispose();
140
+ },
141
+ };
142
+ }
@@ -0,0 +1,104 @@
1
+ /* menu — Stepbro's translucent options panel that grows out of its button (an anchored .mk-dialog). */
2
+ .mk-menu {
3
+ --mk-menu-bg: color-mix(in srgb, var(--mk-surface-3) 78%, transparent);
4
+ --mk-menu-radius: 28px;
5
+ --mk-menu-item-radius: 20px;
6
+ --mk-dialog-bg: var(--mk-menu-bg);
7
+ --mk-dialog-radius: var(--mk-menu-radius);
8
+ --mk-dialog-shadow: 0 16px 48px -12px rgb(0 0 0 / 0.35);
9
+ width: max-content;
10
+ min-width: 220px;
11
+ max-width: min(320px, calc(100vw - 32px));
12
+ padding: 8px;
13
+ backdrop-filter: blur(24px) saturate(1.6);
14
+ -webkit-backdrop-filter: blur(24px) saturate(1.6);
15
+ }
16
+
17
+ .mk-menu[open] {
18
+ gap: 2px;
19
+ }
20
+
21
+ .mk-menu [data-mk-item] {
22
+ position: relative;
23
+ display: flex;
24
+ align-items: center;
25
+ gap: 12px;
26
+ width: 100%;
27
+ min-height: 48px;
28
+ padding: 0 16px;
29
+ border: 0;
30
+ border-radius: var(--mk-menu-item-radius);
31
+ background: transparent;
32
+ color: var(--mk-text);
33
+ font-family: var(--md-sys-typescale-body-large-font);
34
+ font-size: var(--md-sys-typescale-label-large-size);
35
+ font-weight: var(--md-sys-typescale-label-large-weight);
36
+ letter-spacing: var(--md-sys-typescale-label-large-tracking);
37
+ text-align: left;
38
+ text-decoration: none;
39
+ white-space: nowrap;
40
+ cursor: pointer;
41
+ outline: none;
42
+ transition: background-color 0.15s linear;
43
+ }
44
+
45
+ .mk-menu [data-mk-item]:hover,
46
+ .mk-menu [data-mk-item]:focus-visible {
47
+ background: color-mix(in srgb, var(--mk-text) 8%, transparent);
48
+ }
49
+
50
+ .mk-menu [data-mk-item]:focus-visible {
51
+ box-shadow: inset 0 0 0 2px var(--mk-accent);
52
+ }
53
+
54
+ .mk-menu [data-mk-item][aria-disabled='true'],
55
+ .mk-menu [data-mk-item]:disabled {
56
+ opacity: 0.38;
57
+ cursor: default;
58
+ }
59
+
60
+ .mk-menu [data-mk-item] .mk-icon,
61
+ .mk-menu [data-mk-item] svg {
62
+ flex: none;
63
+ color: var(--mk-muted);
64
+ font-size: 20px;
65
+ }
66
+
67
+ .mk-menu [data-mk-separator] {
68
+ height: 1px;
69
+ margin: 6px 12px;
70
+ border: 0;
71
+ background: var(--mk-outline-soft);
72
+ }
73
+
74
+ .mk-menu [data-mk-label] {
75
+ padding: 8px 16px 4px;
76
+ color: var(--mk-muted);
77
+ font-size: var(--md-sys-typescale-label-medium-size);
78
+ font-weight: var(--md-sys-typescale-label-medium-weight);
79
+ }
80
+
81
+ mk-menu {
82
+ display: contents;
83
+ }
84
+
85
+ .mk-menu[data-mk-ticket] {
86
+ --mk-menu-radius: 2px;
87
+ --mk-menu-item-radius: 0px;
88
+ --mk-menu-bg: var(--mk-surface-1);
89
+ padding: 20px;
90
+ min-width: 280px;
91
+ border-block: 6px dotted var(--mk-outline-soft);
92
+ backdrop-filter: none;
93
+ }
94
+ .mk-menu[data-mk-ticket] [data-mk-item] { border-bottom: 1px dashed var(--mk-outline-soft); min-height: 64px; font-size: 20px; }
95
+
96
+ mk-menu:not(:defined) {
97
+ display: none;
98
+ }
99
+
100
+ @media (prefers-reduced-motion: reduce) {
101
+ .mk-menu [data-mk-item] {
102
+ transition: none;
103
+ }
104
+ }
@@ -0,0 +1,82 @@
1
+ 'use client';
2
+
3
+ import { createContext, useContext, useRef, type ButtonHTMLAttributes, type DialogHTMLAttributes, type MutableRefObject, type ReactNode } from 'react';
4
+ import { useLatest } from '../../react/use-recipe';
5
+ import { useSurfaceEngine, useSurfaceRoot, type SurfaceContextValue, type SurfaceRootProps } from '../dialog/react';
6
+ import { createMenu, type MenuInstance } from './engine';
7
+
8
+ interface MenuContextValue extends SurfaceContextValue<MenuInstance> {
9
+ ticket: boolean;
10
+ onSelect: MutableRefObject<((value: string, item: HTMLElement) => void) | undefined>;
11
+ closeOnSelect: boolean;
12
+ }
13
+
14
+ const MenuContext = createContext<MenuContextValue | null>(null);
15
+
16
+ function useMenuContext(part: string) {
17
+ const value = useContext(MenuContext);
18
+ if (!value) throw new Error(`<Menu.${part}> must be inside <Menu.Root>`);
19
+ return value;
20
+ }
21
+
22
+ export interface MenuRootProps extends Omit<SurfaceRootProps, 'placement' | 'backdrop' | 'drag' | 'autoResize'> {
23
+ ticket?: boolean;
24
+ onSelect?: (value: string, item: HTMLElement) => void;
25
+ closeOnSelect?: boolean;
26
+ children?: ReactNode;
27
+ }
28
+
29
+ function Root({ onSelect, closeOnSelect = true, ticket = false, children, ...props }: MenuRootProps) {
30
+ const surface = useSurfaceRoot<MenuInstance>(props);
31
+ const select = useLatest(onSelect);
32
+ return <MenuContext.Provider value={{ ...surface, onSelect: select, closeOnSelect, ticket }}>{children}</MenuContext.Provider>;
33
+ }
34
+
35
+ function Trigger({ onClick, children, ...props }: ButtonHTMLAttributes<HTMLButtonElement>) {
36
+ const context = useMenuContext('Trigger');
37
+ return (
38
+ <button
39
+ type="button"
40
+ aria-haspopup="menu"
41
+ aria-expanded={context.open}
42
+ {...props}
43
+ onClick={(event) => {
44
+ onClick?.(event);
45
+ if (event.defaultPrevented) return;
46
+ context.origin.current = event.currentTarget;
47
+ context.setOpen(!context.open);
48
+ }}
49
+ >
50
+ {children}
51
+ </button>
52
+ );
53
+ }
54
+
55
+ function Content({ className, children, ...props }: Omit<DialogHTMLAttributes<HTMLDialogElement>, 'open'>) {
56
+ const context = useMenuContext('Content');
57
+ const closeOnSelect = useRef(context.closeOnSelect);
58
+ closeOnSelect.current = context.closeOnSelect;
59
+ const ref = useSurfaceEngine(context, (dialog, options) =>
60
+ createMenu(dialog, { ...options, ticket: context.ticket, closeOnSelect: closeOnSelect.current, onSelect: (value, item) => context.onSelect.current?.(value, item) }),
61
+ );
62
+ return (
63
+ <dialog ref={ref} className={['mk-dialog mk-menu', className].filter(Boolean).join(' ')} {...props}>
64
+ {children}
65
+ </dialog>
66
+ );
67
+ }
68
+
69
+ function Item({ value, children, ...props }: ButtonHTMLAttributes<HTMLButtonElement> & { value?: string }) {
70
+ return (
71
+ <button type="button" data-mk-item="" data-mk-value={value} {...props}>
72
+ {children}
73
+ </button>
74
+ );
75
+ }
76
+
77
+ function Separator() {
78
+ return <hr data-mk-separator="" />;
79
+ }
80
+
81
+ /** Stepbro's options menu: <Menu.Root onSelect><Menu.Trigger/><Menu.Content><Menu.Item value/>…</Menu.Content></Menu.Root>. */
82
+ export const Menu = { Root, Trigger, Content, Item, Separator };
@@ -0,0 +1,85 @@
1
+ # nav
2
+
3
+ La navegación de Stepbro (Money y Tasks): siete estilos de escritorio y una sola barra de cristal en el móvil (< 680 px), dentro de una cáscara de app (`.mk-shell`).
4
+
5
+ | Estilo | Stepbro | Cómo se ve |
6
+ |---|---|---|
7
+ | `clasica` | style-1 | Barra lateral con borde; la página actual en una píldora rellena |
8
+ | `bonita` | style-2 (por defecto en Money) | Píldoras de texto centradas que se ensanchan al pasar y al estar activas (padding con resorte, 0.3 s `cubic(.38,.49,0,1.5)`) |
9
+ | `dock` | style-3 | Barra flotante de cristal centrada abajo; el botón activo se ensancha a 80 px |
10
+ | `guapa` | style-4 | La vista es una tarjeta sobre la cáscara tintada; la página actual es un chip blanco |
11
+ | `tasks` | style-5 | Riel de botones redondos de 56 px; el activo se cuadra (radio 0.3 s `cubic(.38,.49,0,2)`) |
12
+ | `melon` | style-6 | Barra lateral suave con un lavado primario en la página actual |
13
+ | `tunnel` | style-7 | Palabras grandes; la que tiene el ratón y sus vecinas crecen como un dock (1.2 / 1.09 / 1.03) |
14
+
15
+ `accent` (variant-1 de Stepbro) pinta la página actual en primary-container. Al cambiar de página el icono se rellena (`mk-nav-icon`, 125 ms, `FILL 0 → 1`, para fuentes de iconos como Material Symbols). Con `reveal`, al arrancar la cáscara se descubre desde el primer botón (`revealFrom`, como el `Nav` de Stepbro).
16
+
17
+ Origen: Stepbro `navigation-styles.css`, `navigation.json`, componente `Nav` (Tasks).
18
+
19
+ ## HTML / Astro
20
+
21
+ ```html
22
+ <div class="mk-shell" style="height: 100dvh">
23
+ <main class="mk-shell__view">
24
+ <header class="mk-shell__header"><h1 class="mk-shell__title">Inicio</h1></header>
25
+ …
26
+ </main>
27
+ <mk-nav class="mk-nav mk-nav--bonita" nav-style="bonita" storage-key="mi-app-nav">
28
+ <div class="mk-nav__buttons">
29
+ <a class="mk-nav__button" href="/" aria-current="page"><svg class="mk-nav__icon">…</svg><span class="mk-nav__name">Inicio</span></a>
30
+ <a class="mk-nav__button" href="/movimientos"><svg class="mk-nav__icon">…</svg><span class="mk-nav__name">Movimientos</span></a>
31
+ </div>
32
+ <div class="mk-nav__actions">
33
+ <button class="mk-nav__button mk-nav__button--action" type="button"><svg class="mk-nav__icon">…</svg><span class="mk-nav__name">Opciones</span></button>
34
+ </div>
35
+ </mk-nav>
36
+ </div>
37
+
38
+ <script>
39
+ import { defineElements } from '@yewdiel21/motion-kit/elements';
40
+ defineElements(['mk-nav']);
41
+ document.querySelector('mk-nav').setStyle('dock');
42
+ </script>
43
+ ```
44
+
45
+ Escribe la clase `mk-nav--<estilo>` en el marcado para que se vea bien antes de que cargue el JS. Sin custom element: `createNav(document.querySelector('.mk-nav'), opciones)` sobre un `<nav>`.
46
+
47
+ Partes: `.mk-nav__button--first` (botón de arriba: cuenta, espacio de trabajo), `.mk-nav__buttons`, `.mk-nav__actions` (`.mk-nav__button--action`), `.mk-nav__options` + `.mk-nav__label` (bloque extra que solo muestran clásica, guapa, melon y tunnel), `.mk-nav__icon`, `.mk-nav__name`, `data-mk-hide-mobile` (se esconde en el móvil). Vista: `.mk-shell__view`, `.mk-shell__header`, `.mk-shell__title`, `.mk-shell__content` (cada estilo ajusta su cabecera).
48
+
49
+ ## React
50
+
51
+ ```tsx
52
+ const [page, setPage] = useState('home');
53
+
54
+ <div className="mk-shell" style={{ height: '100dvh' }}>
55
+ <main className="mk-shell__view">…</main>
56
+ <Nav
57
+ navStyle="tasks"
58
+ items={[
59
+ { value: 'home', label: 'Inicio', icon: <HomeIcon /> },
60
+ { value: 'movements', label: 'Movimientos', icon: <RowsIcon /> },
61
+ ]}
62
+ active={page}
63
+ onSelect={(value) => setPage(value)}
64
+ />
65
+ </div>
66
+ ```
67
+
68
+ `first` y `actions` aceptan tus botones (`className="mk-nav__button mk-nav__button--first"` / `--action`). Con `href` el item es un enlace.
69
+
70
+ ## API
71
+
72
+ `createNav(root, { style, accent, reveal, storageKey, labels: { nav }, onSelect })` → `{ select(índice | valor), setStyle(nombre), setAccent(on), index, style, items, destroy }`.
73
+
74
+ - Al hacer clic se emite `mk:navigate` (cancelable; `detail`: `index`, `value`, `item`, `originalEvent`). Para una SPA, llama `event.detail.originalEvent.preventDefault()` y navega con tu router.
75
+ - `select()` solo marca la página actual (sin evento): úsalo para seguir a tu router.
76
+ - `storageKey` guarda el estilo elegido en `localStorage` (Stepbro: `money-nav`).
77
+ - `NAV_STYLES` lista los siete nombres.
78
+
79
+ ## Variables CSS
80
+
81
+ `--mk-nav-icon-size` (24px; 16px en guapa y melon con acento). Colores de los roles Material (`surface-container-*`, `primary-container`, `tertiary-container`…).
82
+
83
+ ## Accesibilidad
84
+
85
+ Un `<nav>` con `aria-label` ("Principal" por defecto); la página actual lleva `aria-current="page"`. Cuando un estilo esconde el texto (dock, tasks, móvil) solo lo oculta a la vista: el botón conserva su nombre para lectores de pantalla. Foco visible en cada botón. Con movimiento reducido no hay transiciones ni revelado.