picasso-skill 1.0.0 → 1.2.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/README.md +70 -45
- package/agents/picasso.md +326 -0
- package/bin/install.mjs +54 -24
- package/package.json +5 -3
- package/skills/picasso/references/accessibility.md +172 -0
- package/skills/picasso/references/design-system.md +14 -14
- package/skills/picasso/references/generative-art.md +626 -32
- package/skills/picasso/references/motion-and-animation.md +2 -2
- package/skills/picasso/references/react-patterns.md +193 -91
- package/skills/picasso/references/responsive-design.md +349 -15
- package/skills/picasso/references/sensory-design.md +294 -50
- package/SKILL.md +0 -202
- package/references/anti-patterns.md +0 -95
- package/references/color-and-contrast.md +0 -174
- package/references/component-patterns.md +0 -113
- package/references/design-system.md +0 -176
- package/references/generative-art.md +0 -54
- package/references/interaction-design.md +0 -162
- package/references/motion-and-animation.md +0 -260
- package/references/react-patterns.md +0 -216
- package/references/responsive-design.md +0 -118
- package/references/sensory-design.md +0 -125
- package/references/spatial-design.md +0 -176
- package/references/typography.md +0 -168
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
## Table of Contents
|
|
4
4
|
1. Component Architecture
|
|
5
|
-
2.
|
|
6
|
-
3.
|
|
7
|
-
4.
|
|
8
|
-
5.
|
|
9
|
-
6.
|
|
10
|
-
7.
|
|
5
|
+
2. React 19 Features
|
|
6
|
+
3. State Management
|
|
7
|
+
4. Performance
|
|
8
|
+
5. Composition Patterns
|
|
9
|
+
6. Data Fetching & Mutations
|
|
10
|
+
7. Styling & Tailwind v4
|
|
11
|
+
8. Dark Mode Toggle
|
|
12
|
+
9. Common Mistakes
|
|
11
13
|
|
|
12
14
|
---
|
|
13
15
|
|
|
@@ -27,7 +29,6 @@ components/
|
|
|
27
29
|
user-card/
|
|
28
30
|
user-card.tsx
|
|
29
31
|
user-card.test.tsx
|
|
30
|
-
user-card.module.css
|
|
31
32
|
types.ts
|
|
32
33
|
```
|
|
33
34
|
|
|
@@ -44,11 +45,67 @@ components/
|
|
|
44
45
|
|
|
45
46
|
---
|
|
46
47
|
|
|
47
|
-
## 2.
|
|
48
|
+
## 2. React 19 Features
|
|
49
|
+
|
|
50
|
+
### The `use` Hook
|
|
51
|
+
Read promises and context directly in render. Works inside conditionals and loops unlike other hooks:
|
|
52
|
+
```tsx
|
|
53
|
+
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
|
|
54
|
+
const user = use(userPromise); // suspends until resolved
|
|
55
|
+
return <h1>{user.name}</h1>;
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### `useActionState` for Form Actions
|
|
60
|
+
Manages form state, pending status, and server action results in one hook:
|
|
61
|
+
```tsx
|
|
62
|
+
'use client';
|
|
63
|
+
import { useActionState } from 'react';
|
|
64
|
+
import { submitForm } from './actions';
|
|
65
|
+
|
|
66
|
+
function ContactForm() {
|
|
67
|
+
const [state, formAction, isPending] = useActionState(submitForm, { message: '' });
|
|
68
|
+
return (
|
|
69
|
+
<form action={formAction}>
|
|
70
|
+
<input name="email" type="email" required />
|
|
71
|
+
<button disabled={isPending}>{isPending ? 'Sending...' : 'Submit'}</button>
|
|
72
|
+
</form>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### `useFormStatus` for Submission State
|
|
78
|
+
Access the parent form's pending state from within child components:
|
|
79
|
+
```tsx
|
|
80
|
+
'use client';
|
|
81
|
+
import { useFormStatus } from 'react-dom';
|
|
82
|
+
|
|
83
|
+
function SubmitButton() {
|
|
84
|
+
const { pending } = useFormStatus();
|
|
85
|
+
return <button disabled={pending}>{pending ? 'Saving...' : 'Save'}</button>;
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `useOptimistic` for Instant Feedback
|
|
90
|
+
Show optimistic state while an async action completes:
|
|
91
|
+
```tsx
|
|
92
|
+
const [optimistic, addOptimistic] = useOptimistic(
|
|
93
|
+
messages,
|
|
94
|
+
(current, newMsg: string) => [...current, { text: newMsg, pending: true }]
|
|
95
|
+
);
|
|
96
|
+
// Call addOptimistic(value) before await -- renders instantly, reverts on error
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### React Compiler
|
|
100
|
+
React 19 ships with an opt-in compiler that auto-memoizes components, hooks, and expressions. When the compiler is enabled, remove manual `React.memo`, `useMemo`, and `useCallback` calls -- the compiler handles it. Check your project's `react-compiler` config before deciding whether to memoize manually.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 3. State Management
|
|
48
105
|
|
|
49
106
|
### Where State Lives
|
|
50
107
|
1. **URL state**: filters, pagination, search queries (use `searchParams`)
|
|
51
|
-
2. **Server state**: data from APIs (use React Query
|
|
108
|
+
2. **Server state**: data from APIs (use server components or React Query/SWR for client)
|
|
52
109
|
3. **Local state**: form inputs, UI toggles, hover/focus state (use `useState`)
|
|
53
110
|
4. **Shared local state**: state needed by siblings (lift to parent, or use context)
|
|
54
111
|
5. **Global state**: rarely needed (auth user, theme preference, feature flags)
|
|
@@ -59,94 +116,49 @@ components/
|
|
|
59
116
|
- Prefer `useReducer` over `useState` when the next state depends on the previous state or when managing more than 3 related state variables.
|
|
60
117
|
|
|
61
118
|
```tsx
|
|
62
|
-
// Bad:
|
|
63
|
-
const [
|
|
64
|
-
|
|
65
|
-
useEffect(() => {
|
|
66
|
-
setFilteredItems(items.filter(i => i.active));
|
|
67
|
-
}, [items]);
|
|
68
|
-
|
|
69
|
-
// Good: compute during render
|
|
70
|
-
const [items, setItems] = useState(data);
|
|
71
|
-
const filteredItems = items.filter(i => i.active);
|
|
119
|
+
// Bad: derived state in useEffect // Good: compute during render
|
|
120
|
+
const [filtered, setFiltered] = useState([]); const filtered = items.filter(i => i.active);
|
|
121
|
+
useEffect(() => setFiltered(items.filter(i => i.active)), [items]);
|
|
72
122
|
```
|
|
73
123
|
|
|
74
124
|
---
|
|
75
125
|
|
|
76
|
-
##
|
|
126
|
+
## 4. Performance
|
|
77
127
|
|
|
78
128
|
### Rendering
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
- Use `useCallback` for callbacks passed to memoized children
|
|
129
|
+
- With React Compiler enabled: skip manual `React.memo`, `useMemo`, `useCallback`
|
|
130
|
+
- Without the compiler: use `React.memo` only for components that re-render often with the same props; use `useMemo` for expensive computations only; use `useCallback` for callbacks passed to memoized children
|
|
82
131
|
- Use `key` props correctly (stable, unique identifiers, never array indices for reorderable lists)
|
|
83
132
|
|
|
84
133
|
### Code Splitting
|
|
134
|
+
Use `React.lazy` + `Suspense` for client components (works in Next.js App Router and plain React):
|
|
85
135
|
```tsx
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const Chart = dynamic(() => import('./chart'), {
|
|
89
|
-
loading: () => <ChartSkeleton />,
|
|
90
|
-
ssr: false,
|
|
91
|
-
});
|
|
136
|
+
const Chart = lazy(() => import('./chart'));
|
|
137
|
+
// Wrap in <Suspense fallback={<ChartSkeleton />}><Chart /></Suspense>
|
|
92
138
|
```
|
|
139
|
+
Next.js App Router also provides automatic route-level splitting per `page.tsx`/`layout.tsx`.
|
|
93
140
|
|
|
94
141
|
### Virtualization
|
|
95
|
-
For lists with 100+ items, use `react-window
|
|
96
|
-
```tsx
|
|
97
|
-
import { FixedSizeList } from 'react-window';
|
|
98
|
-
|
|
99
|
-
<FixedSizeList height={600} itemCount={items.length} itemSize={48} width="100%">
|
|
100
|
-
{({ index, style }) => <Row style={style} item={items[index]} />}
|
|
101
|
-
</FixedSizeList>
|
|
102
|
-
```
|
|
142
|
+
For lists with 100+ items, use `@tanstack/virtual` or `react-window`.
|
|
103
143
|
|
|
104
144
|
### Image Optimization
|
|
105
|
-
Use `next/image` in Next.js or `loading="lazy"` with explicit `width`/`height
|
|
145
|
+
Use `next/image` in Next.js or `loading="lazy"` with explicit `width`/`height`. Always set `aspect-ratio` to prevent layout shift.
|
|
106
146
|
|
|
107
147
|
---
|
|
108
148
|
|
|
109
|
-
##
|
|
149
|
+
## 5. Composition Patterns
|
|
110
150
|
|
|
111
151
|
### Compound Components
|
|
112
|
-
Components that share implicit state through context
|
|
113
|
-
```tsx
|
|
114
|
-
<Select value={value} onChange={setValue}>
|
|
115
|
-
<Select.Trigger>Choose a fruit</Select.Trigger>
|
|
116
|
-
<Select.Content>
|
|
117
|
-
<Select.Item value="apple">Apple</Select.Item>
|
|
118
|
-
<Select.Item value="banana">Banana</Select.Item>
|
|
119
|
-
</Select.Content>
|
|
120
|
-
</Select>
|
|
121
|
-
```
|
|
152
|
+
Components that share implicit state through context (e.g., `<Select>`, `<Select.Trigger>`, `<Select.Item>` pattern). Parent holds state, children consume via context.
|
|
122
153
|
|
|
123
154
|
### Slot Pattern
|
|
124
|
-
Flexible
|
|
125
|
-
```tsx
|
|
126
|
-
function Card({ header, children, footer }) {
|
|
127
|
-
return (
|
|
128
|
-
<div className="card">
|
|
129
|
-
{header && <div className="card-header">{header}</div>}
|
|
130
|
-
<div className="card-body">{children}</div>
|
|
131
|
-
{footer && <div className="card-footer">{footer}</div>}
|
|
132
|
-
</div>
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### Render Props (when needed)
|
|
138
|
-
For components that need to share behavior, not UI:
|
|
139
|
-
```tsx
|
|
140
|
-
<DataFetcher url="/api/users">
|
|
141
|
-
{({ data, isLoading }) => isLoading ? <Skeleton /> : <UserList users={data} />}
|
|
142
|
-
</DataFetcher>
|
|
143
|
-
```
|
|
155
|
+
Flexible composition through named children (`header`, `children`, `footer` as props). Avoids rigid component trees and enables flexible layouts without render props.
|
|
144
156
|
|
|
145
157
|
---
|
|
146
158
|
|
|
147
|
-
##
|
|
159
|
+
## 6. Data Fetching & Mutations
|
|
148
160
|
|
|
149
|
-
### Server Components (preferred)
|
|
161
|
+
### Server Components (preferred for reads)
|
|
150
162
|
```tsx
|
|
151
163
|
async function UserList() {
|
|
152
164
|
const users = await fetch('/api/users').then(r => r.json());
|
|
@@ -154,19 +166,30 @@ async function UserList() {
|
|
|
154
166
|
}
|
|
155
167
|
```
|
|
156
168
|
|
|
157
|
-
###
|
|
169
|
+
### Server Actions (preferred for mutations)
|
|
170
|
+
Define actions in a `'use server'` file and call from client components via form `action`:
|
|
158
171
|
```tsx
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
);
|
|
172
|
+
// actions.ts
|
|
173
|
+
'use server';
|
|
174
|
+
import { revalidatePath } from 'next/cache';
|
|
175
|
+
|
|
176
|
+
export async function createTodo(formData: FormData) {
|
|
177
|
+
await db.todo.create({ data: { title: formData.get('title') as string } });
|
|
178
|
+
revalidatePath('/todos');
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
```tsx
|
|
182
|
+
// todo-form.tsx -- 'use client'
|
|
183
|
+
import { createTodo } from './actions';
|
|
184
|
+
export default function TodoForm() {
|
|
185
|
+
return <form action={createTodo}><input name="title" required /><button>Add</button></form>;
|
|
165
186
|
}
|
|
166
187
|
```
|
|
167
188
|
|
|
168
|
-
|
|
169
|
-
|
|
189
|
+
Server Actions handle serialization, error boundaries, and progressive enhancement. Prefer them over API route handlers for mutations.
|
|
190
|
+
|
|
191
|
+
### Client-Side with Suspense + Error Boundaries
|
|
192
|
+
Always wrap data-fetching components:
|
|
170
193
|
```tsx
|
|
171
194
|
<ErrorBoundary fallback={<ErrorMessage />}>
|
|
172
195
|
<Suspense fallback={<Skeleton />}>
|
|
@@ -177,7 +200,29 @@ Wrap data-fetching components in error boundaries:
|
|
|
177
200
|
|
|
178
201
|
---
|
|
179
202
|
|
|
180
|
-
##
|
|
203
|
+
## 7. Styling & Tailwind v4
|
|
204
|
+
|
|
205
|
+
### Tailwind v4 Changes
|
|
206
|
+
Tailwind v4 uses a CSS-first configuration model. Key differences from v3:
|
|
207
|
+
|
|
208
|
+
- **No `tailwind.config.js`**: configuration lives in CSS using the `@theme` directive
|
|
209
|
+
- **New import**: use `@import "tailwindcss"` instead of `@tailwind base/components/utilities` directives
|
|
210
|
+
- **CSS variables for all tokens**: every design token is automatically exposed as a CSS custom property (`--color-blue-500`, `--spacing-4`, etc.)
|
|
211
|
+
|
|
212
|
+
```css
|
|
213
|
+
/* app/globals.css */
|
|
214
|
+
@import "tailwindcss";
|
|
215
|
+
|
|
216
|
+
@theme {
|
|
217
|
+
--color-brand: #6366f1;
|
|
218
|
+
--color-surface: #ffffff;
|
|
219
|
+
--color-surface-dark: #0f172a;
|
|
220
|
+
--font-display: "Inter", sans-serif;
|
|
221
|
+
--breakpoint-3xl: 1920px;
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
You can reference these tokens anywhere in CSS or JS via `var(--color-brand)` without any build-step workaround.
|
|
181
226
|
|
|
182
227
|
### Tailwind Best Practices
|
|
183
228
|
- Use Tailwind's core utility classes (pre-defined classes only in Claude artifacts)
|
|
@@ -193,24 +238,81 @@ import styles from './button.module.css';
|
|
|
193
238
|
```
|
|
194
239
|
|
|
195
240
|
### Semantic HTML
|
|
196
|
-
Use the right element, not
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
241
|
+
Use the right element: `<nav>` for navigation, `<main>` for primary content, `<section>` for thematic grouping, `<article>` for self-contained content, `<button>` for actions, `<a>` for navigation. Do not use bare `div` where semantic elements apply.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## 8. Dark Mode Toggle
|
|
246
|
+
|
|
247
|
+
Complete dark mode implementation with localStorage persistence, system preference detection, and flash prevention.
|
|
248
|
+
|
|
249
|
+
### Blocking Script (prevents flash of wrong theme)
|
|
250
|
+
Place inline in `<head>` before stylesheets (in Next.js, use `<script dangerouslySetInnerHTML>` in `app/layout.tsx`):
|
|
251
|
+
```html
|
|
252
|
+
<script>
|
|
253
|
+
(function() {
|
|
254
|
+
var s = localStorage.getItem('theme');
|
|
255
|
+
var theme = s || (matchMedia('(prefers-color-scheme:dark)').matches ? 'dark' : 'light');
|
|
256
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
257
|
+
})();
|
|
258
|
+
</script>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Theme Toggle Hook
|
|
262
|
+
```tsx
|
|
263
|
+
'use client';
|
|
264
|
+
function useTheme() {
|
|
265
|
+
const [theme, setThemeState] = useState<'light' | 'dark'>(() =>
|
|
266
|
+
typeof window === 'undefined' ? 'light'
|
|
267
|
+
: (document.documentElement.getAttribute('data-theme') as 'light' | 'dark') || 'light'
|
|
268
|
+
);
|
|
269
|
+
const setTheme = useCallback((next: 'light' | 'dark') => {
|
|
270
|
+
document.documentElement.setAttribute('data-theme', next);
|
|
271
|
+
localStorage.setItem('theme', next);
|
|
272
|
+
setThemeState(next);
|
|
273
|
+
}, []);
|
|
274
|
+
const toggle = useCallback(() => setTheme(theme === 'dark' ? 'light' : 'dark'), [theme, setTheme]);
|
|
275
|
+
// Listen for system preference changes when no explicit choice is stored
|
|
276
|
+
useEffect(() => {
|
|
277
|
+
const mq = matchMedia('(prefers-color-scheme: dark)');
|
|
278
|
+
const h = (e: MediaQueryListEvent) => { if (!localStorage.getItem('theme')) setTheme(e.matches ? 'dark' : 'light'); };
|
|
279
|
+
mq.addEventListener('change', h);
|
|
280
|
+
return () => mq.removeEventListener('change', h);
|
|
281
|
+
}, [setTheme]);
|
|
282
|
+
return { theme, setTheme, toggle };
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### CSS Setup with Tailwind v4
|
|
287
|
+
```css
|
|
288
|
+
@import "tailwindcss";
|
|
289
|
+
|
|
290
|
+
@theme {
|
|
291
|
+
--color-bg: #ffffff;
|
|
292
|
+
--color-text: #0f172a;
|
|
293
|
+
--color-surface: #f8fafc;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
[data-theme="dark"] {
|
|
297
|
+
--color-bg: #0f172a;
|
|
298
|
+
--color-text: #f8fafc;
|
|
299
|
+
--color-surface: #1e293b;
|
|
300
|
+
}
|
|
301
|
+
```
|
|
204
302
|
|
|
205
303
|
---
|
|
206
304
|
|
|
207
|
-
##
|
|
305
|
+
## 9. Common Mistakes
|
|
208
306
|
|
|
209
307
|
- Using `useEffect` for derived state (compute during render instead)
|
|
210
308
|
- Putting everything in global state (most state should be local or server-derived)
|
|
211
309
|
- Using `index` as `key` for dynamic lists
|
|
212
|
-
- Wrapping every component in `React.memo`
|
|
310
|
+
- Wrapping every component in `React.memo` (let the React Compiler handle this)
|
|
213
311
|
- Using `any` in TypeScript (defeats the purpose of type safety)
|
|
214
312
|
- Fetching data in `useEffect` when a server component would suffice
|
|
215
313
|
- Not using Suspense boundaries (the whole page flashes instead of parts loading independently)
|
|
216
314
|
- Prop drilling through 5+ levels (use composition or context)
|
|
315
|
+
- Using API route handlers for mutations when Server Actions are simpler
|
|
316
|
+
- Not using `useOptimistic` for actions that need instant visual feedback
|
|
317
|
+
- Manual memoization when the React Compiler is enabled (causes unnecessary code noise)
|
|
318
|
+
- Using `@tailwind` directives or `tailwind.config.js` in Tailwind v4 projects (use `@import "tailwindcss"` and `@theme` instead)
|