@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.
- package/NOTICE +38 -0
- package/README.md +80 -2
- package/bin/motion-kit.mjs +242 -0
- package/css.d.ts +2 -0
- package/dist/auto/index.d.ts +63 -0
- package/dist/auto/index.js +207 -0
- package/dist/auto/index.js.map +1 -0
- package/dist/chunk-2BQGFOA7.js +177 -0
- package/dist/chunk-2BQGFOA7.js.map +1 -0
- package/dist/chunk-34XYOXHG.js +2374 -0
- package/dist/chunk-34XYOXHG.js.map +1 -0
- package/dist/chunk-6OJAP6ZL.js +832 -0
- package/dist/chunk-6OJAP6ZL.js.map +1 -0
- package/dist/chunk-ABNRDDO7.js +28 -0
- package/dist/chunk-ABNRDDO7.js.map +1 -0
- package/dist/chunk-FS2HJVVY.js +3320 -0
- package/dist/chunk-FS2HJVVY.js.map +1 -0
- package/dist/chunk-J27KH2A2.js +157 -0
- package/dist/chunk-J27KH2A2.js.map +1 -0
- package/dist/chunk-KLY5BRRI.js +18 -0
- package/dist/chunk-KLY5BRRI.js.map +1 -0
- package/dist/chunk-Q4LVFNLN.js +1617 -0
- package/dist/chunk-Q4LVFNLN.js.map +1 -0
- package/dist/chunk-RFDIZYFX.js +304 -0
- package/dist/chunk-RFDIZYFX.js.map +1 -0
- package/dist/chunk-TGV27ZWM.js +17 -0
- package/dist/chunk-TGV27ZWM.js.map +1 -0
- package/dist/confetti-BSX1CLLV.d.ts +41 -0
- package/dist/css/components/chat-background.css +87 -0
- package/dist/css/components/chat.css +237 -0
- package/dist/css/components/cursor.css +178 -0
- package/dist/css/components/dialog.css +259 -0
- package/dist/css/components/island.css +301 -0
- package/dist/css/components/menu.css +105 -0
- package/dist/css/components/nav.css +1054 -0
- package/dist/css/components/steps.css +50 -0
- package/dist/css/components/theme-picker.css +115 -0
- package/dist/css/components/toast.css +115 -0
- package/dist/css/controls.css +950 -0
- package/dist/css/effects.css +587 -0
- package/dist/css/fonts.css +3 -0
- package/dist/css/themes/black-green.css +126 -0
- package/dist/css/themes/black.css +126 -0
- package/dist/css/themes/blue.css +126 -0
- package/dist/css/themes/brown.css +126 -0
- package/dist/css/themes/cold-blue.css +126 -0
- package/dist/css/themes/funny.css +126 -0
- package/dist/css/themes/gray.css +126 -0
- package/dist/css/themes/green.css +126 -0
- package/dist/css/themes/modern-1.css +126 -0
- package/dist/css/themes/neogreen.css +126 -0
- package/dist/css/themes/new.css +126 -0
- package/dist/css/themes/new2.css +126 -0
- package/dist/css/themes/oled.css +126 -0
- package/dist/css/themes/orange.css +126 -0
- package/dist/css/themes/pastel-pink.css +126 -0
- package/dist/css/themes/pink.css +126 -0
- package/dist/css/themes/purple.css +126 -0
- package/dist/css/themes/red.css +126 -0
- package/dist/css/themes/super-blue.css +126 -0
- package/dist/css/themes.css +2431 -0
- package/dist/css/tokens.css +452 -0
- package/dist/elements/index.d.ts +183 -0
- package/dist/elements/index.js +472 -0
- package/dist/elements/index.js.map +1 -0
- package/dist/engine-CUr9QijM.d.ts +46 -0
- package/dist/gl/index.d.ts +61 -0
- package/dist/gl/index.js +18 -0
- package/dist/gl/index.js.map +1 -0
- package/dist/index.css +5296 -0
- package/dist/index.d.ts +440 -0
- package/dist/index.js +390 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.d.ts +476 -0
- package/dist/react/index.js +969 -0
- package/dist/react/index.js.map +1 -0
- package/dist/reduced-BM4Ls0g5.d.ts +566 -0
- package/dist/reveal-WFUWDrei.d.ts +416 -0
- package/dist/store-Bqy_obnD.d.ts +195 -0
- package/dist/theme/generate.d.ts +18 -0
- package/dist/theme/generate.js +64 -0
- package/dist/theme/generate.js.map +1 -0
- package/dist/theme/index.d.ts +17 -0
- package/dist/theme/index.js +32 -0
- package/dist/theme/index.js.map +1 -0
- package/dist/theme-BFMoCzi6.d.ts +158 -0
- package/dist/types-BkS_raL8.d.ts +7 -0
- package/package.json +114 -3
- package/registry.json +469 -0
- package/skills/motion-kit/SKILL.md +189 -0
- package/skills/motion-kit/reference/components.md +86 -0
- package/skills/motion-kit/reference/design.md +92 -0
- package/skills/motion-kit/reference/recipes.md +172 -0
- package/src/auto/auto-init.ts +151 -0
- package/src/auto/index.ts +3 -0
- package/src/auto/parse-options.ts +68 -0
- package/src/components/chat/README.md +90 -0
- package/src/components/chat/chat.css +236 -0
- package/src/components/chat/element.ts +98 -0
- package/src/components/chat/engine.ts +295 -0
- package/src/components/chat/react.tsx +196 -0
- package/src/components/chat-background/README.md +50 -0
- package/src/components/chat-background/audio.ts +56 -0
- package/src/components/chat-background/chat-background.css +86 -0
- package/src/components/chat-background/element.ts +31 -0
- package/src/components/chat-background/engine.ts +108 -0
- package/src/components/chat-background/react.tsx +36 -0
- package/src/components/cursor/README.md +53 -0
- package/src/components/cursor/cursor.css +177 -0
- package/src/components/cursor/element.ts +23 -0
- package/src/components/cursor/engine.ts +254 -0
- package/src/components/cursor/react.tsx +26 -0
- package/src/components/dialog/README.md +106 -0
- package/src/components/dialog/dialog.css +258 -0
- package/src/components/dialog/element.ts +118 -0
- package/src/components/dialog/engine.ts +432 -0
- package/src/components/dialog/react.tsx +200 -0
- package/src/components/icon.tsx +10 -0
- package/src/components/icons.ts +24 -0
- package/src/components/island/README.md +91 -0
- package/src/components/island/element.ts +45 -0
- package/src/components/island/engine.ts +200 -0
- package/src/components/island/island.css +300 -0
- package/src/components/island/react.tsx +115 -0
- package/src/components/menu/README.md +67 -0
- package/src/components/menu/element.ts +55 -0
- package/src/components/menu/engine.ts +142 -0
- package/src/components/menu/menu.css +104 -0
- package/src/components/menu/react.tsx +82 -0
- package/src/components/nav/README.md +85 -0
- package/src/components/nav/element.ts +39 -0
- package/src/components/nav/engine.ts +164 -0
- package/src/components/nav/nav.css +1053 -0
- package/src/components/nav/react.tsx +90 -0
- package/src/components/steps/README.md +70 -0
- package/src/components/steps/element.ts +43 -0
- package/src/components/steps/engine.ts +185 -0
- package/src/components/steps/react.tsx +91 -0
- package/src/components/steps/steps.css +49 -0
- package/src/components/theme-picker/README.md +57 -0
- package/src/components/theme-picker/element.ts +20 -0
- package/src/components/theme-picker/engine.ts +91 -0
- package/src/components/theme-picker/react.tsx +31 -0
- package/src/components/theme-picker/theme-picker.css +114 -0
- package/src/components/toast/README.md +60 -0
- package/src/components/toast/element.ts +25 -0
- package/src/components/toast/engine.ts +483 -0
- package/src/components/toast/react.tsx +22 -0
- package/src/components/toast/store.ts +122 -0
- package/src/components/toast/toast.css +114 -0
- package/src/core/channels.ts +165 -0
- package/src/core/curves.ts +35 -0
- package/src/core/frame.ts +12 -0
- package/src/core/gsap.ts +65 -0
- package/src/core/loop.ts +44 -0
- package/src/core/math.ts +9 -0
- package/src/core/physics-spring.ts +59 -0
- package/src/core/pointer.ts +63 -0
- package/src/core/reduced.ts +21 -0
- package/src/core/scope.ts +70 -0
- package/src/core/scroll-track.ts +106 -0
- package/src/core/split.ts +68 -0
- package/src/core/spring.ts +22 -0
- package/src/core/styles.ts +31 -0
- package/src/core/timing.ts +204 -0
- package/src/core/types.ts +20 -0
- package/src/core/visible.ts +19 -0
- package/src/css/base.css +142 -0
- package/src/css/controls/README.md +122 -0
- package/src/css/controls/burger.css +67 -0
- package/src/css/controls/button.css +226 -0
- package/src/css/controls/checkbox.css +53 -0
- package/src/css/controls/chip.css +85 -0
- package/src/css/controls/field.css +124 -0
- package/src/css/controls/glass.css +220 -0
- package/src/css/controls/icon.css +26 -0
- package/src/css/controls/segmented.css +60 -0
- package/src/css/controls/switch.css +72 -0
- package/src/css/default-theme.css +123 -0
- package/src/css/effects/README.md +134 -0
- package/src/css/effects/anim.css +224 -0
- package/src/css/effects/fade.css +15 -0
- package/src/css/effects/float.css +64 -0
- package/src/css/effects/frost.css +62 -0
- package/src/css/effects/grain.css +58 -0
- package/src/css/effects/loading.css +60 -0
- package/src/css/effects/success.css +22 -0
- package/src/css/effects/text.css +67 -0
- package/src/css/fonts.css +3 -0
- package/src/css/states.css +51 -0
- package/src/css/themes/black-green.css +126 -0
- package/src/css/themes/black.css +126 -0
- package/src/css/themes/blue.css +126 -0
- package/src/css/themes/brown.css +126 -0
- package/src/css/themes/cold-blue.css +126 -0
- package/src/css/themes/funny.css +126 -0
- package/src/css/themes/gray.css +126 -0
- package/src/css/themes/green.css +126 -0
- package/src/css/themes/modern-1.css +126 -0
- package/src/css/themes/neogreen.css +126 -0
- package/src/css/themes/new.css +126 -0
- package/src/css/themes/new2.css +126 -0
- package/src/css/themes/oled.css +126 -0
- package/src/css/themes/orange.css +126 -0
- package/src/css/themes/pastel-pink.css +126 -0
- package/src/css/themes/pink.css +126 -0
- package/src/css/themes/purple.css +126 -0
- package/src/css/themes/red.css +126 -0
- package/src/css/themes/super-blue.css +126 -0
- package/src/css/tokens.css +182 -0
- package/src/elements/base.ts +76 -0
- package/src/elements/index.ts +48 -0
- package/src/gl/index.ts +15 -0
- package/src/gl/liquid-blend/README.md +23 -0
- package/src/gl/liquid-blend/create-liquid-blend.ts +7 -0
- package/src/gl/liquid-blend/shader.ts +9 -0
- package/src/gl/matcap/README.md +23 -0
- package/src/gl/matcap/create-matcap.ts +7 -0
- package/src/gl/matcap/shader.ts +16 -0
- package/src/gl/particle-hero/README.md +41 -0
- package/src/gl/particle-hero/create-particle-hero.ts +544 -0
- package/src/gl/particle-hero/particle-hero.css +19 -0
- package/src/gl/particle-hero/shaders.ts +84 -0
- package/src/gl/shader-blur/README.md +23 -0
- package/src/gl/shader-blur/create-shader-blur.ts +7 -0
- package/src/gl/shader-blur/shader.ts +11 -0
- package/src/gl/shader-grain/README.md +23 -0
- package/src/gl/shader-grain/create-shader-grain.ts +7 -0
- package/src/gl/shader-grain/shader.ts +6 -0
- package/src/gl/shared/create-shader-effect.ts +186 -0
- package/src/gl/shared/shader.css +3 -0
- package/src/gl/shared/shaders.ts +30 -0
- package/src/index.ts +177 -0
- package/src/motion/anchor.ts +45 -0
- package/src/motion/chat.ts +97 -0
- package/src/motion/confetti.ts +130 -0
- package/src/motion/count.ts +95 -0
- package/src/motion/details.ts +70 -0
- package/src/motion/drag-dismiss.ts +231 -0
- package/src/motion/effects.ts +81 -0
- package/src/motion/entrances.ts +27 -0
- package/src/motion/floating.ts +389 -0
- package/src/motion/flow.ts +115 -0
- package/src/motion/indicator.ts +44 -0
- package/src/motion/layout.ts +39 -0
- package/src/motion/liquid.ts +227 -0
- package/src/motion/morph.ts +540 -0
- package/src/motion/reveal.ts +112 -0
- package/src/motion/sheet.ts +65 -0
- package/src/motion/surface.ts +108 -0
- package/src/react/animated-number.tsx +30 -0
- package/src/react/flip-group.tsx +44 -0
- package/src/react/frost.tsx +33 -0
- package/src/react/index.ts +80 -0
- package/src/react/slot-text.tsx +44 -0
- package/src/react/theme-provider.tsx +42 -0
- package/src/react/use-card-dock.ts +9 -0
- package/src/react/use-cascade.ts +9 -0
- package/src/react/use-contrast.ts +9 -0
- package/src/react/use-counter.ts +8 -0
- package/src/react/use-curved-carousel.ts +10 -0
- package/src/react/use-depth-grid.ts +9 -0
- package/src/react/use-details.ts +7 -0
- package/src/react/use-dock-pill.ts +10 -0
- package/src/react/use-drag-ruler.ts +10 -0
- package/src/react/use-fill-text.ts +9 -0
- package/src/react/use-fit-text.ts +8 -0
- package/src/react/use-flow.ts +8 -0
- package/src/react/use-frame-zoom.ts +9 -0
- package/src/react/use-infinite-reel.ts +8 -0
- package/src/react/use-letters.ts +9 -0
- package/src/react/use-liquid-blend.ts +8 -0
- package/src/react/use-marquee.ts +9 -0
- package/src/react/use-mask-text.ts +9 -0
- package/src/react/use-matcap.ts +8 -0
- package/src/react/use-parallax.ts +9 -0
- package/src/react/use-particle-hero.ts +9 -0
- package/src/react/use-pile.ts +8 -0
- package/src/react/use-pop.ts +8 -0
- package/src/react/use-radial-menu.ts +10 -0
- package/src/react/use-recipe.ts +38 -0
- package/src/react/use-reflow.ts +9 -0
- package/src/react/use-repel-bento.ts +8 -0
- package/src/react/use-reveal.ts +8 -0
- package/src/react/use-rotator.ts +10 -0
- package/src/react/use-shader-blur.ts +8 -0
- package/src/react/use-shader-grain.ts +8 -0
- package/src/react/use-slot-text.ts +17 -0
- package/src/react/use-snap-strip.ts +9 -0
- package/src/react/use-sortable.ts +14 -0
- package/src/react/use-spring-cards.ts +8 -0
- package/src/react/use-stretch-footer.ts +20 -0
- package/src/react/use-tabs.ts +10 -0
- package/src/react/use-text-roll.ts +9 -0
- package/src/react/use-ticket-menu.ts +15 -0
- package/src/react/use-words.ts +8 -0
- package/src/recipes/card-dock/README.md +36 -0
- package/src/recipes/card-dock/card-dock.css +178 -0
- package/src/recipes/card-dock/create-card-dock.ts +97 -0
- package/src/recipes/cascade/README.md +33 -0
- package/src/recipes/cascade/create-cascade.ts +111 -0
- package/src/recipes/contrast/README.md +29 -0
- package/src/recipes/contrast/create-contrast.ts +68 -0
- package/src/recipes/counter/README.md +42 -0
- package/src/recipes/counter/create-counter.ts +84 -0
- package/src/recipes/curved-carousel/README.md +50 -0
- package/src/recipes/curved-carousel/carousel-math.ts +20 -0
- package/src/recipes/curved-carousel/create-curved-carousel.ts +257 -0
- package/src/recipes/curved-carousel/curved-carousel.css +50 -0
- package/src/recipes/depth-grid/README.md +30 -0
- package/src/recipes/depth-grid/create-depth-grid.ts +120 -0
- package/src/recipes/details/README.md +31 -0
- package/src/recipes/details/create-details.ts +27 -0
- package/src/recipes/dock-pill/README.md +23 -0
- package/src/recipes/dock-pill/create-dock-pill.ts +68 -0
- package/src/recipes/dock-pill/dock-pill.css +5 -0
- package/src/recipes/drag-ruler/README.md +21 -0
- package/src/recipes/drag-ruler/create-drag-ruler.ts +95 -0
- package/src/recipes/drag-ruler/drag-ruler.css +16 -0
- package/src/recipes/fill-text/README.md +27 -0
- package/src/recipes/fill-text/create-fill-text.ts +93 -0
- package/src/recipes/fit-text/README.md +37 -0
- package/src/recipes/fit-text/create-fit-text.ts +55 -0
- package/src/recipes/fit-text/fit-text.css +6 -0
- package/src/recipes/flow/README.md +46 -0
- package/src/recipes/flow/create-flow.ts +55 -0
- package/src/recipes/frame-zoom/README.md +29 -0
- package/src/recipes/frame-zoom/create-frame-zoom.ts +91 -0
- package/src/recipes/infinite-reel/README.md +23 -0
- package/src/recipes/infinite-reel/create-infinite-reel.ts +140 -0
- package/src/recipes/infinite-reel/infinite-reel.css +8 -0
- package/src/recipes/letters/README.md +30 -0
- package/src/recipes/letters/create-letters.ts +113 -0
- package/src/recipes/marquee/README.md +32 -0
- package/src/recipes/marquee/create-marquee.ts +74 -0
- package/src/recipes/marquee/marquee.css +41 -0
- package/src/recipes/mask-text/README.md +39 -0
- package/src/recipes/mask-text/create-mask-text.ts +134 -0
- package/src/recipes/parallax/README.md +31 -0
- package/src/recipes/parallax/create-parallax.ts +74 -0
- package/src/recipes/pile/README.md +48 -0
- package/src/recipes/pile/create-pile.ts +50 -0
- package/src/recipes/pile/pile-math.ts +27 -0
- package/src/recipes/pile/pile.css +63 -0
- package/src/recipes/pill/README.md +30 -0
- package/src/recipes/pill/pill.css +48 -0
- package/src/recipes/pop/README.md +36 -0
- package/src/recipes/pop/create-pop.ts +52 -0
- package/src/recipes/radial-menu/README.md +44 -0
- package/src/recipes/radial-menu/create-radial-menu.ts +96 -0
- package/src/recipes/radial-menu/radial-menu.css +45 -0
- package/src/recipes/reflow/README.md +47 -0
- package/src/recipes/reflow/create-reflow.ts +49 -0
- package/src/recipes/repel-bento/README.md +21 -0
- package/src/recipes/repel-bento/create-repel-bento.ts +65 -0
- package/src/recipes/repel-bento/repel-bento.css +3 -0
- package/src/recipes/reveal/README.md +36 -0
- package/src/recipes/reveal/create-reveal.ts +48 -0
- package/src/recipes/rotator/README.md +21 -0
- package/src/recipes/rotator/create-rotator.ts +66 -0
- package/src/recipes/rotator/rotator.css +3 -0
- package/src/recipes/slot-text/README.md +44 -0
- package/src/recipes/slot-text/create-slot-text.ts +244 -0
- package/src/recipes/slot-text/slot-text.css +42 -0
- package/src/recipes/snap-strip/README.md +31 -0
- package/src/recipes/snap-strip/create-snap-strip.ts +133 -0
- package/src/recipes/snap-strip/snap-strip.css +23 -0
- package/src/recipes/sortable/README.md +88 -0
- package/src/recipes/sortable/create-sortable.ts +863 -0
- package/src/recipes/sortable/sortable.css +40 -0
- package/src/recipes/spring-cards/README.md +48 -0
- package/src/recipes/spring-cards/create-spring-cards.ts +140 -0
- package/src/recipes/spring-cards/spring-cards.css +23 -0
- package/src/recipes/stretch-footer/README.md +44 -0
- package/src/recipes/stretch-footer/create-stretch-footer.ts +69 -0
- package/src/recipes/stretch-footer/stretch-footer.css +28 -0
- package/src/recipes/tabs/README.md +39 -0
- package/src/recipes/tabs/create-tabs.ts +91 -0
- package/src/recipes/tabs/tabs.css +19 -0
- package/src/recipes/text-roll/README.md +35 -0
- package/src/recipes/text-roll/create-text-roll.ts +137 -0
- package/src/recipes/text-roll/text-roll.css +21 -0
- package/src/recipes/ticket-menu/README.md +24 -0
- package/src/recipes/ticket-menu/create-ticket-menu.ts +6 -0
- package/src/recipes/words/README.md +43 -0
- package/src/recipes/words/create-words.ts +114 -0
- package/src/theme/css-format.ts +32 -0
- package/src/theme/generate.ts +72 -0
- package/src/theme/index.ts +15 -0
- package/src/theme/presets.ts +79 -0
- 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);
|