solidjs-motion 0.1.0 → 0.1.1
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/CHANGELOG.md +11 -0
- package/README.md +291 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,17 @@ All notable changes to `solidjs-motion` / `@solidjs-motion/motion` are documente
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.1] — 2026-05-20
|
|
9
|
+
|
|
10
|
+
### Docs
|
|
11
|
+
|
|
12
|
+
- Expanded README with a 12-recipe "Recipes" section covering the full v0.1
|
|
13
|
+
surface: reactive `useMotion`, `<motion.X>` proxy + variant cascade,
|
|
14
|
+
`motion.create` HOC, MotionValues + `createTransform`/`createSpring`,
|
|
15
|
+
`createScroll`/`createInView`/`createTemplate`, `<Presence>` (single +
|
|
16
|
+
list + `mode="wait"`), drag with constraints, `<MotionConfig>` +
|
|
17
|
+
`createReducedMotion`.
|
|
18
|
+
|
|
8
19
|
## [0.1.0] — 2026-05-20
|
|
9
20
|
|
|
10
21
|
First public release. Five phases of the port plan land together: the canonical
|
package/README.md
CHANGED
|
@@ -46,6 +46,297 @@ export function Card() {
|
|
|
46
46
|
with motion's initial styles; user refs and motion's ref both fire; the initial style is
|
|
47
47
|
serialized into SSR HTML so the first paint is flicker-free.
|
|
48
48
|
|
|
49
|
+
## Recipes
|
|
50
|
+
|
|
51
|
+
### 1. Reactive options
|
|
52
|
+
|
|
53
|
+
Pass a function to `useMotion` to track Solid signals inside the target.
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { useMotion } from "solidjs-motion"
|
|
57
|
+
import { createSignal } from "solid-js"
|
|
58
|
+
|
|
59
|
+
export function Toggle() {
|
|
60
|
+
const [open, setOpen] = createSignal(false)
|
|
61
|
+
const motion = useMotion(() => ({
|
|
62
|
+
animate: { rotate: open() ? 180 : 0 },
|
|
63
|
+
transition: { duration: 0.3 },
|
|
64
|
+
}))
|
|
65
|
+
return (
|
|
66
|
+
<button onClick={() => setOpen((p) => !p)} {...motion()}>
|
|
67
|
+
↑
|
|
68
|
+
</button>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 2. `<motion.X>` proxy with variants
|
|
74
|
+
|
|
75
|
+
Every HTML/SVG tag is reachable off `motion`. Variant labels on the parent cascade to
|
|
76
|
+
descendants through `m.Provider` (auto-installed by the proxy).
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
import { motion } from "solidjs-motion"
|
|
80
|
+
|
|
81
|
+
const variants = {
|
|
82
|
+
rest: { y: 0, scale: 1 },
|
|
83
|
+
lift: { y: -8, scale: 1.04 },
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function Card() {
|
|
87
|
+
return (
|
|
88
|
+
<motion.article animate="rest" hover="lift" variants={variants}>
|
|
89
|
+
<motion.h2 variants={variants}>Inherits lift on hover</motion.h2>
|
|
90
|
+
</motion.article>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 3. `motion.create(Component)` HOC
|
|
96
|
+
|
|
97
|
+
Wrap a custom component to make it motion-aware. The wrapped component must spread its
|
|
98
|
+
props (including `ref`) onto a single DOM-element root.
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
import { motion } from "solidjs-motion"
|
|
102
|
+
import type { ComponentProps } from "solid-js"
|
|
103
|
+
|
|
104
|
+
function Button(props: ComponentProps<"button">) {
|
|
105
|
+
return <button {...props} class={`btn ${props.class ?? ""}`} />
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const MotionButton = motion.create(Button)
|
|
109
|
+
|
|
110
|
+
export function Stage() {
|
|
111
|
+
return (
|
|
112
|
+
<MotionButton hover={{ scale: 1.05 }} press={{ scale: 0.95 }}>
|
|
113
|
+
Press me
|
|
114
|
+
</MotionButton>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 4. MotionValues + `createTransform`
|
|
120
|
+
|
|
121
|
+
MotionValues are the source of truth for animated state. They're both Solid Accessors and
|
|
122
|
+
upstream `MotionValue`s — drop them straight into `style`.
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
import { motion, createMotionValue, createTransform } from "solidjs-motion"
|
|
126
|
+
|
|
127
|
+
export function FadeSlider() {
|
|
128
|
+
const x = createMotionValue(0)
|
|
129
|
+
const opacity = createTransform(x, [-100, 0, 100], [0, 1, 0])
|
|
130
|
+
return (
|
|
131
|
+
<motion.div
|
|
132
|
+
drag="x"
|
|
133
|
+
dragConstraints={{ left: -100, right: 100 }}
|
|
134
|
+
style={{ x, opacity }}
|
|
135
|
+
>
|
|
136
|
+
Drag me
|
|
137
|
+
</motion.div>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 5. Spring-smoothed pointer
|
|
143
|
+
|
|
144
|
+
`createSpring` mirrors any numeric input with physics smoothing.
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
import { motion, createMotionValue, createSpring } from "solidjs-motion"
|
|
148
|
+
import { onCleanup, onMount } from "solid-js"
|
|
149
|
+
|
|
150
|
+
export function Cursor() {
|
|
151
|
+
const x = createMotionValue(0)
|
|
152
|
+
const y = createMotionValue(0)
|
|
153
|
+
const sx = createSpring(x, { stiffness: 200, damping: 30 })
|
|
154
|
+
const sy = createSpring(y, { stiffness: 200, damping: 30 })
|
|
155
|
+
|
|
156
|
+
onMount(() => {
|
|
157
|
+
const move = (e: PointerEvent) => {
|
|
158
|
+
x.set(e.clientX)
|
|
159
|
+
y.set(e.clientY)
|
|
160
|
+
}
|
|
161
|
+
window.addEventListener("pointermove", move)
|
|
162
|
+
onCleanup(() => window.removeEventListener("pointermove", move))
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
return <motion.div class="cursor" style={{ x: sx, y: sy }} />
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 6. Scroll-linked progress bar
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
import { motion, createScroll, createTransform } from "solidjs-motion"
|
|
173
|
+
|
|
174
|
+
export function ProgressBar() {
|
|
175
|
+
const { scrollYProgress } = createScroll()
|
|
176
|
+
const width = createTransform(scrollYProgress, [0, 1], ["0%", "100%"])
|
|
177
|
+
return <motion.div class="progress" style={{ width }} />
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### 7. Viewport-triggered fade-in
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
import { motion } from "solidjs-motion"
|
|
185
|
+
|
|
186
|
+
export function FadeInOnce() {
|
|
187
|
+
return (
|
|
188
|
+
<motion.section
|
|
189
|
+
initial={{ opacity: 0, y: 40 }}
|
|
190
|
+
inView={{ opacity: 1, y: 0 }}
|
|
191
|
+
inViewOptions={{ once: true, margin: "0px 0px -10% 0px" }}
|
|
192
|
+
>
|
|
193
|
+
Comes in once, stays.
|
|
194
|
+
</motion.section>
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### 8. `createTemplate` for interpolated strings
|
|
200
|
+
|
|
201
|
+
Build a `MotionValue<string>` from interpolated MVs/Accessors — feed it to any string-valued
|
|
202
|
+
CSS property.
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
import { motion, createMotionValue, createTemplate } from "solidjs-motion"
|
|
206
|
+
|
|
207
|
+
export function GradientBox() {
|
|
208
|
+
const angle = createMotionValue(0)
|
|
209
|
+
const background = createTemplate`linear-gradient(${angle}deg, #f0f, #0ff)`
|
|
210
|
+
return (
|
|
211
|
+
<motion.div
|
|
212
|
+
hover={{ rotate: 360 }}
|
|
213
|
+
transition={{ duration: 2 }}
|
|
214
|
+
style={{ background }}
|
|
215
|
+
/>
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 9. `<Presence>` for exit animations
|
|
221
|
+
|
|
222
|
+
Wrap a conditionally-rendered child to animate its exit before unmount.
|
|
223
|
+
|
|
224
|
+
```tsx
|
|
225
|
+
import { motion, Presence } from "solidjs-motion"
|
|
226
|
+
import { Show, createSignal } from "solid-js"
|
|
227
|
+
|
|
228
|
+
export function Drawer() {
|
|
229
|
+
const [open, setOpen] = createSignal(false)
|
|
230
|
+
return (
|
|
231
|
+
<>
|
|
232
|
+
<button onClick={() => setOpen((p) => !p)}>Toggle</button>
|
|
233
|
+
<Presence>
|
|
234
|
+
<Show when={open()}>
|
|
235
|
+
<motion.aside
|
|
236
|
+
initial={{ x: -300 }}
|
|
237
|
+
animate={{ x: 0 }}
|
|
238
|
+
exit={{ x: -300 }}
|
|
239
|
+
transition={{ duration: 0.25 }}
|
|
240
|
+
>
|
|
241
|
+
Drawer content
|
|
242
|
+
</motion.aside>
|
|
243
|
+
</Show>
|
|
244
|
+
</Presence>
|
|
245
|
+
</>
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### 10. `mode="wait"` + list exits
|
|
251
|
+
|
|
252
|
+
`mode="wait"` plays the outgoing child's `exit` fully before the incoming child enters.
|
|
253
|
+
For lists, `Presence` wraps a `<For>` and animates add/remove together.
|
|
254
|
+
|
|
255
|
+
```tsx
|
|
256
|
+
import { motion, Presence } from "solidjs-motion"
|
|
257
|
+
import { For, Show, createSignal } from "solid-js"
|
|
258
|
+
|
|
259
|
+
export function Tabs() {
|
|
260
|
+
const [tab, setTab] = createSignal("a")
|
|
261
|
+
return (
|
|
262
|
+
<Presence mode="wait">
|
|
263
|
+
<Show when={tab()} keyed>
|
|
264
|
+
{(t) => (
|
|
265
|
+
<motion.div
|
|
266
|
+
initial={{ opacity: 0, y: 8 }}
|
|
267
|
+
animate={{ opacity: 1, y: 0 }}
|
|
268
|
+
exit={{ opacity: 0, y: -8 }}
|
|
269
|
+
>
|
|
270
|
+
Tab {t}
|
|
271
|
+
</motion.div>
|
|
272
|
+
)}
|
|
273
|
+
</Show>
|
|
274
|
+
</Presence>
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function Notifications(props: { items: () => string[] }) {
|
|
279
|
+
return (
|
|
280
|
+
<Presence>
|
|
281
|
+
<For each={props.items()}>
|
|
282
|
+
{(msg) => (
|
|
283
|
+
<motion.li
|
|
284
|
+
initial={{ opacity: 0, x: -16 }}
|
|
285
|
+
animate={{ opacity: 1, x: 0 }}
|
|
286
|
+
exit={{ opacity: 0, x: 16 }}
|
|
287
|
+
>
|
|
288
|
+
{msg}
|
|
289
|
+
</motion.li>
|
|
290
|
+
)}
|
|
291
|
+
</For>
|
|
292
|
+
</Presence>
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### 11. Drag with constraints
|
|
298
|
+
|
|
299
|
+
`dragConstraints` accepts numeric bounds or a parent ref. `dragElastic` controls overshoot.
|
|
300
|
+
|
|
301
|
+
```tsx
|
|
302
|
+
import { motion } from "solidjs-motion"
|
|
303
|
+
|
|
304
|
+
export function DraggableCard() {
|
|
305
|
+
let bounds!: HTMLDivElement
|
|
306
|
+
return (
|
|
307
|
+
<div ref={bounds} class="bounds">
|
|
308
|
+
<motion.div
|
|
309
|
+
drag
|
|
310
|
+
dragConstraints={bounds}
|
|
311
|
+
dragElastic={0.2}
|
|
312
|
+
whileDrag={{ scale: 1.05 }}
|
|
313
|
+
>
|
|
314
|
+
Drag inside
|
|
315
|
+
</motion.div>
|
|
316
|
+
</div>
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### 12. `<MotionConfig>` + reduced motion
|
|
322
|
+
|
|
323
|
+
`<MotionConfig>` flows defaults (transition, reduced-motion mode, CSP nonce) to descendants.
|
|
324
|
+
`createReducedMotion()` reads the system preference directly.
|
|
325
|
+
|
|
326
|
+
```tsx
|
|
327
|
+
import { MotionConfig, createReducedMotion, motion } from "solidjs-motion"
|
|
328
|
+
|
|
329
|
+
export function App() {
|
|
330
|
+
const reduced = createReducedMotion()
|
|
331
|
+
return (
|
|
332
|
+
<MotionConfig reducedMotion="user" transition={{ duration: 0.4, ease: "easeOut" }}>
|
|
333
|
+
<motion.div animate={{ x: 100 }}>Honors `prefers-reduced-motion`</motion.div>
|
|
334
|
+
<p>System reduced-motion: {String(reduced())}</p>
|
|
335
|
+
</MotionConfig>
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
49
340
|
## Roadmap
|
|
50
341
|
|
|
51
342
|
### Shipped
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solidjs-motion",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "An animation library for SolidJS — port of motion/react patterns built on the framework-agnostic motion package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"typecheck": "tsc --noEmit",
|
|
33
33
|
"clean": "rm -rf dist node_modules",
|
|
34
34
|
"publish:npm": "bun run build && npm publish --access public",
|
|
35
|
-
"publish:jsr": "bunx jsr publish"
|
|
35
|
+
"publish:jsr": "bunx jsr publish --token $JSR_TOKEN"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"solid-js": "^1.9.0",
|