@famgia/omnify-laravel 0.0.118 → 0.0.120
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-3YANFHE5.js → chunk-NMX3TLZT.js} +35 -6
- package/dist/{chunk-3YANFHE5.js.map → chunk-NMX3TLZT.js.map} +1 -1
- package/dist/index.cjs +34 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +1 -1
- package/dist/plugin.cjs +34 -5
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.js +1 -1
- package/package.json +4 -4
- package/stubs/ai-guides/claude-checklists/react.md.stub +108 -0
- package/stubs/ai-guides/claude-rules/laravel-controllers.md.stub +57 -0
- package/stubs/ai-guides/claude-rules/laravel-migrations.md.stub +47 -0
- package/stubs/ai-guides/claude-rules/laravel-tests.md.stub +52 -0
- package/stubs/ai-guides/claude-rules/naming.md.stub +5 -0
- package/stubs/ai-guides/claude-rules/performance.md.stub +5 -0
- package/stubs/ai-guides/claude-rules/react-components.md.stub +67 -0
- package/stubs/ai-guides/claude-rules/schema-yaml.md.stub +69 -0
- package/stubs/ai-guides/claude-rules/security.md.stub +5 -0
- package/stubs/ai-guides/cursor/omnify-schema.mdc.stub +339 -0
- package/stubs/ai-guides/cursor/react-design.mdc.stub +693 -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 +336 -0
- package/stubs/ai-guides/cursor/schema-create.mdc.stub +344 -0
- package/stubs/ai-guides/react/README.md.stub +221 -0
- package/stubs/ai-guides/react/antd-guide.md.stub +457 -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 +671 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# Frontend Architecture Guide
|
|
2
|
+
|
|
3
|
+
> **Related docs:**
|
|
4
|
+
> - [Design Philosophy](./design-philosophy.md) ⭐ **Start here** - Architecture, principles
|
|
5
|
+
> - [Types Guide](./types-guide.md) - Where to define types
|
|
6
|
+
> - [Service Pattern](./service-pattern.md) - API services
|
|
7
|
+
> - [TanStack Query](./tanstack-query.md) - Data fetching
|
|
8
|
+
> - [Ant Design](./antd-guide.md) - UI components
|
|
9
|
+
> - [i18n](./i18n-guide.md) - Multi-language
|
|
10
|
+
> - [DateTime](./datetime-guide.md) - Day.js, UTC handling
|
|
11
|
+
> - [Laravel Integration](./laravel-integration.md) - Backend integration
|
|
12
|
+
> - [Checklists](./checklist.md) - Before commit, new resource
|
|
13
|
+
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
See [Design Philosophy](./design-philosophy.md) for architecture diagram and principles.
|
|
17
|
+
|
|
18
|
+
**Stack**: Next.js 16 + TypeScript + Ant Design 6 + TanStack Query + Axios
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Directory Structure
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
frontend/src/
|
|
26
|
+
├── app/ # Next.js App Router (Pages)
|
|
27
|
+
│ ├── layout.tsx # Root: Providers wrapper
|
|
28
|
+
│ ├── page.tsx # Public: Home page
|
|
29
|
+
│ │
|
|
30
|
+
│ ├── (auth)/ # Group: Auth pages (no layout)
|
|
31
|
+
│ │ ├── login/page.tsx
|
|
32
|
+
│ │ └── register/page.tsx
|
|
33
|
+
│ │
|
|
34
|
+
│ └── (dashboard)/ # Group: Protected pages
|
|
35
|
+
│ ├── layout.tsx # Shared: Sidebar + Header
|
|
36
|
+
│ ├── page.tsx # /dashboard
|
|
37
|
+
│ └── users/ # Resource: Users
|
|
38
|
+
│ ├── page.tsx # GET /users (List)
|
|
39
|
+
│ ├── new/page.tsx # POST /users (Create)
|
|
40
|
+
│ └── [id]/
|
|
41
|
+
│ ├── page.tsx # GET /users/:id (Show)
|
|
42
|
+
│ └── edit/page.tsx # PUT /users/:id (Edit)
|
|
43
|
+
│
|
|
44
|
+
├── features/ # Feature-specific components & hooks
|
|
45
|
+
│ ├── users/ # User feature
|
|
46
|
+
│ │ ├── UserTable.tsx # Only used in users feature
|
|
47
|
+
│ │ ├── UserForm.tsx
|
|
48
|
+
│ │ └── useUserFilters.ts # Feature-specific hook
|
|
49
|
+
│ └── posts/
|
|
50
|
+
│ ├── PostCard.tsx
|
|
51
|
+
│ └── PostEditor.tsx
|
|
52
|
+
│
|
|
53
|
+
├── components/ # SHARED components (2+ features)
|
|
54
|
+
│ ├── layouts/ # Layout wrappers
|
|
55
|
+
│ │ ├── DashboardLayout.tsx
|
|
56
|
+
│ │ └── AuthLayout.tsx
|
|
57
|
+
│ └── common/ # Reusable UI
|
|
58
|
+
│ ├── DataTable.tsx # Generic table
|
|
59
|
+
│ └── PageHeader.tsx
|
|
60
|
+
│
|
|
61
|
+
├── services/ # API Service Layer (ALWAYS here)
|
|
62
|
+
│ ├── auth.ts # POST /login, /logout, /register
|
|
63
|
+
│ └── users.ts # CRUD /api/users
|
|
64
|
+
│
|
|
65
|
+
├── hooks/ # SHARED hooks (2+ features)
|
|
66
|
+
│ ├── useAuth.ts # App-wide auth
|
|
67
|
+
│ └── useDebounce.ts # Utility hook
|
|
68
|
+
│
|
|
69
|
+
├── lib/ # Core Infrastructure
|
|
70
|
+
│ ├── api.ts # Axios instance + interceptors
|
|
71
|
+
│ ├── query.tsx # QueryClient provider
|
|
72
|
+
│ ├── queryKeys.ts # Query key factory
|
|
73
|
+
│ └── dayjs.ts # Day.js config + utilities
|
|
74
|
+
│
|
|
75
|
+
├── i18n/ # Internationalization
|
|
76
|
+
│ ├── config.ts # Locales config
|
|
77
|
+
│ ├── request.ts # Server-side locale detection
|
|
78
|
+
│ └── messages/ # Translation files
|
|
79
|
+
│ ├── ja.json
|
|
80
|
+
│ ├── en.json
|
|
81
|
+
│ └── vi.json
|
|
82
|
+
│
|
|
83
|
+
└── types/ # TypeScript Types
|
|
84
|
+
└── model/ # Omnify auto-generated types
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## When to Use Which Folder
|
|
90
|
+
|
|
91
|
+
### Decision Rules
|
|
92
|
+
|
|
93
|
+
| Question | Answer | Location |
|
|
94
|
+
| ------------------------------------ | ----------- | --------------------- |
|
|
95
|
+
| Component used in how many features? | 1 feature | `features/{feature}/` |
|
|
96
|
+
| Component used in how many features? | 2+ features | `components/common/` |
|
|
97
|
+
| Is it a layout wrapper? | Yes | `components/layouts/` |
|
|
98
|
+
| Is it a service (API calls)? | Yes | `services/` (ALWAYS) |
|
|
99
|
+
| Hook used in how many features? | 1 feature | `features/{feature}/` |
|
|
100
|
+
| Hook used in how many features? | 2+ features | `hooks/` |
|
|
101
|
+
|
|
102
|
+
### Flowchart
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
┌─────────────────────────────────────────────────────────┐
|
|
106
|
+
│ NEW COMPONENT │
|
|
107
|
+
└─────────────────────┬───────────────────────────────────┘
|
|
108
|
+
│
|
|
109
|
+
Used in how many features?
|
|
110
|
+
│
|
|
111
|
+
┌─────────────┴─────────────┐
|
|
112
|
+
│ │
|
|
113
|
+
1 feature 2+ features
|
|
114
|
+
│ │
|
|
115
|
+
▼ ▼
|
|
116
|
+
features/{feature}/ components/common/
|
|
117
|
+
UserTable.tsx DataTable.tsx
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
┌─────────────────────────────────────────────────────────┐
|
|
122
|
+
│ NEW HOOK │
|
|
123
|
+
└─────────────────────┬───────────────────────────────────┘
|
|
124
|
+
│
|
|
125
|
+
Used in how many features?
|
|
126
|
+
│
|
|
127
|
+
┌─────────────┴─────────────┐
|
|
128
|
+
│ │
|
|
129
|
+
1 feature 2+ features
|
|
130
|
+
│ │
|
|
131
|
+
▼ ▼
|
|
132
|
+
features/{feature}/ hooks/
|
|
133
|
+
useUserFilters.ts useDebounce.ts
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
┌─────────────────────────────────────────────────────────┐
|
|
138
|
+
│ NEW SERVICE │
|
|
139
|
+
└─────────────────────┬───────────────────────────────────┘
|
|
140
|
+
│
|
|
141
|
+
ALWAYS
|
|
142
|
+
│
|
|
143
|
+
▼
|
|
144
|
+
services/
|
|
145
|
+
users.ts
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Examples
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// ❌ WRONG: Service in features folder
|
|
152
|
+
features/users/services/users.ts
|
|
153
|
+
|
|
154
|
+
// ✅ CORRECT: Service always centralized
|
|
155
|
+
services/users.ts
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// ❌ WRONG: Feature-specific component in components/
|
|
160
|
+
components/users/UserTable.tsx // Only used in users feature
|
|
161
|
+
|
|
162
|
+
// ✅ CORRECT: Feature-specific in features/
|
|
163
|
+
features/users/UserTable.tsx
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
// ❌ WRONG: Shared component in features/
|
|
168
|
+
features/users/DataTable.tsx // Used in users AND posts
|
|
169
|
+
|
|
170
|
+
// ✅ CORRECT: Shared in components/
|
|
171
|
+
components/common/DataTable.tsx
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Naming Conventions
|
|
177
|
+
|
|
178
|
+
### Files
|
|
179
|
+
|
|
180
|
+
| Type | Pattern | Example |
|
|
181
|
+
| --------- | ------------------------ | ------------------------------- |
|
|
182
|
+
| Component | PascalCase | `UserForm.tsx`, `DataTable.tsx` |
|
|
183
|
+
| Hook | camelCase + `use` prefix | `useAuth.ts`, `useUsers.ts` |
|
|
184
|
+
| Service | camelCase | `users.ts`, `auth.ts` |
|
|
185
|
+
| Utility | camelCase | `utils.ts`, `formatters.ts` |
|
|
186
|
+
| Type | camelCase or PascalCase | `types.ts`, `User.ts` |
|
|
187
|
+
| Page | lowercase | `page.tsx`, `layout.tsx` |
|
|
188
|
+
|
|
189
|
+
### Code
|
|
190
|
+
|
|
191
|
+
| Type | Pattern | Example |
|
|
192
|
+
| -------------- | --------------------- | --------------------------- |
|
|
193
|
+
| Component | PascalCase | `function UserForm()` |
|
|
194
|
+
| Hook | camelCase + `use` | `function useAuth()` |
|
|
195
|
+
| Service object | camelCase + `Service` | `const userService = {}` |
|
|
196
|
+
| Interface | PascalCase | `interface User` |
|
|
197
|
+
| Type | PascalCase | `type UserFormData` |
|
|
198
|
+
| Constant | UPPER_SNAKE_CASE | `const API_TIMEOUT = 30000` |
|
|
199
|
+
| Function | camelCase | `function formatDate()` |
|
|
200
|
+
| Variable | camelCase | `const userData = ...` |
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Types
|
|
205
|
+
|
|
206
|
+
See [Types Guide](./types-guide.md) for complete type definition rules.
|
|
207
|
+
|
|
208
|
+
**Quick reference:**
|
|
209
|
+
|
|
210
|
+
| Type | Location |
|
|
211
|
+
| ------------------ | --------------------------------------- |
|
|
212
|
+
| Model (User, Post) | `@/types/model` (Omnify auto-generated) |
|
|
213
|
+
| Input types | Service file (colocated) |
|
|
214
|
+
| Props | Component file |
|
|
215
|
+
| API Response | `lib/api.ts` |
|
|
216
|
+
|
|
217
|
+
**Omnify files:**
|
|
218
|
+
- `base/`, `rules/`, `enum/` → ❌ DO NOT EDIT
|
|
219
|
+
- `User.ts` (root level) → ✅ CAN EDIT (extension)
|
|
220
|
+
|
|
221
|
+
See also: [Omnify TypeScript Guide](../omnify/typescript-guide.md)
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
# Ant Design Guide
|
|
2
|
+
|
|
3
|
+
> **Related:** [README](./README.md) | [i18n](./i18n-guide.md)
|
|
4
|
+
|
|
5
|
+
## ⚠️ IMPORTANT: Use Ant Design First
|
|
6
|
+
|
|
7
|
+
**ALWAYS check if Ant Design has a component before creating your own.**
|
|
8
|
+
|
|
9
|
+
Ant Design provides 60+ components: https://ant.design/components/overview
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// ✅ DO: Use Ant Design components
|
|
13
|
+
import { Table, Form, Input, Button, Modal, Card, Descriptions } from "antd";
|
|
14
|
+
|
|
15
|
+
// ❌ DON'T: Create custom components that Ant Design already has
|
|
16
|
+
// DON'T create: CustomTable, CustomModal, CustomForm, CustomButton
|
|
17
|
+
// DON'T create: DataGrid, Popup, FormInput
|
|
18
|
+
|
|
19
|
+
// ✅ DO: Extend Ant Design if needed
|
|
20
|
+
function UserTable(props: { users: User[] }) {
|
|
21
|
+
return <Table dataSource={props.users} columns={...} />; // Wraps AntD Table
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ❌ DON'T: Build from scratch
|
|
25
|
+
function UserTable(props: { users: User[] }) {
|
|
26
|
+
return <table><tbody>{users.map(...)}</tbody></table>; // WRONG!
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## ⚠️ No New Libraries Without Permission
|
|
33
|
+
|
|
34
|
+
**DO NOT install new npm packages without explicit user approval.**
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
# ❌ DON'T: Install without asking
|
|
38
|
+
npm install lodash
|
|
39
|
+
npm install moment
|
|
40
|
+
npm install react-table
|
|
41
|
+
|
|
42
|
+
# ✅ DO: Ask first
|
|
43
|
+
"Do you want to install library X for Y?"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Already installed libraries (use these):**
|
|
47
|
+
- UI: `antd`, `@ant-design/icons`
|
|
48
|
+
- HTTP: `axios`
|
|
49
|
+
- State: `@tanstack/react-query`
|
|
50
|
+
- Styling: `tailwindcss`
|
|
51
|
+
- i18n: `next-intl`
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## When to Create a Component
|
|
56
|
+
|
|
57
|
+
| Create Component | Don't Create |
|
|
58
|
+
| ------------------------------- | ----------------------------- |
|
|
59
|
+
| Used in 2+ places | Used only once |
|
|
60
|
+
| Has own state/logic (>50 lines) | Simple markup (<30 lines) |
|
|
61
|
+
| Needs unit testing | Trivial display |
|
|
62
|
+
| Complex props interface | Few inline props |
|
|
63
|
+
| **Ant Design doesn't have it** | **Ant Design already has it** |
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Container vs Presentational
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// ============================================================================
|
|
71
|
+
// CONTAINER COMPONENT (Smart) - pages or complex components
|
|
72
|
+
// - Fetches data
|
|
73
|
+
// - Handles mutations
|
|
74
|
+
// - Contains business logic
|
|
75
|
+
// ============================================================================
|
|
76
|
+
|
|
77
|
+
// app/(dashboard)/users/page.tsx
|
|
78
|
+
"use client";
|
|
79
|
+
|
|
80
|
+
import { useState } from "react";
|
|
81
|
+
import { useQuery } from "@tanstack/react-query";
|
|
82
|
+
import { userService, UserListParams } from "@/services/users";
|
|
83
|
+
import { queryKeys } from "@/lib/queryKeys";
|
|
84
|
+
import { UserTable } from "@/components/tables/UserTable";
|
|
85
|
+
|
|
86
|
+
export default function UsersPage() {
|
|
87
|
+
const [filters, setFilters] = useState<UserListParams>({ page: 1 });
|
|
88
|
+
|
|
89
|
+
const { data, isLoading } = useQuery({
|
|
90
|
+
queryKey: queryKeys.users.list(filters),
|
|
91
|
+
queryFn: () => userService.list(filters),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<UserTable
|
|
96
|
+
users={data?.data ?? []}
|
|
97
|
+
loading={isLoading}
|
|
98
|
+
pagination={data?.meta}
|
|
99
|
+
onPageChange={(page) => setFilters({ ...filters, page })}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ============================================================================
|
|
105
|
+
// PRESENTATIONAL COMPONENT (Dumb) - reusable UI
|
|
106
|
+
// - Receives data via props
|
|
107
|
+
// - No data fetching
|
|
108
|
+
// - No business logic
|
|
109
|
+
// ============================================================================
|
|
110
|
+
|
|
111
|
+
// components/tables/UserTable.tsx
|
|
112
|
+
import { Table } from "antd";
|
|
113
|
+
import type { User } from "@/types/model";
|
|
114
|
+
import type { PaginatedResponse } from "@/lib/api";
|
|
115
|
+
|
|
116
|
+
interface UserTableProps {
|
|
117
|
+
users: User[];
|
|
118
|
+
loading: boolean;
|
|
119
|
+
pagination?: PaginatedResponse<User>["meta"];
|
|
120
|
+
onPageChange: (page: number) => void;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function UserTable({ users, loading, pagination, onPageChange }: UserTableProps) {
|
|
124
|
+
return (
|
|
125
|
+
<Table
|
|
126
|
+
dataSource={users}
|
|
127
|
+
loading={loading}
|
|
128
|
+
rowKey="id"
|
|
129
|
+
pagination={{
|
|
130
|
+
current: pagination?.current_page,
|
|
131
|
+
total: pagination?.total,
|
|
132
|
+
onChange: onPageChange,
|
|
133
|
+
}}
|
|
134
|
+
columns={[
|
|
135
|
+
{ title: "ID", dataIndex: "id" },
|
|
136
|
+
{ title: "Name", dataIndex: "name" },
|
|
137
|
+
{ title: "Email", dataIndex: "email" },
|
|
138
|
+
]}
|
|
139
|
+
/>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Form Pattern with Laravel Validation
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
"use client";
|
|
150
|
+
|
|
151
|
+
import { Form, Input, Button, message } from "antd";
|
|
152
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
153
|
+
import { useTranslations } from "next-intl";
|
|
154
|
+
import { getFormErrors } from "@/lib/api";
|
|
155
|
+
import { queryKeys } from "@/lib/queryKeys";
|
|
156
|
+
import { userService } from "@/services/users";
|
|
157
|
+
|
|
158
|
+
export default function UserForm() {
|
|
159
|
+
const t = useTranslations();
|
|
160
|
+
const [form] = Form.useForm();
|
|
161
|
+
const queryClient = useQueryClient();
|
|
162
|
+
|
|
163
|
+
const mutation = useMutation({
|
|
164
|
+
mutationFn: userService.create,
|
|
165
|
+
onSuccess: () => {
|
|
166
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.users.all });
|
|
167
|
+
message.success(t("messages.created"));
|
|
168
|
+
form.resetFields();
|
|
169
|
+
},
|
|
170
|
+
onError: (error) => {
|
|
171
|
+
// This maps Laravel's 422 { errors: { email: ["Already exists"] } }
|
|
172
|
+
// to Ant Design's form.setFields format
|
|
173
|
+
form.setFields(getFormErrors(error));
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<Form
|
|
179
|
+
form={form}
|
|
180
|
+
layout="vertical"
|
|
181
|
+
onFinish={(values) => mutation.mutate(values)}
|
|
182
|
+
>
|
|
183
|
+
<Form.Item
|
|
184
|
+
name="name"
|
|
185
|
+
label={t("common.name")}
|
|
186
|
+
rules={[{ required: true }]}
|
|
187
|
+
>
|
|
188
|
+
<Input />
|
|
189
|
+
</Form.Item>
|
|
190
|
+
|
|
191
|
+
<Form.Item
|
|
192
|
+
name="email"
|
|
193
|
+
label={t("auth.email")}
|
|
194
|
+
rules={[{ required: true }, { type: "email" }]}
|
|
195
|
+
>
|
|
196
|
+
<Input />
|
|
197
|
+
</Form.Item>
|
|
198
|
+
|
|
199
|
+
<Form.Item>
|
|
200
|
+
<Button
|
|
201
|
+
type="primary"
|
|
202
|
+
htmlType="submit"
|
|
203
|
+
loading={mutation.isPending}
|
|
204
|
+
>
|
|
205
|
+
{t("common.save")}
|
|
206
|
+
</Button>
|
|
207
|
+
</Form.Item>
|
|
208
|
+
</Form>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## ⚠️ Ant Design 6+ Breaking Changes & Deprecated Props
|
|
216
|
+
|
|
217
|
+
> **CRITICAL**: Always use the latest prop names. Using deprecated props will show console warnings and may break in future versions.
|
|
218
|
+
|
|
219
|
+
### Deprecated Props Reference Table
|
|
220
|
+
|
|
221
|
+
| Component | ❌ Deprecated | ✅ Use Instead | Warning Message |
|
|
222
|
+
| ------------ | -------------------------- | ----------------------- | -------------------------------------------------- |
|
|
223
|
+
| **Divider** | `orientation` | `titlePlacement` | `orientation` is used for direction, use `titlePlacement` |
|
|
224
|
+
| Modal | `visible` | `open` | |
|
|
225
|
+
| Drawer | `visible` | `open` | |
|
|
226
|
+
| Dropdown | `visible` | `open` | |
|
|
227
|
+
| Tooltip | `visible` | `open` | |
|
|
228
|
+
| Popover | `visible` | `open` | |
|
|
229
|
+
| Popconfirm | `visible` | `open` | |
|
|
230
|
+
| Select | `dropdownMatchSelectWidth` | `popupMatchSelectWidth` | |
|
|
231
|
+
| TreeSelect | `dropdownMatchSelectWidth` | `popupMatchSelectWidth` | |
|
|
232
|
+
| Cascader | `dropdownMatchSelectWidth` | `popupMatchSelectWidth` | |
|
|
233
|
+
| AutoComplete | `dropdownMatchSelectWidth` | `popupMatchSelectWidth` | |
|
|
234
|
+
| Table | `filterDropdownVisible` | `filterDropdownOpen` | |
|
|
235
|
+
| DatePicker | `dropdownClassName` | `popupClassName` | |
|
|
236
|
+
| TimePicker | `dropdownClassName` | `popupClassName` | |
|
|
237
|
+
| Mentions | `dropdownClassName` | `popupClassName` | |
|
|
238
|
+
| Tag | `closable` | `closeIcon={false}` | Use `closeIcon={false}` to hide close icon |
|
|
239
|
+
| Notification | `message` | `description` | Some API changes in v6 |
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
// ❌ DON'T: Use deprecated props (causes console warnings)
|
|
243
|
+
<Divider orientation="left">Title</Divider>
|
|
244
|
+
<Modal visible={isOpen}>
|
|
245
|
+
<Dropdown visible={show}>
|
|
246
|
+
<Select dropdownMatchSelectWidth={false}>
|
|
247
|
+
|
|
248
|
+
// ✅ DO: Use new props (Ant Design 6+)
|
|
249
|
+
<Divider titlePlacement="left">Title</Divider>
|
|
250
|
+
<Modal open={isOpen}>
|
|
251
|
+
<Dropdown open={show}>
|
|
252
|
+
<Select popupMatchSelectWidth={false}>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 🚨 Common Ant Design 6+ Mistakes
|
|
258
|
+
|
|
259
|
+
### 1. Divider - `orientation` vs `titlePlacement`
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
// ❌ WRONG: This will show a deprecation warning!
|
|
263
|
+
<Divider orientation="left">Section Title</Divider>
|
|
264
|
+
// Warning: [antd: Divider] `orientation` is used for direction, please use `titlePlacement` replace this
|
|
265
|
+
|
|
266
|
+
// ✅ CORRECT: Use titlePlacement for title position
|
|
267
|
+
<Divider titlePlacement="left">Section Title</Divider>
|
|
268
|
+
<Divider titlePlacement="center">Section Title</Divider>
|
|
269
|
+
<Divider titlePlacement="right">Section Title</Divider>
|
|
270
|
+
|
|
271
|
+
// For horizontal/vertical dividers, use `type`:
|
|
272
|
+
<Divider type="horizontal" /> // default
|
|
273
|
+
<Divider type="vertical" />
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### 2. Modal/Drawer - `visible` vs `open`
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
// ❌ WRONG
|
|
280
|
+
const [visible, setVisible] = useState(false);
|
|
281
|
+
<Modal visible={visible} onCancel={() => setVisible(false)}>
|
|
282
|
+
|
|
283
|
+
// ✅ CORRECT
|
|
284
|
+
const [open, setOpen] = useState(false);
|
|
285
|
+
<Modal open={open} onCancel={() => setOpen(false)}>
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### 3. Form.Item - `dependencies` must be array
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
// ❌ WRONG: dependencies as string
|
|
292
|
+
<Form.Item dependencies="password">
|
|
293
|
+
|
|
294
|
+
// ✅ CORRECT: dependencies as array
|
|
295
|
+
<Form.Item dependencies={['password']}>
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### 4. Table - Column `render` function signature
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
// ❌ WRONG: Using wrong parameter order
|
|
302
|
+
columns={[{
|
|
303
|
+
render: (record, text) => <span>{text}</span> // WRONG ORDER!
|
|
304
|
+
}]}
|
|
305
|
+
|
|
306
|
+
// ✅ CORRECT: (text, record, index)
|
|
307
|
+
columns={[{
|
|
308
|
+
render: (text, record, index) => <span>{text}</span>
|
|
309
|
+
}]}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### 5. Select/TreeSelect - Option rendering
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
// ❌ WRONG: Using children in v6+
|
|
316
|
+
<Select>
|
|
317
|
+
<Select.Option value="1">Option 1</Select.Option>
|
|
318
|
+
</Select>
|
|
319
|
+
|
|
320
|
+
// ✅ PREFERRED: Use options prop (better performance)
|
|
321
|
+
<Select options={[
|
|
322
|
+
{ value: '1', label: 'Option 1' },
|
|
323
|
+
{ value: '2', label: 'Option 2' },
|
|
324
|
+
]} />
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### 6. DatePicker - Dayjs instead of Moment
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
// ❌ WRONG: Ant Design 6+ uses dayjs, not moment
|
|
331
|
+
import moment from 'moment';
|
|
332
|
+
<DatePicker value={moment(date)} />
|
|
333
|
+
|
|
334
|
+
// ✅ CORRECT: Use dayjs
|
|
335
|
+
import dayjs from 'dayjs';
|
|
336
|
+
<DatePicker value={dayjs(date)} />
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### 7. ConfigProvider - Theme customization
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
// ❌ WRONG: Old less variable approach
|
|
343
|
+
@primary-color: #1890ff;
|
|
344
|
+
|
|
345
|
+
// ✅ CORRECT: Use ConfigProvider theme token
|
|
346
|
+
<ConfigProvider
|
|
347
|
+
theme={{
|
|
348
|
+
token: {
|
|
349
|
+
colorPrimary: '#1890ff',
|
|
350
|
+
borderRadius: 6,
|
|
351
|
+
},
|
|
352
|
+
}}
|
|
353
|
+
>
|
|
354
|
+
<App />
|
|
355
|
+
</ConfigProvider>
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### 8. App Component - message/notification/modal
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
// ❌ WRONG: Direct import (won't respect ConfigProvider)
|
|
362
|
+
import { message } from 'antd';
|
|
363
|
+
message.success('Done!');
|
|
364
|
+
|
|
365
|
+
// ✅ CORRECT: Use App.useApp() hook
|
|
366
|
+
import { App } from 'antd';
|
|
367
|
+
|
|
368
|
+
function MyComponent() {
|
|
369
|
+
const { message, notification, modal } = App.useApp();
|
|
370
|
+
|
|
371
|
+
const handleClick = () => {
|
|
372
|
+
message.success('Done!'); // Respects ConfigProvider theme
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Wrap your app with App component
|
|
377
|
+
<ConfigProvider theme={...}>
|
|
378
|
+
<App>
|
|
379
|
+
<YourApp />
|
|
380
|
+
</App>
|
|
381
|
+
</ConfigProvider>
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### 9. Icons - Named imports required
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
// ❌ WRONG: Default import
|
|
388
|
+
import Icon from '@ant-design/icons';
|
|
389
|
+
<Icon type="user" />
|
|
390
|
+
|
|
391
|
+
// ✅ CORRECT: Named imports
|
|
392
|
+
import { UserOutlined, EditOutlined } from '@ant-design/icons';
|
|
393
|
+
<UserOutlined />
|
|
394
|
+
<EditOutlined />
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### 10. Grid - `xs`, `sm`, etc. as objects
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
// ❌ CAREFUL: Mixing number and object syntax
|
|
401
|
+
<Col xs={24} sm={{ span: 12, offset: 6 }}>
|
|
402
|
+
|
|
403
|
+
// ✅ PREFERRED: Be consistent
|
|
404
|
+
<Col xs={{ span: 24 }} sm={{ span: 12, offset: 6 }}>
|
|
405
|
+
// OR
|
|
406
|
+
<Col xs={24} sm={12}>
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## Anti-Patterns
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
// ❌ Creating components that Ant Design already has
|
|
415
|
+
function CustomButton({ children }) { ... } // Use <Button> from antd
|
|
416
|
+
function CustomModal({ visible }) { ... } // Use <Modal> from antd
|
|
417
|
+
function CustomTable({ data }) { ... } // Use <Table> from antd
|
|
418
|
+
function DataGrid({ rows }) { ... } // Use <Table> from antd
|
|
419
|
+
|
|
420
|
+
// ❌ Installing libraries without permission
|
|
421
|
+
npm install lodash // Ask first!
|
|
422
|
+
npm install react-icons // Use @ant-design/icons
|
|
423
|
+
npm install styled-components // Use Tailwind CSS
|
|
424
|
+
|
|
425
|
+
// ❌ API call in component (bypass service layer)
|
|
426
|
+
function UserList() {
|
|
427
|
+
const { data } = useQuery({
|
|
428
|
+
queryKey: ["users"],
|
|
429
|
+
queryFn: () => axios.get("/api/users"), // WRONG: Use service
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ❌ Business logic in component
|
|
434
|
+
function UserList() {
|
|
435
|
+
const users = data?.filter(u => u.active).sort((a, b) => a.name > b.name);
|
|
436
|
+
// Move to service or utility function
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// ❌ Hardcoded strings - use i18n
|
|
440
|
+
<Button>Save</Button> // WRONG
|
|
441
|
+
<Button>{t("common.save")}</Button> // CORRECT
|
|
442
|
+
|
|
443
|
+
// ❌ Multiple sources of truth
|
|
444
|
+
const [users, setUsers] = useState([]); // Local state
|
|
445
|
+
const { data } = useQuery({ ... }); // Server state
|
|
446
|
+
// Pick one: TanStack Query for server data
|
|
447
|
+
|
|
448
|
+
// ❌ Prop drilling
|
|
449
|
+
<Parent data={data}>
|
|
450
|
+
<Child data={data}>
|
|
451
|
+
<GrandChild data={data} /> // Use Context or pass minimal props
|
|
452
|
+
</Child>
|
|
453
|
+
</Parent>
|
|
454
|
+
|
|
455
|
+
// ❌ Giant components (>200 lines)
|
|
456
|
+
// Split into smaller components or extract hooks
|
|
457
|
+
```
|