autoworkflow 3.1.4 → 3.5.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/.claude/commands/analyze.md +19 -0
- package/.claude/commands/audit.md +174 -11
- package/.claude/commands/build.md +39 -0
- package/.claude/commands/commit.md +25 -0
- package/.claude/commands/fix.md +23 -0
- package/.claude/commands/plan.md +18 -0
- package/.claude/commands/suggest.md +23 -0
- package/.claude/commands/verify.md +18 -0
- package/.claude/hooks/post-bash-router.sh +20 -0
- package/.claude/hooks/post-commit.sh +140 -0
- package/.claude/hooks/pre-edit.sh +129 -0
- package/.claude/hooks/session-check.sh +79 -0
- package/.claude/settings.json +40 -6
- package/.claude/settings.local.json +3 -1
- package/.claude/skills/actix.md +337 -0
- package/.claude/skills/alembic.md +504 -0
- package/.claude/skills/angular.md +237 -0
- package/.claude/skills/api-design.md +187 -0
- package/.claude/skills/aspnet-core.md +377 -0
- package/.claude/skills/astro.md +245 -0
- package/.claude/skills/auth-clerk.md +327 -0
- package/.claude/skills/auth-firebase.md +367 -0
- package/.claude/skills/auth-nextauth.md +359 -0
- package/.claude/skills/auth-supabase.md +368 -0
- package/.claude/skills/axum.md +386 -0
- package/.claude/skills/blazor.md +456 -0
- package/.claude/skills/chi.md +348 -0
- package/.claude/skills/code-review.md +133 -0
- package/.claude/skills/csharp.md +296 -0
- package/.claude/skills/css-modules.md +325 -0
- package/.claude/skills/cypress.md +343 -0
- package/.claude/skills/debugging.md +133 -0
- package/.claude/skills/diesel.md +392 -0
- package/.claude/skills/django.md +301 -0
- package/.claude/skills/docker.md +319 -0
- package/.claude/skills/doctrine.md +473 -0
- package/.claude/skills/documentation.md +182 -0
- package/.claude/skills/dotnet.md +409 -0
- package/.claude/skills/drizzle.md +293 -0
- package/.claude/skills/echo.md +321 -0
- package/.claude/skills/eloquent.md +256 -0
- package/.claude/skills/emotion.md +426 -0
- package/.claude/skills/entity-framework.md +370 -0
- package/.claude/skills/express.md +316 -0
- package/.claude/skills/fastapi.md +329 -0
- package/.claude/skills/fastify.md +299 -0
- package/.claude/skills/fiber.md +315 -0
- package/.claude/skills/flask.md +322 -0
- package/.claude/skills/gin.md +342 -0
- package/.claude/skills/git.md +116 -0
- package/.claude/skills/github-actions.md +353 -0
- package/.claude/skills/go.md +377 -0
- package/.claude/skills/gorm.md +409 -0
- package/.claude/skills/graphql.md +478 -0
- package/.claude/skills/hibernate.md +379 -0
- package/.claude/skills/hono.md +306 -0
- package/.claude/skills/java.md +400 -0
- package/.claude/skills/jest.md +313 -0
- package/.claude/skills/jpa.md +282 -0
- package/.claude/skills/kotlin.md +347 -0
- package/.claude/skills/kubernetes.md +363 -0
- package/.claude/skills/laravel.md +414 -0
- package/.claude/skills/mcp-browser.md +320 -0
- package/.claude/skills/mcp-database.md +219 -0
- package/.claude/skills/mcp-fetch.md +241 -0
- package/.claude/skills/mcp-filesystem.md +204 -0
- package/.claude/skills/mcp-github.md +217 -0
- package/.claude/skills/mcp-memory.md +240 -0
- package/.claude/skills/mcp-search.md +218 -0
- package/.claude/skills/mcp-slack.md +262 -0
- package/.claude/skills/micronaut.md +388 -0
- package/.claude/skills/mongodb.md +319 -0
- package/.claude/skills/mongoose.md +355 -0
- package/.claude/skills/mysql.md +281 -0
- package/.claude/skills/nestjs.md +335 -0
- package/.claude/skills/nextjs-app-router.md +260 -0
- package/.claude/skills/nextjs-pages.md +172 -0
- package/.claude/skills/nuxt.md +202 -0
- package/.claude/skills/openapi.md +489 -0
- package/.claude/skills/performance.md +199 -0
- package/.claude/skills/php.md +398 -0
- package/.claude/skills/playwright.md +371 -0
- package/.claude/skills/postgresql.md +257 -0
- package/.claude/skills/prisma.md +293 -0
- package/.claude/skills/pydantic.md +304 -0
- package/.claude/skills/pytest.md +313 -0
- package/.claude/skills/python.md +272 -0
- package/.claude/skills/quarkus.md +377 -0
- package/.claude/skills/react.md +230 -0
- package/.claude/skills/redis.md +391 -0
- package/.claude/skills/refactoring.md +143 -0
- package/.claude/skills/remix.md +246 -0
- package/.claude/skills/rest-api.md +490 -0
- package/.claude/skills/rocket.md +366 -0
- package/.claude/skills/rust.md +341 -0
- package/.claude/skills/sass.md +380 -0
- package/.claude/skills/sea-orm.md +382 -0
- package/.claude/skills/security.md +167 -0
- package/.claude/skills/sequelize.md +395 -0
- package/.claude/skills/spring-boot.md +416 -0
- package/.claude/skills/sqlalchemy.md +269 -0
- package/.claude/skills/sqlx-rust.md +408 -0
- package/.claude/skills/state-jotai.md +346 -0
- package/.claude/skills/state-mobx.md +353 -0
- package/.claude/skills/state-pinia.md +431 -0
- package/.claude/skills/state-redux.md +337 -0
- package/.claude/skills/state-tanstack-query.md +434 -0
- package/.claude/skills/state-zustand.md +340 -0
- package/.claude/skills/styled-components.md +403 -0
- package/.claude/skills/svelte.md +238 -0
- package/.claude/skills/sveltekit.md +207 -0
- package/.claude/skills/symfony.md +437 -0
- package/.claude/skills/tailwind.md +279 -0
- package/.claude/skills/terraform.md +394 -0
- package/.claude/skills/testing-library.md +371 -0
- package/.claude/skills/trpc.md +426 -0
- package/.claude/skills/typeorm.md +368 -0
- package/.claude/skills/vitest.md +330 -0
- package/.claude/skills/vue.md +202 -0
- package/.claude/skills/warp.md +365 -0
- package/README.md +135 -52
- package/package.json +1 -1
- package/system/triggers.md +152 -11
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# Styled Components Skill
|
|
2
|
+
|
|
3
|
+
## Basic Usage
|
|
4
|
+
\`\`\`tsx
|
|
5
|
+
import styled from 'styled-components';
|
|
6
|
+
|
|
7
|
+
// Basic styled component
|
|
8
|
+
const Container = styled.div\`
|
|
9
|
+
max-width: 1200px;
|
|
10
|
+
margin: 0 auto;
|
|
11
|
+
padding: 0 1rem;
|
|
12
|
+
\`;
|
|
13
|
+
|
|
14
|
+
// With pseudo-selectors
|
|
15
|
+
const Button = styled.button\`
|
|
16
|
+
padding: 0.5rem 1rem;
|
|
17
|
+
background: #3b82f6;
|
|
18
|
+
color: white;
|
|
19
|
+
border: none;
|
|
20
|
+
border-radius: 0.375rem;
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
transition: background 0.2s;
|
|
23
|
+
|
|
24
|
+
&:hover {
|
|
25
|
+
background: #2563eb;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
&:focus {
|
|
29
|
+
outline: none;
|
|
30
|
+
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&:disabled {
|
|
34
|
+
opacity: 0.5;
|
|
35
|
+
cursor: not-allowed;
|
|
36
|
+
}
|
|
37
|
+
\`;
|
|
38
|
+
|
|
39
|
+
// Nesting
|
|
40
|
+
const Card = styled.div\`
|
|
41
|
+
padding: 1.5rem;
|
|
42
|
+
border-radius: 0.5rem;
|
|
43
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
44
|
+
|
|
45
|
+
h2 {
|
|
46
|
+
margin: 0 0 1rem;
|
|
47
|
+
font-size: 1.25rem;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
p {
|
|
51
|
+
color: #6b7280;
|
|
52
|
+
}
|
|
53
|
+
\`;
|
|
54
|
+
\`\`\`
|
|
55
|
+
|
|
56
|
+
## Props & Dynamic Styles
|
|
57
|
+
\`\`\`tsx
|
|
58
|
+
// TypeScript props interface
|
|
59
|
+
interface ButtonProps {
|
|
60
|
+
variant?: 'primary' | 'secondary' | 'danger';
|
|
61
|
+
size?: 'sm' | 'md' | 'lg';
|
|
62
|
+
fullWidth?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const Button = styled.button<ButtonProps>\`
|
|
66
|
+
/* Dynamic styles based on props */
|
|
67
|
+
padding: \${({ size }) => {
|
|
68
|
+
switch (size) {
|
|
69
|
+
case 'sm': return '0.25rem 0.5rem';
|
|
70
|
+
case 'lg': return '0.75rem 1.5rem';
|
|
71
|
+
default: return '0.5rem 1rem';
|
|
72
|
+
}
|
|
73
|
+
}};
|
|
74
|
+
|
|
75
|
+
background: \${({ variant }) => {
|
|
76
|
+
switch (variant) {
|
|
77
|
+
case 'secondary': return '#6b7280';
|
|
78
|
+
case 'danger': return '#ef4444';
|
|
79
|
+
default: return '#3b82f6';
|
|
80
|
+
}
|
|
81
|
+
}};
|
|
82
|
+
|
|
83
|
+
width: \${({ fullWidth }) => fullWidth ? '100%' : 'auto'};
|
|
84
|
+
|
|
85
|
+
font-size: \${({ size }) => size === 'sm' ? '0.875rem' : '1rem'};
|
|
86
|
+
\`;
|
|
87
|
+
|
|
88
|
+
// Usage
|
|
89
|
+
<Button variant="primary" size="lg" fullWidth>Submit</Button>
|
|
90
|
+
\`\`\`
|
|
91
|
+
|
|
92
|
+
## Theming
|
|
93
|
+
\`\`\`tsx
|
|
94
|
+
// theme.ts
|
|
95
|
+
export const theme = {
|
|
96
|
+
colors: {
|
|
97
|
+
primary: '#3b82f6',
|
|
98
|
+
secondary: '#6b7280',
|
|
99
|
+
danger: '#ef4444',
|
|
100
|
+
success: '#10b981',
|
|
101
|
+
background: '#ffffff',
|
|
102
|
+
text: '#1f2937',
|
|
103
|
+
textMuted: '#6b7280',
|
|
104
|
+
},
|
|
105
|
+
spacing: {
|
|
106
|
+
xs: '0.25rem',
|
|
107
|
+
sm: '0.5rem',
|
|
108
|
+
md: '1rem',
|
|
109
|
+
lg: '1.5rem',
|
|
110
|
+
xl: '2rem',
|
|
111
|
+
},
|
|
112
|
+
borderRadius: {
|
|
113
|
+
sm: '0.25rem',
|
|
114
|
+
md: '0.375rem',
|
|
115
|
+
lg: '0.5rem',
|
|
116
|
+
full: '9999px',
|
|
117
|
+
},
|
|
118
|
+
shadows: {
|
|
119
|
+
sm: '0 1px 2px rgba(0, 0, 0, 0.05)',
|
|
120
|
+
md: '0 4px 6px rgba(0, 0, 0, 0.1)',
|
|
121
|
+
lg: '0 10px 15px rgba(0, 0, 0, 0.1)',
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export type Theme = typeof theme;
|
|
126
|
+
|
|
127
|
+
// styled.d.ts - TypeScript declaration
|
|
128
|
+
import 'styled-components';
|
|
129
|
+
declare module 'styled-components' {
|
|
130
|
+
export interface DefaultTheme extends Theme {}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// App.tsx - Provider setup
|
|
134
|
+
import { ThemeProvider } from 'styled-components';
|
|
135
|
+
import { theme } from './theme';
|
|
136
|
+
|
|
137
|
+
function App() {
|
|
138
|
+
return (
|
|
139
|
+
<ThemeProvider theme={theme}>
|
|
140
|
+
<YourApp />
|
|
141
|
+
</ThemeProvider>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Using theme in components
|
|
146
|
+
const Card = styled.div\`
|
|
147
|
+
padding: \${({ theme }) => theme.spacing.lg};
|
|
148
|
+
background: \${({ theme }) => theme.colors.background};
|
|
149
|
+
border-radius: \${({ theme }) => theme.borderRadius.lg};
|
|
150
|
+
box-shadow: \${({ theme }) => theme.shadows.md};
|
|
151
|
+
\`;
|
|
152
|
+
|
|
153
|
+
const Button = styled.button\`
|
|
154
|
+
background: \${({ theme }) => theme.colors.primary};
|
|
155
|
+
color: white;
|
|
156
|
+
padding: \${({ theme }) => \`\${theme.spacing.sm} \${theme.spacing.md}\`};
|
|
157
|
+
\`;
|
|
158
|
+
\`\`\`
|
|
159
|
+
|
|
160
|
+
## Extending & Composing
|
|
161
|
+
\`\`\`tsx
|
|
162
|
+
// Extend existing styled component
|
|
163
|
+
const Button = styled.button\`
|
|
164
|
+
padding: 0.5rem 1rem;
|
|
165
|
+
border-radius: 0.375rem;
|
|
166
|
+
\`;
|
|
167
|
+
|
|
168
|
+
const PrimaryButton = styled(Button)\`
|
|
169
|
+
background: #3b82f6;
|
|
170
|
+
color: white;
|
|
171
|
+
\`;
|
|
172
|
+
|
|
173
|
+
const OutlineButton = styled(Button)\`
|
|
174
|
+
background: transparent;
|
|
175
|
+
border: 1px solid #3b82f6;
|
|
176
|
+
color: #3b82f6;
|
|
177
|
+
\`;
|
|
178
|
+
|
|
179
|
+
// Extend any component (must accept className prop)
|
|
180
|
+
const Link = styled(RouterLink)\`
|
|
181
|
+
color: #3b82f6;
|
|
182
|
+
text-decoration: none;
|
|
183
|
+
|
|
184
|
+
&:hover {
|
|
185
|
+
text-decoration: underline;
|
|
186
|
+
}
|
|
187
|
+
\`;
|
|
188
|
+
|
|
189
|
+
// Style HTML element with 'as' prop
|
|
190
|
+
<Button as="a" href="/link">Link styled as button</Button>
|
|
191
|
+
<Container as="section">Section container</Container>
|
|
192
|
+
\`\`\`
|
|
193
|
+
|
|
194
|
+
## Animations
|
|
195
|
+
\`\`\`tsx
|
|
196
|
+
import styled, { keyframes, css } from 'styled-components';
|
|
197
|
+
|
|
198
|
+
// Define keyframes
|
|
199
|
+
const fadeIn = keyframes\`
|
|
200
|
+
from { opacity: 0; transform: translateY(-10px); }
|
|
201
|
+
to { opacity: 1; transform: translateY(0); }
|
|
202
|
+
\`;
|
|
203
|
+
|
|
204
|
+
const spin = keyframes\`
|
|
205
|
+
from { transform: rotate(0deg); }
|
|
206
|
+
to { transform: rotate(360deg); }
|
|
207
|
+
\`;
|
|
208
|
+
|
|
209
|
+
const pulse = keyframes\`
|
|
210
|
+
0%, 100% { opacity: 1; }
|
|
211
|
+
50% { opacity: 0.5; }
|
|
212
|
+
\`;
|
|
213
|
+
|
|
214
|
+
// Use in styled component
|
|
215
|
+
const FadeInDiv = styled.div\`
|
|
216
|
+
animation: \${fadeIn} 0.3s ease-out;
|
|
217
|
+
\`;
|
|
218
|
+
|
|
219
|
+
const Spinner = styled.div\`
|
|
220
|
+
width: 24px;
|
|
221
|
+
height: 24px;
|
|
222
|
+
border: 2px solid #e5e7eb;
|
|
223
|
+
border-top-color: #3b82f6;
|
|
224
|
+
border-radius: 50%;
|
|
225
|
+
animation: \${spin} 0.8s linear infinite;
|
|
226
|
+
\`;
|
|
227
|
+
|
|
228
|
+
// Conditional animation
|
|
229
|
+
const AnimatedCard = styled.div<{ isVisible: boolean }>\`
|
|
230
|
+
opacity: 0;
|
|
231
|
+
|
|
232
|
+
\${({ isVisible }) => isVisible && css\`
|
|
233
|
+
animation: \${fadeIn} 0.5s ease-out forwards;
|
|
234
|
+
\`}
|
|
235
|
+
\`;
|
|
236
|
+
\`\`\`
|
|
237
|
+
|
|
238
|
+
## Global Styles
|
|
239
|
+
\`\`\`tsx
|
|
240
|
+
import { createGlobalStyle } from 'styled-components';
|
|
241
|
+
|
|
242
|
+
const GlobalStyle = createGlobalStyle\`
|
|
243
|
+
*, *::before, *::after {
|
|
244
|
+
box-sizing: border-box;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
html {
|
|
248
|
+
font-size: 16px;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
body {
|
|
252
|
+
margin: 0;
|
|
253
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
254
|
+
background: \${({ theme }) => theme.colors.background};
|
|
255
|
+
color: \${({ theme }) => theme.colors.text};
|
|
256
|
+
line-height: 1.5;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
a {
|
|
260
|
+
color: \${({ theme }) => theme.colors.primary};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* CSS reset or normalize styles */
|
|
264
|
+
h1, h2, h3, h4, h5, h6, p {
|
|
265
|
+
margin: 0;
|
|
266
|
+
}
|
|
267
|
+
\`;
|
|
268
|
+
|
|
269
|
+
// In App.tsx
|
|
270
|
+
function App() {
|
|
271
|
+
return (
|
|
272
|
+
<ThemeProvider theme={theme}>
|
|
273
|
+
<GlobalStyle />
|
|
274
|
+
<YourApp />
|
|
275
|
+
</ThemeProvider>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
\`\`\`
|
|
279
|
+
|
|
280
|
+
## CSS Helper & Mixins
|
|
281
|
+
\`\`\`tsx
|
|
282
|
+
import styled, { css } from 'styled-components';
|
|
283
|
+
|
|
284
|
+
// Reusable style mixins
|
|
285
|
+
const flexCenter = css\`
|
|
286
|
+
display: flex;
|
|
287
|
+
align-items: center;
|
|
288
|
+
justify-content: center;
|
|
289
|
+
\`;
|
|
290
|
+
|
|
291
|
+
const truncate = css\`
|
|
292
|
+
overflow: hidden;
|
|
293
|
+
text-overflow: ellipsis;
|
|
294
|
+
white-space: nowrap;
|
|
295
|
+
\`;
|
|
296
|
+
|
|
297
|
+
const visuallyHidden = css\`
|
|
298
|
+
position: absolute;
|
|
299
|
+
width: 1px;
|
|
300
|
+
height: 1px;
|
|
301
|
+
padding: 0;
|
|
302
|
+
margin: -1px;
|
|
303
|
+
overflow: hidden;
|
|
304
|
+
clip: rect(0, 0, 0, 0);
|
|
305
|
+
border: 0;
|
|
306
|
+
\`;
|
|
307
|
+
|
|
308
|
+
// Media query helper
|
|
309
|
+
const media = {
|
|
310
|
+
sm: (styles: TemplateStringsArray) => css\`
|
|
311
|
+
@media (min-width: 640px) { \${styles} }
|
|
312
|
+
\`,
|
|
313
|
+
md: (styles: TemplateStringsArray) => css\`
|
|
314
|
+
@media (min-width: 768px) { \${styles} }
|
|
315
|
+
\`,
|
|
316
|
+
lg: (styles: TemplateStringsArray) => css\`
|
|
317
|
+
@media (min-width: 1024px) { \${styles} }
|
|
318
|
+
\`,
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
// Usage
|
|
322
|
+
const Card = styled.div\`
|
|
323
|
+
\${flexCenter}
|
|
324
|
+
padding: 1rem;
|
|
325
|
+
|
|
326
|
+
\${media.md\`
|
|
327
|
+
padding: 2rem;
|
|
328
|
+
\`}
|
|
329
|
+
\`;
|
|
330
|
+
|
|
331
|
+
const Title = styled.h1\`
|
|
332
|
+
\${truncate}
|
|
333
|
+
max-width: 300px;
|
|
334
|
+
\`;
|
|
335
|
+
\`\`\`
|
|
336
|
+
|
|
337
|
+
## Attrs & Default Props
|
|
338
|
+
\`\`\`tsx
|
|
339
|
+
// Add default attributes
|
|
340
|
+
const Input = styled.input.attrs<{ type?: string }>((props) => ({
|
|
341
|
+
type: props.type || 'text',
|
|
342
|
+
}))\`
|
|
343
|
+
padding: 0.5rem;
|
|
344
|
+
border: 1px solid #d1d5db;
|
|
345
|
+
border-radius: 0.375rem;
|
|
346
|
+
\`;
|
|
347
|
+
|
|
348
|
+
const PasswordInput = styled(Input).attrs({ type: 'password' })\`\`;
|
|
349
|
+
|
|
350
|
+
// Dynamic attrs
|
|
351
|
+
const Button = styled.button.attrs<{ disabled?: boolean }>((props) => ({
|
|
352
|
+
disabled: props.disabled,
|
|
353
|
+
'aria-disabled': props.disabled,
|
|
354
|
+
}))\`
|
|
355
|
+
/* styles */
|
|
356
|
+
\`;
|
|
357
|
+
\`\`\`
|
|
358
|
+
|
|
359
|
+
## Server-Side Rendering
|
|
360
|
+
\`\`\`tsx
|
|
361
|
+
// Next.js setup (pages/_document.tsx)
|
|
362
|
+
import Document, { DocumentContext } from 'next/document';
|
|
363
|
+
import { ServerStyleSheet } from 'styled-components';
|
|
364
|
+
|
|
365
|
+
export default class MyDocument extends Document {
|
|
366
|
+
static async getInitialProps(ctx: DocumentContext) {
|
|
367
|
+
const sheet = new ServerStyleSheet();
|
|
368
|
+
const originalRenderPage = ctx.renderPage;
|
|
369
|
+
|
|
370
|
+
try {
|
|
371
|
+
ctx.renderPage = () =>
|
|
372
|
+
originalRenderPage({
|
|
373
|
+
enhanceApp: (App) => (props) =>
|
|
374
|
+
sheet.collectStyles(<App {...props} />),
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
const initialProps = await Document.getInitialProps(ctx);
|
|
378
|
+
return {
|
|
379
|
+
...initialProps,
|
|
380
|
+
styles: [initialProps.styles, sheet.getStyleElement()],
|
|
381
|
+
};
|
|
382
|
+
} finally {
|
|
383
|
+
sheet.seal();
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
\`\`\`
|
|
388
|
+
|
|
389
|
+
## ❌ DON'T
|
|
390
|
+
- Create styled components inside render functions
|
|
391
|
+
- Use inline styles when styled-components works
|
|
392
|
+
- Forget to type props in TypeScript
|
|
393
|
+
- Create overly complex conditional styles
|
|
394
|
+
- Skip ThemeProvider when using theme
|
|
395
|
+
|
|
396
|
+
## ✅ DO
|
|
397
|
+
- Define styled components outside of render
|
|
398
|
+
- Use theme for consistent values
|
|
399
|
+
- Keep styles colocated with components
|
|
400
|
+
- Use props for dynamic styles
|
|
401
|
+
- Create reusable mixins with css helper
|
|
402
|
+
- Use attrs for default/computed attributes
|
|
403
|
+
- Setup SSR properly for production
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# Svelte Skill
|
|
2
|
+
|
|
3
|
+
## Component Structure
|
|
4
|
+
\`\`\`svelte
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import { onMount, onDestroy, createEventDispatcher } from 'svelte';
|
|
7
|
+
|
|
8
|
+
// Props
|
|
9
|
+
export let userId: string;
|
|
10
|
+
export let initialCount = 0;
|
|
11
|
+
|
|
12
|
+
// Local state
|
|
13
|
+
let count = initialCount;
|
|
14
|
+
let user: User | null = null;
|
|
15
|
+
|
|
16
|
+
// Reactive declarations
|
|
17
|
+
$: doubleCount = count * 2;
|
|
18
|
+
$: if (count > 10) {
|
|
19
|
+
console.log('Count exceeded 10!');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Event dispatcher
|
|
23
|
+
const dispatch = createEventDispatcher<{
|
|
24
|
+
update: number;
|
|
25
|
+
submit: { data: FormData };
|
|
26
|
+
}>();
|
|
27
|
+
|
|
28
|
+
function increment() {
|
|
29
|
+
count++;
|
|
30
|
+
dispatch('update', count);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Lifecycle
|
|
34
|
+
onMount(async () => {
|
|
35
|
+
user = await fetchUser(userId);
|
|
36
|
+
return () => {
|
|
37
|
+
// Cleanup (alternative to onDestroy)
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<div>
|
|
43
|
+
<h2>{user?.name ?? 'Loading...'}</h2>
|
|
44
|
+
<p>Count: {count} (Double: {doubleCount})</p>
|
|
45
|
+
<button on:click={increment}>Increment</button>
|
|
46
|
+
</div>
|
|
47
|
+
\`\`\`
|
|
48
|
+
|
|
49
|
+
## Stores
|
|
50
|
+
\`\`\`typescript
|
|
51
|
+
// stores/auth.ts
|
|
52
|
+
import { writable, derived, readable } from 'svelte/store';
|
|
53
|
+
|
|
54
|
+
// Writable store
|
|
55
|
+
export const user = writable<User | null>(null);
|
|
56
|
+
|
|
57
|
+
// With custom methods
|
|
58
|
+
function createAuthStore() {
|
|
59
|
+
const { subscribe, set, update } = writable<User | null>(null);
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
subscribe,
|
|
63
|
+
login: async (email: string, password: string) => {
|
|
64
|
+
const user = await api.login(email, password);
|
|
65
|
+
set(user);
|
|
66
|
+
},
|
|
67
|
+
logout: () => set(null),
|
|
68
|
+
updateProfile: (data: Partial<User>) => {
|
|
69
|
+
update(u => u ? { ...u, ...data } : null);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const auth = createAuthStore();
|
|
75
|
+
|
|
76
|
+
// Derived store
|
|
77
|
+
export const isLoggedIn = derived(user, $user => !!$user);
|
|
78
|
+
|
|
79
|
+
// Readable store (external source)
|
|
80
|
+
export const time = readable(new Date(), (set) => {
|
|
81
|
+
const interval = setInterval(() => set(new Date()), 1000);
|
|
82
|
+
return () => clearInterval(interval);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Usage in component
|
|
86
|
+
<script>
|
|
87
|
+
import { user, isLoggedIn, auth } from './stores/auth';
|
|
88
|
+
|
|
89
|
+
// Auto-subscription with $
|
|
90
|
+
$: console.log('User:', $user);
|
|
91
|
+
$: console.log('Logged in:', $isLoggedIn);
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
{#if $isLoggedIn}
|
|
95
|
+
<p>Welcome, {$user.name}</p>
|
|
96
|
+
<button on:click={() => auth.logout()}>Logout</button>
|
|
97
|
+
{/if}
|
|
98
|
+
\`\`\`
|
|
99
|
+
|
|
100
|
+
## Event Handling
|
|
101
|
+
\`\`\`svelte
|
|
102
|
+
<!-- Basic events -->
|
|
103
|
+
<button on:click={handleClick}>Click</button>
|
|
104
|
+
<button on:click={() => count++}>Inline</button>
|
|
105
|
+
|
|
106
|
+
<!-- Event modifiers -->
|
|
107
|
+
<button on:click|once={handleClick}>Click once</button>
|
|
108
|
+
<button on:click|preventDefault={handleSubmit}>Submit</button>
|
|
109
|
+
<button on:click|stopPropagation={handleClick}>Stop bubbling</button>
|
|
110
|
+
|
|
111
|
+
<!-- Component events -->
|
|
112
|
+
<Child on:update={handleUpdate} />
|
|
113
|
+
<Child on:update /> <!-- Forward to parent -->
|
|
114
|
+
|
|
115
|
+
<!-- DOM event forwarding -->
|
|
116
|
+
<button on:click>
|
|
117
|
+
<slot />
|
|
118
|
+
</button>
|
|
119
|
+
\`\`\`
|
|
120
|
+
|
|
121
|
+
## Slots
|
|
122
|
+
\`\`\`svelte
|
|
123
|
+
<!-- Card.svelte -->
|
|
124
|
+
<div class="card">
|
|
125
|
+
<header>
|
|
126
|
+
<slot name="header">Default Header</slot>
|
|
127
|
+
</header>
|
|
128
|
+
|
|
129
|
+
<main>
|
|
130
|
+
<slot>Default content</slot>
|
|
131
|
+
</main>
|
|
132
|
+
|
|
133
|
+
<footer>
|
|
134
|
+
<slot name="footer" user={currentUser}>
|
|
135
|
+
<!-- Slot props -->
|
|
136
|
+
</slot>
|
|
137
|
+
</footer>
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<!-- Usage -->
|
|
141
|
+
<Card>
|
|
142
|
+
<svelte:fragment slot="header">
|
|
143
|
+
<h1>Title</h1>
|
|
144
|
+
</svelte:fragment>
|
|
145
|
+
|
|
146
|
+
<p>Main content</p>
|
|
147
|
+
|
|
148
|
+
<div slot="footer" let:user>
|
|
149
|
+
<p>Footer for {user.name}</p>
|
|
150
|
+
</div>
|
|
151
|
+
</Card>
|
|
152
|
+
\`\`\`
|
|
153
|
+
|
|
154
|
+
## Context API
|
|
155
|
+
\`\`\`svelte
|
|
156
|
+
<!-- Parent.svelte -->
|
|
157
|
+
<script>
|
|
158
|
+
import { setContext } from 'svelte';
|
|
159
|
+
import { writable } from 'svelte/store';
|
|
160
|
+
|
|
161
|
+
const theme = writable('dark');
|
|
162
|
+
setContext('theme', theme);
|
|
163
|
+
</script>
|
|
164
|
+
|
|
165
|
+
<!-- Child.svelte (any depth) -->
|
|
166
|
+
<script>
|
|
167
|
+
import { getContext } from 'svelte';
|
|
168
|
+
|
|
169
|
+
const theme = getContext('theme');
|
|
170
|
+
</script>
|
|
171
|
+
|
|
172
|
+
<div class={$theme}>Content</div>
|
|
173
|
+
\`\`\`
|
|
174
|
+
|
|
175
|
+
## Transitions & Animations
|
|
176
|
+
\`\`\`svelte
|
|
177
|
+
<script>
|
|
178
|
+
import { fade, fly, slide, scale } from 'svelte/transition';
|
|
179
|
+
import { flip } from 'svelte/animate';
|
|
180
|
+
|
|
181
|
+
let visible = true;
|
|
182
|
+
let items = [1, 2, 3];
|
|
183
|
+
</script>
|
|
184
|
+
|
|
185
|
+
<!-- Basic transition -->
|
|
186
|
+
{#if visible}
|
|
187
|
+
<div transition:fade>Fades in/out</div>
|
|
188
|
+
{/if}
|
|
189
|
+
|
|
190
|
+
<!-- In/out transitions -->
|
|
191
|
+
{#if visible}
|
|
192
|
+
<div in:fly={{ y: 200 }} out:fade>
|
|
193
|
+
Flies in, fades out
|
|
194
|
+
</div>
|
|
195
|
+
{/if}
|
|
196
|
+
|
|
197
|
+
<!-- Animate list reordering -->
|
|
198
|
+
{#each items as item (item)}
|
|
199
|
+
<div animate:flip={{ duration: 300 }}>
|
|
200
|
+
{item}
|
|
201
|
+
</div>
|
|
202
|
+
{/each}
|
|
203
|
+
\`\`\`
|
|
204
|
+
|
|
205
|
+
## Bindings
|
|
206
|
+
\`\`\`svelte
|
|
207
|
+
<!-- Two-way binding -->
|
|
208
|
+
<input bind:value={name} />
|
|
209
|
+
<input type="checkbox" bind:checked={accepted} />
|
|
210
|
+
<select bind:value={selected}>
|
|
211
|
+
<option value="a">A</option>
|
|
212
|
+
</select>
|
|
213
|
+
|
|
214
|
+
<!-- Group binding -->
|
|
215
|
+
{#each options as option}
|
|
216
|
+
<input type="checkbox" bind:group={selectedOptions} value={option} />
|
|
217
|
+
{/each}
|
|
218
|
+
|
|
219
|
+
<!-- Element binding -->
|
|
220
|
+
<canvas bind:this={canvasElement}></canvas>
|
|
221
|
+
|
|
222
|
+
<!-- Component binding -->
|
|
223
|
+
<Child bind:value />
|
|
224
|
+
\`\`\`
|
|
225
|
+
|
|
226
|
+
## ❌ DON'T
|
|
227
|
+
- Mutate objects/arrays without reassignment (won't trigger reactivity)
|
|
228
|
+
- Forget $ prefix when reading stores in script
|
|
229
|
+
- Use context for frequently changing data (use stores)
|
|
230
|
+
- Create stores inside components (create outside)
|
|
231
|
+
|
|
232
|
+
## ✅ DO
|
|
233
|
+
- Use \`$:\` for reactive statements
|
|
234
|
+
- Use stores for shared state
|
|
235
|
+
- Keep components small
|
|
236
|
+
- Use transitions for smooth UX
|
|
237
|
+
- Use context for static/rarely-changing data
|
|
238
|
+
- Reassign arrays/objects to trigger updates: \`items = [...items, newItem]\`
|