@akanjs/cli 0.0.146 → 0.0.147
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 +7 -26
- package/cjs/index.js +175 -18
- package/cjs/src/guidelines/cssRule/cssRule.instruction.md +1 -1
- package/cjs/src/guidelines/docPageRule/docPageRule.instruction.md +59 -52
- package/cjs/src/guidelines/modelConstant/modelConstant.instruction.md +335 -752
- package/cjs/src/guidelines/modelTemplate/modelTemplate.instruction.md +418 -391
- package/cjs/src/guidelines/modelUnit/modelUnit.instruction.md +0 -292
- package/cjs/src/guidelines/scalarModule/scalarModule.instruction.md +84 -0
- package/cjs/src/templates/app/main.js +1 -2
- package/esm/index.js +183 -26
- package/esm/src/guidelines/cssRule/cssRule.instruction.md +1 -1
- package/esm/src/guidelines/docPageRule/docPageRule.instruction.md +59 -52
- package/esm/src/guidelines/modelConstant/modelConstant.instruction.md +335 -752
- package/esm/src/guidelines/modelTemplate/modelTemplate.instruction.md +418 -391
- package/esm/src/guidelines/modelUnit/modelUnit.instruction.md +0 -292
- package/esm/src/guidelines/scalarModule/scalarModule.instruction.md +84 -0
- package/esm/src/templates/app/main.js +1 -2
- package/package.json +1 -1
- package/src/guideline/guideline.command.d.ts +3 -1
- package/src/guideline/guideline.prompt.d.ts +15 -1
- package/src/guideline/guideline.runner.d.ts +17 -3
- package/src/guideline/guideline.script.d.ts +8 -2
- package/src/guidelines/cssRule/cssRule.instruction.md +1 -1
- package/src/guidelines/docPageRule/docPageRule.instruction.md +59 -52
- package/src/guidelines/modelConstant/modelConstant.instruction.md +335 -752
- package/src/guidelines/modelTemplate/modelTemplate.instruction.md +418 -391
- package/src/guidelines/modelUnit/modelUnit.instruction.md +0 -292
- package/src/guidelines/scalarModule/scalarModule.instruction.md +84 -0
|
@@ -10,568 +10,595 @@ Model.Template.tsx files are client-side React components that define the form s
|
|
|
10
10
|
4. **UI Consistency**: Ensure uniform form design across the application
|
|
11
11
|
5. **Type Safety**: Enforce data types based on Model.Constant definitions
|
|
12
12
|
6. **Internationalization**: Support multi-language form fields with dictionary integration
|
|
13
|
+
7. **Component Reusability**: Enable form sections to be used in multiple contexts (pages, modals, utilities)
|
|
13
14
|
|
|
14
15
|
## File Location and Naming Convention
|
|
15
16
|
|
|
17
|
+
### Standard Path
|
|
18
|
+
|
|
16
19
|
```
|
|
17
|
-
libs
|
|
20
|
+
{apps,libs}/*/lib/[model]/[Model].Template.tsx
|
|
18
21
|
```
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
### Examples:
|
|
21
24
|
|
|
22
|
-
- `libs/
|
|
23
|
-
- `apps/
|
|
25
|
+
- `libs/game/lib/map/Map.Template.tsx`
|
|
26
|
+
- `apps/lu/lib/feeling/Feeling.Template.tsx`
|
|
27
|
+
- `libs/shared/lib/user/User.Template.tsx`
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
### Naming Rules:
|
|
26
30
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
31
|
+
- PascalCase for model names (e.g., `Map.Template.tsx`)
|
|
32
|
+
- Place in the same directory as other model files (`[Model].store.ts`, `[Model].constant.ts`)
|
|
33
|
+
- Export named components (typically `General`, `Advanced`, `Compact`)
|
|
30
34
|
|
|
31
|
-
##
|
|
35
|
+
## Basic Structure and Component Patterns
|
|
32
36
|
|
|
33
|
-
###
|
|
37
|
+
### Minimum Structure
|
|
34
38
|
|
|
35
39
|
```tsx
|
|
36
|
-
// File: libs/[domain]/lib/[model]/[Model].Template.tsx
|
|
37
40
|
"use client";
|
|
38
|
-
import { Field } from "@
|
|
39
|
-
import { cnst } from "@[project]/constant";
|
|
41
|
+
import { Field } from "@shared/ui";
|
|
40
42
|
import { st } from "@[project]/client";
|
|
41
43
|
import { usePage } from "@[project]/lib/usePage";
|
|
42
44
|
|
|
43
45
|
export const General = ({ id }: { id?: string }) => {
|
|
44
|
-
const form = st.use.[model]Form();
|
|
46
|
+
const form = st.use.[model]Form();
|
|
45
47
|
const { l } = usePage();
|
|
46
48
|
|
|
47
49
|
return (
|
|
48
|
-
<div className="grid grid-cols-1 gap-4
|
|
50
|
+
<div className="grid grid-cols-1 gap-4">
|
|
49
51
|
<Field.Text
|
|
50
|
-
label={l.field
|
|
51
|
-
value={form.
|
|
52
|
-
onChange={(v) => st.do.
|
|
53
|
-
required
|
|
54
|
-
maxLength={30}
|
|
55
|
-
/>
|
|
56
|
-
|
|
57
|
-
<Field.Select
|
|
58
|
-
label={l.field.status}
|
|
59
|
-
value={form.status}
|
|
60
|
-
options={cnst.[Model]Status.options}
|
|
61
|
-
onChange={(v) => st.do.setStatusOn[Model](v)}
|
|
52
|
+
label={l.field("model", "fieldName")}
|
|
53
|
+
value={form.fieldName}
|
|
54
|
+
onChange={(v) => st.do.setFieldNameOn[Model](v)}
|
|
62
55
|
/>
|
|
63
|
-
|
|
64
|
-
{/* Add other fields */}
|
|
65
56
|
</div>
|
|
66
57
|
);
|
|
67
58
|
};
|
|
68
59
|
```
|
|
69
60
|
|
|
70
|
-
###
|
|
71
|
-
|
|
72
|
-
Model.Template components connect to Zustand store slices that follow a consistent pattern:
|
|
61
|
+
### Component Organization Patterns
|
|
73
62
|
|
|
74
63
|
```tsx
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
64
|
+
// Single-file pattern
|
|
65
|
+
export const General = () => { /* main form */ }
|
|
66
|
+
export const Advanced = () => { /* advanced fields */ }
|
|
67
|
+
|
|
68
|
+
// Multi-file pattern (recommended)
|
|
69
|
+
[Model].Template/
|
|
70
|
+
├─ General.tsx
|
|
71
|
+
├─ Sections/
|
|
72
|
+
│ ├─ BasicInfo.tsx
|
|
73
|
+
│ ├─ ContactDetails.tsx
|
|
74
|
+
```
|
|
80
75
|
|
|
81
|
-
|
|
82
|
-
interface [Model]Action {
|
|
83
|
-
setNameOn[Model]: (name: string) => void;
|
|
84
|
-
setStatusOn[Model]: (status: cnst.[Model]Status) => void;
|
|
85
|
-
// other actions...
|
|
86
|
-
}
|
|
76
|
+
## Integration with Store State Management
|
|
87
77
|
|
|
88
|
-
|
|
78
|
+
### Connecting to Zustand Store
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
// Access form state
|
|
89
82
|
const form = st.use.[model]Form();
|
|
90
|
-
|
|
83
|
+
|
|
84
|
+
// Update fields
|
|
85
|
+
<Field.Text
|
|
86
|
+
value={form.name}
|
|
87
|
+
onChange={st.do.setNameOn[Model]}
|
|
88
|
+
/>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Store Action Patterns
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
// Simple field update
|
|
95
|
+
st.do.setNameOn[Model](value);
|
|
96
|
+
|
|
97
|
+
// Nested field update
|
|
98
|
+
st.do.writeOn[Model](["address", "city"], value);
|
|
99
|
+
|
|
100
|
+
// List operations
|
|
101
|
+
st.do.addItemOn[Model](newItem);
|
|
102
|
+
st.do.removeItemOn[Model](index);
|
|
91
103
|
```
|
|
92
104
|
|
|
93
|
-
###
|
|
105
|
+
### Initialization and Cleanup
|
|
94
106
|
|
|
95
|
-
|
|
107
|
+
```tsx
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (id) {
|
|
110
|
+
// Load existing data
|
|
111
|
+
void st.do.load[Model](id);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return () => {
|
|
115
|
+
// Reset form on unmount
|
|
116
|
+
st.do.reset[Model]Form();
|
|
117
|
+
};
|
|
118
|
+
}, [id]);
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Field Types and Form Components
|
|
122
|
+
|
|
123
|
+
### Core Field Components
|
|
124
|
+
|
|
125
|
+
| Component | Usage | Example |
|
|
126
|
+
| -------------------- | --------------------- | ------------------------------------------- |
|
|
127
|
+
| `Field.Text` | Text input | `<Field.Text value={form.name} />` |
|
|
128
|
+
| `Field.TextArea` | Multi-line text | `<Field.TextArea value={form.desc} />` |
|
|
129
|
+
| `Field.Number` | Numeric input | `<Field.Number value={form.age} />` |
|
|
130
|
+
| `Field.Select` | Dropdown selection | `<Field.Select options={statusOptions} />` |
|
|
131
|
+
| `Field.ToggleSelect` | Toggleable options | `<Field.ToggleSelect items={types} />` |
|
|
132
|
+
| `Field.Tags` | Tag input | `<Field.Tags value={form.tags} />` |
|
|
133
|
+
| `Field.Img` | Image upload | `<Field.Img sliceName="model" />` |
|
|
134
|
+
| `Field.Date` | Date picker | `<Field.Date value={form.dueDate} />` |
|
|
135
|
+
| `Field.Parent` | Relationship selector | `<Field.Parent sliceName="relatedModel" />` |
|
|
136
|
+
| `Field.Slate` | Rich text editor | `<Field.Slate valuePath="content" />` |
|
|
137
|
+
|
|
138
|
+
### Complex Field Example
|
|
96
139
|
|
|
97
140
|
```tsx
|
|
98
|
-
<Field.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
<
|
|
104
|
-
<Field.
|
|
105
|
-
<Field.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
<Field.MultiSelect /> // Multi-value selection
|
|
113
|
-
<Field.RichText /> // Rich text editor
|
|
114
|
-
<Field.CodeEditor /> // Code editor
|
|
115
|
-
<Field.Location /> // Map location picker
|
|
116
|
-
<Field.Slider /> // Numeric range slider
|
|
117
|
-
<Field.Password /> // Password input with visibility toggle
|
|
141
|
+
<Field.List
|
|
142
|
+
label={l.field("map", "spawnPositions")}
|
|
143
|
+
value={form.spawnPositions}
|
|
144
|
+
onAdd={() => st.do.addSpawnPosition(defaultPosition)}
|
|
145
|
+
renderItem={(position, idx) => (
|
|
146
|
+
<div className="flex gap-4">
|
|
147
|
+
<Field.Text value={position.key} onChange={(v) => st.do.writeOnMap(["spawnPositions", idx, "key"], v)} />
|
|
148
|
+
<Field.DoubleNumber
|
|
149
|
+
value={position.position}
|
|
150
|
+
onChange={(v) => st.do.writeOnMap(["spawnPositions", idx, "position"], v)}
|
|
151
|
+
/>
|
|
152
|
+
</div>
|
|
153
|
+
)}
|
|
154
|
+
/>
|
|
118
155
|
```
|
|
119
156
|
|
|
120
|
-
|
|
157
|
+
## Form Validation Approaches
|
|
121
158
|
|
|
122
|
-
Field
|
|
159
|
+
### Field-Level Validation
|
|
123
160
|
|
|
124
161
|
```tsx
|
|
125
|
-
<Field.
|
|
126
|
-
label="Email"
|
|
162
|
+
<Field.Email
|
|
127
163
|
value={form.email}
|
|
128
|
-
onChange={(v) => st.do.setEmailOnUser(v)}
|
|
129
164
|
validate={(v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v)}
|
|
130
165
|
errorMessage="Invalid email format"
|
|
131
166
|
required
|
|
132
|
-
maxLength={50}
|
|
133
167
|
/>
|
|
134
168
|
```
|
|
135
169
|
|
|
136
|
-
|
|
170
|
+
### Custom Validation Functions
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
// Phone validation
|
|
174
|
+
<Field.Phone
|
|
175
|
+
value={form.phone}
|
|
176
|
+
validate={(v) => isPhoneNumber(v)}
|
|
177
|
+
/>
|
|
178
|
+
|
|
179
|
+
// Custom business rule
|
|
180
|
+
<Field.Number
|
|
181
|
+
value={form.quantity}
|
|
182
|
+
validate={(v) => v > 0 ? true : "Must be positive"}
|
|
183
|
+
/>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Validation Props
|
|
137
187
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
188
|
+
| Prop | Description | Example |
|
|
189
|
+
| ----------------------- | -------------------- | ------------------------------- |
|
|
190
|
+
| `required` | Mandatory field | `required` |
|
|
191
|
+
| `min`/`max` | Number range | `min={0} max={100}` |
|
|
192
|
+
| `minLength`/`maxLength` | Text length | `minLength={2}` |
|
|
193
|
+
| `validate` | Custom validation fn | `validate={(v) => isValid(v)}` |
|
|
194
|
+
| `errorMessage` | Custom error text | `errorMessage="Invalid format"` |
|
|
145
195
|
|
|
146
|
-
|
|
196
|
+
## Internationalization with usePage
|
|
147
197
|
|
|
148
|
-
|
|
198
|
+
### Dictionary Integration
|
|
149
199
|
|
|
150
200
|
```tsx
|
|
151
201
|
const { l } = usePage();
|
|
152
202
|
|
|
203
|
+
// Basic field
|
|
204
|
+
<Field.Text label={l.field("model", "name")} />
|
|
205
|
+
|
|
206
|
+
// With description
|
|
153
207
|
<Field.Text
|
|
154
|
-
label={l.field
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
208
|
+
label={l.field("model", "email")}
|
|
209
|
+
desc={l.desc("model", "email")}
|
|
210
|
+
/>
|
|
211
|
+
|
|
212
|
+
// Dynamic content
|
|
213
|
+
<div>{l(`profileVerify.enum-type-${type}`)}</div>
|
|
158
214
|
```
|
|
159
215
|
|
|
160
|
-
|
|
216
|
+
### Dictionary Structure
|
|
161
217
|
|
|
162
|
-
|
|
218
|
+
```tsx
|
|
219
|
+
// Example dictionary
|
|
220
|
+
export const dictionary = {
|
|
221
|
+
field: {
|
|
222
|
+
model: {
|
|
223
|
+
name: { en: "Name", ko: "이름" },
|
|
224
|
+
email: { en: "Email", ko: "이메일" },
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
desc: {
|
|
228
|
+
model: {
|
|
229
|
+
email: { en: "Your contact email", ko: "연락처 이메일" },
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Form Layout Patterns and Best Practices
|
|
236
|
+
|
|
237
|
+
### Responsive Grid Layout
|
|
163
238
|
|
|
164
239
|
```tsx
|
|
165
240
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
166
|
-
<Field.Text label="First Name"
|
|
167
|
-
<Field.Text label="Last Name"
|
|
168
|
-
<Field.Email label="Email" className="md:col-span-2"
|
|
241
|
+
<Field.Text label="First Name" />
|
|
242
|
+
<Field.Text label="Last Name" />
|
|
243
|
+
<Field.Email label="Email" className="md:col-span-2" />
|
|
169
244
|
</div>
|
|
170
245
|
```
|
|
171
246
|
|
|
172
|
-
###
|
|
247
|
+
### Section Grouping
|
|
173
248
|
|
|
174
249
|
```tsx
|
|
175
|
-
<
|
|
176
|
-
<
|
|
177
|
-
|
|
178
|
-
<
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
<legend className="text-lg font-medium">{l.section.contactInfo}</legend>
|
|
183
|
-
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">{/* Contact info fields */}</div>
|
|
184
|
-
</fieldset>
|
|
185
|
-
</div>
|
|
250
|
+
<section className="mb-6">
|
|
251
|
+
<h3 className="mb-3 text-xl font-semibold">Personal Information</h3>
|
|
252
|
+
<div className="space-y-4">
|
|
253
|
+
<Field.Text label="Full Name" />
|
|
254
|
+
<Field.Date label="Birth Date" />
|
|
255
|
+
</div>
|
|
256
|
+
</section>
|
|
186
257
|
```
|
|
187
258
|
|
|
188
|
-
### Conditional
|
|
259
|
+
### Conditional Sections
|
|
189
260
|
|
|
190
261
|
```tsx
|
|
191
262
|
{
|
|
192
263
|
form.type === "business" && (
|
|
193
|
-
|
|
194
|
-
<
|
|
195
|
-
<Field.Text label="
|
|
196
|
-
|
|
264
|
+
<section>
|
|
265
|
+
<h3>Business Details</h3>
|
|
266
|
+
<Field.Text label="Company Name" />
|
|
267
|
+
<Field.Text label="Tax ID" />
|
|
268
|
+
</section>
|
|
197
269
|
);
|
|
198
270
|
}
|
|
199
271
|
```
|
|
200
272
|
|
|
201
|
-
###
|
|
273
|
+
### Best Practices
|
|
202
274
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
<div className="space-y-6">
|
|
210
|
-
<BasicInfo />
|
|
211
|
-
<ContactDetails />
|
|
212
|
-
<Preferences />
|
|
213
|
-
</div>
|
|
214
|
-
);
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
const BasicInfo = () => {
|
|
218
|
-
const form = st.use.userForm();
|
|
219
|
-
// Component implementation
|
|
220
|
-
};
|
|
221
|
-
```
|
|
275
|
+
1. Use consistent spacing (`gap-4`, `mb-4`)
|
|
276
|
+
2. Group related fields visually
|
|
277
|
+
3. Place important fields above the fold
|
|
278
|
+
4. Use appropriate field types for data
|
|
279
|
+
5. Provide clear validation feedback
|
|
280
|
+
6. Optimize for mobile-first experiences
|
|
222
281
|
|
|
223
|
-
## Using
|
|
282
|
+
## Using Templates in Pages, Utilities, and Zones
|
|
224
283
|
|
|
225
|
-
###
|
|
284
|
+
### In Page Components
|
|
226
285
|
|
|
227
286
|
```tsx
|
|
228
|
-
//
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
onSubmit="/[model]"
|
|
238
|
-
>
|
|
239
|
-
<[Model].Template.General />
|
|
240
|
-
</Model.Edit>
|
|
241
|
-
);
|
|
242
|
-
}
|
|
287
|
+
// Creation page
|
|
288
|
+
<Model.Edit sliceName="model" type="form">
|
|
289
|
+
<Model.Template.General />
|
|
290
|
+
</Model.Edit>
|
|
291
|
+
|
|
292
|
+
// Edit page
|
|
293
|
+
<Model.Edit modelId={params.id} sliceName="model">
|
|
294
|
+
<Model.Template.General id={params.id} />
|
|
295
|
+
</Model.Edit>
|
|
243
296
|
```
|
|
244
297
|
|
|
245
|
-
###
|
|
298
|
+
### In Utility Components
|
|
246
299
|
|
|
247
300
|
```tsx
|
|
248
|
-
//
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
>
|
|
259
|
-
<[Model].Template.General id={params.id} />
|
|
260
|
-
</Model.Edit>
|
|
261
|
-
);
|
|
262
|
-
}
|
|
301
|
+
// Modal-based editing
|
|
302
|
+
<Modal.Open opens={`edit-${id}`}>
|
|
303
|
+
<button>Edit</button>
|
|
304
|
+
</Modal.Open>
|
|
305
|
+
|
|
306
|
+
<Modal.Window name={`edit-${id}`}>
|
|
307
|
+
<Model.Edit modelId={id} sliceName="model">
|
|
308
|
+
<Model.Template.Compact />
|
|
309
|
+
</Model.Edit>
|
|
310
|
+
</Modal.Window>
|
|
263
311
|
```
|
|
264
312
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
### In Modal Dialogs
|
|
313
|
+
### In Zone Components
|
|
268
314
|
|
|
269
315
|
```tsx
|
|
270
|
-
//
|
|
271
|
-
|
|
272
|
-
|
|
316
|
+
// List view with create form
|
|
317
|
+
<Data.ListContainer
|
|
318
|
+
renderTemplate={() => <Model.Template.General />}
|
|
319
|
+
/>
|
|
273
320
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
</Modal.Open>
|
|
279
|
-
|
|
280
|
-
<Modal.Window name={`edit-${id}`}>
|
|
281
|
-
<Model.Edit
|
|
282
|
-
modelId={id}
|
|
283
|
-
sliceName="[model]"
|
|
284
|
-
>
|
|
285
|
-
<[Model].Template.General id={id} />
|
|
286
|
-
</Model.Edit>
|
|
287
|
-
</Modal.Window>
|
|
288
|
-
</>
|
|
289
|
-
);
|
|
321
|
+
// Inline editor
|
|
322
|
+
<Zone.InlineEdit>
|
|
323
|
+
<Model.Template.Compact />
|
|
324
|
+
</Zone.InlineEdit>
|
|
290
325
|
```
|
|
291
326
|
|
|
292
|
-
|
|
327
|
+
## State Management and Lifecycle Methods
|
|
328
|
+
|
|
329
|
+
### Lifecycle Hooks
|
|
293
330
|
|
|
294
331
|
```tsx
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
332
|
+
useEffect(() => {
|
|
333
|
+
// Initialize form data
|
|
334
|
+
if (id) void st.do.loadModel(id);
|
|
298
335
|
|
|
299
|
-
|
|
300
|
-
|
|
336
|
+
// Initialize related data
|
|
337
|
+
void st.do.initRelatedData();
|
|
301
338
|
|
|
302
|
-
return (
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
</div>
|
|
308
|
-
);
|
|
309
|
-
};
|
|
339
|
+
return () => {
|
|
340
|
+
// Cleanup on unmount
|
|
341
|
+
st.do.resetModelForm();
|
|
342
|
+
};
|
|
343
|
+
}, [id]);
|
|
310
344
|
```
|
|
311
345
|
|
|
312
|
-
|
|
346
|
+
### Form State Flow
|
|
313
347
|
|
|
314
|
-
|
|
348
|
+
1. **Initialization**: Load data when component mounts
|
|
349
|
+
2. **User Interaction**: Update state via store actions
|
|
350
|
+
3. **Validation**: Validate on change/blur/submit
|
|
351
|
+
4. **Submission**: Process form data
|
|
352
|
+
5. **Cleanup**: Reset state on unmount
|
|
315
353
|
|
|
316
|
-
|
|
317
|
-
// libs/[domain]/lib/[model]/[Model].Zone.tsx
|
|
318
|
-
import { Data } from "@shared/ui";
|
|
319
|
-
import { [Model] } from "@[project]/client";
|
|
320
|
-
|
|
321
|
-
export const AdminZone = () => (
|
|
322
|
-
<Data.ListContainer
|
|
323
|
-
renderTemplate={() => <[Model].Template.General />}
|
|
324
|
-
// ...other props
|
|
325
|
-
/>
|
|
326
|
-
);
|
|
327
|
-
```
|
|
354
|
+
## Performance Optimization Techniques
|
|
328
355
|
|
|
329
|
-
###
|
|
356
|
+
### Memoization
|
|
330
357
|
|
|
331
358
|
```tsx
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
export const InlineEditor = ({ id }: { id: string }) => (
|
|
337
|
-
<Model.EditInline
|
|
338
|
-
modelId={id}
|
|
339
|
-
sliceName="[model]"
|
|
340
|
-
>
|
|
341
|
-
<[Model].Template.Compact id={id} />
|
|
342
|
-
</Model.EditInline>
|
|
343
|
-
);
|
|
359
|
+
const FormSection = React.memo(({ fields }) => {
|
|
360
|
+
// Component implementation
|
|
361
|
+
});
|
|
344
362
|
```
|
|
345
363
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
### 1. Component Organization
|
|
349
|
-
|
|
350
|
-
Split complex templates into logical sections:
|
|
364
|
+
### Debounced Inputs
|
|
351
365
|
|
|
352
366
|
```tsx
|
|
353
|
-
|
|
354
|
-
[Model].Template.tsx
|
|
355
|
-
├─ General.tsx // Main form
|
|
356
|
-
├─ Advanced.tsx // Advanced fields
|
|
357
|
-
├─ Compact.tsx // Simplified form
|
|
358
|
-
└─ Sections/ // Form sections
|
|
359
|
-
├─ Basic.tsx
|
|
360
|
-
└─ Metadata.tsx
|
|
361
|
-
```
|
|
367
|
+
import { useDebounce } from "@akanjs/hooks";
|
|
362
368
|
|
|
363
|
-
|
|
369
|
+
const [input, setInput] = useState("");
|
|
370
|
+
const debouncedInput = useDebounce(input, 300);
|
|
364
371
|
|
|
365
|
-
- Use `st.use.[model]Form()` for accessing form state
|
|
366
|
-
- Use `st.do.set[Field]On[Model]()` for updating form fields
|
|
367
|
-
- Perform form initialization in useEffect:
|
|
368
|
-
|
|
369
|
-
```tsx
|
|
370
372
|
useEffect(() => {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
st.do.set[Model]Form(data.[model]);
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// Reset form on unmount
|
|
378
|
-
return () => st.do.reset[Model]Form();
|
|
379
|
-
}, [id]);
|
|
373
|
+
st.do.search(debouncedInput);
|
|
374
|
+
}, [debouncedInput]);
|
|
380
375
|
```
|
|
381
376
|
|
|
382
|
-
###
|
|
383
|
-
|
|
384
|
-
- **Field-level validation**: Use the `validate` prop on field components
|
|
385
|
-
- **Form-level validation**: Use custom validation logic before submission
|
|
386
|
-
- **Server-side validation**: Always validate in the service layer as well
|
|
377
|
+
### Lazy Loading
|
|
387
378
|
|
|
388
379
|
```tsx
|
|
389
|
-
|
|
390
|
-
const handleSubmit = () => {
|
|
391
|
-
const errors = validateForm(form);
|
|
392
|
-
if (Object.keys(errors).length > 0) {
|
|
393
|
-
st.do.setFormErrors(errors);
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
380
|
+
const HeavyEditor = React.lazy(() => import("./HeavyEditor"));
|
|
396
381
|
|
|
397
|
-
|
|
398
|
-
|
|
382
|
+
<Suspense fallback={<Loader />}>
|
|
383
|
+
<HeavyEditor />
|
|
384
|
+
</Suspense>;
|
|
399
385
|
```
|
|
400
386
|
|
|
401
|
-
|
|
387
|
+
## Accessibility Considerations
|
|
402
388
|
|
|
403
|
-
|
|
404
|
-
- Use controlled components consistently
|
|
405
|
-
- Debounce input changes for expensive operations:
|
|
389
|
+
### Accessible Form Patterns
|
|
406
390
|
|
|
407
391
|
```tsx
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
392
|
+
// Proper labeling
|
|
393
|
+
<label htmlFor="email-field">Email</label>
|
|
394
|
+
<input id="email-field" type="email" />
|
|
395
|
+
|
|
396
|
+
// ARIA attributes
|
|
397
|
+
<div
|
|
398
|
+
role="alert"
|
|
399
|
+
aria-live="polite"
|
|
400
|
+
>
|
|
401
|
+
{error && <p>{error}</p>}
|
|
402
|
+
</div>
|
|
411
403
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
}, [debouncedValue]);
|
|
404
|
+
// Keyboard navigation
|
|
405
|
+
<button onKeyDown={handleKeyNavigation}>Submit</button>
|
|
415
406
|
```
|
|
416
407
|
|
|
417
|
-
###
|
|
408
|
+
### Accessibility Requirements
|
|
418
409
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
410
|
+
1. Associate all inputs with labels
|
|
411
|
+
2. Provide ARIA attributes for custom components
|
|
412
|
+
3. Ensure proper focus management
|
|
413
|
+
4. Support keyboard navigation
|
|
414
|
+
5. Provide error messages as live regions
|
|
415
|
+
6. Use sufficient color contrast
|
|
423
416
|
|
|
424
|
-
|
|
425
|
-
<label htmlFor="name-field" className="block text-sm font-medium">
|
|
426
|
-
Name
|
|
427
|
-
</label>
|
|
428
|
-
<input
|
|
429
|
-
id="name-field"
|
|
430
|
-
type="text"
|
|
431
|
-
aria-describedby="name-description"
|
|
432
|
-
// ...
|
|
433
|
-
/>
|
|
434
|
-
<p id="name-description" className="text-xs text-gray-500">
|
|
435
|
-
Enter your full name as it appears on your ID
|
|
436
|
-
</p>
|
|
437
|
-
```
|
|
417
|
+
## Common Patterns and Reusable Components
|
|
438
418
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
### Conditional Fields
|
|
419
|
+
### Reusable Form Sections
|
|
442
420
|
|
|
443
421
|
```tsx
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
422
|
+
// AddressSection.tsx
|
|
423
|
+
export const AddressSection = () => (
|
|
424
|
+
<>
|
|
425
|
+
<Field.Text label="Street" />
|
|
426
|
+
<Field.Text label="City" />
|
|
427
|
+
<Field.Text label="Zip Code" />
|
|
428
|
+
</>
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
// Usage
|
|
432
|
+
<AddressSection />;
|
|
453
433
|
```
|
|
454
434
|
|
|
455
|
-
### Field
|
|
435
|
+
### Dynamic Field Arrays
|
|
456
436
|
|
|
457
437
|
```tsx
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
<
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
);
|
|
469
|
-
};
|
|
438
|
+
<Field.List
|
|
439
|
+
value={form.items}
|
|
440
|
+
onAdd={() => st.do.addItem(defaultItem)}
|
|
441
|
+
renderItem={(item, index) => (
|
|
442
|
+
<div key={index}>
|
|
443
|
+
<Field.Text value={item.name} />
|
|
444
|
+
<Field.Number value={item.quantity} />
|
|
445
|
+
</div>
|
|
446
|
+
)}
|
|
447
|
+
/>
|
|
470
448
|
```
|
|
471
449
|
|
|
472
|
-
###
|
|
450
|
+
### Compound Components
|
|
473
451
|
|
|
474
452
|
```tsx
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
<input value={value} onChange={handleChange} className="special-input" />
|
|
484
|
-
</div>
|
|
485
|
-
);
|
|
486
|
-
};
|
|
453
|
+
// DockerRegistryField.tsx
|
|
454
|
+
const DockerRegistry = ({ value, onChange }) => (
|
|
455
|
+
<>
|
|
456
|
+
<Field.Text label="URI" value={value.uri} />
|
|
457
|
+
<Field.Text label="Username" value={value.username} />
|
|
458
|
+
<Field.Text label="Password" value={value.password} />
|
|
459
|
+
</>
|
|
460
|
+
);
|
|
487
461
|
```
|
|
488
462
|
|
|
489
463
|
## Form State Integration with Database Models
|
|
490
464
|
|
|
491
|
-
|
|
465
|
+
### Type Alignment
|
|
492
466
|
|
|
493
467
|
```tsx
|
|
494
|
-
//
|
|
495
|
-
@Model.Input("
|
|
496
|
-
export class
|
|
468
|
+
// Model.constant.ts
|
|
469
|
+
@Model.Input("ProjectInput")
|
|
470
|
+
export class ProjectInput {
|
|
497
471
|
@Field.Prop(() => String)
|
|
498
472
|
name: string;
|
|
499
|
-
|
|
500
|
-
@Field.Prop(() => String)
|
|
501
|
-
description: string;
|
|
502
473
|
}
|
|
503
474
|
|
|
504
|
-
//
|
|
505
|
-
const form = st.use.
|
|
475
|
+
// Project.Template.tsx
|
|
476
|
+
const form = st.use.projectForm(); // Type: ProjectInput
|
|
506
477
|
|
|
507
|
-
<Field.Text
|
|
508
|
-
value={form.name}
|
|
509
|
-
onChange={(v) => st.do.setNameOn[Model](v)}
|
|
510
|
-
/>
|
|
478
|
+
<Field.Text value={form.name} onChange={st.do.setNameOnProject} />;
|
|
511
479
|
```
|
|
512
480
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
### Form Not Updating
|
|
481
|
+
### Data Transformation
|
|
516
482
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
483
|
+
```tsx
|
|
484
|
+
// Before submission
|
|
485
|
+
const handleSubmit = () => {
|
|
486
|
+
const dataToSend = transformFormData(form);
|
|
487
|
+
st.do.createProject(dataToSend);
|
|
488
|
+
};
|
|
521
489
|
|
|
522
|
-
|
|
490
|
+
// After load
|
|
491
|
+
useEffect(() => {
|
|
492
|
+
if (data) {
|
|
493
|
+
const formData = transformAPIData(data);
|
|
494
|
+
st.do.setProjectForm(formData);
|
|
495
|
+
}
|
|
496
|
+
}, [data]);
|
|
497
|
+
```
|
|
523
498
|
|
|
524
|
-
|
|
525
|
-
- Check field requirements in `[model].constant.ts`
|
|
526
|
-
- Verify error messages are properly displayed
|
|
527
|
-
- Test edge cases like empty strings and special characters
|
|
499
|
+
## Troubleshooting Common Issues
|
|
528
500
|
|
|
529
|
-
###
|
|
501
|
+
### Common Problems and Solutions
|
|
530
502
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
503
|
+
| Issue | Solution |
|
|
504
|
+
| ----------------------- | ---------------------------------------------- |
|
|
505
|
+
| Form not updating | Verify Zustand actions are connected correctly |
|
|
506
|
+
| Validation not working | Check validate prop and error handling |
|
|
507
|
+
| Performance issues | Memoize components, debounce inputs |
|
|
508
|
+
| Type mismatches | Ensure constant types match form state |
|
|
509
|
+
| Missing translations | Verify dictionary keys and scopes |
|
|
510
|
+
| Form reset issues | Check cleanup useEffect dependencies |
|
|
511
|
+
| API submission failures | Validate server-side requirements |
|
|
535
512
|
|
|
536
|
-
###
|
|
513
|
+
### Debugging Tips
|
|
537
514
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
515
|
+
1. Check Zustand store state with Redux DevTools
|
|
516
|
+
2. Verify prop drilling in complex forms
|
|
517
|
+
3. Test validation rules independently
|
|
518
|
+
4. Check network requests for API errors
|
|
519
|
+
5. Verify dictionary key paths with `l()` function
|
|
520
|
+
6. Ensure all required fields have `required` prop
|
|
542
521
|
|
|
543
|
-
## Integration with API Calls
|
|
522
|
+
## Integration with API Calls and Form Submission
|
|
544
523
|
|
|
545
|
-
|
|
524
|
+
### Submission Workflow
|
|
546
525
|
|
|
547
526
|
```tsx
|
|
548
|
-
|
|
549
|
-
createUser: async () => {
|
|
550
|
-
const { userForm } = get();
|
|
527
|
+
const handleSubmit = async () => {
|
|
551
528
|
try {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
529
|
+
// Validate form
|
|
530
|
+
const errors = validateForm(form);
|
|
531
|
+
if (Object.keys(errors).length) throw errors;
|
|
532
|
+
|
|
533
|
+
// Submit data
|
|
534
|
+
if (id) {
|
|
535
|
+
await st.do.updateModel(id, form);
|
|
536
|
+
} else {
|
|
537
|
+
await st.do.createModel(form);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Redirect on success
|
|
541
|
+
router.push("/success");
|
|
560
542
|
} catch (error) {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
});
|
|
564
|
-
throw error;
|
|
543
|
+
// Handle API errors
|
|
544
|
+
st.do.setFormErrors(error);
|
|
565
545
|
}
|
|
566
546
|
};
|
|
567
547
|
|
|
568
548
|
// In component
|
|
569
|
-
|
|
549
|
+
<Button onClick={handleSubmit}>Save</Button>;
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### API Error Handling
|
|
553
|
+
|
|
554
|
+
```tsx
|
|
555
|
+
// In store
|
|
556
|
+
createModel: async (form) => {
|
|
570
557
|
try {
|
|
571
|
-
await
|
|
572
|
-
// Handle success (navigation, toast, etc.)
|
|
558
|
+
return await fetch.createModel(form);
|
|
573
559
|
} catch (error) {
|
|
574
|
-
//
|
|
560
|
+
// Transform API errors to form errors
|
|
561
|
+
const formErrors = transformErrors(error);
|
|
562
|
+
set({ formErrors });
|
|
563
|
+
throw formErrors;
|
|
575
564
|
}
|
|
576
565
|
};
|
|
577
566
|
```
|
|
567
|
+
|
|
568
|
+
### Success/Failure Feedback
|
|
569
|
+
|
|
570
|
+
```tsx
|
|
571
|
+
// Using toast notifications
|
|
572
|
+
toast.success(l("model.createdSuccess"));
|
|
573
|
+
|
|
574
|
+
// Inline messages
|
|
575
|
+
{
|
|
576
|
+
submitError && <div className="text-error">{submitError}</div>;
|
|
577
|
+
}
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
## Additional Best Practices
|
|
581
|
+
|
|
582
|
+
### Component Design Principles
|
|
583
|
+
|
|
584
|
+
1. **Single Responsibility**: Each template should focus on one model
|
|
585
|
+
2. **Reusability**: Design components to work in different contexts
|
|
586
|
+
3. **Composition**: Build complex forms from simple components
|
|
587
|
+
4. **Consistency**: Follow established patterns across all templates
|
|
588
|
+
5. **Accessibility**: Meet WCAG 2.1 standards for all form elements
|
|
589
|
+
|
|
590
|
+
### Performance Guidelines
|
|
591
|
+
|
|
592
|
+
1. Avoid unnecessary state updates
|
|
593
|
+
2. Use React.memo for expensive components
|
|
594
|
+
3. Virtualize long lists of fields
|
|
595
|
+
4. Debounce rapid state updates
|
|
596
|
+
5. Lazy load heavy form sections
|
|
597
|
+
|
|
598
|
+
### Testing Recommendations
|
|
599
|
+
|
|
600
|
+
1. Unit test validation functions
|
|
601
|
+
2. Test form submission flows
|
|
602
|
+
3. Verify internationalization coverage
|
|
603
|
+
4. Test accessibility with screen readers
|
|
604
|
+
5. Perform cross-browser testing
|