@famgia/omnify-typescript 0.0.67 → 0.0.69
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/{chunk-4L77AHAC.js → chunk-6I4O23X6.js} +521 -66
- package/dist/chunk-6I4O23X6.js.map +1 -0
- package/dist/index.cjs +761 -65
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +138 -2
- package/dist/index.d.ts +138 -2
- package/dist/index.js +227 -1
- package/dist/index.js.map +1 -1
- package/dist/plugin.cjs +624 -75
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +6 -0
- package/dist/plugin.d.ts +6 -0
- package/dist/plugin.js +96 -11
- package/dist/plugin.js.map +1 -1
- package/package.json +3 -3
- package/scripts/postinstall.js +29 -40
- package/stubs/JapaneseAddressField.tsx.stub +289 -0
- package/stubs/JapaneseBankField.tsx.stub +212 -0
- package/stubs/JapaneseNameField.tsx.stub +194 -0
- package/stubs/ai-guides/checklists/react.md.stub +108 -0
- package/stubs/ai-guides/cursor/react-design.mdc.stub +289 -0
- package/stubs/ai-guides/cursor/react-form.mdc.stub +277 -0
- package/stubs/ai-guides/cursor/react-services.mdc.stub +304 -0
- package/stubs/ai-guides/cursor/react.mdc.stub +254 -0
- package/stubs/ai-guides/react/README.md.stub +221 -0
- package/stubs/ai-guides/react/antd-guide.md.stub +294 -0
- package/stubs/ai-guides/react/checklist.md.stub +108 -0
- package/stubs/ai-guides/react/datetime-guide.md.stub +137 -0
- package/stubs/ai-guides/react/design-philosophy.md.stub +363 -0
- package/stubs/ai-guides/react/i18n-guide.md.stub +211 -0
- package/stubs/ai-guides/react/laravel-integration.md.stub +181 -0
- package/stubs/ai-guides/react/service-pattern.md.stub +180 -0
- package/stubs/ai-guides/react/tanstack-query.md.stub +339 -0
- package/stubs/ai-guides/react/types-guide.md.stub +524 -0
- package/stubs/components-index.ts.stub +13 -0
- package/stubs/form-validation.ts.stub +106 -0
- package/stubs/rules/index.ts.stub +48 -0
- package/stubs/rules/kana.ts.stub +291 -0
- package/stubs/use-form-mutation.ts.stub +117 -0
- package/stubs/zod-i18n.ts.stub +32 -0
- package/ai-guides/antdesign-guide.md +0 -401
- package/ai-guides/typescript-guide.md +0 -310
- package/dist/chunk-4L77AHAC.js.map +0 -1
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Frontend Design System - Japanese Business UI Guidelines
|
|
3
|
+
globs: ["{{TYPESCRIPT_BASE}}/**/*.tsx", "{{TYPESCRIPT_BASE}}/**/*.css"]
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# React Design System
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
This design system follows **Japanese Business UI principles**: compact, functional, and minimal.
|
|
12
|
+
Primary color: Violet (#7C3AED).
|
|
13
|
+
|
|
14
|
+
## Design Principles
|
|
15
|
+
|
|
16
|
+
### 日本のビジネスUI (Japanese Business UI)
|
|
17
|
+
|
|
18
|
+
1. **コンパクト (Compact)** - High information density, minimal wasted space
|
|
19
|
+
2. **機能的 (Functional)** - Prioritize usability and efficiency
|
|
20
|
+
3. **シンプル (Simple)** - Minimize visual noise, flat design
|
|
21
|
+
4. **高密度 (High Density)** - More information visible on screen
|
|
22
|
+
|
|
23
|
+
## Color System
|
|
24
|
+
|
|
25
|
+
### HSL-Based Color Harmony
|
|
26
|
+
|
|
27
|
+
All colors follow HSL color theory with consistent saturation (~84%) for harmony.
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
Primary Hue: 258° (Violet)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Brand Colors
|
|
34
|
+
|
|
35
|
+
| Role | Hex | HSL | Usage |
|
|
36
|
+
| ----------------- | --------- | -------------- | -------------------------------- |
|
|
37
|
+
| **Primary** | `#7C3AED` | 258°, 84%, 58% | Buttons, links, sidebar, accents |
|
|
38
|
+
| **Primary Light** | `#9061F9` | 258°, 84%, 68% | Hover states, selected items |
|
|
39
|
+
| **Primary Dark** | `#6D28D9` | 258°, 84%, 50% | Active states, sub-menus |
|
|
40
|
+
|
|
41
|
+
### Semantic Colors (Complementary Harmony)
|
|
42
|
+
|
|
43
|
+
| Role | Hex | HSL | Usage |
|
|
44
|
+
| ----------- | --------- | -------------- | ---------------------------------- |
|
|
45
|
+
| **Success** | `#10B981` | 160°, 84%, 39% | Success messages, positive actions |
|
|
46
|
+
| **Warning** | `#F59E0B` | 38°, 92%, 50% | Warnings, caution states |
|
|
47
|
+
| **Error** | `#EF4444` | 0°, 84%, 60% | Errors, destructive actions |
|
|
48
|
+
|
|
49
|
+
### Neutral Colors (Violet Undertone)
|
|
50
|
+
|
|
51
|
+
| Role | Hex | Usage |
|
|
52
|
+
| ------------------------ | --------- | ----------------------------- |
|
|
53
|
+
| **Text Primary** | `#1E1B2E` | Main text, headings |
|
|
54
|
+
| **Text Secondary** | `#4B5563` | Secondary text, labels |
|
|
55
|
+
| **Text Tertiary** | `#9CA3AF` | Placeholder, disabled text |
|
|
56
|
+
| **Background Layout** | `#F8F7FA` | Page background (violet-gray) |
|
|
57
|
+
| **Background Container** | `#FFFFFF` | Cards, containers |
|
|
58
|
+
| **Border** | `#E5E7EB` | Borders, dividers |
|
|
59
|
+
| **Border Secondary** | `#F3F4F6` | Subtle borders |
|
|
60
|
+
|
|
61
|
+
### Color Usage Rules
|
|
62
|
+
|
|
63
|
+
1. **60-30-10 Rule**: 60% neutral, 30% primary, 10% accent
|
|
64
|
+
2. **Same Saturation Family**: Keep semantic colors at ~84% saturation
|
|
65
|
+
3. **Cool Tone Neutrals**: Grays should have slight violet undertone
|
|
66
|
+
4. **WCAG AA Compliance**: Maintain contrast ratio ≥ 4.5:1 for text
|
|
67
|
+
|
|
68
|
+
## Typography
|
|
69
|
+
|
|
70
|
+
### Locale-Specific Font Stacks
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
const fontFamilies = {
|
|
74
|
+
// Japanese - CJK optimized
|
|
75
|
+
ja: "'Noto Sans JP', 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif",
|
|
76
|
+
|
|
77
|
+
// English - Modern western
|
|
78
|
+
en: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
79
|
+
|
|
80
|
+
// Vietnamese - Light, clean font with diacritics support
|
|
81
|
+
vi: "'Inter', 'Nunito Sans', -apple-system, BlinkMacSystemFont, sans-serif",
|
|
82
|
+
};
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Font Sizes (Compact)
|
|
86
|
+
|
|
87
|
+
| Token | Size | Usage |
|
|
88
|
+
| ------------------ | ---- | -------------- |
|
|
89
|
+
| `fontSize` | 13px | Base text |
|
|
90
|
+
| `fontSizeLG` | 14px | Large text |
|
|
91
|
+
| `fontSizeHeading1` | 24px | Page titles |
|
|
92
|
+
| `fontSizeHeading2` | 20px | Section titles |
|
|
93
|
+
| `fontSizeHeading3` | 16px | Card titles |
|
|
94
|
+
| `fontSizeHeading4` | 14px | Sub-headings |
|
|
95
|
+
| `fontSizeHeading5` | 13px | Small headings |
|
|
96
|
+
|
|
97
|
+
### Line Height
|
|
98
|
+
|
|
99
|
+
- Default: `1.5` (compact, suitable for Japanese text)
|
|
100
|
+
|
|
101
|
+
## Spacing System
|
|
102
|
+
|
|
103
|
+
### Compact Spacing Scale
|
|
104
|
+
|
|
105
|
+
| Token | Value | Usage |
|
|
106
|
+
| ----------- | ----- | --------------- |
|
|
107
|
+
| `paddingXS` | 4px | Minimal spacing |
|
|
108
|
+
| `paddingSM` | 8px | Small spacing |
|
|
109
|
+
| `padding` | 12px | Default spacing |
|
|
110
|
+
| `paddingLG` | 16px | Large spacing |
|
|
111
|
+
| `marginXS` | 4px | Minimal margin |
|
|
112
|
+
| `marginSM` | 8px | Small margin |
|
|
113
|
+
| `margin` | 12px | Default margin |
|
|
114
|
+
| `marginLG` | 16px | Large margin |
|
|
115
|
+
|
|
116
|
+
### Layout Spacing
|
|
117
|
+
|
|
118
|
+
- Content margin: `12px`
|
|
119
|
+
- Content padding: `16px`
|
|
120
|
+
- Header height: `48px`
|
|
121
|
+
- Sidebar width: `180px`
|
|
122
|
+
|
|
123
|
+
## Border Radius
|
|
124
|
+
|
|
125
|
+
### Subtle, Professional Corners
|
|
126
|
+
|
|
127
|
+
| Token | Value | Usage |
|
|
128
|
+
| ---------------- | ----- | ------------------------ |
|
|
129
|
+
| `borderRadiusSM` | 2px | Tags, badges |
|
|
130
|
+
| `borderRadius` | 4px | Buttons, inputs, cards |
|
|
131
|
+
| `borderRadiusLG` | 6px | Modals, large containers |
|
|
132
|
+
|
|
133
|
+
> **Note**: Japanese business UIs prefer subtle corners over heavily rounded designs.
|
|
134
|
+
|
|
135
|
+
## Shadows
|
|
136
|
+
|
|
137
|
+
### Almost Flat Design
|
|
138
|
+
|
|
139
|
+
```css
|
|
140
|
+
/* Primary shadow - very subtle */
|
|
141
|
+
boxShadow: "0 1px 2px rgba(0, 0, 0, 0.03)"
|
|
142
|
+
|
|
143
|
+
/* Secondary shadow - hover states */
|
|
144
|
+
boxShadowSecondary: "0 1px 3px rgba(0, 0, 0, 0.04)"
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
> **Note**: Prefer borders over shadows for Japanese business style.
|
|
148
|
+
|
|
149
|
+
## Control Sizes
|
|
150
|
+
|
|
151
|
+
### Compact Controls
|
|
152
|
+
|
|
153
|
+
| Token | Value | Usage |
|
|
154
|
+
| ----------------- | ----- | --------------------- |
|
|
155
|
+
| `controlHeightSM` | 28px | Small buttons, inputs |
|
|
156
|
+
| `controlHeight` | 32px | Default controls |
|
|
157
|
+
| `controlHeightLG` | 36px | Large controls |
|
|
158
|
+
|
|
159
|
+
## Component Guidelines
|
|
160
|
+
|
|
161
|
+
### Buttons
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
Button: {
|
|
165
|
+
controlHeight: 32,
|
|
166
|
+
paddingInline: 12,
|
|
167
|
+
fontWeight: 500,
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Inputs
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
Input: {
|
|
175
|
+
controlHeight: 32,
|
|
176
|
+
paddingInline: 8,
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Tables
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
Table: {
|
|
184
|
+
cellPaddingBlock: 8,
|
|
185
|
+
cellPaddingInline: 8,
|
|
186
|
+
headerBg: "#F8F7FA",
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Forms
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
Form: {
|
|
194
|
+
itemMarginBottom: 16,
|
|
195
|
+
verticalLabelPadding: "0 0 4px",
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Menu (Dark Theme for Sidebar)
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
Menu: {
|
|
203
|
+
itemHeight: 36,
|
|
204
|
+
itemMarginBlock: 2,
|
|
205
|
+
itemMarginInline: 4,
|
|
206
|
+
darkItemBg: "#7C3AED",
|
|
207
|
+
darkSubMenuItemBg: "#6D28D9",
|
|
208
|
+
darkItemSelectedBg: "#9061F9",
|
|
209
|
+
darkItemSelectedColor: "#FFFFFF",
|
|
210
|
+
darkItemColor: "rgba(255, 255, 255, 0.9)",
|
|
211
|
+
darkItemHoverBg: "rgba(144, 97, 249, 0.6)",
|
|
212
|
+
darkItemHoverColor: "#FFFFFF",
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Layout
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
Layout: {
|
|
220
|
+
siderBg: "#7C3AED",
|
|
221
|
+
headerPadding: "0 16px",
|
|
222
|
+
headerHeight: 48,
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Implementation
|
|
227
|
+
|
|
228
|
+
### Ant Design Theme Configuration
|
|
229
|
+
|
|
230
|
+
All design tokens are configured in:
|
|
231
|
+
```
|
|
232
|
+
frontend/src/components/AntdThemeProvider.tsx
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Google Fonts Loading
|
|
236
|
+
|
|
237
|
+
Fonts are loaded in:
|
|
238
|
+
```
|
|
239
|
+
frontend/src/app/layout.tsx
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Required fonts:
|
|
243
|
+
- Noto Sans JP (Japanese)
|
|
244
|
+
- Inter (English)
|
|
245
|
+
- Be Vietnam Pro (Vietnamese)
|
|
246
|
+
|
|
247
|
+
## Do's and Don'ts
|
|
248
|
+
|
|
249
|
+
### ✅ Do
|
|
250
|
+
|
|
251
|
+
- Use compact spacing (8-16px)
|
|
252
|
+
- Keep shadows minimal or none
|
|
253
|
+
- Use subtle border radius (2-6px)
|
|
254
|
+
- Maintain high information density
|
|
255
|
+
- Use locale-specific fonts
|
|
256
|
+
- Follow HSL color harmony
|
|
257
|
+
|
|
258
|
+
### ❌ Don't
|
|
259
|
+
|
|
260
|
+
- Use excessive padding (>24px)
|
|
261
|
+
- Use heavy shadows
|
|
262
|
+
- Use large border radius (>10px)
|
|
263
|
+
- Leave large empty spaces
|
|
264
|
+
- Mix warm and cool grays
|
|
265
|
+
- Use colors outside the harmony system
|
|
266
|
+
|
|
267
|
+
## Quick Reference
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
// Primary colors
|
|
271
|
+
const primary = "#7C3AED";
|
|
272
|
+
const primaryLight = "#9061F9";
|
|
273
|
+
const primaryDark = "#6D28D9";
|
|
274
|
+
|
|
275
|
+
// Semantic colors
|
|
276
|
+
const success = "#10B981";
|
|
277
|
+
const warning = "#F59E0B";
|
|
278
|
+
const error = "#EF4444";
|
|
279
|
+
|
|
280
|
+
// Neutrals
|
|
281
|
+
const textPrimary = "#1E1B2E";
|
|
282
|
+
const bgLayout = "#F8F7FA";
|
|
283
|
+
const border = "#E5E7EB";
|
|
284
|
+
|
|
285
|
+
// Sizes
|
|
286
|
+
const controlHeight = 32;
|
|
287
|
+
const borderRadius = 4;
|
|
288
|
+
const padding = 12;
|
|
289
|
+
```
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Form development guide - Omnify patterns, Zod validation, backend errors"
|
|
3
|
+
globs: ["{{TYPESCRIPT_BASE}}/features/**", "{{TYPESCRIPT_BASE}}/components/**"]
|
|
4
|
+
alwaysApply: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Form Development Guide
|
|
8
|
+
|
|
9
|
+
## Quick Start
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { Form, Input, Button, Space, Card, Divider } from 'antd';
|
|
13
|
+
import type { FormInstance } from 'antd';
|
|
14
|
+
import { zodRule, setZodLocale } from '@/omnify/lib';
|
|
15
|
+
import { OmnifyForm } from '@/omnify/components';
|
|
16
|
+
import {
|
|
17
|
+
type Customer,
|
|
18
|
+
type CustomerCreate,
|
|
19
|
+
customerSchemas,
|
|
20
|
+
customerI18n,
|
|
21
|
+
getCustomerFieldLabel,
|
|
22
|
+
getCustomerFieldPlaceholder,
|
|
23
|
+
} from '@/omnify/schemas';
|
|
24
|
+
|
|
25
|
+
const LOCALE = 'ja'; // TODO: Get from context
|
|
26
|
+
|
|
27
|
+
export function CustomerForm({
|
|
28
|
+
form,
|
|
29
|
+
initialValues,
|
|
30
|
+
onSubmit,
|
|
31
|
+
loading = false,
|
|
32
|
+
isEdit = false,
|
|
33
|
+
onCancel,
|
|
34
|
+
}: {
|
|
35
|
+
form: FormInstance<CustomerCreate>;
|
|
36
|
+
initialValues?: Partial<Customer>;
|
|
37
|
+
onSubmit: (values: CustomerCreate) => void;
|
|
38
|
+
loading?: boolean;
|
|
39
|
+
isEdit?: boolean;
|
|
40
|
+
onCancel?: () => void;
|
|
41
|
+
}) {
|
|
42
|
+
// Set locale for validation messages
|
|
43
|
+
setZodLocale(LOCALE);
|
|
44
|
+
|
|
45
|
+
// Helper functions
|
|
46
|
+
const label = (key: string) => getCustomerFieldLabel(key, LOCALE);
|
|
47
|
+
const placeholder = (key: string) => getCustomerFieldPlaceholder(key, LOCALE);
|
|
48
|
+
const rule = (key: keyof typeof customerSchemas) =>
|
|
49
|
+
zodRule(customerSchemas[key], label(key));
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<Card>
|
|
53
|
+
<Form
|
|
54
|
+
form={form}
|
|
55
|
+
layout="horizontal"
|
|
56
|
+
labelCol={{ span: 6 }}
|
|
57
|
+
wrapperCol={{ span: 18 }}
|
|
58
|
+
initialValues={initialValues}
|
|
59
|
+
onFinish={onSubmit}
|
|
60
|
+
style={{ maxWidth: 800 }}
|
|
61
|
+
>
|
|
62
|
+
{/* Section: Contact */}
|
|
63
|
+
<Divider orientation="left">Contact</Divider>
|
|
64
|
+
|
|
65
|
+
<Form.Item
|
|
66
|
+
name="email"
|
|
67
|
+
label={label('email')}
|
|
68
|
+
rules={[rule('email')]}
|
|
69
|
+
>
|
|
70
|
+
<Input type="email" placeholder={placeholder('email')} />
|
|
71
|
+
</Form.Item>
|
|
72
|
+
|
|
73
|
+
{/* Submit Buttons */}
|
|
74
|
+
<Form.Item wrapperCol={{ offset: 6, span: 18 }}>
|
|
75
|
+
<Space>
|
|
76
|
+
<Button type="primary" htmlType="submit" loading={loading}>
|
|
77
|
+
{isEdit ? 'Update' : 'Create'}
|
|
78
|
+
</Button>
|
|
79
|
+
{onCancel && <Button onClick={onCancel}>Cancel</Button>}
|
|
80
|
+
</Space>
|
|
81
|
+
</Form.Item>
|
|
82
|
+
</Form>
|
|
83
|
+
</Card>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Key Patterns
|
|
89
|
+
|
|
90
|
+
### 1. Imports
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// Omnify utilities
|
|
94
|
+
import { zodRule, setZodLocale } from '@/omnify/lib';
|
|
95
|
+
import { OmnifyForm } from '@/omnify/components';
|
|
96
|
+
|
|
97
|
+
// Model-specific (generated by Omnify)
|
|
98
|
+
import {
|
|
99
|
+
type Customer,
|
|
100
|
+
type CustomerCreate,
|
|
101
|
+
customerSchemas,
|
|
102
|
+
customerI18n,
|
|
103
|
+
getCustomerFieldLabel,
|
|
104
|
+
getCustomerFieldPlaceholder,
|
|
105
|
+
} from '@/omnify/schemas';
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 2. Helper Functions (MUST define in every form)
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
const LOCALE = 'ja'; // Get from context in real app
|
|
112
|
+
|
|
113
|
+
// Set locale once at start
|
|
114
|
+
setZodLocale(LOCALE);
|
|
115
|
+
|
|
116
|
+
// Define helper functions
|
|
117
|
+
const label = (key: string) => getCustomerFieldLabel(key, LOCALE);
|
|
118
|
+
const placeholder = (key: string) => getCustomerFieldPlaceholder(key, LOCALE);
|
|
119
|
+
const rule = (key: keyof typeof customerSchemas) =>
|
|
120
|
+
zodRule(customerSchemas[key], label(key));
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 3. Form.Item Pattern
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
<Form.Item
|
|
127
|
+
name="email" // Field name (matches backend)
|
|
128
|
+
label={label('email')} // i18n label
|
|
129
|
+
rules={[rule('email')]} // Zod validation
|
|
130
|
+
>
|
|
131
|
+
<Input
|
|
132
|
+
type="email"
|
|
133
|
+
placeholder={placeholder('email')} // i18n placeholder
|
|
134
|
+
/>
|
|
135
|
+
</Form.Item>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 4. Section Dividers
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
<Divider orientation="left">Contact Info</Divider>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Compound Fields (Japan Plugin)
|
|
145
|
+
|
|
146
|
+
For Japanese compound types, use `OmnifyForm` components:
|
|
147
|
+
|
|
148
|
+
### JapaneseName
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
<OmnifyForm.JapaneseName
|
|
152
|
+
schemas={customerSchemas}
|
|
153
|
+
i18n={customerI18n}
|
|
154
|
+
prefix="name"
|
|
155
|
+
required
|
|
156
|
+
/>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### JapaneseAddress
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
<OmnifyForm.JapaneseAddress
|
|
163
|
+
form={form}
|
|
164
|
+
schemas={customerSchemas}
|
|
165
|
+
i18n={customerI18n}
|
|
166
|
+
prefix="address"
|
|
167
|
+
/>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### JapaneseBank
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
<OmnifyForm.JapaneseBank
|
|
174
|
+
schemas={customerSchemas}
|
|
175
|
+
i18n={customerI18n}
|
|
176
|
+
prefix="bank"
|
|
177
|
+
/>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Props Pattern
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
interface CustomerFormProps {
|
|
184
|
+
form: FormInstance<CustomerCreate>; // REQUIRED - from parent
|
|
185
|
+
initialValues?: Partial<Customer>; // For edit mode
|
|
186
|
+
onSubmit: (values: CustomerCreate) => void;
|
|
187
|
+
loading?: boolean;
|
|
188
|
+
isEdit?: boolean;
|
|
189
|
+
onCancel?: () => void;
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Page Component (Parent)
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
import { Form } from 'antd';
|
|
197
|
+
import { useFormMutation } from '@/hooks/useFormMutation';
|
|
198
|
+
import { customerService } from '@/services/customers';
|
|
199
|
+
import { queryKeys } from '@/lib/queryKeys';
|
|
200
|
+
import { CustomerForm } from '@/features/customers/CustomerForm';
|
|
201
|
+
import type { CustomerCreate } from '@/omnify/schemas';
|
|
202
|
+
|
|
203
|
+
export default function CreateCustomerPage() {
|
|
204
|
+
const [form] = Form.useForm<CustomerCreate>();
|
|
205
|
+
|
|
206
|
+
const { mutate, isPending } = useFormMutation({
|
|
207
|
+
form,
|
|
208
|
+
mutationFn: customerService.create,
|
|
209
|
+
invalidateKeys: [queryKeys.customers.all],
|
|
210
|
+
successMessage: "messages.created",
|
|
211
|
+
redirectTo: "/customers",
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<CustomerForm
|
|
216
|
+
form={form}
|
|
217
|
+
onSubmit={(values) => mutate(values)}
|
|
218
|
+
loading={isPending}
|
|
219
|
+
/>
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Backend Errors (422)
|
|
225
|
+
|
|
226
|
+
`useFormMutation` automatically handles:
|
|
227
|
+
1. `form.setFields(getFormErrors(error))` - display on form fields
|
|
228
|
+
2. `message.error(validationMessage)` - toast message
|
|
229
|
+
|
|
230
|
+
**Field names MUST match Laravel:**
|
|
231
|
+
```tsx
|
|
232
|
+
// Laravel: { errors: { "name_lastname": ["..."] } }
|
|
233
|
+
<Form.Item name="name_lastname"> // ✅ Matches
|
|
234
|
+
<Form.Item name="lastName"> // ❌ Doesn't match
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
## Checklist
|
|
238
|
+
|
|
239
|
+
- [ ] Import from `@/omnify/lib`, `@/omnify/components`, `@/omnify/schemas`
|
|
240
|
+
- [ ] Define `label()`, `placeholder()`, `rule()` helper functions
|
|
241
|
+
- [ ] Call `setZodLocale(LOCALE)` at start
|
|
242
|
+
- [ ] Form receives `form` prop from parent (not create own)
|
|
243
|
+
- [ ] Use `Divider` for sections
|
|
244
|
+
- [ ] Use `OmnifyForm.*` for compound fields (JapaneseName, etc.)
|
|
245
|
+
- [ ] Field names match Laravel field names
|
|
246
|
+
- [ ] Submit buttons with loading state
|
|
247
|
+
|
|
248
|
+
## Common Mistakes
|
|
249
|
+
|
|
250
|
+
```tsx
|
|
251
|
+
// ❌ Wrong - Create form instance in form component
|
|
252
|
+
function MyForm() {
|
|
253
|
+
const [form] = Form.useForm(); // DON'T create here
|
|
254
|
+
return <Form form={form}>...
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ✅ Correct - Receive from parent
|
|
258
|
+
function MyForm({ form }: { form: FormInstance }) {
|
|
259
|
+
return <Form form={form}>...
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
```tsx
|
|
264
|
+
// ❌ Wrong - Inline validation message
|
|
265
|
+
rules={[{ required: true, message: 'Email is required' }]}
|
|
266
|
+
|
|
267
|
+
// ✅ Correct - Use zodRule with i18n
|
|
268
|
+
rules={[rule('email')]}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
```tsx
|
|
272
|
+
// ❌ Wrong - Hardcoded placeholder
|
|
273
|
+
<Input placeholder="Enter email" />
|
|
274
|
+
|
|
275
|
+
// ✅ Correct - Use placeholder helper
|
|
276
|
+
<Input placeholder={placeholder('email')} />
|
|
277
|
+
```
|