wcz-layout 7.6.0 → 7.6.2
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.
Potentially problematic release.
This version of wcz-layout might be problematic. Click here for more details.
- package/dist/{RouterListItemButton-CvfZk2zD.js → RouterListItemButton-DeaQB4ym.js} +1 -1
- package/dist/{RouterListItemButton-CvfZk2zD.js.map → RouterListItemButton-DeaQB4ym.js.map} +1 -1
- package/dist/components/core/Layout.d.ts +1 -1
- package/dist/components/core/navigation/NavigationList.d.ts +4 -4
- package/dist/components/core/navigation/NavigationListItem.d.ts +3 -3
- package/dist/components.js +2 -2
- package/dist/hooks.js +1 -1
- package/dist/index.js +617 -621
- package/dist/index.js.map +1 -1
- package/dist/lib/auth/msalClient.d.ts +8 -2
- package/dist/middleware.js +11 -11
- package/dist/models/Navigation.d.ts +23 -11
- package/dist/{queries-DzKY6YXz.js → queries-D-DV5lCw.js} +3 -3
- package/dist/{queries-DzKY6YXz.js.map → queries-D-DV5lCw.js.map} +1 -1
- package/dist/query.js +2 -2
- package/dist/{queryClient-uWNhcABg.js → queryClient-B__OEZ70.js} +1 -1
- package/dist/{queryClient-uWNhcABg.js.map → queryClient-B__OEZ70.js.map} +1 -1
- package/dist/{msalClient-BLrbVP5z.js → utils-C4oJ0tr5.js} +58 -47
- package/dist/utils-C4oJ0tr5.js.map +1 -0
- package/dist/utils.js +5 -5
- package/package.json +38 -6
- package/skills/api-routes/SKILL.md +251 -0
- package/skills/auth/SKILL.md +268 -0
- package/skills/data-grid/SKILL.md +229 -0
- package/skills/database-schema/SKILL.md +182 -0
- package/skills/dialogs-notifications/SKILL.md +241 -0
- package/skills/forms-validation/SKILL.md +331 -0
- package/skills/forms-validation/references/field-components.md +212 -0
- package/skills/layout-navigation/SKILL.md +259 -0
- package/skills/project-initialization/SKILL.md +181 -0
- package/skills/project-structure/SKILL.md +157 -0
- package/skills/tanstack-db-collections/SKILL.md +270 -0
- package/skills/ui-pages/SKILL.md +278 -0
- package/dist/msalClient-BLrbVP5z.js.map +0 -1
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dialogs-notifications
|
|
3
|
+
description: >
|
|
4
|
+
Use useDialogs for alert/confirm/custom dialogs and useNotification for
|
|
5
|
+
snackbar messages. Promise-based dialog API with DialogProps type for
|
|
6
|
+
custom dialogs. Notification severity (success, error, warning, info)
|
|
7
|
+
with configurable autoHideDuration. Activate when showing confirmations,
|
|
8
|
+
alerts, custom dialogs, or toast notifications.
|
|
9
|
+
type: core
|
|
10
|
+
library: wcz-layout
|
|
11
|
+
library_version: "7.6.1"
|
|
12
|
+
sources:
|
|
13
|
+
- "wcz-layout:src/hooks/DialogsHooks.tsx"
|
|
14
|
+
- "wcz-layout:src/providers/DialogsProvider.tsx"
|
|
15
|
+
- "wcz-layout:src/contexts/NotificationContext.tsx"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Dialogs & Notifications
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
Both hooks are available inside the `LayoutProvider` tree (which includes `DialogsProvider` and `NotificationProvider`):
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import { useDialogs, useNotification } from "wcz-layout/hooks";
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Core Patterns
|
|
29
|
+
|
|
30
|
+
### Confirmation dialog before deletion
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { useDialogs, useNotification } from "wcz-layout/hooks";
|
|
34
|
+
import { todoCollection } from "~/lib/db/collections/todoCollection";
|
|
35
|
+
import type { Todo } from "~/schemas/todo";
|
|
36
|
+
|
|
37
|
+
function DeleteButton({ todo }: { todo: Todo }) {
|
|
38
|
+
const { confirm } = useDialogs();
|
|
39
|
+
const { notify } = useNotification();
|
|
40
|
+
|
|
41
|
+
const handleDelete = async () => {
|
|
42
|
+
const confirmed = await confirm("Delete this item?");
|
|
43
|
+
if (confirmed) {
|
|
44
|
+
todoCollection.delete(todo);
|
|
45
|
+
notify("Item deleted", { severity: "success" });
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return <Button onClick={handleDelete}>Delete</Button>;
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Alert dialog
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
const { alert } = useDialogs();
|
|
57
|
+
|
|
58
|
+
await alert("Operation completed successfully", { title: "Success" });
|
|
59
|
+
// Resolves when user clicks OK
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Custom dialog
|
|
63
|
+
|
|
64
|
+
Define the dialog component with `DialogProps`:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import type { DialogProps } from "wcz-layout/hooks";
|
|
68
|
+
import {
|
|
69
|
+
Dialog,
|
|
70
|
+
DialogTitle,
|
|
71
|
+
DialogContent,
|
|
72
|
+
DialogActions,
|
|
73
|
+
Button,
|
|
74
|
+
TextField,
|
|
75
|
+
} from "@mui/material";
|
|
76
|
+
import { useState } from "react";
|
|
77
|
+
|
|
78
|
+
interface NotePayload {
|
|
79
|
+
title: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function NoteDialog({ payload, open, onClose }: DialogProps<NotePayload, string>) {
|
|
83
|
+
const [note, setNote] = useState("");
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<Dialog open={open} onClose={() => onClose("")} maxWidth="sm" fullWidth>
|
|
87
|
+
<DialogTitle>{payload.title}</DialogTitle>
|
|
88
|
+
<DialogContent>
|
|
89
|
+
<TextField
|
|
90
|
+
autoFocus
|
|
91
|
+
fullWidth
|
|
92
|
+
label="Note"
|
|
93
|
+
value={note}
|
|
94
|
+
onChange={(e) => setNote(e.target.value)}
|
|
95
|
+
/>
|
|
96
|
+
</DialogContent>
|
|
97
|
+
<DialogActions>
|
|
98
|
+
<Button onClick={() => onClose("")}>Cancel</Button>
|
|
99
|
+
<Button onClick={() => onClose(note)} variant="contained">
|
|
100
|
+
Save
|
|
101
|
+
</Button>
|
|
102
|
+
</DialogActions>
|
|
103
|
+
</Dialog>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Open the custom dialog and await its result:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
const { open } = useDialogs();
|
|
112
|
+
|
|
113
|
+
const note = await open(NoteDialog, { title: "Add a note" });
|
|
114
|
+
if (note) {
|
|
115
|
+
// user entered a note
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Dialog API reference
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
interface DialogHook {
|
|
123
|
+
alert(message: string, options?: { title?: string }): Promise<void>;
|
|
124
|
+
confirm(message: string, options?: { title?: string; cancelText?: string }): Promise<boolean>;
|
|
125
|
+
open<TPayload, TResult>(
|
|
126
|
+
Component: ComponentType<DialogProps<TPayload, TResult>>,
|
|
127
|
+
payload?: TPayload,
|
|
128
|
+
options?: DialogOptions,
|
|
129
|
+
): Promise<TResult>;
|
|
130
|
+
close<TResult>(dialogPromise: Promise<TResult>, result: TResult): Promise<TResult>;
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Notifications
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
const { notify } = useNotification();
|
|
138
|
+
|
|
139
|
+
notify("Changes saved", { severity: "success" });
|
|
140
|
+
notify("Something went wrong", { severity: "error" });
|
|
141
|
+
notify("Check your input", { severity: "warning" });
|
|
142
|
+
notify("New version available", { severity: "info" });
|
|
143
|
+
|
|
144
|
+
// Custom auto-hide duration (default: 5000ms)
|
|
145
|
+
notify("Processing...", { severity: "info", autoHideDuration: 10000 });
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### After form submission
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
const form = useLayoutForm({
|
|
152
|
+
defaultValues: { name: "" } as Todo,
|
|
153
|
+
validators: { onChange: TodoSchema },
|
|
154
|
+
onSubmit: ({ value }) => {
|
|
155
|
+
todoCollection.insert(value);
|
|
156
|
+
notify("Todo created", { severity: "success" });
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Common Mistakes
|
|
162
|
+
|
|
163
|
+
### HIGH Using window.confirm instead of useDialogs
|
|
164
|
+
|
|
165
|
+
Wrong:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
if (window.confirm("Delete this item?")) {
|
|
169
|
+
todoCollection.delete(todo);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Correct:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
const { confirm } = useDialogs();
|
|
177
|
+
|
|
178
|
+
const confirmed = await confirm("Delete this item?");
|
|
179
|
+
if (confirmed) {
|
|
180
|
+
todoCollection.delete(todo);
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`window.confirm` is a blocking browser dialog that ignores MUI theming and i18n. `useDialogs().confirm` provides themed, async/await MUI dialogs.
|
|
185
|
+
|
|
186
|
+
Source: wcz-layout:src/hooks/DialogsHooks.tsx
|
|
187
|
+
|
|
188
|
+
### CRITICAL Custom dialog not calling onClose
|
|
189
|
+
|
|
190
|
+
Wrong:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
function MyDialog({ payload, open }: DialogProps<string>) {
|
|
194
|
+
return (
|
|
195
|
+
<Dialog open={open}>
|
|
196
|
+
<Button onClick={() => {}}>Done</Button>
|
|
197
|
+
</Dialog>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Correct:
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
function MyDialog({ payload, open, onClose }: DialogProps<string>) {
|
|
206
|
+
return (
|
|
207
|
+
<Dialog open={open} onClose={() => onClose()}>
|
|
208
|
+
<Button onClick={() => onClose()}>Done</Button>
|
|
209
|
+
</Dialog>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`open()` returns a `Promise` that resolves when `onClose(result)` is called. Forgetting `onClose` leaves the promise hanging permanently and the dialog stuck open. Always wire both the `Dialog` `onClose` prop and button click handlers.
|
|
215
|
+
|
|
216
|
+
Source: wcz-layout:src/providers/DialogsProvider.tsx
|
|
217
|
+
|
|
218
|
+
### HIGH Using useDialogs outside LayoutProvider tree
|
|
219
|
+
|
|
220
|
+
Wrong:
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
// Component rendered outside LayoutProvider
|
|
224
|
+
function StandaloneComponent() {
|
|
225
|
+
const { alert } = useDialogs(); // context has unsafe default — crashes on call
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Correct:
|
|
230
|
+
|
|
231
|
+
Ensure all components using `useDialogs` or `useNotification` are rendered inside the `LayoutProvider` component tree (which wraps `DialogsProvider` and `NotificationProvider`).
|
|
232
|
+
|
|
233
|
+
`DialogsContext` uses an unsafe cast default value. Accessing the context outside the provider doesn't throw on `useContext`, but crashes when `alert`, `confirm`, or `open` are invoked.
|
|
234
|
+
|
|
235
|
+
Source: wcz-layout:src/contexts/DialogsContext.ts
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
See also:
|
|
240
|
+
|
|
241
|
+
- skills/forms-validation/SKILL.md — Form submissions typically show notifications or confirmations.
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: forms-validation
|
|
3
|
+
description: >
|
|
4
|
+
Build forms with useLayoutForm hook (primary) and withLayoutForm for
|
|
5
|
+
composable sub-forms. 13 pre-registered MUI field components via
|
|
6
|
+
form.AppField: TextField, NumberField, Autocomplete, Checkbox, Switch,
|
|
7
|
+
RadioGroup, Slider, DatePicker, DateRangePicker, TimePicker,
|
|
8
|
+
TimeRangePicker, DateTimePicker, DateTimeRangePicker. SubmitButton in
|
|
9
|
+
form.AppForm. Zod onChange validators. FormOmittedProps type. Activate
|
|
10
|
+
when creating or modifying forms with validation.
|
|
11
|
+
type: core
|
|
12
|
+
library: wcz-layout
|
|
13
|
+
library_version: "7.6.1"
|
|
14
|
+
sources:
|
|
15
|
+
- "wcz-layout:src/hooks/FormHooks.ts"
|
|
16
|
+
- "wcz-layout:src/components/form/"
|
|
17
|
+
- "wcz-layout:src/lib/utils.ts"
|
|
18
|
+
references:
|
|
19
|
+
- "references/field-components.md"
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# Forms & Validation
|
|
23
|
+
|
|
24
|
+
## Setup
|
|
25
|
+
|
|
26
|
+
Import the form hook and Zod schema:
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { useLayoutForm } from "wcz-layout/hooks";
|
|
30
|
+
import { TodoSchema } from "~/schemas/todo";
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Core Patterns
|
|
34
|
+
|
|
35
|
+
### Basic form with useLayoutForm
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { useLayoutForm } from "wcz-layout/hooks";
|
|
39
|
+
import { TodoSchema } from "~/schemas/todo";
|
|
40
|
+
import type { Todo } from "~/schemas/todo";
|
|
41
|
+
import { todoCollection } from "~/lib/db/collections/todoCollection";
|
|
42
|
+
import { uuidv7 } from "uuidv7";
|
|
43
|
+
|
|
44
|
+
function TodoForm() {
|
|
45
|
+
const form = useLayoutForm({
|
|
46
|
+
defaultValues: {
|
|
47
|
+
id: uuidv7(),
|
|
48
|
+
name: "",
|
|
49
|
+
description: "",
|
|
50
|
+
isCompleted: false,
|
|
51
|
+
} as Todo,
|
|
52
|
+
validators: {
|
|
53
|
+
onChange: TodoSchema,
|
|
54
|
+
},
|
|
55
|
+
onSubmit: ({ value }) => {
|
|
56
|
+
todoCollection.insert(value);
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<form.AppForm>
|
|
62
|
+
<form.AppField
|
|
63
|
+
name="name"
|
|
64
|
+
children={(field) => <field.TextField label="Name" required />}
|
|
65
|
+
/>
|
|
66
|
+
<form.AppField
|
|
67
|
+
name="description"
|
|
68
|
+
children={(field) => <field.TextField label="Description" multiline rows={3} />}
|
|
69
|
+
/>
|
|
70
|
+
<form.AppField
|
|
71
|
+
name="isCompleted"
|
|
72
|
+
children={(field) => <field.Checkbox label="Completed" />}
|
|
73
|
+
/>
|
|
74
|
+
<form.SubmitButton />
|
|
75
|
+
</form.AppForm>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Available field components
|
|
81
|
+
|
|
82
|
+
All 13 field components are accessed through `field.*` inside `form.AppField`:
|
|
83
|
+
|
|
84
|
+
| Component | MUI base | Use case |
|
|
85
|
+
| --------------------------- | --------------------------- | ----------------------------- |
|
|
86
|
+
| `field.TextField` | TextField | Text input, multiline |
|
|
87
|
+
| `field.NumberField` | TextField (number) | Numeric input |
|
|
88
|
+
| `field.Autocomplete` | Autocomplete | Search/select with options |
|
|
89
|
+
| `field.Checkbox` | FormControlLabel + Checkbox | Boolean toggle |
|
|
90
|
+
| `field.Switch` | FormControlLabel + Switch | Boolean toggle (switch) |
|
|
91
|
+
| `field.RadioGroup` | RadioGroup | Single selection from options |
|
|
92
|
+
| `field.Slider` | Slider | Range/value slider |
|
|
93
|
+
| `field.DatePicker` | DatePicker | Date only |
|
|
94
|
+
| `field.DateRangePicker` | DateRangePicker | Date range |
|
|
95
|
+
| `field.TimePicker` | TimePicker | Time only |
|
|
96
|
+
| `field.TimeRangePicker` | TimeRangePicker | Time range |
|
|
97
|
+
| `field.DateTimePicker` | DateTimePicker | Date + time |
|
|
98
|
+
| `field.DateTimeRangePicker` | DateTimeRangePicker | Date + time range |
|
|
99
|
+
|
|
100
|
+
See references/field-components.md for detailed prop surfaces.
|
|
101
|
+
|
|
102
|
+
### Edit form with existing data
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
function TodoEditForm({ todo }: { todo: Todo }) {
|
|
106
|
+
const form = useLayoutForm({
|
|
107
|
+
defaultValues: todo,
|
|
108
|
+
validators: {
|
|
109
|
+
onChange: TodoSchema,
|
|
110
|
+
},
|
|
111
|
+
onSubmit: ({ value }) => {
|
|
112
|
+
todoCollection.update(value);
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<form.AppForm>
|
|
118
|
+
<form.AppField
|
|
119
|
+
name="name"
|
|
120
|
+
children={(field) => <field.TextField label="Name" required />}
|
|
121
|
+
/>
|
|
122
|
+
<form.SubmitButton />
|
|
123
|
+
</form.AppForm>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Sub-form composition with withLayoutForm
|
|
129
|
+
|
|
130
|
+
Use `withLayoutForm` when splitting a large form into reusable sub-form components:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { withLayoutForm } from "wcz-layout/hooks";
|
|
134
|
+
import { TodoSchema } from "~/schemas/todo";
|
|
135
|
+
|
|
136
|
+
const TodoDetailsSubForm = withLayoutForm({
|
|
137
|
+
defaultValues: { name: "", description: "" },
|
|
138
|
+
render: ({ form }) => (
|
|
139
|
+
<>
|
|
140
|
+
<form.AppField
|
|
141
|
+
name="name"
|
|
142
|
+
children={(field) => <field.TextField label="Name" required />}
|
|
143
|
+
/>
|
|
144
|
+
<form.AppField
|
|
145
|
+
name="description"
|
|
146
|
+
children={(field) => <field.TextField label="Description" multiline />}
|
|
147
|
+
/>
|
|
148
|
+
</>
|
|
149
|
+
),
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Autocomplete with API data
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
<form.AppField
|
|
157
|
+
name="assigneeId"
|
|
158
|
+
children={(field) => (
|
|
159
|
+
<field.Autocomplete
|
|
160
|
+
label="Assignee"
|
|
161
|
+
options={users}
|
|
162
|
+
getOptionLabel={(user) => user.displayName}
|
|
163
|
+
isOptionEqualToValue={(option, value) => option.id === value.id}
|
|
164
|
+
/>
|
|
165
|
+
)}
|
|
166
|
+
/>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Date pickers
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
<form.AppField
|
|
173
|
+
name="dueDate"
|
|
174
|
+
children={(field) => <field.DatePicker label="Due Date" />}
|
|
175
|
+
/>
|
|
176
|
+
|
|
177
|
+
<form.AppField
|
|
178
|
+
name="dateRange"
|
|
179
|
+
children={(field) => (
|
|
180
|
+
<field.DateRangePicker
|
|
181
|
+
localeText={{ start: "Start Date", end: "End Date" }}
|
|
182
|
+
/>
|
|
183
|
+
)}
|
|
184
|
+
/>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Common Mistakes
|
|
188
|
+
|
|
189
|
+
### CRITICAL Using raw MUI TextField instead of form.AppField
|
|
190
|
+
|
|
191
|
+
Wrong:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
<TextField
|
|
195
|
+
name="title"
|
|
196
|
+
value={title}
|
|
197
|
+
onChange={(e) => setTitle(e.target.value)}
|
|
198
|
+
/>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Correct:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
<form.AppField
|
|
205
|
+
name="title"
|
|
206
|
+
children={(field) => <field.TextField label="Title" />}
|
|
207
|
+
/>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`useLayoutForm` pre-registers all field components. Using raw MUI inputs bypasses TanStack Form state management, validation, and error display.
|
|
211
|
+
|
|
212
|
+
Source: wcz-layout:src/hooks/FormHooks.ts
|
|
213
|
+
|
|
214
|
+
### HIGH Passing name/value/onChange to AppField components
|
|
215
|
+
|
|
216
|
+
Wrong:
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
<form.AppField
|
|
220
|
+
name="title"
|
|
221
|
+
children={(field) => (
|
|
222
|
+
<field.TextField
|
|
223
|
+
label="Title"
|
|
224
|
+
name="title"
|
|
225
|
+
value={field.state.value}
|
|
226
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
227
|
+
/>
|
|
228
|
+
)}
|
|
229
|
+
/>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Correct:
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
<form.AppField
|
|
236
|
+
name="title"
|
|
237
|
+
children={(field) => <field.TextField label="Title" />}
|
|
238
|
+
/>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
`FormOmittedProps` explicitly strips `name`, `value`, `onChange`, `onBlur`, `error`, `helperText`, `renderInput`, `type`, and `aria-label`. These are managed internally by `useFieldContext`. Passing them causes conflicts or is silently ignored.
|
|
242
|
+
|
|
243
|
+
Source: wcz-layout:src/lib/utils.ts
|
|
244
|
+
|
|
245
|
+
### HIGH Not wrapping SubmitButton in form.AppForm
|
|
246
|
+
|
|
247
|
+
Wrong:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
<div>
|
|
251
|
+
<form.AppField name="name" children={(field) => <field.TextField label="Name" />} />
|
|
252
|
+
<form.SubmitButton /> {/* Outside AppForm — crashes */}
|
|
253
|
+
</div>
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Correct:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
<form.AppForm>
|
|
260
|
+
<form.AppField name="name" children={(field) => <field.TextField label="Name" />} />
|
|
261
|
+
<form.SubmitButton />
|
|
262
|
+
</form.AppForm>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
`SubmitButton` uses `useFormContext()` to read `canSubmit` and `isSubmitting` state. It must be rendered inside `form.AppForm`.
|
|
266
|
+
|
|
267
|
+
Source: wcz-layout:src/components/form/FormSubmitButton.tsx
|
|
268
|
+
|
|
269
|
+
### HIGH Using useMemo or useCallback in form components
|
|
270
|
+
|
|
271
|
+
Wrong:
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
const handleSubmit = useCallback(() => form.handleSubmit(), [form]);
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Correct:
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
const handleSubmit = () => form.handleSubmit();
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
React Compiler handles memoization. Manual `useMemo` / `useCallback` is forbidden per project conventions.
|
|
284
|
+
|
|
285
|
+
Source: copilot-instructions.md
|
|
286
|
+
|
|
287
|
+
Cross-skill: See also skills/ui-pages/SKILL.md § Common Mistakes
|
|
288
|
+
|
|
289
|
+
### MEDIUM FormRadioGroup numeric values become strings
|
|
290
|
+
|
|
291
|
+
Wrong:
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
<field.RadioGroup
|
|
295
|
+
options={[
|
|
296
|
+
{ label: "Low", value: 1 },
|
|
297
|
+
{ label: "High", value: 2 },
|
|
298
|
+
]}
|
|
299
|
+
/>
|
|
300
|
+
// field.state.value is "1" not 1
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Correct:
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
<field.RadioGroup
|
|
307
|
+
options={[
|
|
308
|
+
{ label: "Low", value: "1" },
|
|
309
|
+
{ label: "High", value: "2" },
|
|
310
|
+
]}
|
|
311
|
+
/>
|
|
312
|
+
// Use string values consistently, or convert in onSubmit
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Radio group `onChange` always returns `event.target.value` as a string. Numeric option values round-trip as strings unless explicitly converted.
|
|
316
|
+
|
|
317
|
+
Source: wcz-layout:src/components/form/FormRadioGroup.tsx
|
|
318
|
+
|
|
319
|
+
### HIGH Tension: Type safety vs. rapid prototyping
|
|
320
|
+
|
|
321
|
+
Quick forms using `useState` + manual validation bypass the enforced pattern. Always use `useLayoutForm` + Zod schema derived from Drizzle — even for simple forms. The boilerplate pays off in type safety and consistency.
|
|
322
|
+
|
|
323
|
+
See also: skills/database-schema/SKILL.md § Common Mistakes
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
See also:
|
|
328
|
+
|
|
329
|
+
- skills/database-schema/SKILL.md — Zod schemas used as form validators.
|
|
330
|
+
- skills/dialogs-notifications/SKILL.md — Form submissions typically show notifications.
|
|
331
|
+
- skills/ui-pages/SKILL.md — Create/edit pages embed forms.
|