mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-11 02:33:10 +08:00
Revise motion UI documentation structure and content
Refactor motion UI documentation for clarity and consistency. Update sections on usage, scenarios, and examples.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
````md
|
||||
---
|
||||
name: motion-ui
|
||||
description: "Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns."
|
||||
@@ -11,66 +12,64 @@ Focused on **performance, accessibility, and usability** — not decoration.
|
||||
|
||||
**by Jatan**
|
||||
|
||||
---
|
||||
|
||||
## When to Use
|
||||
|
||||
Use this motion system when motion:
|
||||
|
||||
* Guides attention (e.g., onboarding, key actions)
|
||||
* Communicates state (loading, success, error, transitions)
|
||||
* Preserves spatial continuity (layout changes, navigation)
|
||||
- Guides attention (onboarding, primary actions)
|
||||
- Communicates state (loading, success, error, transitions)
|
||||
- Preserves spatial continuity (navigation, layout changes)
|
||||
|
||||
### Appropriate Scenarios
|
||||
|
||||
* Interactive components (buttons, modals, menus)
|
||||
* State transitions (loading → loaded, open → closed)
|
||||
* Navigation and layout continuity (shared elements, crossfade)
|
||||
- Interactive UI (buttons, modals, menus)
|
||||
- State transitions (open/close, loading states)
|
||||
- Navigation transitions and shared elements
|
||||
|
||||
### Considerations
|
||||
|
||||
* **Accessibility**: Always support reduced motion
|
||||
* **Device adaptation**: Adjust for low-end devices
|
||||
* **Performance trade-offs**: Prefer responsiveness over visual smoothness
|
||||
- Accessibility must be preserved (reduced motion support)
|
||||
- Low-end device performance must be respected
|
||||
- Prefer responsiveness over visual smoothness
|
||||
|
||||
### Avoid Using Motion When
|
||||
### Avoid Motion When
|
||||
|
||||
* It is purely decorative
|
||||
* It reduces usability or clarity
|
||||
* It impacts performance negatively
|
||||
- It is purely decorative
|
||||
- It reduces clarity or usability
|
||||
- It impacts performance
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
### Core Principle
|
||||
## Core Principle
|
||||
|
||||
Motion must:
|
||||
|
||||
* Guide attention
|
||||
* Communicate state
|
||||
* Preserve spatial continuity
|
||||
- Guide attention
|
||||
- Communicate state
|
||||
- Preserve spatial continuity
|
||||
|
||||
If it does none → remove it.
|
||||
|
||||
---
|
||||
|
||||
### Installation
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install motion
|
||||
```
|
||||
````
|
||||
|
||||
---
|
||||
|
||||
### Version
|
||||
## Versions
|
||||
|
||||
* `motion/react` → default
|
||||
* `framer-motion` → legacy
|
||||
|
||||
Do not mix.
|
||||
* `framer-motion` → legacy (do not mix)
|
||||
|
||||
---
|
||||
|
||||
### Motion Tokens
|
||||
## Motion Tokens
|
||||
|
||||
```ts
|
||||
export const motionTokens = {
|
||||
@@ -93,23 +92,25 @@ export const motionTokens = {
|
||||
|
||||
---
|
||||
|
||||
### Performance Rules
|
||||
## Performance Rules
|
||||
|
||||
**Safe**
|
||||
### Safe Properties
|
||||
|
||||
* transform
|
||||
* opacity
|
||||
|
||||
**Avoid**
|
||||
### Avoid
|
||||
|
||||
* width / height
|
||||
* top / left
|
||||
* width
|
||||
* height
|
||||
* top
|
||||
* left
|
||||
|
||||
Rule: responsiveness > smoothness
|
||||
|
||||
---
|
||||
|
||||
### Device Adaptation
|
||||
## Device Adaptation
|
||||
|
||||
```ts
|
||||
const isLowEnd =
|
||||
@@ -121,22 +122,26 @@ const duration = isLowEnd ? 0.2 : 0.4
|
||||
|
||||
---
|
||||
|
||||
### Accessibility
|
||||
## Accessibility
|
||||
|
||||
#### JS (useReducedMotion)
|
||||
### Reduced Motion (React)
|
||||
|
||||
```tsx
|
||||
import { motion, useReducedMotion } from "motion/react"
|
||||
|
||||
const reduce = useReducedMotion()
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: reduce ? 0 : 24 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
/>
|
||||
export function Example() {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: reduce ? 0 : 24 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
#### CSS
|
||||
### CSS
|
||||
|
||||
```css
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
@@ -150,7 +155,7 @@ const reduce = useReducedMotion()
|
||||
}
|
||||
```
|
||||
|
||||
#### Tailwind
|
||||
### Tailwind
|
||||
|
||||
```html
|
||||
<div class="motion-safe:animate-fade motion-reduce:opacity-100"></div>
|
||||
@@ -158,74 +163,79 @@ const reduce = useReducedMotion()
|
||||
|
||||
---
|
||||
|
||||
### Architecture & Patterns
|
||||
## Core Patterns
|
||||
|
||||
#### Core Patterns
|
||||
|
||||
* Hover → `whileHover`
|
||||
* Tap → `whileTap`
|
||||
* In view → `whileInView`
|
||||
* Scroll linked → `useScroll`
|
||||
* Conditional → `AnimatePresence`
|
||||
* Layout small → `layout`
|
||||
* Layout large → avoid
|
||||
* Complex → `useAnimate`
|
||||
|
||||
#### Layout & Transitions
|
||||
|
||||
* Shared transitions → `layoutId`
|
||||
* Presence transitions → `AnimatePresence`
|
||||
* hover → whileHover
|
||||
* tap → whileTap
|
||||
* in-view → whileInView
|
||||
* scroll → useScroll
|
||||
* conditional → AnimatePresence
|
||||
* small layout → layout
|
||||
* large layout → avoid
|
||||
* complex → useAnimate
|
||||
|
||||
---
|
||||
|
||||
### Advanced Patterns (Concepts)
|
||||
## Layout System
|
||||
|
||||
* Parallax (scroll-linked transforms)
|
||||
* Scroll storytelling (sticky sections)
|
||||
* 3D tilt (pointer-based transforms)
|
||||
* Crossfade (shared layoutId)
|
||||
* Progressive reveal (clip-path)
|
||||
* Skeleton loading (looped opacity)
|
||||
* Micro-interactions (hover/tap feedback)
|
||||
* Spring system (physics-based motion)
|
||||
* layoutId → shared transitions
|
||||
* AnimatePresence → mount/unmount transitions
|
||||
|
||||
---
|
||||
|
||||
### Modal Essentials
|
||||
## Advanced Patterns
|
||||
|
||||
* Focus trap
|
||||
* Escape close
|
||||
* Scroll lock
|
||||
* ARIA roles
|
||||
* Parallax scrolling
|
||||
* Scroll storytelling sections
|
||||
* 3D pointer tilt
|
||||
* Crossfade transitions
|
||||
* Clip-path reveals
|
||||
* Skeleton loading loops
|
||||
* Micro-interactions
|
||||
* Spring physics motion
|
||||
|
||||
#### Minimal Example
|
||||
---
|
||||
|
||||
## Modal System (Production Safe)
|
||||
|
||||
```tsx
|
||||
import { useEffect, useRef } from "react"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { motion, AnimatePresence } from "motion/react"
|
||||
|
||||
// Placeholder hooks (implement or replace with libraries)
|
||||
type ModalProps = {
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
function useFocusTrap(ref: React.RefObject<HTMLElement>, active: boolean) {
|
||||
useEffect(() => {
|
||||
if (!active || !ref.current) return
|
||||
|
||||
const el = ref.current
|
||||
|
||||
const focusable = el.querySelectorAll<HTMLElement>(
|
||||
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
||||
)
|
||||
|
||||
const first = focusable[0]
|
||||
const last = focusable[focusable.length - 1]
|
||||
|
||||
function handleKey(e: KeyboardEvent) {
|
||||
if (first) first.focus()
|
||||
|
||||
const handleKey = (e: KeyboardEvent) => {
|
||||
if (e.key !== "Tab") return
|
||||
if (!first || !last) return
|
||||
|
||||
if (e.shiftKey && document.activeElement === first) {
|
||||
e.preventDefault(); last?.focus()
|
||||
e.preventDefault()
|
||||
last.focus()
|
||||
} else if (!e.shiftKey && document.activeElement === last) {
|
||||
e.preventDefault(); first?.focus()
|
||||
e.preventDefault()
|
||||
first.focus()
|
||||
}
|
||||
}
|
||||
|
||||
el.addEventListener("keydown", handleKey)
|
||||
first?.focus()
|
||||
return () => el.removeEventListener("keydown", handleKey)
|
||||
}, [active, ref])
|
||||
}
|
||||
@@ -233,25 +243,31 @@ function useFocusTrap(ref: React.RefObject<HTMLElement>, active: boolean) {
|
||||
function useScrollLock(active: boolean) {
|
||||
useEffect(() => {
|
||||
if (!active) return
|
||||
|
||||
const prev = document.body.style.overflow
|
||||
document.body.style.overflow = "hidden"
|
||||
return () => { document.body.style.overflow = prev }
|
||||
|
||||
return () => {
|
||||
document.body.style.overflow = prev
|
||||
}
|
||||
}, [active])
|
||||
}
|
||||
|
||||
export function Modal({ open, closeModal }: { open: boolean; closeModal: () => void }) {
|
||||
export function Modal({ open, onClose }: ModalProps) {
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
|
||||
useFocusTrap(ref, open)
|
||||
useScrollLock(open)
|
||||
|
||||
useEffect(() => {
|
||||
function onKey(e: KeyboardEvent) {
|
||||
if (e.key === "Escape") closeModal()
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") onClose()
|
||||
}
|
||||
if (open) window.addEventListener("keydown", onKey)
|
||||
return () => window.removeEventListener("keydown", onKey)
|
||||
}, [open, closeModal])
|
||||
|
||||
if (open) window.addEventListener("keydown", onKeyDown)
|
||||
|
||||
return () => window.removeEventListener("keydown", onKeyDown)
|
||||
}, [open, onClose])
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
@@ -259,6 +275,7 @@ export function Modal({ open, closeModal }: { open: boolean; closeModal: () => v
|
||||
<motion.div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Modal"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
@@ -271,118 +288,43 @@ export function Modal({ open, closeModal }: { open: boolean; closeModal: () => v
|
||||
exit={{ scale: 0.95, opacity: 0 }}
|
||||
className="bg-white p-6 rounded"
|
||||
>
|
||||
<button onClick={closeModal}>Close</button>
|
||||
<button onClick={onClose}>Close</button>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
// Usage
|
||||
export function Example() {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const openModal = () => setOpen(true)
|
||||
const closeModal = () => setOpen(false)
|
||||
---
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={openModal}>Open</button>
|
||||
<Modal open={open} closeModal={closeModal} />
|
||||
</>
|
||||
)
|
||||
## Scroll Parallax
|
||||
|
||||
```tsx
|
||||
import { useScroll, useTransform, motion } from "motion/react"
|
||||
|
||||
export function Parallax() {
|
||||
const { scrollYProgress } = useScroll()
|
||||
const y = useTransform(scrollYProgress, [0, 1], [0, -80])
|
||||
|
||||
return <motion.div style={{ y }} />
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### SSR Safety
|
||||
|
||||
* Match initial states
|
||||
* Avoid implicit animation origins
|
||||
|
||||
---
|
||||
|
||||
### Debugging
|
||||
|
||||
Check:
|
||||
|
||||
* Wrong import
|
||||
* Missing `"use client"`
|
||||
* Missing `key`
|
||||
* Hydration mismatch
|
||||
* Layout misuse
|
||||
* State-driven animation
|
||||
|
||||
---
|
||||
|
||||
### QA
|
||||
|
||||
* No CLS
|
||||
* Keyboard works
|
||||
* Focus trapped
|
||||
* ARIA correct
|
||||
* Reduced motion works
|
||||
* No hydration warnings
|
||||
* Animations stop on unmount
|
||||
|
||||
---
|
||||
|
||||
### Anti-Patterns
|
||||
|
||||
* Animating layout properties
|
||||
* Infinite animations without purpose
|
||||
* Over-staggering lists
|
||||
* Ignoring reduced motion
|
||||
* Using motion for decoration
|
||||
|
||||
---
|
||||
|
||||
### Philosophy
|
||||
|
||||
Motion is interaction design.
|
||||
|
||||
---
|
||||
|
||||
### Final Rule
|
||||
|
||||
> If motion does not improve UX → remove it.
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Button Interaction
|
||||
## Skeleton Loading
|
||||
|
||||
```tsx
|
||||
import { motion } from "motion/react"
|
||||
|
||||
export function Button() {
|
||||
return (
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.97 }}
|
||||
>
|
||||
Click me
|
||||
</motion.button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Reduced Motion Example
|
||||
|
||||
```tsx
|
||||
import { motion, useReducedMotion } from "motion/react"
|
||||
|
||||
export function FadeIn() {
|
||||
const reduce = useReducedMotion()
|
||||
|
||||
export function Skeleton() {
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: reduce ? 0 : 24 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="bg-gray-200 h-6 w-full"
|
||||
animate={{ opacity: [0.5, 1, 0.5] }}
|
||||
transition={{ repeat: Infinity, duration: 1.2 }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -390,7 +332,19 @@ export function FadeIn() {
|
||||
|
||||
---
|
||||
|
||||
### Stagger List
|
||||
## Shared Layout
|
||||
|
||||
```tsx
|
||||
import { motion } from "motion/react"
|
||||
|
||||
export function Shared() {
|
||||
return <motion.div layoutId="shared" />
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Stagger List
|
||||
|
||||
```tsx
|
||||
import { motion } from "motion/react"
|
||||
@@ -410,8 +364,10 @@ const item = {
|
||||
export function List() {
|
||||
return (
|
||||
<motion.ul variants={container} initial="hidden" animate="visible">
|
||||
{[1,2,3].map(i => (
|
||||
<motion.li key={i} variants={item}>Item {i}</motion.li>
|
||||
{[1, 2, 3].map(i => (
|
||||
<motion.li key={i} variants={item}>
|
||||
Item {i}
|
||||
</motion.li>
|
||||
))}
|
||||
</motion.ul>
|
||||
)
|
||||
@@ -420,68 +376,37 @@ export function List() {
|
||||
|
||||
---
|
||||
|
||||
### Modal with AnimatePresence
|
||||
## Debug Checklist
|
||||
|
||||
```tsx
|
||||
import { motion, AnimatePresence } from "motion/react"
|
||||
|
||||
export function Modal({ open }) {
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{open && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.95 }}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
```
|
||||
* correct import (`motion/react`)
|
||||
* `"use client"` in Next.js
|
||||
* no missing keys
|
||||
* no layout shift (CLS)
|
||||
* no hydration mismatch
|
||||
* reduced motion works
|
||||
* keyboard navigation works
|
||||
|
||||
---
|
||||
|
||||
### Scroll Parallax
|
||||
## Anti-Patterns
|
||||
|
||||
```tsx
|
||||
import { useScroll, useTransform, motion } from "motion/react"
|
||||
|
||||
export function Parallax() {
|
||||
const { scrollYProgress } = useScroll()
|
||||
const y = useTransform(scrollYProgress, [0, 1], [0, -80])
|
||||
|
||||
return <motion.div style={{ y }} />
|
||||
}
|
||||
```
|
||||
* animating layout (width/height)
|
||||
* decorative motion
|
||||
* infinite motion without purpose
|
||||
* ignoring reduced motion
|
||||
* over-staggering lists
|
||||
|
||||
---
|
||||
|
||||
### Skeleton Loading
|
||||
## Philosophy
|
||||
|
||||
```tsx
|
||||
import { motion } from "motion/react"
|
||||
|
||||
export function Skeleton() {
|
||||
return (
|
||||
<motion.div
|
||||
className="bg-gray-200 h-6 w-full"
|
||||
animate={{ opacity: [0.6, 1, 0.6] }}
|
||||
transition={{ repeat: Infinity }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
```
|
||||
Motion is interaction design.
|
||||
|
||||
---
|
||||
|
||||
### Shared Layout (Crossfade)
|
||||
## Final Rule
|
||||
|
||||
```tsx
|
||||
import { motion } from "motion/react"
|
||||
> If motion does not improve UX → remove it.
|
||||
|
||||
export function Shared() {
|
||||
return <motion.div layoutId="shared" />
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user