ginskill-init 1.0.1 → 1.0.3
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/README.md +109 -46
- 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,414 @@
|
|
|
1
|
+
# Ant Design — Form, Table, Modal, Button Deep Reference
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## FORM
|
|
6
|
+
|
|
7
|
+
### Form Props
|
|
8
|
+
|
|
9
|
+
| Prop | Type | Default | Notes |
|
|
10
|
+
|------|------|---------|-------|
|
|
11
|
+
| `form` | `FormInstance` | — | Created by `Form.useForm()` |
|
|
12
|
+
| `layout` | `horizontal \| vertical \| inline` | `horizontal` | |
|
|
13
|
+
| `initialValues` | `object` | — | **Not reactive after mount** — use `setFieldsValue()` for async data |
|
|
14
|
+
| `onFinish` | `(values) => void` | — | Called after successful validation |
|
|
15
|
+
| `onFinishFailed` | `({ values, errorFields }) => void` | — | Called when validation fails |
|
|
16
|
+
| `onValuesChange` | `(changed, all) => void` | — | Fires on any change |
|
|
17
|
+
| `disabled` | `boolean` | `false` | Disables all antd controls |
|
|
18
|
+
| `size` | `small \| middle \| large` | — | |
|
|
19
|
+
| `variant` | `outlined \| borderless \| filled \| underlined` | `outlined` | |
|
|
20
|
+
| `requiredMark` | `boolean \| 'optional'` | `true` | |
|
|
21
|
+
| `labelCol` | `ColProps` | — | Grid layout for labels |
|
|
22
|
+
| `wrapperCol` | `ColProps` | — | Grid layout for inputs |
|
|
23
|
+
| `scrollToFirstError` | `boolean \| ScrollOptions` | `false` | |
|
|
24
|
+
| `validateMessages` | `ValidateMessages` | — | Override validation message templates |
|
|
25
|
+
| `preserve` | `boolean` | `true` | Keep unmounted field values |
|
|
26
|
+
|
|
27
|
+
### Form.Item Props
|
|
28
|
+
|
|
29
|
+
| Prop | Type | Notes |
|
|
30
|
+
|------|------|-------|
|
|
31
|
+
| `name` | `string \| number \| (string\|number)[]` | Field path; supports nested `['user', 'address']` |
|
|
32
|
+
| `label` | `ReactNode` | |
|
|
33
|
+
| `rules` | `Rule[]` | |
|
|
34
|
+
| `required` | `boolean` | Shows asterisk only; add rule for actual validation |
|
|
35
|
+
| `valuePropName` | `string` | Use `'checked'` for Checkbox/Switch (default: `'value'`) |
|
|
36
|
+
| `validateTrigger` | `string \| string[]` | When to validate (default: `'onChange'`) |
|
|
37
|
+
| `dependencies` | `NamePath[]` | Re-validates when listed fields change |
|
|
38
|
+
| `noStyle` | `boolean` | Removes visual styling; field still registers |
|
|
39
|
+
| `hasFeedback` | `boolean` | Shows validation status icon |
|
|
40
|
+
| `initialValue` | any | Field-level default; overrides Form `initialValues` for this field |
|
|
41
|
+
| `tooltip` | `ReactNode \| TooltipProps` | Tooltip on label |
|
|
42
|
+
| `normalize` | `(value, prev, all) => any` | Transform value before storing |
|
|
43
|
+
| `getValueFromEvent` | `(event) => any` | Extract value from change event |
|
|
44
|
+
|
|
45
|
+
### FormInstance Methods
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
const [form] = Form.useForm<MyValues>();
|
|
49
|
+
|
|
50
|
+
// Reading
|
|
51
|
+
form.getFieldValue('email');
|
|
52
|
+
form.getFieldsValue(['email', 'name']);
|
|
53
|
+
form.getFieldsValue(true); // all including unstored
|
|
54
|
+
form.getFieldError('email'); // string[]
|
|
55
|
+
form.isFieldTouched('email'); // boolean
|
|
56
|
+
|
|
57
|
+
// Writing
|
|
58
|
+
form.setFieldValue('email', 'x@x.com');
|
|
59
|
+
form.setFieldsValue({ email: 'x', name: 'y' });
|
|
60
|
+
|
|
61
|
+
// Validation
|
|
62
|
+
form.validateFields(); // Promise<Values>
|
|
63
|
+
form.validateFields(['email', 'name']);
|
|
64
|
+
|
|
65
|
+
// Reset & Scroll
|
|
66
|
+
form.resetFields();
|
|
67
|
+
form.resetFields(['email']);
|
|
68
|
+
form.scrollToField('email');
|
|
69
|
+
|
|
70
|
+
// Programmatic submit
|
|
71
|
+
form.submit();
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Hooks
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
// Reactive field watch — preferred over getFieldValue in render
|
|
78
|
+
const email = Form.useWatch('email', form);
|
|
79
|
+
const address = Form.useWatch(['user', 'address'], form); // nested
|
|
80
|
+
|
|
81
|
+
// Access form status inside custom component inside Form.Item
|
|
82
|
+
const { status, errors } = Form.Item.useStatus();
|
|
83
|
+
|
|
84
|
+
// Get nearest ancestor form without prop drilling
|
|
85
|
+
const form = Form.useFormInstance();
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Validation Rules
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
rules={[
|
|
92
|
+
{ required: true, message: 'Required' },
|
|
93
|
+
{ type: 'email', message: 'Invalid email' },
|
|
94
|
+
{ type: 'url' },
|
|
95
|
+
{ min: 6, max: 50 },
|
|
96
|
+
{ len: 11, message: 'Must be exactly 11 chars' },
|
|
97
|
+
{ pattern: /^[a-z]+$/i, message: 'Letters only' },
|
|
98
|
+
{ whitespace: true, message: 'Cannot be only whitespace' },
|
|
99
|
+
{ warningOnly: true, message: 'This is just a warning' },
|
|
100
|
+
|
|
101
|
+
// Async validator
|
|
102
|
+
{
|
|
103
|
+
validator: async (_, value) => {
|
|
104
|
+
if (!value || value.length < 3) throw new Error('Too short');
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
// Cross-field (compare with another field)
|
|
109
|
+
({ getFieldValue }) => ({
|
|
110
|
+
validator(_, value) {
|
|
111
|
+
if (!value || getFieldValue('password') === value) return Promise.resolve();
|
|
112
|
+
return Promise.reject(new Error('Passwords must match'));
|
|
113
|
+
},
|
|
114
|
+
}),
|
|
115
|
+
]}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Form.List — Dynamic Fields
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
<Form.List name="users">
|
|
122
|
+
{(fields, { add, remove }) => (
|
|
123
|
+
<>
|
|
124
|
+
{fields.map((field) => (
|
|
125
|
+
<Form.Item key={field.key}>
|
|
126
|
+
<Form.Item {...field} name={[field.name, 'email']} noStyle
|
|
127
|
+
rules={[{ type: 'email', required: true }]}>
|
|
128
|
+
<Input placeholder="Email" />
|
|
129
|
+
</Form.Item>
|
|
130
|
+
<Button onClick={() => remove(field.name)}>Remove</Button>
|
|
131
|
+
</Form.Item>
|
|
132
|
+
))}
|
|
133
|
+
<Button onClick={() => add()}>Add</Button>
|
|
134
|
+
<Button onClick={() => add({ email: 'default@example.com' })}>Add with Default</Button>
|
|
135
|
+
</>
|
|
136
|
+
)}
|
|
137
|
+
</Form.List>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Common Form Mistakes
|
|
141
|
+
|
|
142
|
+
1. **`valuePropName="checked"` required for Checkbox/Switch** — otherwise stores event object
|
|
143
|
+
2. **`initialValues` is not reactive** — use `form.setFieldsValue()` for async data loads
|
|
144
|
+
3. **`htmlType="submit"` required on submit Button** — `onClick={() => form.submit()}` bypasses form events
|
|
145
|
+
4. **Do NOT call `form.getFieldsValue()` during render** — use `Form.useWatch()` instead
|
|
146
|
+
5. **Do NOT wrap multiple controls in one `Form.Item` with `name`** — use `noStyle` on inner items
|
|
147
|
+
6. **`Form.useForm()` only works in functional components** — use `ref` for class components
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## TABLE
|
|
152
|
+
|
|
153
|
+
### Table Props
|
|
154
|
+
|
|
155
|
+
| Prop | Type | Default | Notes |
|
|
156
|
+
|------|------|---------|-------|
|
|
157
|
+
| `dataSource` | `T[]` | — | Array of data |
|
|
158
|
+
| `columns` | `ColumnsType<T>` | — | Column config |
|
|
159
|
+
| `rowKey` | `string \| (record) => string` | `'key'` | **Always set this** |
|
|
160
|
+
| `pagination` | `TablePaginationConfig \| false` | — | `false` disables pagination |
|
|
161
|
+
| `loading` | `boolean \| SpinProps` | `false` | Loading overlay |
|
|
162
|
+
| `rowSelection` | `TableRowSelection<T>` | — | Checkbox/radio selection |
|
|
163
|
+
| `expandable` | `ExpandableConfig<T>` | — | Expandable rows |
|
|
164
|
+
| `onChange` | `(pagination, filters, sorter, extra) => void` | — | Server-side handler |
|
|
165
|
+
| `scroll` | `{ x?, y? }` | — | Scrollable area |
|
|
166
|
+
| `sticky` | `boolean \| { offsetHeader }` | `false` | Sticky header |
|
|
167
|
+
| `virtual` | `boolean` | `false` | Virtual scrolling (requires `scroll.y`) |
|
|
168
|
+
| `size` | `large \| middle \| small` | `large` | Density |
|
|
169
|
+
| `bordered` | `boolean` | `false` | Cell borders |
|
|
170
|
+
| `summary` | `(data) => ReactNode` | — | Summary row |
|
|
171
|
+
| `onRow` | `(record, index) => HTMLAttributes` | — | Row event handlers |
|
|
172
|
+
|
|
173
|
+
### Column Props
|
|
174
|
+
|
|
175
|
+
| Prop | Type | Notes |
|
|
176
|
+
|------|------|-------|
|
|
177
|
+
| `title` | `ReactNode` | Header content |
|
|
178
|
+
| `dataIndex` | `string \| string[]` | Use array for nested: `['user', 'name']` |
|
|
179
|
+
| `key` | `string` | Required if no `dataIndex` |
|
|
180
|
+
| `render` | `(value, record, index) => ReactNode` | Custom cell renderer |
|
|
181
|
+
| `width` | `string \| number` | **Required with `fixed`** |
|
|
182
|
+
| `fixed` | `'left' \| 'right'` | Requires `scroll.x` on table |
|
|
183
|
+
| `sorter` | `boolean \| CompareFn \| { compare, multiple }` | |
|
|
184
|
+
| `filters` | `{ text, value }[]` | Filter dropdown options |
|
|
185
|
+
| `onFilter` | `(value, record) => boolean` | Client-side filter |
|
|
186
|
+
| `filterSearch` | `boolean` | Search in filter dropdown |
|
|
187
|
+
| `filteredValue` | `any[]` | Controlled filter |
|
|
188
|
+
| `sortOrder` | `'ascend' \| 'descend' \| null` | Controlled sort |
|
|
189
|
+
| `ellipsis` | `boolean` | Truncate with tooltip |
|
|
190
|
+
| `align` | `left \| right \| center` | |
|
|
191
|
+
| `responsive` | `Breakpoint[]` | Hide at certain breakpoints |
|
|
192
|
+
| `hidden` | `boolean` | v5.13+ — hide column |
|
|
193
|
+
| `children` | `ColumnsType<T>` | Grouped header columns |
|
|
194
|
+
| `onCell` | `(record, index) => HTMLAttributes` | Cell events/props |
|
|
195
|
+
| `shouldCellUpdate` | `(record, prev) => boolean` | Optimize re-renders |
|
|
196
|
+
|
|
197
|
+
### TypeScript Column Pattern
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
import type { TableColumnsType, TableProps } from 'antd';
|
|
201
|
+
|
|
202
|
+
const columns: TableColumnsType<User> = [
|
|
203
|
+
{
|
|
204
|
+
title: 'Name', dataIndex: 'name', key: 'name', fixed: 'left', width: 150,
|
|
205
|
+
sorter: (a, b) => a.name.localeCompare(b.name),
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
title: 'Status', dataIndex: 'status',
|
|
209
|
+
filters: [{ text: 'Active', value: 'active' }, { text: 'Inactive', value: 'inactive' }],
|
|
210
|
+
onFilter: (value, record) => record.status === value,
|
|
211
|
+
render: (status) => <Tag color={status === 'active' ? 'green' : 'red'}>{status}</Tag>,
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
title: 'Actions', key: 'actions', fixed: 'right', width: 120,
|
|
215
|
+
render: (_, record) => <Button onClick={() => edit(record)}>Edit</Button>,
|
|
216
|
+
},
|
|
217
|
+
];
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Row Selection
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
const [selectedKeys, setSelectedKeys] = useState<React.Key[]>([]);
|
|
224
|
+
|
|
225
|
+
const rowSelection: TableProps<User>['rowSelection'] = {
|
|
226
|
+
type: 'checkbox',
|
|
227
|
+
selectedRowKeys: selectedKeys,
|
|
228
|
+
onChange: (keys) => setSelectedKeys(keys),
|
|
229
|
+
getCheckboxProps: (record) => ({ disabled: record.status === 'inactive' }),
|
|
230
|
+
selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT, Table.SELECTION_NONE],
|
|
231
|
+
preserveSelectedRowKeys: true, // keep selection across pages
|
|
232
|
+
};
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Server-Side onChange
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
const handleChange: TableProps<User>['onChange'] = (pagination, filters, sorter, extra) => {
|
|
239
|
+
// extra.action: 'paginate' | 'sort' | 'filter'
|
|
240
|
+
fetchData({
|
|
241
|
+
page: pagination.current,
|
|
242
|
+
pageSize: pagination.pageSize,
|
|
243
|
+
sortField: Array.isArray(sorter) ? undefined : sorter.field,
|
|
244
|
+
sortOrder: Array.isArray(sorter) ? undefined : sorter.order,
|
|
245
|
+
filters,
|
|
246
|
+
});
|
|
247
|
+
};
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Common Table Mistakes
|
|
251
|
+
|
|
252
|
+
1. **Always set `rowKey`** — never use array index; breaks selection/expansion when filtered
|
|
253
|
+
2. **Fixed columns require explicit `width` + `scroll.x`** on the table
|
|
254
|
+
3. **Nested `dataIndex` needs array** — `['user', 'name']` not `'user.name'`
|
|
255
|
+
4. **Memoize `columns`** — `useMemo` prevents table re-renders on every parent render
|
|
256
|
+
5. **`virtual` requires `scroll.y`** — needs fixed container height
|
|
257
|
+
6. **`sorter` must return `0` for equal items** — not a non-zero value
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## MODAL
|
|
262
|
+
|
|
263
|
+
### Modal Props
|
|
264
|
+
|
|
265
|
+
| Prop | Type | Default | Notes |
|
|
266
|
+
|------|------|---------|-------|
|
|
267
|
+
| `open` | `boolean` | `false` | Visibility |
|
|
268
|
+
| `title` | `ReactNode` | — | Header |
|
|
269
|
+
| `footer` | `ReactNode \| null \| function` | OK + Cancel | `null` removes footer |
|
|
270
|
+
| `width` | `string \| number \| BreakpointMap` | `520` | Responsive: `{ xs: '95%', md: 600 }` |
|
|
271
|
+
| `centered` | `boolean` | `false` | Vertically center |
|
|
272
|
+
| `onOk` | `(e) => void` | — | OK button handler |
|
|
273
|
+
| `onCancel` | `(e) => void` | — | Cancel/close handler |
|
|
274
|
+
| `confirmLoading` | `boolean` | `false` | Loading on OK button |
|
|
275
|
+
| `okText` | `ReactNode` | `'OK'` | |
|
|
276
|
+
| `cancelText` | `ReactNode` | `'Cancel'` | |
|
|
277
|
+
| `okButtonProps` | `ButtonProps` | — | |
|
|
278
|
+
| `destroyOnHidden` | `boolean` | `false` | Unmount children when closed (v5.25+) |
|
|
279
|
+
| `forceRender` | `boolean` | `false` | Pre-render hidden content |
|
|
280
|
+
| `keyboard` | `boolean` | `true` | ESC closes modal |
|
|
281
|
+
| `mask` | `boolean` | `true` | Show backdrop |
|
|
282
|
+
| `afterClose` | `() => void` | — | After close animation |
|
|
283
|
+
| `zIndex` | `number` | `1000` | |
|
|
284
|
+
| `classNames` | `{ header, body, footer, mask }` | — | Semantic class overrides |
|
|
285
|
+
| `styles` | `{ header, body, footer, mask }` | — | Semantic style overrides |
|
|
286
|
+
|
|
287
|
+
### Static Methods
|
|
288
|
+
|
|
289
|
+
```tsx
|
|
290
|
+
Modal.confirm({
|
|
291
|
+
title: 'Delete?',
|
|
292
|
+
content: 'This cannot be undone.',
|
|
293
|
+
okText: 'Yes, delete',
|
|
294
|
+
okType: 'danger',
|
|
295
|
+
onOk: async () => { await deleteItem(); }, // Promise keeps OK loading until resolved
|
|
296
|
+
});
|
|
297
|
+
Modal.info({ ... });
|
|
298
|
+
Modal.success({ ... });
|
|
299
|
+
Modal.warning({ ... });
|
|
300
|
+
Modal.error({ ... });
|
|
301
|
+
Modal.destroyAll();
|
|
302
|
+
|
|
303
|
+
// Update / close returned instance
|
|
304
|
+
const instance = Modal.confirm({ ... });
|
|
305
|
+
instance.update({ title: 'New title' });
|
|
306
|
+
instance.destroy();
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### useModal — Preferred for Context (theme/locale)
|
|
310
|
+
|
|
311
|
+
```tsx
|
|
312
|
+
const App = () => {
|
|
313
|
+
const [modal, contextHolder] = Modal.useModal();
|
|
314
|
+
|
|
315
|
+
return (
|
|
316
|
+
<>
|
|
317
|
+
{contextHolder} {/* Must be inside context providers */}
|
|
318
|
+
<Button onClick={() => modal.confirm({ title: 'Sure?', onOk: del })}>Delete</Button>
|
|
319
|
+
</>
|
|
320
|
+
);
|
|
321
|
+
};
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Modal + Form Pattern
|
|
325
|
+
|
|
326
|
+
```tsx
|
|
327
|
+
const EditModal = ({ record, onClose, onSave }) => {
|
|
328
|
+
const [form] = Form.useForm();
|
|
329
|
+
const [loading, setLoading] = useState(false);
|
|
330
|
+
|
|
331
|
+
const handleOk = async () => {
|
|
332
|
+
try {
|
|
333
|
+
const values = await form.validateFields();
|
|
334
|
+
setLoading(true);
|
|
335
|
+
await onSave(values);
|
|
336
|
+
onClose();
|
|
337
|
+
} finally {
|
|
338
|
+
setLoading(false);
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
return (
|
|
343
|
+
<Modal
|
|
344
|
+
open={!!record}
|
|
345
|
+
title="Edit"
|
|
346
|
+
onOk={handleOk}
|
|
347
|
+
onCancel={onClose}
|
|
348
|
+
confirmLoading={loading}
|
|
349
|
+
destroyOnHidden // resets form state when closed
|
|
350
|
+
width={600}
|
|
351
|
+
>
|
|
352
|
+
<Form form={form} layout="vertical" initialValues={record}>
|
|
353
|
+
<Form.Item name="name" label="Name" rules={[{ required: true }]}>
|
|
354
|
+
<Input />
|
|
355
|
+
</Form.Item>
|
|
356
|
+
</Form>
|
|
357
|
+
</Modal>
|
|
358
|
+
);
|
|
359
|
+
};
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Common Modal Mistakes
|
|
363
|
+
|
|
364
|
+
1. **Use `Modal.useModal()` not `Modal.confirm()`** in app code — static methods lose context
|
|
365
|
+
2. **Use `destroyOnHidden`** (v5.25+) not deprecated `destroyOnClose` — ensures form resets
|
|
366
|
+
3. **`footer={null}`** removes footer entirely — empty array `[]` shows footer with no buttons
|
|
367
|
+
4. **`contextHolder` must be inside relevant providers** to inherit theme/locale context
|
|
368
|
+
5. **`onOk` Promise enables auto confirmLoading** in static methods — manual management needed in component form
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## BUTTON
|
|
373
|
+
|
|
374
|
+
### Button Props
|
|
375
|
+
|
|
376
|
+
| Prop | Type | Default | Notes |
|
|
377
|
+
|------|------|---------|-------|
|
|
378
|
+
| `type` | `primary \| default \| dashed \| text \| link` | `default` | Legacy shorthand |
|
|
379
|
+
| `color` | `default \| primary \| danger \| blue \| green \| ...13 presets` | — | v5.21+ |
|
|
380
|
+
| `variant` | `outlined \| dashed \| solid \| filled \| text \| link` | — | v5.21+ |
|
|
381
|
+
| `size` | `large \| middle \| small` | `middle` | |
|
|
382
|
+
| `shape` | `default \| circle \| round` | `default` | |
|
|
383
|
+
| `block` | `boolean` | `false` | Full width |
|
|
384
|
+
| `loading` | `boolean \| { delay?, icon? }` | `false` | `delay` prevents spinner flash |
|
|
385
|
+
| `disabled` | `boolean` | `false` | |
|
|
386
|
+
| `danger` | `boolean` | `false` | Red color intent |
|
|
387
|
+
| `ghost` | `boolean` | `false` | Transparent bg — for colored backgrounds |
|
|
388
|
+
| `icon` | `ReactNode` | — | |
|
|
389
|
+
| `iconPosition` | `start \| end` | `start` | |
|
|
390
|
+
| `href` | `string` | — | Renders as `<a>` |
|
|
391
|
+
| `target` | `string` | — | Used with `href` |
|
|
392
|
+
| `htmlType` | `submit \| reset \| button` | `button` | **Must be `submit` in forms** |
|
|
393
|
+
| `onClick` | `(event) => void` | — | |
|
|
394
|
+
|
|
395
|
+
### type → color + variant mapping (v5.21+)
|
|
396
|
+
|
|
397
|
+
| `type` | Equivalent |
|
|
398
|
+
|--------|-----------|
|
|
399
|
+
| `primary` | `color="primary" variant="solid"` |
|
|
400
|
+
| `default` | `color="default" variant="outlined"` |
|
|
401
|
+
| `dashed` | `color="default" variant="dashed"` |
|
|
402
|
+
| `text` | `color="default" variant="text"` |
|
|
403
|
+
| `link` | `color="default" variant="link"` |
|
|
404
|
+
| `primary` + `danger` | `color="danger" variant="solid"` |
|
|
405
|
+
| `primary` + `ghost` | `color="primary" variant="outlined"` |
|
|
406
|
+
|
|
407
|
+
### Common Button Mistakes
|
|
408
|
+
|
|
409
|
+
1. **`htmlType="submit"` is required** inside `<Form>` — default `"button"` won't trigger `onFinish`
|
|
410
|
+
2. **`danger` is a modifier, not a `type`** — there is no `type="danger"`
|
|
411
|
+
3. **`ghost` only works on colored backgrounds** — invisible on white
|
|
412
|
+
4. **`loading={{ delay: 300 }}`** prevents spinner flash for fast operations
|
|
413
|
+
5. **Icon-only buttons need `aria-label`** — `<Button shape="circle" icon={<X />} aria-label="Close" />`
|
|
414
|
+
6. **Don't mix `type` and `color+variant` simultaneously** — `color`+`variant` wins
|