motion-presets-kit 0.1.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/package.json ADDED
@@ -0,0 +1,117 @@
1
+ {
2
+ "name": "motion-presets-kit",
3
+ "version": "0.1.0",
4
+ "description": "Set tipado y validado en runtime de helpers y presets para motion/react: fade, slide, scale, clipReveal, parentVariants, más componentes como TextAnimate, Marquee, Draggable y el hook useCounter.",
5
+ "keywords": [
6
+ "motion",
7
+ "motion-react",
8
+ "framer-motion",
9
+ "animation",
10
+ "presets",
11
+ "variants",
12
+ "react",
13
+ "typescript",
14
+ "zod",
15
+ "agent-skill",
16
+ "ai"
17
+ ],
18
+ "license": "MIT",
19
+ "author": "Orlando Lopez",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/OrlandoDev17/motion-presets-kit.git"
23
+ },
24
+ "homepage": "https://github.com/OrlandoDev17/motion-presets-kit#readme",
25
+ "bugs": {
26
+ "url": "https://github.com/OrlandoDev17/motion-presets-kit/issues"
27
+ },
28
+ "type": "module",
29
+ "main": "./dist/index.cjs",
30
+ "module": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "import": {
35
+ "types": "./dist/index.d.ts",
36
+ "default": "./dist/index.js"
37
+ },
38
+ "require": {
39
+ "types": "./dist/index.d.cts",
40
+ "default": "./dist/index.cjs"
41
+ }
42
+ },
43
+ "./presets": {
44
+ "import": {
45
+ "types": "./dist/presets.d.ts",
46
+ "default": "./dist/presets.js"
47
+ },
48
+ "require": {
49
+ "types": "./dist/presets.d.cts",
50
+ "default": "./dist/presets.cjs"
51
+ }
52
+ },
53
+ "./components": {
54
+ "import": {
55
+ "types": "./dist/components.d.ts",
56
+ "default": "./dist/components.js"
57
+ },
58
+ "require": {
59
+ "types": "./dist/components.d.cts",
60
+ "default": "./dist/components.cjs"
61
+ }
62
+ },
63
+ "./package.json": "./package.json"
64
+ },
65
+ "files": [
66
+ "dist/**/*.js",
67
+ "dist/**/*.cjs",
68
+ "dist/**/*.d.ts",
69
+ "dist/**/*.d.cts",
70
+ "dist/**/*.map",
71
+ "bin",
72
+ "scripts",
73
+ "skill",
74
+ "README.md",
75
+ "LICENSE"
76
+ ],
77
+ "bin": {
78
+ "motion-presets-kit": "./bin/cli.mjs"
79
+ },
80
+ "sideEffects": false,
81
+ "scripts": {
82
+ "build": "tsup",
83
+ "dev": "tsup --watch",
84
+ "typecheck": "tsc --noEmit",
85
+ "test": "pnpm build && node --test --experimental-test-module-mocks \"test/**/*.test.mjs\"",
86
+ "lint": "biome check .",
87
+ "lint:fix": "biome check --write .",
88
+ "playground": "pnpm --filter playground dev",
89
+ "postinstall": "node ./scripts/postinstall.mjs",
90
+ "prepublishOnly": "pnpm typecheck && pnpm lint && pnpm test"
91
+ },
92
+ "peerDependencies": {
93
+ "motion": "^12.0.0",
94
+ "react": "^18.0.0 || ^19.0.0",
95
+ "react-dom": "^18.0.0 || ^19.0.0",
96
+ "zod": "^4.0.0"
97
+ },
98
+ "devDependencies": {
99
+ "@biomejs/biome": "^2.2.4",
100
+ "@types/node": "^24.0.0",
101
+ "@types/react": "^18.3.0",
102
+ "@types/react-dom": "^18.3.0",
103
+ "motion": "^12.0.0",
104
+ "react": "^18.3.0",
105
+ "react-dom": "^18.3.0",
106
+ "tsup": "^8.5.0",
107
+ "typescript": "^5.8.0",
108
+ "zod": "^4.4.0"
109
+ },
110
+ "publishConfig": {
111
+ "access": "public",
112
+ "provenance": false
113
+ },
114
+ "engines": {
115
+ "node": ">=18"
116
+ }
117
+ }
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from "node:path";
3
+ import { createInterface } from "node:readline";
4
+ import {
5
+ detectTargets,
6
+ installSkill,
7
+ PACKAGE_ROOT,
8
+ SKILL_NAME,
9
+ } from "../bin/skill.mjs";
10
+
11
+ const PROMPT_TIMEOUT_MS = 10_000;
12
+
13
+ function note(message) {
14
+ if (process.env.MOTION_PRESETS_KIT_DEBUG) {
15
+ console.log(`[${SKILL_NAME}] ${message}`);
16
+ }
17
+ }
18
+
19
+ async function ask(question) {
20
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
21
+ return new Promise((resolveAnswer) => {
22
+ const timer = setTimeout(() => {
23
+ resolveAnswer("");
24
+ rl.close();
25
+ }, PROMPT_TIMEOUT_MS);
26
+ rl.question(question, (reply) => {
27
+ clearTimeout(timer);
28
+ resolveAnswer(reply.trim());
29
+ rl.close();
30
+ });
31
+ });
32
+ }
33
+
34
+ async function main() {
35
+ if (process.env.MOTION_PRESETS_KIT_SKIP_SKILL) {
36
+ return note("omitida por MOTION_PRESETS_KIT_SKIP_SKILL");
37
+ }
38
+ if (process.env.CI) return note("omitida en CI");
39
+ if (process.env.npm_config_global === "true") {
40
+ return note("omitida en instalación global");
41
+ }
42
+
43
+ const initCwd = process.env.INIT_CWD
44
+ ? resolve(process.env.INIT_CWD)
45
+ : process.cwd();
46
+ if (initCwd === PACKAGE_ROOT) return note("omitida en el repo de la librería");
47
+
48
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
49
+ console.log(
50
+ `[${SKILL_NAME}] ¿Usas agentes de IA? Instala la skill con: npx motion-presets-kit add-skill`,
51
+ );
52
+ return;
53
+ }
54
+
55
+ const answer = await ask(
56
+ `[${SKILL_NAME}] ¿Instalar la skill para tu agente de IA (opencode/Claude/agents)? [y/N] `,
57
+ );
58
+ if (!/^y(es)?$/i.test(answer)) {
59
+ console.log(
60
+ `[${SKILL_NAME}] Puedes instalarla después con: npx motion-presets-kit add-skill`,
61
+ );
62
+ return;
63
+ }
64
+
65
+ const targets = detectTargets({ tool: "all" });
66
+ const results = installSkill(targets);
67
+ for (const result of results) {
68
+ const suffix = result.status === "exists" ? "ya existía" : "instalada";
69
+ console.log(`[${SKILL_NAME}] Skill ${suffix}: ${result.destination}`);
70
+ }
71
+ }
72
+
73
+ main().catch((error) => {
74
+ note(`postinstall omitido: ${error instanceof Error ? error.message : error}`);
75
+ });
@@ -0,0 +1,221 @@
1
+ ---
2
+ name: motion-presets-kit
3
+ description: Guía de uso de motion-presets-kit, presets y componentes de animación para motion/react (fade, slide, scale, clipReveal, parentVariants, TextAnimate, Marquee, Draggable, useCounter). Úsala siempre que escribas animaciones con motion-presets-kit para conocer la API, los valores por defecto y los patrones correctos.
4
+ ---
5
+
6
+ # motion-presets-kit
7
+
8
+ Set tipado y validado en runtime (Zod) de helpers y presets para `motion/react`.
9
+ La API es **variant-returning**: funciones puras que devuelven `Variants`, no
10
+ componentes wrapper. También incluye componentes para casos complejos.
11
+
12
+ ## Instalación
13
+
14
+ ```bash
15
+ pnpm add motion-presets-kit motion zod react react-dom
16
+ ```
17
+
18
+ `motion`, `zod`, `react` y `react-dom` son **peer dependencies**. Compatible con
19
+ React 18+ / 19 y `motion` ^12.
20
+
21
+ ## Entry points
22
+
23
+ | Import | Contenido |
24
+ |---|---|
25
+ | `motion-presets-kit` | todo (presets + componentes + schemas) |
26
+ | `motion-presets-kit/presets` | `fade`, `slide`, `scale`, `clipReveal`, `parentVariants` |
27
+ | `motion-presets-kit/components` | `TextAnimate`, `Marquee`, `Draggable`, `useCounter` |
28
+
29
+ Importa desde el entry point más específico posible: cada preset es
30
+ tree-shakeable.
31
+
32
+ ## Opciones comunes a todos los presets
33
+
34
+ | Opción | Default | Descripción |
35
+ |---|---|---|
36
+ | `duration` | `0.5` | Segundos de la transición. |
37
+ | `delay` | `0` | Retardo en segundos. |
38
+ | `ease` | `[0.16, 1, 0.3, 1]` | Nombre, bezier de 4 números o función. |
39
+ | `spring` | — | Si se define, **sobrescribe** `duration` y `ease`. |
40
+ | `excludeDelay` | `false` | Omite el `delay` propio (para heredar el stagger del padre). |
41
+ | `reducedMotion` | `true` | Respeta `prefers-reduced-motion`. |
42
+
43
+ Uso:
44
+
45
+ ```tsx
46
+ import { motion } from "motion/react";
47
+ import { fade } from "motion-presets-kit/presets";
48
+
49
+ <motion.div variants={fade({ direction: "up" })} initial="initial" animate="animate">
50
+ Hola
51
+ </motion.div>;
52
+ ```
53
+
54
+ ## Presets
55
+
56
+ ### `fade(options?)`
57
+
58
+ Fade de entrada con dirección, blur y escala opcionales.
59
+ Extras: `direction` (`"up" | "down" | "left" | "right" | "none"`, default `"up"`),
60
+ `distance` (`60`), `blur` (`0`, px), `scale` (`1`).
61
+
62
+ ```tsx
63
+ fade({ direction: "left", distance: 40, blur: 8, scale: 0.98 });
64
+ ```
65
+
66
+ ### `slide(options?)`
67
+
68
+ Desplazamiento puro **sin `opacity`**.
69
+ Extras: `direction` (default `"right"`), `distance` (`100`).
70
+
71
+ ```tsx
72
+ slide({ direction: "right" }); // initial { x: -100 } → animate { x: 0 }
73
+ ```
74
+
75
+ ### `scale(options?)`
76
+
77
+ Entrada con escala + opacidad: `from` → `1` y `opacity: 0 → 1`.
78
+ Extras: `from` (default `0.8`).
79
+
80
+ ```tsx
81
+ scale({ from: 0.5 });
82
+ ```
83
+
84
+ ### `clipReveal(options?)`
85
+
86
+ Reveal cinematográfico animando `clipPath` (con escala opcional).
87
+ Extras: `direction` (default `"up"`), `scale` (opcional, sin default).
88
+
89
+ ```tsx
90
+ clipReveal({ direction: "up", scale: 1.1 });
91
+ ```
92
+
93
+ ### `parentVariants(options?)`
94
+
95
+ Contenedor para animar hijos en cascada. Extras: `delayChildren` (`0`) y
96
+ `startDelay` (`0`).
97
+
98
+ ```tsx
99
+ <motion.div variants={parentVariants({ delayChildren: 0.12 })} initial="initial" animate="animate">
100
+ {items.map((item) => (
101
+ <motion.li key={item} variants={fade({ excludeDelay: true })}>
102
+ {item}
103
+ </motion.li>
104
+ ))}
105
+ </motion.div>;
106
+ ```
107
+
108
+ ## Componentes y hooks
109
+
110
+ ### `<TextAnimate>`
111
+
112
+ Anima texto por palabra o letra, preservando HTML inline, con resaltado.
113
+
114
+ - `text` (requerido, string; **acepta HTML inline**)
115
+ - `as` (tag intrínseco, default `"p"`)
116
+ - `by` (`"word" | "letter"`, default `"word"`)
117
+ - `type` (`"blurIn" | "slideUp" | "slideDown" | "slideLeft" | "slideRight" | "typeWriter"`, default `"slideUp"`)
118
+ - `duration` (`0.4`), `startDelay` (`0`)
119
+ - `highlight` (`string[]`), `highlightClassName` (`string`)
120
+ - `reducedMotion` (`true`)
121
+ - Usa `whileInView` con `viewport={{ once: true }}`.
122
+
123
+ ```tsx
124
+ <TextAnimate text="Hola <strong>mundo</strong>" by="letter" type="blurIn" highlight={["mundo"]} />
125
+ ```
126
+
127
+ Advertencia: el HTML inline se inyecta con `dangerouslySetInnerHTML`; sanitiza el
128
+ `text` si viene de input no confiable.
129
+
130
+ ### `<Marquee>`
131
+
132
+ Scroll infinito en CSS puro (`@keyframes`), con children duplicados.
133
+
134
+ - `children` (requerido), `speed` (segundos/vuelta, default `20`; debe ser > 0)
135
+ - `direction` (`"left" | "right"`, default `"left"`)
136
+ - `pauseOnHover` (`true`), `gap` (default `"2rem"`)
137
+ - `reducedMotion` (`true`)
138
+
139
+ ```tsx
140
+ <Marquee speed={30} direction="right" gap="1.5rem">
141
+ <span>Logo 1</span>
142
+ <span>Logo 2</span>
143
+ </Marquee>
144
+ ```
145
+
146
+ ### `<Draggable>`
147
+
148
+ Wrapper de `motion.div` con drag configurable.
149
+
150
+ - `axis` (`"x" | "y" | "both"`, default `"both"`)
151
+ - `bounds` (`"parent" | "document" | { top, right, bottom, left }`)
152
+ - `snapToGrid: [number, number]`
153
+ - `dragElastic`, `dragMomentum`, `dragControls`
154
+ - `onDragStart` / `onDrag` / `onDragEnd`
155
+
156
+ ```tsx
157
+ <Draggable axis="x" bounds="parent" snapToGrid={[20, 20]}>
158
+ <div>Arrastrame</div>
159
+ </Draggable>
160
+ ```
161
+
162
+ Con `bounds="parent"` el elemento se restringe a su contenedor; el contenedor
163
+ debe poder medirse (normalmente `position: relative`).
164
+
165
+ ### `useCounter(ref, options?)`
166
+
167
+ Contador animado que escribe el valor formateado en `ref.current.textContent` y
168
+ expone el valor actual de forma reactiva.
169
+
170
+ - `from` (`0`), `to` (`100`), `decimals` (`0`)
171
+ - `prefix`, `suffix`, `separator` (`","`)
172
+ - `format: (value: number) => string` (sobrescribe el formateo)
173
+ - `duration` (`1`), `reducedMotion` (`true`)
174
+ - Devuelve `{ value, start, reset }`.
175
+
176
+ ```tsx
177
+ const ref = useRef<HTMLSpanElement>(null);
178
+ const { start, reset } = useCounter(ref, { to: 1250, suffix: " px" });
179
+ return (
180
+ <>
181
+ <span ref={ref} />
182
+ <button onClick={start}>Start</button>
183
+ <button onClick={reset}>Reset</button>
184
+ </>
185
+ );
186
+ ```
187
+
188
+ ## Reducción de movimiento
189
+
190
+ Todos los presets aceptan `reducedMotion` (default `true`). Con
191
+ `prefers-reduced-motion: reduce` activo:
192
+
193
+ - Los presets devuelven `initial === animate` con el estado final.
194
+ - `TextAnimate` muestra el texto sin animar.
195
+ - `Marquee` no reproduce el loop.
196
+ - `useCounter` muestra el valor final directamente.
197
+
198
+ En SSR/servidor `reducedMotion` se evalúa como `false` (no hay `window`), por lo
199
+ que los presets devuelven variants estáticas y no rompen el render.
200
+
201
+ ## Errores comunes
202
+
203
+ - **Olvidar `initial`/`animate`**: los presets devuelven `Variants`; el elemento
204
+ `motion.*` debe declarar `initial="initial"` y `animate="animate"` (o
205
+ `whileInView="animate"`).
206
+ - **Stagger que no funciona**: los hijos dentro de `parentVariants()` deben usar
207
+ `excludeDelay: true`, si no su `delay` propio sobrescribe el del padre (CL-001).
208
+ - **`slide` no desvanece**: es intencional, `slide` no incluye `opacity`.
209
+ - **`bounds="parent"` sin contenedor posicionado**: el padre debe existir y
210
+ poder medirse; usa `position: relative`.
211
+ - **Opciones inválidas**: no lanzan; se emite un `warn()` con prefijo
212
+ `[motion-presets-kit]` y se usan los valores por defecto.
213
+ - **`format`/`ease`/`spring` inválidos**: Zod los valida en runtime; si no son
214
+ válidos se avisa y se usan los defaults.
215
+
216
+ ## Referencia rápida
217
+
218
+ ```tsx
219
+ import { fade, slide, scale, clipReveal, parentVariants } from "motion-presets-kit/presets";
220
+ import { TextAnimate, Marquee, Draggable, useCounter } from "motion-presets-kit/components";
221
+ ```