wcz-layout 7.6.1 → 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.
- package/dist/components/core/navigation/NavigationList.d.ts +4 -4
- package/dist/components/core/navigation/NavigationListItem.d.ts +3 -3
- package/dist/index.js.map +1 -1
- package/dist/models/Navigation.d.ts +23 -11
- package/package.json +8 -2
- 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
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: data-grid
|
|
3
|
+
description: >
|
|
4
|
+
MUI X DataGrid Premium with wcz-layout helpers. ChipInputCell for chip
|
|
5
|
+
rendering in cells (getLabel for complex objects). EditableColumnHeader
|
|
6
|
+
for pencil icon headers. RouterGridActionsCellItem for type-safe row
|
|
7
|
+
action navigation. Column definitions with renderCell/renderHeader.
|
|
8
|
+
Feed rows from useLiveQuery collection data. Activate when building
|
|
9
|
+
data tables or grids.
|
|
10
|
+
type: composition
|
|
11
|
+
library: wcz-layout
|
|
12
|
+
library_version: "7.6.1"
|
|
13
|
+
requires:
|
|
14
|
+
- tanstack-db-collections
|
|
15
|
+
- ui-pages
|
|
16
|
+
sources:
|
|
17
|
+
- "wcz-layout:src/components/data-grid/ChipInputCell.tsx"
|
|
18
|
+
- "wcz-layout:src/components/data-grid/EditableColumnHeader.tsx"
|
|
19
|
+
- "wcz-layout:src/components/router/RouterGridActionsCellItem.tsx"
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# Data Grid
|
|
23
|
+
|
|
24
|
+
## Setup
|
|
25
|
+
|
|
26
|
+
Install `@mui/x-data-grid-premium` (peer dependency). Use `DataGridPremium`, not the community `DataGrid`.
|
|
27
|
+
|
|
28
|
+
## Core Patterns
|
|
29
|
+
|
|
30
|
+
### Basic data grid with collection data
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { DataGridPremium, type GridColDef } from "@mui/x-data-grid-premium";
|
|
34
|
+
import { useLiveQuery } from "@tanstack/react-db";
|
|
35
|
+
import { todoCollection } from "~/lib/db/collections/todoCollection";
|
|
36
|
+
import type { Todo } from "~/schemas/todo";
|
|
37
|
+
|
|
38
|
+
const columns: GridColDef<Todo>[] = [
|
|
39
|
+
{ field: "name", headerName: "Name", flex: 1 },
|
|
40
|
+
{ field: "isCompleted", headerName: "Completed", type: "boolean", width: 120 },
|
|
41
|
+
{ field: "createdAt", headerName: "Created", type: "dateTime", width: 180 },
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
function TodoGrid() {
|
|
45
|
+
const { data: todos } = useLiveQuery((query) =>
|
|
46
|
+
query.from({ todos: todoCollection })
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<DataGridPremium
|
|
51
|
+
rows={todos}
|
|
52
|
+
columns={columns}
|
|
53
|
+
getRowId={(row) => row.id}
|
|
54
|
+
autoHeight
|
|
55
|
+
/>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Row actions with RouterGridActionsCellItem
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { RouterGridActionsCellItem } from "wcz-layout/components";
|
|
64
|
+
import EditIcon from "@mui/icons-material/Edit";
|
|
65
|
+
import DeleteIcon from "@mui/icons-material/Delete";
|
|
66
|
+
import type { GridColDef } from "@mui/x-data-grid-premium";
|
|
67
|
+
|
|
68
|
+
const columns: GridColDef<Todo>[] = [
|
|
69
|
+
{ field: "name", headerName: "Name", flex: 1 },
|
|
70
|
+
{
|
|
71
|
+
field: "actions",
|
|
72
|
+
type: "actions",
|
|
73
|
+
width: 100,
|
|
74
|
+
getActions: (params) => [
|
|
75
|
+
<RouterGridActionsCellItem
|
|
76
|
+
key="edit"
|
|
77
|
+
icon={<EditIcon />}
|
|
78
|
+
label="Edit"
|
|
79
|
+
to="/todos/edit/$id"
|
|
80
|
+
params={{ id: params.row.id }}
|
|
81
|
+
/>,
|
|
82
|
+
<RouterGridActionsCellItem
|
|
83
|
+
key="delete"
|
|
84
|
+
icon={<DeleteIcon />}
|
|
85
|
+
label="Delete"
|
|
86
|
+
onClick={() => handleDelete(params.row)}
|
|
87
|
+
showInMenu
|
|
88
|
+
/>,
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`RouterGridActionsCellItem` wraps MUI's `GridActionsCellItem` with TanStack Router's `createLink` for type-safe SPA navigation.
|
|
95
|
+
|
|
96
|
+
### ChipInputCell for array/tag columns
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { ChipInputCell } from "wcz-layout/components";
|
|
100
|
+
import type { GridColDef } from "@mui/x-data-grid-premium";
|
|
101
|
+
|
|
102
|
+
const columns: GridColDef<Todo>[] = [
|
|
103
|
+
{
|
|
104
|
+
field: "tags",
|
|
105
|
+
headerName: "Tags",
|
|
106
|
+
flex: 1,
|
|
107
|
+
renderCell: (params) => <ChipInputCell params={params} />,
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
For complex objects (not plain strings), provide `getLabel`:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
renderCell: (params) => (
|
|
116
|
+
<ChipInputCell
|
|
117
|
+
params={params}
|
|
118
|
+
getLabel={(item) => item.displayName}
|
|
119
|
+
slotProps={{ size: "small", color: "primary" }}
|
|
120
|
+
/>
|
|
121
|
+
),
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`ChipInputCell` renders a horizontal scrollable stack of `Chip` components. It handles both scalar and array values automatically.
|
|
125
|
+
|
|
126
|
+
### EditableColumnHeader
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { EditableColumnHeader } from "wcz-layout/components";
|
|
130
|
+
|
|
131
|
+
const columns: GridColDef<Todo>[] = [
|
|
132
|
+
{
|
|
133
|
+
field: "name",
|
|
134
|
+
headerName: "Name",
|
|
135
|
+
flex: 1,
|
|
136
|
+
editable: true,
|
|
137
|
+
renderHeader: (params) => <EditableColumnHeader {...params} />,
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Renders the column header text with a trailing pencil (Edit) icon to visually indicate editable columns. Purely visual — no interaction.
|
|
143
|
+
|
|
144
|
+
## Common Mistakes
|
|
145
|
+
|
|
146
|
+
### HIGH Using DataGrid instead of DataGridPremium
|
|
147
|
+
|
|
148
|
+
Wrong:
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { DataGrid } from "@mui/x-data-grid";
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Correct:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { DataGridPremium } from "@mui/x-data-grid-premium";
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The library peer-depends on `@mui/x-data-grid-premium`. Using the community `DataGrid` loses Premium features like column grouping, row grouping, tree data, and aggregation.
|
|
161
|
+
|
|
162
|
+
Source: package.json peerDependencies
|
|
163
|
+
|
|
164
|
+
### HIGH Using GridActionsCellItem instead of RouterGridActionsCellItem
|
|
165
|
+
|
|
166
|
+
Wrong:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { GridActionsCellItem } from "@mui/x-data-grid-premium";
|
|
170
|
+
|
|
171
|
+
<GridActionsCellItem
|
|
172
|
+
icon={<EditIcon />}
|
|
173
|
+
label="Edit"
|
|
174
|
+
onClick={() => navigate({ to: `/todos/${id}` })}
|
|
175
|
+
/>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Correct:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { RouterGridActionsCellItem } from "wcz-layout/components";
|
|
182
|
+
|
|
183
|
+
<RouterGridActionsCellItem
|
|
184
|
+
icon={<EditIcon />}
|
|
185
|
+
label="Edit"
|
|
186
|
+
to="/todos/edit/$id"
|
|
187
|
+
params={{ id }}
|
|
188
|
+
/>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Plain `GridActionsCellItem` doesn't support TanStack Router navigation. `RouterGridActionsCellItem` wraps it with `createLink` for type-safe routing without manual `onClick` + `navigate`.
|
|
192
|
+
|
|
193
|
+
Source: wcz-layout:src/components/router/RouterGridActionsCellItem.tsx
|
|
194
|
+
|
|
195
|
+
### MEDIUM Not passing getLabel to ChipInputCell for complex objects
|
|
196
|
+
|
|
197
|
+
Wrong:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// params.value is Array<{ id: string; name: string }>
|
|
201
|
+
<ChipInputCell params={params} />
|
|
202
|
+
// Renders [object Object] chips
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Correct:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
<ChipInputCell
|
|
209
|
+
params={params}
|
|
210
|
+
getLabel={(item) => item.name}
|
|
211
|
+
/>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`ChipInputCell` defaults `getLabel` to identity (value as-is). For objects, you must provide `getLabel` to extract the display string.
|
|
215
|
+
|
|
216
|
+
Source: wcz-layout:src/components/data-grid/ChipInputCell.tsx
|
|
217
|
+
|
|
218
|
+
### HIGH Tension: MUI component API vs. TanStack Router types
|
|
219
|
+
|
|
220
|
+
`RouterGridActionsCellItem` merges MUI grid action props with TanStack Router `LinkProps`. Use `to` + `params` for internal navigation. Using `onClick` + `navigate` bypasses the type-safe link and router prefetching.
|
|
221
|
+
|
|
222
|
+
See also: skills/ui-pages/SKILL.md § Common Mistakes
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
See also:
|
|
227
|
+
|
|
228
|
+
- skills/tanstack-db-collections/SKILL.md — Grid rows come from useLiveQuery results.
|
|
229
|
+
- skills/ui-pages/SKILL.md — Grid pages follow the same routing patterns.
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: database-schema
|
|
3
|
+
description: >
|
|
4
|
+
Design PostgreSQL tables with Drizzle ORM pgTable and derive Zod validation
|
|
5
|
+
schemas via createSelectSchema from drizzle-orm/zod. Covers uuid primary keys,
|
|
6
|
+
column types, Zod refinements (trim, min, max), and type inference. Drizzle
|
|
7
|
+
tables live in src/lib/db/schemas/, Zod schemas in src/schemas/. Activate
|
|
8
|
+
when creating or modifying database tables or validation schemas.
|
|
9
|
+
type: core
|
|
10
|
+
library: wcz-layout
|
|
11
|
+
library_version: "7.6.1"
|
|
12
|
+
sources:
|
|
13
|
+
- "wcz-layout:src/lib/db/schemas/"
|
|
14
|
+
- "wcz-layout:src/schemas/"
|
|
15
|
+
- "drizzle-orm:docs/zod.md"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Database Schema Design
|
|
19
|
+
|
|
20
|
+
## Setup
|
|
21
|
+
|
|
22
|
+
Database schema files live in two directories:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
src/lib/db/schemas/todo.ts # Drizzle pgTable definition
|
|
26
|
+
src/schemas/todo.ts # Zod schema derived from the table
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Core Patterns
|
|
30
|
+
|
|
31
|
+
### Drizzle table definition
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// src/lib/db/schemas/todo.ts
|
|
35
|
+
import { boolean, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
|
36
|
+
|
|
37
|
+
export const todoTable = pgTable("todos", {
|
|
38
|
+
id: uuid().primaryKey(),
|
|
39
|
+
name: text().notNull(),
|
|
40
|
+
description: text(),
|
|
41
|
+
isCompleted: boolean().notNull().default(false),
|
|
42
|
+
createdBy: text().notNull(),
|
|
43
|
+
createdAt: timestamp().notNull().defaultNow(),
|
|
44
|
+
updatedAt: timestamp().notNull().defaultNow(),
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
All tables use `uuid().primaryKey()`. IDs are generated client-side with `uuidv7()`.
|
|
49
|
+
|
|
50
|
+
### Deriving Zod schema from Drizzle
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// src/schemas/todo.ts
|
|
54
|
+
import { createSelectSchema } from "drizzle-orm/zod";
|
|
55
|
+
import { todoTable } from "~/lib/db/schemas/todo";
|
|
56
|
+
|
|
57
|
+
export const TodoSchema = createSelectSchema(todoTable, {
|
|
58
|
+
name: (schema) => schema.trim().min(1),
|
|
59
|
+
description: (schema) => schema.trim().nullish(),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export type Todo = typeof TodoSchema._type;
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`createSelectSchema` generates a Zod schema matching the table columns. The second argument lets you refine individual fields — add `.trim()`, `.min()`, `.max()`, or replace the schema entirely.
|
|
66
|
+
|
|
67
|
+
### Insert schema for mutations
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// src/schemas/todo.ts
|
|
71
|
+
import { createInsertSchema } from "drizzle-orm/zod";
|
|
72
|
+
|
|
73
|
+
export const TodoInsertSchema = createInsertSchema(todoTable, {
|
|
74
|
+
name: (schema) => schema.trim().min(1),
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Use `createInsertSchema` when you need a schema for insert operations that omits fields with defaults (like `id`, `createdAt`).
|
|
79
|
+
|
|
80
|
+
### Type inference
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import type { InferSelectModel, InferInsertModel } from "drizzle-orm";
|
|
84
|
+
import { todoTable } from "~/lib/db/schemas/todo";
|
|
85
|
+
|
|
86
|
+
type Todo = InferSelectModel<typeof todoTable>;
|
|
87
|
+
type NewTodo = InferInsertModel<typeof todoTable>;
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Prefer the Zod `_type` for runtime-validated types. Use Drizzle inference types when you need the raw DB shape without validation.
|
|
91
|
+
|
|
92
|
+
## Common Mistakes
|
|
93
|
+
|
|
94
|
+
### CRITICAL Writing Zod schemas manually instead of deriving from Drizzle
|
|
95
|
+
|
|
96
|
+
Wrong:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { z } from "zod";
|
|
100
|
+
|
|
101
|
+
export const TodoSchema = z.object({
|
|
102
|
+
id: z.string().uuid(),
|
|
103
|
+
name: z.string().trim().min(1),
|
|
104
|
+
isCompleted: z.boolean(),
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Correct:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { createSelectSchema } from "drizzle-orm/zod";
|
|
112
|
+
import { todoTable } from "~/lib/db/schemas/todo";
|
|
113
|
+
|
|
114
|
+
export const TodoSchema = createSelectSchema(todoTable, {
|
|
115
|
+
name: (schema) => schema.trim().min(1),
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Manual Zod schemas drift from the database when columns are added or renamed. `createSelectSchema` keeps them in sync automatically.
|
|
120
|
+
|
|
121
|
+
Source: maintainer interview
|
|
122
|
+
|
|
123
|
+
### HIGH Placing schema files in wrong directory
|
|
124
|
+
|
|
125
|
+
Wrong:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
src/lib/db/schemas/todo.ts # Contains both pgTable AND Zod schema
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Correct:
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
src/lib/db/schemas/todo.ts # Only pgTable definition
|
|
135
|
+
src/schemas/todo.ts # Zod schema derived from the table
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Drizzle table definitions and Zod validation schemas live in separate directories. Mixing them in one file violates the project structure convention.
|
|
139
|
+
|
|
140
|
+
Source: consumer project structure
|
|
141
|
+
|
|
142
|
+
### HIGH Forgetting uuid primary key convention
|
|
143
|
+
|
|
144
|
+
Wrong:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { serial, pgTable, text } from "drizzle-orm/pg-core";
|
|
148
|
+
|
|
149
|
+
export const todoTable = pgTable("todos", {
|
|
150
|
+
id: serial().primaryKey(),
|
|
151
|
+
name: text().notNull(),
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Correct:
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { uuid, pgTable, text } from "drizzle-orm/pg-core";
|
|
159
|
+
|
|
160
|
+
export const todoTable = pgTable("todos", {
|
|
161
|
+
id: uuid().primaryKey(),
|
|
162
|
+
name: text().notNull(),
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
All tables use `uuid().primaryKey()`. IDs are generated client-side with `uuidv7()` for optimistic inserts through TanStack DB collections.
|
|
167
|
+
|
|
168
|
+
Source: consumer project example
|
|
169
|
+
|
|
170
|
+
### HIGH Tension: Type safety vs. rapid prototyping
|
|
171
|
+
|
|
172
|
+
Deriving Zod schemas from Drizzle tables and wiring them through forms requires more setup than quick `useState` + manual validation. Always use the enforced pattern — `createSelectSchema` + `useLayoutForm` — even for simple forms.
|
|
173
|
+
|
|
174
|
+
See also: skills/forms-validation/SKILL.md § Core Patterns
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
See also:
|
|
179
|
+
|
|
180
|
+
- skills/api-routes/SKILL.md — Schemas feed into validationMiddleware(Schema).
|
|
181
|
+
- skills/tanstack-db-collections/SKILL.md — Same Zod schema used as collection schema option.
|
|
182
|
+
- skills/forms-validation/SKILL.md — Zod schemas used as form validators.
|
|
@@ -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.
|