@smicolon/ai-kit 0.3.2 → 0.4.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 +73 -40
- package/dist/index.js +260 -126
- package/package.json +5 -5
- package/.claude-plugin/marketplace.json +0 -369
- package/packs/architect/CHANGELOG.md +0 -17
- package/packs/architect/README.md +0 -58
- package/packs/architect/agents/system-architect.md +0 -768
- package/packs/architect/commands/diagram-create.md +0 -300
- package/packs/better-auth/.mcp.json +0 -14
- package/packs/better-auth/CHANGELOG.md +0 -26
- package/packs/better-auth/README.md +0 -125
- package/packs/better-auth/agents/auth-architect.md +0 -278
- package/packs/better-auth/commands/auth-provider-add.md +0 -265
- package/packs/better-auth/commands/auth-setup.md +0 -298
- package/packs/better-auth/skills/auth-security/SKILL.md +0 -425
- package/packs/better-auth/skills/better-auth-patterns/SKILL.md +0 -455
- package/packs/dev-loop/CHANGELOG.md +0 -69
- package/packs/dev-loop/README.md +0 -155
- package/packs/dev-loop/commands/cancel-dev.md +0 -21
- package/packs/dev-loop/commands/dev-loop.md +0 -72
- package/packs/dev-loop/commands/dev-plan.md +0 -351
- package/packs/dev-loop/hooks/hooks.json +0 -15
- package/packs/dev-loop/hooks/stop-hook.sh +0 -178
- package/packs/dev-loop/scripts/setup-dev-loop.sh +0 -194
- package/packs/dev-loop/skills/tdd-planner/SKILL.md +0 -249
- package/packs/dev-loop/skills/tdd-planner/references/framework-patterns.md +0 -874
- package/packs/dev-loop/skills/tdd-planner/references/good-example.md +0 -260
- package/packs/dev-loop/skills/tdd-planner/references/plan-template.md +0 -275
- package/packs/django/CHANGELOG.md +0 -39
- package/packs/django/README.md +0 -92
- package/packs/django/agents/django-architect.md +0 -182
- package/packs/django/agents/django-builder.md +0 -250
- package/packs/django/agents/django-feature-based.md +0 -420
- package/packs/django/agents/django-reviewer.md +0 -253
- package/packs/django/agents/django-tester.md +0 -230
- package/packs/django/commands/api-endpoint.md +0 -285
- package/packs/django/commands/model-create.md +0 -178
- package/packs/django/commands/test-generate.md +0 -325
- package/packs/django/rules/migrations.md +0 -138
- package/packs/django/rules/models.md +0 -167
- package/packs/django/rules/serializers.md +0 -126
- package/packs/django/rules/services.md +0 -131
- package/packs/django/rules/tests.md +0 -140
- package/packs/django/rules/views.md +0 -102
- package/packs/django/skills/import-convention-enforcer/SKILL.md +0 -226
- package/packs/django/skills/import-convention-enforcer/patterns/django-imports.md +0 -343
- package/packs/django/skills/migration-safety-checker/SKILL.md +0 -375
- package/packs/django/skills/model-entity-validator/SKILL.md +0 -298
- package/packs/django/skills/performance-optimizer/SKILL.md +0 -447
- package/packs/django/skills/red-phase-verifier/SKILL.md +0 -180
- package/packs/django/skills/security-first-validator/SKILL.md +0 -435
- package/packs/django/skills/test-coverage-advisor/SKILL.md +0 -394
- package/packs/django/skills/test-validity-checker/SKILL.md +0 -194
- package/packs/failure-log/CHANGELOG.md +0 -20
- package/packs/failure-log/README.md +0 -168
- package/packs/failure-log/commands/failure-add.md +0 -106
- package/packs/failure-log/commands/failure-list.md +0 -89
- package/packs/failure-log/hooks/hooks.json +0 -16
- package/packs/failure-log/hooks/scripts/inject-failures.sh +0 -64
- package/packs/failure-log/skills/failure-log-manager/SKILL.md +0 -164
- package/packs/flutter/CHANGELOG.md +0 -19
- package/packs/flutter/README.md +0 -170
- package/packs/flutter/agents/flutter-architect.md +0 -166
- package/packs/flutter/agents/flutter-builder.md +0 -303
- package/packs/flutter/agents/release-manager.md +0 -355
- package/packs/flutter/commands/fastlane-setup.md +0 -188
- package/packs/flutter/commands/flutter-build.md +0 -90
- package/packs/flutter/commands/flutter-deploy.md +0 -133
- package/packs/flutter/commands/flutter-test.md +0 -117
- package/packs/flutter/commands/signing-setup.md +0 -209
- package/packs/flutter/hooks/hooks.json +0 -17
- package/packs/flutter/skills/fastlane-knowledge/SKILL.md +0 -193
- package/packs/flutter/skills/flutter-architecture/SKILL.md +0 -127
- package/packs/flutter/skills/store-publishing/SKILL.md +0 -163
- package/packs/hono/CHANGELOG.md +0 -19
- package/packs/hono/README.md +0 -143
- package/packs/hono/agents/hono-architect.md +0 -240
- package/packs/hono/agents/hono-builder.md +0 -285
- package/packs/hono/agents/hono-reviewer.md +0 -279
- package/packs/hono/agents/hono-tester.md +0 -346
- package/packs/hono/commands/middleware-create.md +0 -223
- package/packs/hono/commands/project-init.md +0 -306
- package/packs/hono/commands/route-create.md +0 -153
- package/packs/hono/commands/rpc-client.md +0 -263
- package/packs/hono/skills/cloudflare-bindings/SKILL.md +0 -408
- package/packs/hono/skills/hono-patterns/SKILL.md +0 -309
- package/packs/hono/skills/rpc-typesafe/SKILL.md +0 -388
- package/packs/hono/skills/zod-validation/SKILL.md +0 -332
- package/packs/nestjs/CHANGELOG.md +0 -29
- package/packs/nestjs/README.md +0 -75
- package/packs/nestjs/agents/nestjs-architect.md +0 -402
- package/packs/nestjs/agents/nestjs-builder.md +0 -301
- package/packs/nestjs/agents/nestjs-tester.md +0 -437
- package/packs/nestjs/commands/module-create.md +0 -369
- package/packs/nestjs/rules/controllers.md +0 -92
- package/packs/nestjs/rules/dto.md +0 -124
- package/packs/nestjs/rules/entities.md +0 -102
- package/packs/nestjs/rules/services.md +0 -106
- package/packs/nestjs/skills/barrel-export-manager/SKILL.md +0 -389
- package/packs/nestjs/skills/import-convention-enforcer/SKILL.md +0 -365
- package/packs/nextjs/CHANGELOG.md +0 -36
- package/packs/nextjs/README.md +0 -76
- package/packs/nextjs/agents/frontend-tester.md +0 -680
- package/packs/nextjs/agents/frontend-visual.md +0 -820
- package/packs/nextjs/agents/nextjs-architect.md +0 -331
- package/packs/nextjs/agents/nextjs-modular.md +0 -433
- package/packs/nextjs/commands/component-create.md +0 -398
- package/packs/nextjs/rules/api-routes.md +0 -129
- package/packs/nextjs/rules/components.md +0 -106
- package/packs/nextjs/rules/hooks.md +0 -132
- package/packs/nextjs/skills/accessibility-validator/SKILL.md +0 -445
- package/packs/nextjs/skills/import-convention-enforcer/SKILL.md +0 -399
- package/packs/nextjs/skills/react-form-validator/SKILL.md +0 -569
- package/packs/nuxtjs/CHANGELOG.md +0 -30
- package/packs/nuxtjs/README.md +0 -56
- package/packs/nuxtjs/agents/frontend-tester.md +0 -680
- package/packs/nuxtjs/agents/frontend-visual.md +0 -820
- package/packs/nuxtjs/agents/nuxtjs-architect.md +0 -537
- package/packs/nuxtjs/commands/component-create.md +0 -223
- package/packs/nuxtjs/rules/components.md +0 -101
- package/packs/nuxtjs/rules/composables.md +0 -118
- package/packs/nuxtjs/rules/server-routes.md +0 -127
- package/packs/nuxtjs/skills/accessibility-validator/SKILL.md +0 -183
- package/packs/nuxtjs/skills/import-convention-enforcer/SKILL.md +0 -196
- package/packs/nuxtjs/skills/veevalidate-form-validator/SKILL.md +0 -190
- package/packs/onboard/CHANGELOG.md +0 -22
- package/packs/onboard/README.md +0 -103
- package/packs/onboard/agents/onboard-guide.md +0 -118
- package/packs/onboard/commands/onboard.md +0 -313
- package/packs/onboard/skills/onboard-context-provider/SKILL.md +0 -98
- package/packs/tanstack-router/CHANGELOG.md +0 -30
- package/packs/tanstack-router/README.md +0 -113
- package/packs/tanstack-router/agents/tanstack-architect.md +0 -173
- package/packs/tanstack-router/agents/tanstack-builder.md +0 -360
- package/packs/tanstack-router/agents/tanstack-tester.md +0 -454
- package/packs/tanstack-router/commands/form-create.md +0 -313
- package/packs/tanstack-router/commands/query-create.md +0 -263
- package/packs/tanstack-router/commands/route-create.md +0 -190
- package/packs/tanstack-router/commands/table-create.md +0 -413
- package/packs/tanstack-router/skills/ai-patterns/SKILL.md +0 -370
- package/packs/tanstack-router/skills/db-patterns/SKILL.md +0 -346
- package/packs/tanstack-router/skills/devtools-patterns/SKILL.md +0 -415
- package/packs/tanstack-router/skills/form-patterns/SKILL.md +0 -425
- package/packs/tanstack-router/skills/pacer-patterns/SKILL.md +0 -341
- package/packs/tanstack-router/skills/query-patterns/SKILL.md +0 -359
- package/packs/tanstack-router/skills/router-patterns/SKILL.md +0 -285
- package/packs/tanstack-router/skills/store-patterns/SKILL.md +0 -351
- package/packs/tanstack-router/skills/table-patterns/SKILL.md +0 -531
- package/packs/tanstack-router/skills/tanstack-conventions/SKILL.md +0 -428
- package/packs/tanstack-router/skills/virtual-patterns/SKILL.md +0 -490
- package/packs/worktree/CHANGELOG.md +0 -45
- package/packs/worktree/README.md +0 -219
- package/packs/worktree/commands/wt.md +0 -93
- package/packs/worktree/scripts/wt.sh +0 -957
- package/packs/worktree/skills/worktree-manager/SKILL.md +0 -113
|
@@ -1,425 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: TanStack Form Patterns
|
|
3
|
-
description: >-
|
|
4
|
-
Auto-enforce TanStack Form best practices with Zod validation. Activates when
|
|
5
|
-
creating forms, handling form state, implementing validation, or building
|
|
6
|
-
input components in React applications.
|
|
7
|
-
version: 1.0.0
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# TanStack Form Patterns
|
|
11
|
-
|
|
12
|
-
This skill enforces TanStack Form best practices for type-safe forms with Zod validation.
|
|
13
|
-
|
|
14
|
-
## Basic Form Setup
|
|
15
|
-
|
|
16
|
-
```typescript
|
|
17
|
-
import { useForm } from '@tanstack/react-form'
|
|
18
|
-
import { zodValidator } from '@tanstack/zod-form-adapter'
|
|
19
|
-
import { z } from 'zod'
|
|
20
|
-
|
|
21
|
-
const postSchema = z.object({
|
|
22
|
-
title: z.string().min(3, 'Title must be at least 3 characters'),
|
|
23
|
-
content: z.string().min(10, 'Content must be at least 10 characters'),
|
|
24
|
-
published: z.boolean().default(false),
|
|
25
|
-
tags: z.array(z.string()).min(1, 'At least one tag required'),
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
type PostFormData = z.infer<typeof postSchema>
|
|
29
|
-
|
|
30
|
-
export function PostForm({ onSubmit }: { onSubmit: (data: PostFormData) => void }) {
|
|
31
|
-
const form = useForm({
|
|
32
|
-
defaultValues: {
|
|
33
|
-
title: '',
|
|
34
|
-
content: '',
|
|
35
|
-
published: false,
|
|
36
|
-
tags: [],
|
|
37
|
-
} satisfies PostFormData,
|
|
38
|
-
onSubmit: async ({ value }) => {
|
|
39
|
-
onSubmit(value)
|
|
40
|
-
},
|
|
41
|
-
validatorAdapter: zodValidator(),
|
|
42
|
-
validators: {
|
|
43
|
-
onChange: postSchema,
|
|
44
|
-
},
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<form
|
|
49
|
-
onSubmit={(e) => {
|
|
50
|
-
e.preventDefault()
|
|
51
|
-
form.handleSubmit()
|
|
52
|
-
}}
|
|
53
|
-
>
|
|
54
|
-
{/* Form fields */}
|
|
55
|
-
</form>
|
|
56
|
-
)
|
|
57
|
-
}
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
## Field Components
|
|
61
|
-
|
|
62
|
-
### Text Input Field
|
|
63
|
-
```typescript
|
|
64
|
-
<form.Field
|
|
65
|
-
name="title"
|
|
66
|
-
children={(field) => (
|
|
67
|
-
<div className="field">
|
|
68
|
-
<label htmlFor={field.name}>Title</label>
|
|
69
|
-
<input
|
|
70
|
-
id={field.name}
|
|
71
|
-
name={field.name}
|
|
72
|
-
value={field.state.value}
|
|
73
|
-
onChange={(e) => field.handleChange(e.target.value)}
|
|
74
|
-
onBlur={field.handleBlur}
|
|
75
|
-
aria-invalid={field.state.meta.errors.length > 0}
|
|
76
|
-
aria-describedby={`${field.name}-error`}
|
|
77
|
-
/>
|
|
78
|
-
{field.state.meta.isTouched && field.state.meta.errors.length > 0 && (
|
|
79
|
-
<span id={`${field.name}-error`} className="error">
|
|
80
|
-
{field.state.meta.errors[0]}
|
|
81
|
-
</span>
|
|
82
|
-
)}
|
|
83
|
-
</div>
|
|
84
|
-
)}
|
|
85
|
-
/>
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### Textarea Field
|
|
89
|
-
```typescript
|
|
90
|
-
<form.Field
|
|
91
|
-
name="content"
|
|
92
|
-
children={(field) => (
|
|
93
|
-
<div className="field">
|
|
94
|
-
<label htmlFor={field.name}>Content</label>
|
|
95
|
-
<textarea
|
|
96
|
-
id={field.name}
|
|
97
|
-
name={field.name}
|
|
98
|
-
value={field.state.value}
|
|
99
|
-
onChange={(e) => field.handleChange(e.target.value)}
|
|
100
|
-
onBlur={field.handleBlur}
|
|
101
|
-
rows={5}
|
|
102
|
-
/>
|
|
103
|
-
{field.state.meta.isTouched && field.state.meta.errors.length > 0 && (
|
|
104
|
-
<span className="error">{field.state.meta.errors[0]}</span>
|
|
105
|
-
)}
|
|
106
|
-
</div>
|
|
107
|
-
)}
|
|
108
|
-
/>
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
### Checkbox Field
|
|
112
|
-
```typescript
|
|
113
|
-
<form.Field
|
|
114
|
-
name="published"
|
|
115
|
-
children={(field) => (
|
|
116
|
-
<div className="field-checkbox">
|
|
117
|
-
<input
|
|
118
|
-
id={field.name}
|
|
119
|
-
type="checkbox"
|
|
120
|
-
checked={field.state.value}
|
|
121
|
-
onChange={(e) => field.handleChange(e.target.checked)}
|
|
122
|
-
/>
|
|
123
|
-
<label htmlFor={field.name}>Published</label>
|
|
124
|
-
</div>
|
|
125
|
-
)}
|
|
126
|
-
/>
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### Select Field
|
|
130
|
-
```typescript
|
|
131
|
-
<form.Field
|
|
132
|
-
name="category"
|
|
133
|
-
children={(field) => (
|
|
134
|
-
<div className="field">
|
|
135
|
-
<label htmlFor={field.name}>Category</label>
|
|
136
|
-
<select
|
|
137
|
-
id={field.name}
|
|
138
|
-
value={field.state.value}
|
|
139
|
-
onChange={(e) => field.handleChange(e.target.value)}
|
|
140
|
-
onBlur={field.handleBlur}
|
|
141
|
-
>
|
|
142
|
-
<option value="">Select category...</option>
|
|
143
|
-
<option value="tech">Technology</option>
|
|
144
|
-
<option value="business">Business</option>
|
|
145
|
-
<option value="lifestyle">Lifestyle</option>
|
|
146
|
-
</select>
|
|
147
|
-
</div>
|
|
148
|
-
)}
|
|
149
|
-
/>
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
## Array Fields
|
|
153
|
-
|
|
154
|
-
```typescript
|
|
155
|
-
const tagsSchema = z.array(z.string().min(1)).min(1, 'At least one tag')
|
|
156
|
-
|
|
157
|
-
<form.Field
|
|
158
|
-
name="tags"
|
|
159
|
-
mode="array"
|
|
160
|
-
children={(field) => (
|
|
161
|
-
<div className="field">
|
|
162
|
-
<label>Tags</label>
|
|
163
|
-
{field.state.value.map((_, index) => (
|
|
164
|
-
<div key={index} className="tag-input">
|
|
165
|
-
<form.Field
|
|
166
|
-
name={`tags[${index}]`}
|
|
167
|
-
children={(tagField) => (
|
|
168
|
-
<input
|
|
169
|
-
value={tagField.state.value}
|
|
170
|
-
onChange={(e) => tagField.handleChange(e.target.value)}
|
|
171
|
-
/>
|
|
172
|
-
)}
|
|
173
|
-
/>
|
|
174
|
-
<button
|
|
175
|
-
type="button"
|
|
176
|
-
onClick={() => field.removeValue(index)}
|
|
177
|
-
>
|
|
178
|
-
Remove
|
|
179
|
-
</button>
|
|
180
|
-
</div>
|
|
181
|
-
))}
|
|
182
|
-
<button
|
|
183
|
-
type="button"
|
|
184
|
-
onClick={() => field.pushValue('')}
|
|
185
|
-
>
|
|
186
|
-
Add Tag
|
|
187
|
-
</button>
|
|
188
|
-
{field.state.meta.errors.length > 0 && (
|
|
189
|
-
<span className="error">{field.state.meta.errors[0]}</span>
|
|
190
|
-
)}
|
|
191
|
-
</div>
|
|
192
|
-
)}
|
|
193
|
-
/>
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
## Async Validation
|
|
197
|
-
|
|
198
|
-
```typescript
|
|
199
|
-
const usernameSchema = z.string().min(3)
|
|
200
|
-
|
|
201
|
-
<form.Field
|
|
202
|
-
name="username"
|
|
203
|
-
validators={{
|
|
204
|
-
onChange: usernameSchema,
|
|
205
|
-
onChangeAsyncDebounceMs: 500,
|
|
206
|
-
onChangeAsync: async ({ value }) => {
|
|
207
|
-
const exists = await checkUsernameExists(value)
|
|
208
|
-
if (exists) {
|
|
209
|
-
return 'Username already taken'
|
|
210
|
-
}
|
|
211
|
-
return undefined
|
|
212
|
-
},
|
|
213
|
-
}}
|
|
214
|
-
children={(field) => (
|
|
215
|
-
<div className="field">
|
|
216
|
-
<label htmlFor={field.name}>Username</label>
|
|
217
|
-
<input
|
|
218
|
-
id={field.name}
|
|
219
|
-
value={field.state.value}
|
|
220
|
-
onChange={(e) => field.handleChange(e.target.value)}
|
|
221
|
-
onBlur={field.handleBlur}
|
|
222
|
-
/>
|
|
223
|
-
{field.state.meta.isValidating && <span>Checking...</span>}
|
|
224
|
-
{field.state.meta.errors.length > 0 && (
|
|
225
|
-
<span className="error">{field.state.meta.errors[0]}</span>
|
|
226
|
-
)}
|
|
227
|
-
</div>
|
|
228
|
-
)}
|
|
229
|
-
/>
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
## Form with Mutation
|
|
233
|
-
|
|
234
|
-
```typescript
|
|
235
|
-
import { useCreatePost } from '@/features/posts/hooks'
|
|
236
|
-
|
|
237
|
-
export function CreatePostForm() {
|
|
238
|
-
const createPost = useCreatePost()
|
|
239
|
-
|
|
240
|
-
const form = useForm({
|
|
241
|
-
defaultValues: { title: '', content: '', published: false },
|
|
242
|
-
onSubmit: async ({ value }) => {
|
|
243
|
-
await createPost.mutateAsync(value)
|
|
244
|
-
},
|
|
245
|
-
validatorAdapter: zodValidator(),
|
|
246
|
-
validators: {
|
|
247
|
-
onChange: postSchema,
|
|
248
|
-
},
|
|
249
|
-
})
|
|
250
|
-
|
|
251
|
-
return (
|
|
252
|
-
<form onSubmit={(e) => { e.preventDefault(); form.handleSubmit() }}>
|
|
253
|
-
{/* Fields */}
|
|
254
|
-
<form.Subscribe
|
|
255
|
-
selector={(state) => [state.canSubmit, state.isSubmitting]}
|
|
256
|
-
children={([canSubmit, isSubmitting]) => (
|
|
257
|
-
<button type="submit" disabled={!canSubmit || isSubmitting}>
|
|
258
|
-
{isSubmitting ? 'Creating...' : 'Create Post'}
|
|
259
|
-
</button>
|
|
260
|
-
)}
|
|
261
|
-
/>
|
|
262
|
-
{createPost.isError && (
|
|
263
|
-
<div className="error">{createPost.error.message}</div>
|
|
264
|
-
)}
|
|
265
|
-
</form>
|
|
266
|
-
)
|
|
267
|
-
}
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
## Edit Form with Initial Data
|
|
271
|
-
|
|
272
|
-
```typescript
|
|
273
|
-
interface EditPostFormProps {
|
|
274
|
-
post: Post
|
|
275
|
-
onSuccess: () => void
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
export function EditPostForm({ post, onSuccess }: EditPostFormProps) {
|
|
279
|
-
const updatePost = useUpdatePost()
|
|
280
|
-
|
|
281
|
-
const form = useForm({
|
|
282
|
-
defaultValues: {
|
|
283
|
-
title: post.title,
|
|
284
|
-
content: post.content,
|
|
285
|
-
published: post.published,
|
|
286
|
-
},
|
|
287
|
-
onSubmit: async ({ value }) => {
|
|
288
|
-
await updatePost.mutateAsync({ id: post.id, ...value })
|
|
289
|
-
onSuccess()
|
|
290
|
-
},
|
|
291
|
-
validatorAdapter: zodValidator(),
|
|
292
|
-
validators: {
|
|
293
|
-
onChange: postSchema,
|
|
294
|
-
},
|
|
295
|
-
})
|
|
296
|
-
|
|
297
|
-
return (
|
|
298
|
-
<form onSubmit={(e) => { e.preventDefault(); form.handleSubmit() }}>
|
|
299
|
-
{/* Fields */}
|
|
300
|
-
</form>
|
|
301
|
-
)
|
|
302
|
-
}
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
## Reusable Field Component
|
|
306
|
-
|
|
307
|
-
```typescript
|
|
308
|
-
// components/ui/FormField.tsx
|
|
309
|
-
import type { FieldApi } from '@tanstack/react-form'
|
|
310
|
-
|
|
311
|
-
interface FormFieldProps<T> {
|
|
312
|
-
field: FieldApi<any, any, any, any, T>
|
|
313
|
-
label: string
|
|
314
|
-
type?: 'text' | 'email' | 'password' | 'textarea'
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
export function FormField<T extends string>({
|
|
318
|
-
field,
|
|
319
|
-
label,
|
|
320
|
-
type = 'text',
|
|
321
|
-
}: FormFieldProps<T>) {
|
|
322
|
-
const hasError = field.state.meta.isTouched && field.state.meta.errors.length > 0
|
|
323
|
-
|
|
324
|
-
const inputProps = {
|
|
325
|
-
id: field.name,
|
|
326
|
-
name: field.name,
|
|
327
|
-
value: field.state.value,
|
|
328
|
-
onChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) =>
|
|
329
|
-
field.handleChange(e.target.value as T),
|
|
330
|
-
onBlur: field.handleBlur,
|
|
331
|
-
'aria-invalid': hasError,
|
|
332
|
-
'aria-describedby': hasError ? `${field.name}-error` : undefined,
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
return (
|
|
336
|
-
<div className="form-field">
|
|
337
|
-
<label htmlFor={field.name}>{label}</label>
|
|
338
|
-
{type === 'textarea' ? (
|
|
339
|
-
<textarea {...inputProps} />
|
|
340
|
-
) : (
|
|
341
|
-
<input type={type} {...inputProps} />
|
|
342
|
-
)}
|
|
343
|
-
{hasError && (
|
|
344
|
-
<span id={`${field.name}-error`} className="error" role="alert">
|
|
345
|
-
{field.state.meta.errors[0]}
|
|
346
|
-
</span>
|
|
347
|
-
)}
|
|
348
|
-
</div>
|
|
349
|
-
)
|
|
350
|
-
}
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
## Form State Subscription
|
|
354
|
-
|
|
355
|
-
```typescript
|
|
356
|
-
// Subscribe to specific form state
|
|
357
|
-
<form.Subscribe
|
|
358
|
-
selector={(state) => ({
|
|
359
|
-
canSubmit: state.canSubmit,
|
|
360
|
-
isSubmitting: state.isSubmitting,
|
|
361
|
-
isDirty: state.isDirty,
|
|
362
|
-
errors: state.errors,
|
|
363
|
-
})}
|
|
364
|
-
children={({ canSubmit, isSubmitting, isDirty, errors }) => (
|
|
365
|
-
<div>
|
|
366
|
-
{isDirty && <span>Unsaved changes</span>}
|
|
367
|
-
{errors.length > 0 && <span>Form has errors</span>}
|
|
368
|
-
<button type="submit" disabled={!canSubmit || isSubmitting}>
|
|
369
|
-
{isSubmitting ? 'Saving...' : 'Save'}
|
|
370
|
-
</button>
|
|
371
|
-
</div>
|
|
372
|
-
)}
|
|
373
|
-
/>
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
## Conventions to Enforce
|
|
377
|
-
|
|
378
|
-
1. **Zod for validation** - Always use `zodValidator()` adapter
|
|
379
|
-
2. **Type inference** - Use `z.infer<typeof schema>` for form types
|
|
380
|
-
3. **Accessible forms** - Include labels, aria attributes, error associations
|
|
381
|
-
4. **Touch-based errors** - Show errors only after field interaction
|
|
382
|
-
5. **Submit handling** - Prevent default, use `form.handleSubmit()`
|
|
383
|
-
6. **Mutation integration** - Connect forms to TanStack Query mutations
|
|
384
|
-
7. **Default values** - Always provide with `satisfies` type check
|
|
385
|
-
|
|
386
|
-
## Anti-Patterns to Block
|
|
387
|
-
|
|
388
|
-
```typescript
|
|
389
|
-
// ❌ WRONG: No validation
|
|
390
|
-
const form = useForm({
|
|
391
|
-
defaultValues: { title: '' },
|
|
392
|
-
onSubmit: ({ value }) => save(value),
|
|
393
|
-
})
|
|
394
|
-
|
|
395
|
-
// ✅ CORRECT: Zod validation
|
|
396
|
-
const form = useForm({
|
|
397
|
-
defaultValues: { title: '' },
|
|
398
|
-
validatorAdapter: zodValidator(),
|
|
399
|
-
validators: { onChange: schema },
|
|
400
|
-
onSubmit: ({ value }) => save(value),
|
|
401
|
-
})
|
|
402
|
-
|
|
403
|
-
// ❌ WRONG: Missing error display
|
|
404
|
-
<input value={field.state.value} onChange={(e) => field.handleChange(e.target.value)} />
|
|
405
|
-
|
|
406
|
-
// ✅ CORRECT: Error handling
|
|
407
|
-
<input
|
|
408
|
-
value={field.state.value}
|
|
409
|
-
onChange={(e) => field.handleChange(e.target.value)}
|
|
410
|
-
aria-invalid={field.state.meta.errors.length > 0}
|
|
411
|
-
/>
|
|
412
|
-
{field.state.meta.errors[0] && <span className="error">{field.state.meta.errors[0]}</span>}
|
|
413
|
-
|
|
414
|
-
// ❌ WRONG: No accessibility
|
|
415
|
-
<div>
|
|
416
|
-
<span>Email</span>
|
|
417
|
-
<input />
|
|
418
|
-
</div>
|
|
419
|
-
|
|
420
|
-
// ✅ CORRECT: Proper labeling
|
|
421
|
-
<div>
|
|
422
|
-
<label htmlFor="email">Email</label>
|
|
423
|
-
<input id="email" aria-describedby="email-error" />
|
|
424
|
-
</div>
|
|
425
|
-
```
|