ginskill-init 1.0.2 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ginskill-init might be problematic. Click here for more details.
- package/package.json +1 -1
- package/skills/ant-design/SKILL.md +323 -0
- package/skills/ant-design/docs/components.md +160 -0
- package/skills/ant-design/docs/data-entry.md +406 -0
- package/skills/ant-design/docs/display.md +594 -0
- package/skills/ant-design/docs/feedback.md +451 -0
- package/skills/ant-design/docs/key-components.md +414 -0
- package/skills/ant-design/docs/navigation.md +310 -0
- package/skills/ant-design/docs/pro-components.md +543 -0
- package/skills/ant-design/docs/setup.md +213 -0
- package/skills/ant-design/docs/theme.md +265 -0
- package/skills/ant-design/scripts/fetch-component-docs.sh +169 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
# Ant Design Pro Components (ProTable, ProForm, ProLayout)
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
npm install @ant-design/pro-components
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
> Pro Components build on top of antd for enterprise admin UIs. All three share the same package.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## ProTable
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { ProTable } from '@ant-design/pro-components';
|
|
15
|
+
import type { ProColumns, ActionType } from '@ant-design/pro-components';
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Core Concept
|
|
19
|
+
|
|
20
|
+
Every column in `columns` automatically generates a filter form field. The `request` prop handles data fetching with pagination, sorting, and filtering — no manual state management needed.
|
|
21
|
+
|
|
22
|
+
### ProTableProps (key props)
|
|
23
|
+
|
|
24
|
+
| Prop | Type | Description |
|
|
25
|
+
|------|------|-------------|
|
|
26
|
+
| `columns` | `ProColumns<T>[]` | Column + filter form definitions |
|
|
27
|
+
| `request` | `(params, sort, filter) => Promise<RequestData<T>>` | Data fetcher — the primary API |
|
|
28
|
+
| `actionRef` | `Ref<ActionType>` | Imperative table control (reload, reset, etc.) |
|
|
29
|
+
| `dataSource` | `T[]` | Static data (alternative to `request`) |
|
|
30
|
+
| `rowKey` | `string \| (r) => string` | Unique row key (default `'id'`) |
|
|
31
|
+
| `headerTitle` | `ReactNode` | Title above table |
|
|
32
|
+
| `toolBarRender` | `(action, { selectedRows }) => ReactNode[]` | Toolbar elements |
|
|
33
|
+
| `search` | `SearchConfig \| false` | Filter form config; `false` = hide |
|
|
34
|
+
| `pagination` | `PaginationConfig` | Pagination; default `{ pageSize: 20 }` |
|
|
35
|
+
| `rowSelection` | `TableRowSelection<T>` | Row selection config |
|
|
36
|
+
| `params` | `Record<string, any>` | Extra params always sent to `request` |
|
|
37
|
+
| `options` | `OptionsConfig \| false` | Toolbar icons (reload, density, settings) |
|
|
38
|
+
| `dateFormatter` | `'string' \| 'number' \| false` | Auto-format date params in request |
|
|
39
|
+
|
|
40
|
+
### request function signature
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
type RequestFn<T> = (
|
|
44
|
+
params: {
|
|
45
|
+
pageSize?: number; // current page size
|
|
46
|
+
current?: number; // 1-indexed page number
|
|
47
|
+
[filterKey: string]: any; // filter form values
|
|
48
|
+
},
|
|
49
|
+
sort: Record<string, 'ascend' | 'descend'>, // column sort state
|
|
50
|
+
filter: Record<string, (string | number)[]>, // column filter state
|
|
51
|
+
) => Promise<{
|
|
52
|
+
data: T[];
|
|
53
|
+
success: boolean; // REQUIRED — table checks this
|
|
54
|
+
total?: number; // REQUIRED for pagination
|
|
55
|
+
}>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### ProColumns
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
type ProColumns<T> = {
|
|
62
|
+
// Standard antd column props
|
|
63
|
+
dataIndex?: string | string[];
|
|
64
|
+
title?: string | ReactNode;
|
|
65
|
+
key?: string;
|
|
66
|
+
width?: number | string;
|
|
67
|
+
fixed?: 'left' | 'right';
|
|
68
|
+
align?: 'left' | 'center' | 'right';
|
|
69
|
+
sorter?: boolean | ((a: T, b: T) => number);
|
|
70
|
+
render?: (text, record: T, index, action: ActionType) => ReactNode;
|
|
71
|
+
|
|
72
|
+
// Pro-specific
|
|
73
|
+
valueType?: ProColumnValueType; // determines filter field and display
|
|
74
|
+
valueEnum?: ValueEnumMap; // for select/status display
|
|
75
|
+
hideInSearch?: boolean; // hide from filter form
|
|
76
|
+
hideInTable?: boolean; // hide from table columns
|
|
77
|
+
copyable?: boolean; // show copy icon
|
|
78
|
+
ellipsis?: boolean; // truncate text
|
|
79
|
+
tooltip?: string; // column header tooltip
|
|
80
|
+
fieldProps?: Record<string, any>; // props for the filter field component
|
|
81
|
+
formItemProps?: Record<string, any>; // props for Form.Item in filter
|
|
82
|
+
initialValue?: any; // initial filter value
|
|
83
|
+
request?: () => Promise<{ label, value }[]>; // async options
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### valueType options
|
|
88
|
+
|
|
89
|
+
| valueType | Table display | Filter field |
|
|
90
|
+
|-----------|--------------|--------------|
|
|
91
|
+
| `'text'` | plain text | Input |
|
|
92
|
+
| `'select'` | badge/label | Select |
|
|
93
|
+
| `'date'` | formatted date | DatePicker |
|
|
94
|
+
| `'dateTime'` | date + time | DatePicker (time) |
|
|
95
|
+
| `'dateRange'` | — | RangePicker |
|
|
96
|
+
| `'money'` | `¥1,234.00` | InputNumber |
|
|
97
|
+
| `'percent'` | `12%` | InputNumber |
|
|
98
|
+
| `'progress'` | ProgressBar | Slider |
|
|
99
|
+
| `'status'` | colored badge | Select |
|
|
100
|
+
| `'avatar'` | Avatar | — |
|
|
101
|
+
| `'image'` | image | — |
|
|
102
|
+
| `'switch'` | — | Switch |
|
|
103
|
+
| `'option'` | action links | hidden |
|
|
104
|
+
|
|
105
|
+
### valueEnum format
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
// Object form (key = value, status for badge color)
|
|
109
|
+
valueEnum: {
|
|
110
|
+
active: { text: 'Active', status: 'success' },
|
|
111
|
+
inactive: { text: 'Inactive', status: 'error' },
|
|
112
|
+
pending: { text: 'Pending', status: 'processing' },
|
|
113
|
+
}
|
|
114
|
+
// status: 'success' | 'error' | 'processing' | 'warning' | 'default'
|
|
115
|
+
|
|
116
|
+
// Array form
|
|
117
|
+
valueEnum: [
|
|
118
|
+
{ label: 'Active', value: 'active' },
|
|
119
|
+
{ label: 'Inactive', value: 'inactive' },
|
|
120
|
+
]
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### ActionType methods
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
const actionRef = useRef<ActionType>(null);
|
|
127
|
+
|
|
128
|
+
actionRef.current?.reload(); // reload current page
|
|
129
|
+
actionRef.current?.reloadAndRest(); // reload + reset to page 1
|
|
130
|
+
actionRef.current?.reset(); // reset filter form only
|
|
131
|
+
actionRef.current?.clearSelected(); // clear row selection
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Complete example
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
import { ProTable, ActionType } from '@ant-design/pro-components';
|
|
138
|
+
import type { ProColumns } from '@ant-design/pro-components';
|
|
139
|
+
import { useRef } from 'react';
|
|
140
|
+
|
|
141
|
+
interface User { id: string; name: string; status: string; createdAt: string; }
|
|
142
|
+
|
|
143
|
+
const UserTable = () => {
|
|
144
|
+
const actionRef = useRef<ActionType>(null);
|
|
145
|
+
|
|
146
|
+
const columns: ProColumns<User>[] = [
|
|
147
|
+
{ dataIndex: 'name', title: 'Name' },
|
|
148
|
+
{
|
|
149
|
+
dataIndex: 'status', title: 'Status', valueType: 'select',
|
|
150
|
+
valueEnum: {
|
|
151
|
+
active: { text: 'Active', status: 'success' },
|
|
152
|
+
inactive: { text: 'Inactive', status: 'error' },
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
{ dataIndex: 'createdAt', title: 'Created', valueType: 'dateTime', hideInSearch: true },
|
|
156
|
+
{
|
|
157
|
+
title: 'Actions', valueType: 'option', fixed: 'right', width: 120,
|
|
158
|
+
render: (_, record) => [
|
|
159
|
+
<a key="edit" onClick={() => openEditModal(record)}>Edit</a>,
|
|
160
|
+
<a key="delete" onClick={() => confirmDelete(record.id)}>Delete</a>,
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<ProTable<User>
|
|
167
|
+
actionRef={actionRef}
|
|
168
|
+
columns={columns}
|
|
169
|
+
rowKey="id"
|
|
170
|
+
headerTitle="Users"
|
|
171
|
+
request={async ({ current, pageSize, name, status }) => {
|
|
172
|
+
const res = await api.getUsers({ page: current, size: pageSize, name, status });
|
|
173
|
+
return { data: res.items, success: true, total: res.total };
|
|
174
|
+
}}
|
|
175
|
+
toolBarRender={() => [
|
|
176
|
+
<Button key="add" type="primary" onClick={openCreateModal}>+ Add</Button>,
|
|
177
|
+
<Button key="reload" onClick={() => actionRef.current?.reload()}>Reload</Button>,
|
|
178
|
+
]}
|
|
179
|
+
search={{ labelWidth: 'auto' }}
|
|
180
|
+
pagination={{ pageSize: 20, showSizeChanger: true }}
|
|
181
|
+
/>
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Common Mistakes
|
|
187
|
+
- **Missing `success: true`** in request response — table stays loading forever
|
|
188
|
+
- **Missing `total`** — pagination breaks without it
|
|
189
|
+
- **`dataIndex` mismatch** — must match your API response field names exactly
|
|
190
|
+
- **Filter params are primitives** — complex objects may not serialize correctly in requests
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## ProForm
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
import {
|
|
198
|
+
ProForm, ProFormText, ProFormSelect, ProFormTextArea,
|
|
199
|
+
ProFormDatePicker, ProFormDateRangePicker, ProFormSwitch,
|
|
200
|
+
ProFormCheckbox, ProFormRadio, ProFormMoney, ProFormUploadButton,
|
|
201
|
+
ProFormDependencies, ModalForm, DrawerForm, QueryFilter, StepsForm,
|
|
202
|
+
} from '@ant-design/pro-components';
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Core Concept
|
|
206
|
+
|
|
207
|
+
`onFinish` returns a `Promise` — ProForm automatically manages button loading state. Fields use `name` to bind to form state; `fieldProps` passes props to the underlying component.
|
|
208
|
+
|
|
209
|
+
### ProFormProps (key props)
|
|
210
|
+
|
|
211
|
+
| Prop | Type | Default | Description |
|
|
212
|
+
|------|------|---------|-------------|
|
|
213
|
+
| `onFinish` | `(values) => Promise<boolean \| void>` | — | Submit handler — **must return a Promise** |
|
|
214
|
+
| `initialValues` | `Record<string, any>` | — | Initial form values |
|
|
215
|
+
| `layout` | `'horizontal' \| 'vertical' \| 'inline'` | `'vertical'` | Form layout |
|
|
216
|
+
| `grid` | `boolean` | `false` | Enable grid layout for fields |
|
|
217
|
+
| `colProps` | `ColProps` | — | Default column props for all fields |
|
|
218
|
+
| `submitter` | `SubmitterProps \| false` | — | Submit/reset button config; `false` = hide |
|
|
219
|
+
| `readonly` | `boolean` | `false` | Read-only display mode |
|
|
220
|
+
| `disabled` | `boolean` | `false` | Disable all fields |
|
|
221
|
+
| `scrollToFirstError` | `boolean` | — | Scroll to first error on submit |
|
|
222
|
+
|
|
223
|
+
### Field Width Presets
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// width prop on any ProFormXxx field
|
|
227
|
+
'xs' // 104px — short (ID, code)
|
|
228
|
+
's' // 216px — shorter (name, phone)
|
|
229
|
+
'm' // 328px — standard (default)
|
|
230
|
+
'lg' // 440px — long (URL, tag list)
|
|
231
|
+
'xl' // 552px — very long (description)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### ProForm Field Components
|
|
235
|
+
|
|
236
|
+
All fields share common props:
|
|
237
|
+
```typescript
|
|
238
|
+
{
|
|
239
|
+
name: string | string[]; // field name (required)
|
|
240
|
+
label?: ReactNode; // field label
|
|
241
|
+
rules?: Rule[]; // validation rules
|
|
242
|
+
required?: boolean; // mark as required
|
|
243
|
+
tooltip?: string | ReactNode; // help tooltip
|
|
244
|
+
placeholder?: string;
|
|
245
|
+
width?: 'xs' | 's' | 'm' | 'lg' | 'xl' | number;
|
|
246
|
+
initialValue?: any;
|
|
247
|
+
disabled?: boolean;
|
|
248
|
+
readonly?: boolean;
|
|
249
|
+
fieldProps?: Record<string, any>; // props for underlying component
|
|
250
|
+
formItemProps?: Record<string, any>; // props for Form.Item
|
|
251
|
+
colProps?: ColProps; // column props for grid layout
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Key Fields
|
|
256
|
+
|
|
257
|
+
```tsx
|
|
258
|
+
// Text input
|
|
259
|
+
<ProFormText name="name" label="Name" placeholder="Enter name"
|
|
260
|
+
rules={[{ required: true }, { min: 2 }]} width="m" />
|
|
261
|
+
|
|
262
|
+
// Password
|
|
263
|
+
<ProFormText name="pwd" label="Password" fieldProps={{ type: 'password' }} />
|
|
264
|
+
|
|
265
|
+
// Textarea
|
|
266
|
+
<ProFormTextArea name="bio" label="Bio" fieldProps={{ rows: 4, showCount: true, maxLength: 500 }} />
|
|
267
|
+
|
|
268
|
+
// Select from options
|
|
269
|
+
<ProFormSelect name="role" label="Role"
|
|
270
|
+
options={[{ label: 'Admin', value: 'admin' }, { label: 'User', value: 'user' }]} />
|
|
271
|
+
|
|
272
|
+
// Select from valueEnum
|
|
273
|
+
<ProFormSelect name="status" label="Status"
|
|
274
|
+
valueEnum={{ active: { text: 'Active' }, inactive: { text: 'Inactive' } }} />
|
|
275
|
+
|
|
276
|
+
// Async select options
|
|
277
|
+
<ProFormSelect name="category" label="Category"
|
|
278
|
+
request={async () => {
|
|
279
|
+
const res = await api.getCategories();
|
|
280
|
+
return res.map(c => ({ label: c.name, value: c.id }));
|
|
281
|
+
}} />
|
|
282
|
+
|
|
283
|
+
// Date picker
|
|
284
|
+
<ProFormDatePicker name="date" label="Date" fieldProps={{ format: 'YYYY-MM-DD' }} />
|
|
285
|
+
|
|
286
|
+
// Date range
|
|
287
|
+
<ProFormDateRangePicker name="dateRange" label="Date Range" />
|
|
288
|
+
|
|
289
|
+
// Switch (boolean)
|
|
290
|
+
<ProFormSwitch name="active" label="Active"
|
|
291
|
+
fieldProps={{ checkedChildren: 'On', unCheckedChildren: 'Off' }} />
|
|
292
|
+
|
|
293
|
+
// Checkbox group
|
|
294
|
+
<ProFormCheckbox.Group name="perms" label="Permissions"
|
|
295
|
+
options={[{ label: 'Read', value: 'r' }, { label: 'Write', value: 'w' }]} />
|
|
296
|
+
|
|
297
|
+
// Radio group
|
|
298
|
+
<ProFormRadio.Group name="size" label="Size"
|
|
299
|
+
options={[{ label: 'S', value: 's' }, { label: 'M', value: 'm' }]} />
|
|
300
|
+
|
|
301
|
+
// Money
|
|
302
|
+
<ProFormMoney name="price" label="Price" fieldProps={{ locale: 'en-US' }} />
|
|
303
|
+
|
|
304
|
+
// Upload
|
|
305
|
+
<ProFormUploadButton name="avatar" label="Avatar"
|
|
306
|
+
fieldProps={{ action: '/api/upload', accept: 'image/*', maxCount: 1, listType: 'picture-card' }} />
|
|
307
|
+
|
|
308
|
+
// Conditional fields (dependencies)
|
|
309
|
+
<ProFormDependencies name={['accountType']}>
|
|
310
|
+
{({ accountType }) =>
|
|
311
|
+
accountType === 'business' ? (
|
|
312
|
+
<ProFormText name="companyName" label="Company Name" rules={[{ required: true }]} />
|
|
313
|
+
) : null
|
|
314
|
+
}
|
|
315
|
+
</ProFormDependencies>
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### ProForm Variants
|
|
319
|
+
|
|
320
|
+
```tsx
|
|
321
|
+
// Modal form (trigger opens a modal with the form)
|
|
322
|
+
<ModalForm title="Create User" trigger={<Button type="primary">Add User</Button>}
|
|
323
|
+
onFinish={async (values) => {
|
|
324
|
+
await createUser(values);
|
|
325
|
+
return true; // true closes the modal
|
|
326
|
+
}}>
|
|
327
|
+
<ProFormText name="name" label="Name" rules={[{ required: true }]} />
|
|
328
|
+
<ProFormText name="email" label="Email" rules={[{ type: 'email' }]} />
|
|
329
|
+
</ModalForm>
|
|
330
|
+
|
|
331
|
+
// Drawer form
|
|
332
|
+
<DrawerForm title="Edit Record" trigger={<a>Edit</a>}
|
|
333
|
+
drawerProps={{ destroyOnClose: true }}
|
|
334
|
+
onFinish={async (values) => {
|
|
335
|
+
await updateRecord(values);
|
|
336
|
+
return true;
|
|
337
|
+
}}>
|
|
338
|
+
<ProFormText name="title" label="Title" />
|
|
339
|
+
</DrawerForm>
|
|
340
|
+
|
|
341
|
+
// Query filter (horizontal filter bar)
|
|
342
|
+
<QueryFilter onFinish={async (filters) => setFilters(filters)} span={8} labelWidth="auto">
|
|
343
|
+
<ProFormText name="keyword" label="Keyword" />
|
|
344
|
+
<ProFormSelect name="status" label="Status" options={statusOptions} />
|
|
345
|
+
</QueryFilter>
|
|
346
|
+
|
|
347
|
+
// Step form
|
|
348
|
+
<StepsForm onFinish={async (allValues) => { await submit(allValues); }}>
|
|
349
|
+
<StepsForm.StepForm title="Basic Info">
|
|
350
|
+
<ProFormText name="name" label="Name" rules={[{ required: true }]} />
|
|
351
|
+
</StepsForm.StepForm>
|
|
352
|
+
<StepsForm.StepForm title="Contact">
|
|
353
|
+
<ProFormText name="email" label="Email" rules={[{ required: true, type: 'email' }]} />
|
|
354
|
+
</StepsForm.StepForm>
|
|
355
|
+
</StepsForm>
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Complete ProForm example
|
|
359
|
+
|
|
360
|
+
```tsx
|
|
361
|
+
const CreateUserForm = () => (
|
|
362
|
+
<ProForm
|
|
363
|
+
layout="vertical"
|
|
364
|
+
grid
|
|
365
|
+
colProps={{ span: 12 }}
|
|
366
|
+
onFinish={async (values) => {
|
|
367
|
+
await api.createUser(values);
|
|
368
|
+
message.success('Created!');
|
|
369
|
+
return true;
|
|
370
|
+
}}
|
|
371
|
+
submitter={{ submitButtonProps: { block: true } }}
|
|
372
|
+
>
|
|
373
|
+
<ProFormText name="firstName" label="First Name" rules={[{ required: true }]} />
|
|
374
|
+
<ProFormText name="lastName" label="Last Name" rules={[{ required: true }]} />
|
|
375
|
+
<ProFormText name="email" label="Email" colProps={{ span: 24 }}
|
|
376
|
+
rules={[{ required: true }, { type: 'email' }]} />
|
|
377
|
+
<ProFormSelect name="role" label="Role" colProps={{ span: 24 }}
|
|
378
|
+
options={[{ label: 'Admin', value: 'admin' }, { label: 'User', value: 'user' }]} />
|
|
379
|
+
<ProFormSwitch name="active" label="Active" initialValue={true} />
|
|
380
|
+
</ProForm>
|
|
381
|
+
);
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### Common Mistakes
|
|
385
|
+
- **`onFinish` must return a Promise** — without it, the button loading state doesn't work
|
|
386
|
+
- **Return `true` from `onFinish`** in ModalForm/DrawerForm to close the overlay; `false` or throwing keeps it open
|
|
387
|
+
- **Component props go in `fieldProps`** — `<ProFormText fieldProps={{ maxLength: 20 }}>`, not as root props
|
|
388
|
+
- **Password field**: use `fieldProps={{ type: 'password' }}` on `ProFormText`
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
|
|
392
|
+
## ProLayout
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
import { ProLayout, PageContainer } from '@ant-design/pro-components';
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Core Concept
|
|
399
|
+
|
|
400
|
+
ProLayout generates a sidebar menu from a `route` config object. Use `PageContainer` inside to get automatic breadcrumbs, page title, and footer toolbar.
|
|
401
|
+
|
|
402
|
+
### ProLayoutProps (key props)
|
|
403
|
+
|
|
404
|
+
| Prop | Type | Default | Description |
|
|
405
|
+
|------|------|---------|-------------|
|
|
406
|
+
| `title` | `string \| ReactNode` | `'Ant Design Pro'` | Brand name |
|
|
407
|
+
| `logo` | `string \| ReactNode` | — | Logo in sidebar header |
|
|
408
|
+
| `layout` | `'side' \| 'top' \| 'mix'` | `'side'` | Menu placement |
|
|
409
|
+
| `navTheme` | `'light' \| 'dark' \| 'realDark'` | `'light'` | Sidebar theme |
|
|
410
|
+
| `collapsed` | `boolean` | — | Controlled sidebar collapse |
|
|
411
|
+
| `onCollapse` | `(collapsed) => void` | — | Collapse state change |
|
|
412
|
+
| `siderWidth` | `number` | `256` | Sidebar width (px) |
|
|
413
|
+
| `route` | `{ routes: MenuDataItem[] }` | — | Menu tree definition |
|
|
414
|
+
| `menuProps` | `MenuProps` | — | Passed to the Menu component |
|
|
415
|
+
| `menuDataRender` | `(menuData) => MenuDataItem[]` | — | Transform menu items (e.g., filter by permissions) |
|
|
416
|
+
| `rightContentRender` | `(props) => ReactNode` | — | Right side of header (user dropdown, etc.) |
|
|
417
|
+
| `headerRender` | `(props) => ReactNode` | — | Completely custom header |
|
|
418
|
+
| `footerRender` | `(props) => ReactNode` | — | Footer below content |
|
|
419
|
+
| `fixedHeader` | `boolean` | `false` | Sticky header |
|
|
420
|
+
| `fixSiderbar` | `boolean` | `false` | Sticky sidebar |
|
|
421
|
+
| `token` | `object` | — | Design token overrides |
|
|
422
|
+
| `onPageChange` | `(pathname) => void` | — | Navigation change callback |
|
|
423
|
+
|
|
424
|
+
### MenuDataItem
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
type MenuDataItem = {
|
|
428
|
+
name: string; // REQUIRED — label text (without this, item is hidden!)
|
|
429
|
+
path?: string; // URL path
|
|
430
|
+
icon?: ReactNode | string; // icon (string = antd icon name)
|
|
431
|
+
children?: MenuDataItem[];
|
|
432
|
+
hideInMenu?: boolean; // hide from menu but keep route
|
|
433
|
+
hideChildrenInMenu?: boolean; // show parent, hide children
|
|
434
|
+
hideInBreadcrumb?: boolean;
|
|
435
|
+
disabled?: boolean;
|
|
436
|
+
access?: string; // permission key
|
|
437
|
+
};
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### PageContainer Props
|
|
441
|
+
|
|
442
|
+
| Prop | Type | Description |
|
|
443
|
+
|------|------|-------------|
|
|
444
|
+
| `header.title` | `ReactNode` | Page title (also shown in browser tab) |
|
|
445
|
+
| `header.subTitle` | `ReactNode` | Subtitle |
|
|
446
|
+
| `header.extra` | `ReactNode` | Right side of header (action buttons) |
|
|
447
|
+
| `header.breadcrumb` | `BreadcrumbProps` | Custom breadcrumb config |
|
|
448
|
+
| `footer` | `ReactNode[]` | Footer toolbar (array of buttons) |
|
|
449
|
+
| `loading` | `boolean` | Loading state for content |
|
|
450
|
+
| `ghost` | `boolean` | Transparent background |
|
|
451
|
+
| `fixedHeader` | `boolean` | Sticky page header |
|
|
452
|
+
|
|
453
|
+
### Patterns
|
|
454
|
+
|
|
455
|
+
```tsx
|
|
456
|
+
// Basic layout with router
|
|
457
|
+
const App = () => {
|
|
458
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
459
|
+
|
|
460
|
+
return (
|
|
461
|
+
<ProLayout
|
|
462
|
+
title="My App"
|
|
463
|
+
collapsed={collapsed}
|
|
464
|
+
onCollapse={setCollapsed}
|
|
465
|
+
layout="side"
|
|
466
|
+
navTheme="dark"
|
|
467
|
+
route={{
|
|
468
|
+
routes: [
|
|
469
|
+
{ path: '/dashboard', name: 'Dashboard', icon: 'dashboard' },
|
|
470
|
+
{
|
|
471
|
+
path: '/users', name: 'Users', icon: 'team',
|
|
472
|
+
children: [
|
|
473
|
+
{ path: '/users/list', name: 'All Users' },
|
|
474
|
+
{ path: '/users/roles', name: 'Roles' },
|
|
475
|
+
],
|
|
476
|
+
},
|
|
477
|
+
{ path: '/settings', name: 'Settings', icon: 'setting' },
|
|
478
|
+
],
|
|
479
|
+
}}
|
|
480
|
+
rightContentRender={() => (
|
|
481
|
+
<Space>
|
|
482
|
+
<Avatar src={user.avatar} />
|
|
483
|
+
<Dropdown menu={{ items: userMenuItems }}>
|
|
484
|
+
<span>{user.name}</span>
|
|
485
|
+
</Dropdown>
|
|
486
|
+
</Space>
|
|
487
|
+
)}
|
|
488
|
+
footerRender={() => <Footer style={{ textAlign: 'center' }}>My App © 2024</Footer>}
|
|
489
|
+
>
|
|
490
|
+
<PageContainer header={{ title: 'Dashboard' }}>
|
|
491
|
+
<Content />
|
|
492
|
+
</PageContainer>
|
|
493
|
+
</ProLayout>
|
|
494
|
+
);
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
// Page with action buttons and footer toolbar
|
|
498
|
+
<PageContainer
|
|
499
|
+
header={{
|
|
500
|
+
title: 'User Details',
|
|
501
|
+
subTitle: 'View and edit user info',
|
|
502
|
+
extra: [
|
|
503
|
+
<Button key="edit" type="primary">Edit</Button>,
|
|
504
|
+
<Button key="delete" danger>Delete</Button>,
|
|
505
|
+
],
|
|
506
|
+
}}
|
|
507
|
+
footer={[
|
|
508
|
+
<Button key="cancel">Cancel</Button>,
|
|
509
|
+
<Button key="save" type="primary">Save</Button>,
|
|
510
|
+
]}
|
|
511
|
+
>
|
|
512
|
+
<Descriptions bordered items={userFields} />
|
|
513
|
+
</PageContainer>
|
|
514
|
+
|
|
515
|
+
// Permission-based menu filtering
|
|
516
|
+
<ProLayout
|
|
517
|
+
menuDataRender={(menuData) =>
|
|
518
|
+
menuData.filter(item => hasPermission(item.access))
|
|
519
|
+
}
|
|
520
|
+
route={{ routes: allRoutes }}
|
|
521
|
+
>
|
|
522
|
+
...
|
|
523
|
+
</ProLayout>
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### Common Mistakes
|
|
527
|
+
- **Menu item without `name` is invisible** — always set `name` on every route
|
|
528
|
+
- **Icons as strings**: `icon: 'dashboard'` not `icon: <DashboardOutlined />` (for route configs)
|
|
529
|
+
- **`hideInMenu` vs `hideChildrenInMenu`**: former hides the item completely; latter shows parent but hides its sub-items
|
|
530
|
+
- **Breadcrumbs come from route `path` hierarchy** — missing intermediate routes creates breadcrumb gaps
|
|
531
|
+
|
|
532
|
+
---
|
|
533
|
+
|
|
534
|
+
## When to Use Pro Components
|
|
535
|
+
|
|
536
|
+
| Use Case | Component |
|
|
537
|
+
|----------|-----------|
|
|
538
|
+
| Admin table with server-side filter/sort/pagination | `ProTable` |
|
|
539
|
+
| Create/edit form in modal or drawer | `ModalForm` / `DrawerForm` |
|
|
540
|
+
| Multi-step wizard form | `StepsForm` |
|
|
541
|
+
| Horizontal search/filter bar | `QueryFilter` |
|
|
542
|
+
| Admin dashboard shell (sidebar + header) | `ProLayout` |
|
|
543
|
+
| Page content with breadcrumbs + action buttons | `PageContainer` |
|