mirror of
https://github.com/wshobson/agents.git
synced 2026-03-18 17:47:16 +00:00
style: format all files with prettier
This commit is contained in:
@@ -23,6 +23,7 @@ Create engaging, intuitive interactions through motion, feedback, and thoughtful
|
||||
### 1. Purposeful Motion
|
||||
|
||||
Motion should communicate, not decorate:
|
||||
|
||||
- **Feedback**: Confirm user actions occurred
|
||||
- **Orientation**: Show where elements come from/go to
|
||||
- **Focus**: Direct attention to important changes
|
||||
@@ -30,27 +31,27 @@ Motion should communicate, not decorate:
|
||||
|
||||
### 2. Timing Guidelines
|
||||
|
||||
| Duration | Use Case |
|
||||
|----------|----------|
|
||||
| 100-150ms | Micro-feedback (hovers, clicks) |
|
||||
| 200-300ms | Small transitions (toggles, dropdowns) |
|
||||
| Duration | Use Case |
|
||||
| --------- | ----------------------------------------- |
|
||||
| 100-150ms | Micro-feedback (hovers, clicks) |
|
||||
| 200-300ms | Small transitions (toggles, dropdowns) |
|
||||
| 300-500ms | Medium transitions (modals, page changes) |
|
||||
| 500ms+ | Complex choreographed animations |
|
||||
| 500ms+ | Complex choreographed animations |
|
||||
|
||||
### 3. Easing Functions
|
||||
|
||||
```css
|
||||
/* Common easings */
|
||||
--ease-out: cubic-bezier(0.16, 1, 0.3, 1); /* Decelerate - entering */
|
||||
--ease-in: cubic-bezier(0.55, 0, 1, 0.45); /* Accelerate - exiting */
|
||||
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); /* Both - moving between */
|
||||
--spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* Overshoot - playful */
|
||||
--ease-out: cubic-bezier(0.16, 1, 0.3, 1); /* Decelerate - entering */
|
||||
--ease-in: cubic-bezier(0.55, 0, 1, 0.45); /* Accelerate - exiting */
|
||||
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); /* Both - moving between */
|
||||
--spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* Overshoot - playful */
|
||||
```
|
||||
|
||||
## Quick Start: Button Microinteraction
|
||||
|
||||
```tsx
|
||||
import { motion } from 'framer-motion';
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
export function InteractiveButton({ children, onClick }) {
|
||||
return (
|
||||
@@ -58,7 +59,7 @@ export function InteractiveButton({ children, onClick }) {
|
||||
onClick={onClick}
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
transition={{ type: 'spring', stiffness: 400, damping: 17 }}
|
||||
transition={{ type: "spring", stiffness: 400, damping: 17 }}
|
||||
className="px-4 py-2 bg-blue-600 text-white rounded-lg"
|
||||
>
|
||||
{children}
|
||||
@@ -72,6 +73,7 @@ export function InteractiveButton({ children, onClick }) {
|
||||
### 1. Loading States
|
||||
|
||||
**Skeleton Screens**: Preserve layout while loading
|
||||
|
||||
```tsx
|
||||
function CardSkeleton() {
|
||||
return (
|
||||
@@ -85,6 +87,7 @@ function CardSkeleton() {
|
||||
```
|
||||
|
||||
**Progress Indicators**: Show determinate progress
|
||||
|
||||
```tsx
|
||||
function ProgressBar({ progress }: { progress: number }) {
|
||||
return (
|
||||
@@ -93,7 +96,7 @@ function ProgressBar({ progress }: { progress: number }) {
|
||||
className="h-full bg-blue-600"
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${progress}%` }}
|
||||
transition={{ ease: 'easeOut' }}
|
||||
transition={{ ease: "easeOut" }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -103,6 +106,7 @@ function ProgressBar({ progress }: { progress: number }) {
|
||||
### 2. State Transitions
|
||||
|
||||
**Toggle with smooth transition**:
|
||||
|
||||
```tsx
|
||||
function Toggle({ checked, onChange }) {
|
||||
return (
|
||||
@@ -112,13 +116,13 @@ function Toggle({ checked, onChange }) {
|
||||
onClick={() => onChange(!checked)}
|
||||
className={`
|
||||
relative w-12 h-6 rounded-full transition-colors duration-200
|
||||
${checked ? 'bg-blue-600' : 'bg-gray-300'}
|
||||
${checked ? "bg-blue-600" : "bg-gray-300"}
|
||||
`}
|
||||
>
|
||||
<motion.span
|
||||
className="absolute top-1 left-1 w-4 h-4 bg-white rounded-full shadow"
|
||||
animate={{ x: checked ? 24 : 0 }}
|
||||
transition={{ type: 'spring', stiffness: 500, damping: 30 }}
|
||||
transition={{ type: "spring", stiffness: 500, damping: 30 }}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
@@ -128,8 +132,9 @@ function Toggle({ checked, onChange }) {
|
||||
### 3. Page Transitions
|
||||
|
||||
**Framer Motion layout animations**:
|
||||
|
||||
```tsx
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
|
||||
function PageTransition({ children, key }) {
|
||||
return (
|
||||
@@ -151,6 +156,7 @@ function PageTransition({ children, key }) {
|
||||
### 4. Feedback Patterns
|
||||
|
||||
**Ripple effect on click**:
|
||||
|
||||
```tsx
|
||||
function RippleButton({ children, onClick }) {
|
||||
const [ripples, setRipples] = useState([]);
|
||||
@@ -162,9 +168,9 @@ function RippleButton({ children, onClick }) {
|
||||
y: e.clientY - rect.top,
|
||||
id: Date.now(),
|
||||
};
|
||||
setRipples(prev => [...prev, ripple]);
|
||||
setRipples((prev) => [...prev, ripple]);
|
||||
setTimeout(() => {
|
||||
setRipples(prev => prev.filter(r => r.id !== ripple.id));
|
||||
setRipples((prev) => prev.filter((r) => r.id !== ripple.id));
|
||||
}, 600);
|
||||
onClick?.(e);
|
||||
};
|
||||
@@ -172,7 +178,7 @@ function RippleButton({ children, onClick }) {
|
||||
return (
|
||||
<button onClick={handleClick} className="relative overflow-hidden">
|
||||
{children}
|
||||
{ripples.map(ripple => (
|
||||
{ripples.map((ripple) => (
|
||||
<span
|
||||
key={ripple.id}
|
||||
className="absolute bg-white/30 rounded-full animate-ripple"
|
||||
@@ -187,6 +193,7 @@ function RippleButton({ children, onClick }) {
|
||||
### 5. Gesture Interactions
|
||||
|
||||
**Swipe to dismiss**:
|
||||
|
||||
```tsx
|
||||
function SwipeCard({ children, onDismiss }) {
|
||||
return (
|
||||
@@ -212,29 +219,50 @@ function SwipeCard({ children, onDismiss }) {
|
||||
|
||||
```css
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-fadeIn { animation: fadeIn 0.3s ease-out; }
|
||||
.animate-pulse { animation: pulse 2s ease-in-out infinite; }
|
||||
.animate-spin { animation: spin 1s linear infinite; }
|
||||
.animate-fadeIn {
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
.animate-pulse {
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
.animate-spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
```
|
||||
|
||||
### CSS Transitions
|
||||
|
||||
```css
|
||||
.card {
|
||||
transition: transform 0.2s ease-out, box-shadow 0.2s ease-out;
|
||||
transition:
|
||||
transform 0.2s ease-out,
|
||||
box-shadow 0.2s ease-out;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
@@ -248,7 +276,9 @@ function SwipeCard({ children, onDismiss }) {
|
||||
```css
|
||||
/* Respect user motion preferences */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
@@ -259,7 +289,7 @@ function SwipeCard({ children, onDismiss }) {
|
||||
```tsx
|
||||
function AnimatedComponent() {
|
||||
const prefersReducedMotion = window.matchMedia(
|
||||
'(prefers-reduced-motion: reduce)'
|
||||
"(prefers-reduced-motion: reduce)",
|
||||
).matches;
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user