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,594 @@
|
|
|
1
|
+
# Ant Design — Display Components (Typography, Card, List, Descriptions, Space, Flex)
|
|
2
|
+
|
|
3
|
+
## Typography
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { Typography } from 'antd';
|
|
7
|
+
const { Title, Text, Paragraph, Link } = Typography;
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
### Sub-components
|
|
11
|
+
|
|
12
|
+
| Component | Element | Use |
|
|
13
|
+
|-----------|---------|-----|
|
|
14
|
+
| `Typography.Title` | `<h1>`–`<h5>` | Headings |
|
|
15
|
+
| `Typography.Text` | `<span>` | Inline text, labels |
|
|
16
|
+
| `Typography.Paragraph` | `<div>` | Multi-line blocks |
|
|
17
|
+
| `Typography.Link` | `<a>` | Anchor links |
|
|
18
|
+
|
|
19
|
+
### Shared Props (all sub-components)
|
|
20
|
+
|
|
21
|
+
| Prop | Type | Description |
|
|
22
|
+
|------|------|-------------|
|
|
23
|
+
| `type` | `'secondary' \| 'success' \| 'warning' \| 'danger'` | Semantic color |
|
|
24
|
+
| `disabled` | `boolean` | Gray disabled state |
|
|
25
|
+
| `code` | `boolean` | `<code>` styling |
|
|
26
|
+
| `mark` | `boolean` | `<mark>` highlight |
|
|
27
|
+
| `underline` | `boolean` | Underline |
|
|
28
|
+
| `delete` | `boolean` | Strikethrough |
|
|
29
|
+
| `strong` | `boolean` | Bold |
|
|
30
|
+
| `italic` | `boolean` | Italic |
|
|
31
|
+
| `keyboard` | `boolean` | `<kbd>` styling |
|
|
32
|
+
| `copyable` | `boolean \| CopyConfig` | Copy to clipboard button |
|
|
33
|
+
| `editable` | `boolean \| EditConfig` | Inline editing |
|
|
34
|
+
| `ellipsis` | `boolean \| EllipsisConfig` | Text truncation |
|
|
35
|
+
|
|
36
|
+
### Title-specific
|
|
37
|
+
- `level`: `1 | 2 | 3 | 4 | 5` — heading level (default `1`)
|
|
38
|
+
|
|
39
|
+
### EllipsisConfig
|
|
40
|
+
```typescript
|
|
41
|
+
{
|
|
42
|
+
rows?: number; // max rows before truncation (default 1)
|
|
43
|
+
expandable?: boolean | 'collapsible'; // show expand; 'collapsible' also collapses
|
|
44
|
+
suffix?: string; // appended after truncation
|
|
45
|
+
symbol?: ReactNode | ((expanded) => ReactNode); // custom expand symbol
|
|
46
|
+
tooltip?: ReactNode | TooltipProps; // tooltip when ellipsed
|
|
47
|
+
expanded?: boolean; // controlled expand state
|
|
48
|
+
onExpand?: (e, { expanded }) => void;
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### CopyConfig
|
|
53
|
+
```typescript
|
|
54
|
+
{
|
|
55
|
+
text?: string | (() => string | Promise<string>); // text to copy
|
|
56
|
+
onCopy?: () => void;
|
|
57
|
+
icon?: ReactNode; // [copyIcon, copiedIcon]
|
|
58
|
+
tooltips?: ReactNode; // [hover, success] — false hides
|
|
59
|
+
format?: 'text/plain' | 'text/html';
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### EditConfig
|
|
64
|
+
```typescript
|
|
65
|
+
{
|
|
66
|
+
text?: string; // controlled edit text
|
|
67
|
+
editing?: boolean; // controlled editing state
|
|
68
|
+
onChange?: (value) => void;
|
|
69
|
+
onStart?: () => void;
|
|
70
|
+
onCancel?: () => void;
|
|
71
|
+
onEnd?: () => void;
|
|
72
|
+
maxLength?: number;
|
|
73
|
+
autoSize?: boolean | { minRows?, maxRows? };
|
|
74
|
+
triggerType?: ('icon' | 'text')[]; // default ['icon']
|
|
75
|
+
enterIcon?: ReactNode; // custom save icon; null to hide
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Patterns
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
// Text decoration
|
|
83
|
+
<Text strong>Bold</Text>
|
|
84
|
+
<Text type="secondary">Secondary</Text>
|
|
85
|
+
<Text type="danger">Error</Text>
|
|
86
|
+
<Text code copyable>const x = 1;</Text>
|
|
87
|
+
<Text keyboard>Ctrl</Text>
|
|
88
|
+
<Text mark>highlighted</Text>
|
|
89
|
+
<Text delete>removed</Text>
|
|
90
|
+
|
|
91
|
+
// Title
|
|
92
|
+
<Title level={2}>Section Title</Title>
|
|
93
|
+
|
|
94
|
+
// Copyable paragraph (async)
|
|
95
|
+
<Paragraph copyable={{ text: async () => fetchSecret(), tooltips: ['Copy', 'Copied!'] }}>
|
|
96
|
+
Click to copy secret
|
|
97
|
+
</Paragraph>
|
|
98
|
+
|
|
99
|
+
// Editable — click text to edit
|
|
100
|
+
<Paragraph
|
|
101
|
+
editable={{ onChange: setText, triggerType: ['text'], maxLength: 200 }}>
|
|
102
|
+
{text}
|
|
103
|
+
</Paragraph>
|
|
104
|
+
|
|
105
|
+
// Ellipsis with expand/collapse
|
|
106
|
+
<Paragraph ellipsis={{ rows: 3, expandable: 'collapsible', expanded, onExpand: (_, { expanded }) => setExpanded(expanded) }}>
|
|
107
|
+
{longText}
|
|
108
|
+
</Paragraph>
|
|
109
|
+
|
|
110
|
+
// Tooltip when truncated
|
|
111
|
+
<Text style={{ width: 200 }} ellipsis={{ tooltip: 'Full text here' }}>{longText}</Text>
|
|
112
|
+
|
|
113
|
+
// Link
|
|
114
|
+
<Link href="https://ant.design" target="_blank">Ant Design</Link>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Common Mistakes
|
|
118
|
+
- **`copyable` without `text` prop** copies all rendered children including markup — set `text` explicitly
|
|
119
|
+
- **`ellipsis` requires fixed-width container** — wrap in `<div style={{ width: 300 }}>`
|
|
120
|
+
- **`Text` supports single-line ellipsis only** — `rows` and `expandable` are not available on `Text`
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Card
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { Card } from 'antd';
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### CardProps
|
|
131
|
+
|
|
132
|
+
| Prop | Type | Default | Description |
|
|
133
|
+
|------|------|---------|-------------|
|
|
134
|
+
| `title` | `ReactNode` | — | Card header content |
|
|
135
|
+
| `extra` | `ReactNode` | — | Top-right header content |
|
|
136
|
+
| `cover` | `ReactNode` | — | Cover image/content above body |
|
|
137
|
+
| `actions` | `ReactNode[]` | — | Bottom action icons/links |
|
|
138
|
+
| `loading` | `boolean` | `false` | Show skeleton loading |
|
|
139
|
+
| `hoverable` | `boolean` | `false` | Hover lift effect |
|
|
140
|
+
| `size` | `'medium' \| 'small'` | `'medium'` | Card padding |
|
|
141
|
+
| `type` | `'inner'` | — | Nested card style |
|
|
142
|
+
| `variant` | `'outlined' \| 'borderless'` | `'outlined'` | Visual variant (replaces `bordered`) |
|
|
143
|
+
| `tabList` | `{ key, label }[]` | — | Tabs in card header |
|
|
144
|
+
| `activeTabKey` | `string` | — | Controlled active tab |
|
|
145
|
+
| `onTabChange` | `(key) => void` | — | Tab change handler |
|
|
146
|
+
| `styles` | `{ header?, body?, cover?, actions? }` | — | Semantic inline styles |
|
|
147
|
+
| `classNames` | `{ header?, body?, cover?, actions? }` | — | Semantic CSS classes |
|
|
148
|
+
|
|
149
|
+
### Sub-components
|
|
150
|
+
|
|
151
|
+
**`Card.Meta`** — avatar + title + description block:
|
|
152
|
+
```typescript
|
|
153
|
+
<Card.Meta
|
|
154
|
+
avatar={<Avatar src={url} />}
|
|
155
|
+
title="Card Title"
|
|
156
|
+
description="Description text"
|
|
157
|
+
/>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**`Card.Grid`** — equal-width grid cells inside a card:
|
|
161
|
+
```typescript
|
|
162
|
+
<Card.Grid style={{ width: '25%' }} hoverable={false}>Cell content</Card.Grid>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Patterns
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
// Basic with extra action
|
|
169
|
+
<Card title="Users" extra={<Button type="link">View all</Button>} style={{ width: 300 }}>
|
|
170
|
+
<p>Card content</p>
|
|
171
|
+
</Card>
|
|
172
|
+
|
|
173
|
+
// Cover + Meta + Actions (media card)
|
|
174
|
+
<Card
|
|
175
|
+
style={{ width: 300 }}
|
|
176
|
+
cover={<img alt="cover" src={imageUrl} style={{ height: 200, objectFit: 'cover' }} />}
|
|
177
|
+
actions={[<EditOutlined key="edit" />, <EllipsisOutlined key="more" />]}
|
|
178
|
+
>
|
|
179
|
+
<Card.Meta
|
|
180
|
+
avatar={<Avatar src={avatarUrl} />}
|
|
181
|
+
title="John Doe"
|
|
182
|
+
description="Software Engineer"
|
|
183
|
+
/>
|
|
184
|
+
</Card>
|
|
185
|
+
|
|
186
|
+
// Loading state
|
|
187
|
+
<Card loading={isLoading} actions={actions}>
|
|
188
|
+
<Card.Meta title="Title" description="Description" />
|
|
189
|
+
</Card>
|
|
190
|
+
|
|
191
|
+
// Card with tabs
|
|
192
|
+
const [tab, setTab] = useState('tab1');
|
|
193
|
+
const content = { tab1: <Content1 />, tab2: <Content2 /> };
|
|
194
|
+
<Card title="Dashboard"
|
|
195
|
+
tabList={[{ key: 'tab1', label: 'Overview' }, { key: 'tab2', label: 'Details' }]}
|
|
196
|
+
activeTabKey={tab} onTabChange={setTab}>
|
|
197
|
+
{content[tab]}
|
|
198
|
+
</Card>
|
|
199
|
+
|
|
200
|
+
// Grid layout
|
|
201
|
+
<Card title="Team">
|
|
202
|
+
{members.map(m => (
|
|
203
|
+
<Card.Grid key={m.id} style={{ width: '25%', textAlign: 'center' }}>
|
|
204
|
+
<Avatar src={m.avatar} /><br />{m.name}
|
|
205
|
+
</Card.Grid>
|
|
206
|
+
))}
|
|
207
|
+
</Card>
|
|
208
|
+
|
|
209
|
+
// Inner (nested) card
|
|
210
|
+
<Card title="Outer Card">
|
|
211
|
+
<Card type="inner" title="Inner Card" extra={<a href="#">More</a>}>
|
|
212
|
+
Nested content
|
|
213
|
+
</Card>
|
|
214
|
+
</Card>
|
|
215
|
+
|
|
216
|
+
// Borderless
|
|
217
|
+
<Card variant="borderless">No border</Card>
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Common Mistakes
|
|
221
|
+
- **`bordered` is deprecated** — use `variant="outlined"` or `variant="borderless"`
|
|
222
|
+
- **`headStyle`/`bodyStyle` are deprecated** — use `styles.header` / `styles.body`
|
|
223
|
+
- **`size="default"` is deprecated** — use `size="medium"`
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## List
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
import { List } from 'antd';
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
> **Deprecation notice:** `List` will be removed in antd v6. The replacement will be `Listy` with built-in virtual scrolling.
|
|
234
|
+
|
|
235
|
+
### ListProps
|
|
236
|
+
|
|
237
|
+
| Prop | Type | Default | Description |
|
|
238
|
+
|------|------|---------|-------------|
|
|
239
|
+
| `dataSource` | `T[]` | `[]` | Data array |
|
|
240
|
+
| `renderItem` | `(item, index) => ReactNode` | — | Item renderer |
|
|
241
|
+
| `itemLayout` | `'horizontal' \| 'vertical'` | `'horizontal'` | Item layout |
|
|
242
|
+
| `bordered` | `boolean` | `false` | Show border |
|
|
243
|
+
| `split` | `boolean` | `true` | Dividers between items |
|
|
244
|
+
| `loading` | `boolean \| SpinProps` | `false` | Loading state |
|
|
245
|
+
| `loadMore` | `ReactNode` | — | "Load more" content at bottom |
|
|
246
|
+
| `pagination` | `PaginationConfig \| false` | `false` | Pagination |
|
|
247
|
+
| `grid` | `ListGridType` | — | Grid layout config |
|
|
248
|
+
| `size` | `'small' \| 'default' \| 'large'` | `'default'` | Item size |
|
|
249
|
+
| `header` | `ReactNode` | — | List header |
|
|
250
|
+
| `footer` | `ReactNode` | — | List footer |
|
|
251
|
+
| `rowKey` | `((item) => Key) \| keyof T` | — | Unique key per item |
|
|
252
|
+
| `locale` | `{ emptyText: ReactNode }` | — | Empty state text |
|
|
253
|
+
|
|
254
|
+
### ListGridType
|
|
255
|
+
```typescript
|
|
256
|
+
{
|
|
257
|
+
gutter?: number;
|
|
258
|
+
column?: number;
|
|
259
|
+
xs?: number; sm?: number; md?: number;
|
|
260
|
+
lg?: number; xl?: number; xxl?: number;
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### List.Item Props
|
|
265
|
+
- `actions?: ReactNode[]` — action links/buttons on the right
|
|
266
|
+
- `extra?: ReactNode` — extra content (right side in vertical layout)
|
|
267
|
+
|
|
268
|
+
### List.Item.Meta Props
|
|
269
|
+
- `avatar?: ReactNode`, `title?: ReactNode`, `description?: ReactNode`
|
|
270
|
+
|
|
271
|
+
### Patterns
|
|
272
|
+
|
|
273
|
+
```tsx
|
|
274
|
+
// Basic horizontal list
|
|
275
|
+
<List
|
|
276
|
+
dataSource={users}
|
|
277
|
+
rowKey="id"
|
|
278
|
+
renderItem={(user) => (
|
|
279
|
+
<List.Item actions={[<a key="edit">Edit</a>, <a key="del">Delete</a>]}>
|
|
280
|
+
<List.Item.Meta
|
|
281
|
+
avatar={<Avatar src={user.avatar} />}
|
|
282
|
+
title={<a href="#">{user.name}</a>}
|
|
283
|
+
description={user.email}
|
|
284
|
+
/>
|
|
285
|
+
</List.Item>
|
|
286
|
+
)}
|
|
287
|
+
/>
|
|
288
|
+
|
|
289
|
+
// Responsive grid list (card grid)
|
|
290
|
+
<List
|
|
291
|
+
grid={{ gutter: 16, xs: 1, sm: 2, md: 3, lg: 4, xl: 4 }}
|
|
292
|
+
dataSource={items}
|
|
293
|
+
renderItem={(item) => (
|
|
294
|
+
<List.Item>
|
|
295
|
+
<Card title={item.title}>Content</Card>
|
|
296
|
+
</List.Item>
|
|
297
|
+
)}
|
|
298
|
+
/>
|
|
299
|
+
|
|
300
|
+
// Vertical layout with cover
|
|
301
|
+
<List
|
|
302
|
+
itemLayout="vertical"
|
|
303
|
+
dataSource={articles}
|
|
304
|
+
renderItem={(a) => (
|
|
305
|
+
<List.Item extra={<img width={200} src={a.cover} alt="cover" />}>
|
|
306
|
+
<List.Item.Meta title={<a href="#">{a.title}</a>} description={a.author} />
|
|
307
|
+
{a.excerpt}
|
|
308
|
+
</List.Item>
|
|
309
|
+
)}
|
|
310
|
+
/>
|
|
311
|
+
|
|
312
|
+
// Load more pattern
|
|
313
|
+
const [data, setData] = useState([]);
|
|
314
|
+
const [loading, setLoading] = useState(false);
|
|
315
|
+
const loadMore = !loading ? (
|
|
316
|
+
<div style={{ textAlign: 'center', margin: '12px 0' }}>
|
|
317
|
+
<Button onClick={fetchMore}>Load more</Button>
|
|
318
|
+
</div>
|
|
319
|
+
) : null;
|
|
320
|
+
<List dataSource={data} loadMore={loadMore} renderItem={...} />
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Common Mistakes
|
|
324
|
+
- **Missing `rowKey`** causes React reconciliation issues with dynamic data
|
|
325
|
+
- **Pagination doesn't fetch data** — you must handle page changes in `onChange`
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## Descriptions
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
import { Descriptions } from 'antd';
|
|
333
|
+
import type { DescriptionsProps } from 'antd';
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### DescriptionsProps
|
|
337
|
+
|
|
338
|
+
| Prop | Type | Default | Description |
|
|
339
|
+
|------|------|---------|-------------|
|
|
340
|
+
| `items` | `DescriptionsItemType[]` | — | **Preferred** (v5.8+). Item definitions array |
|
|
341
|
+
| `title` | `ReactNode` | — | Header title |
|
|
342
|
+
| `extra` | `ReactNode` | — | Top-right corner content |
|
|
343
|
+
| `bordered` | `boolean` | `false` | Table-style borders |
|
|
344
|
+
| `layout` | `'horizontal' \| 'vertical'` | `'horizontal'` | Orientation |
|
|
345
|
+
| `column` | `number \| Partial<Record<Breakpoint, number>>` | `3` | Items per row (responsive object supported) |
|
|
346
|
+
| `size` | `'large' \| 'medium' \| 'small'` | `'medium'` | Row padding |
|
|
347
|
+
| `colon` | `boolean` | `true` | Show colon after label |
|
|
348
|
+
| `styles` | `{ label?, content?, header?, root? }` | — | Semantic inline styles |
|
|
349
|
+
| `classNames` | `{ label?, content?, header?, root? }` | — | Semantic CSS classes |
|
|
350
|
+
|
|
351
|
+
### DescriptionsItemType
|
|
352
|
+
```typescript
|
|
353
|
+
{
|
|
354
|
+
key?: React.Key;
|
|
355
|
+
label?: ReactNode;
|
|
356
|
+
children?: ReactNode;
|
|
357
|
+
span?: number | 'filled' | Partial<Record<Breakpoint, number>>;
|
|
358
|
+
styles?: { label?, content? };
|
|
359
|
+
classNames?: { label?, content? };
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Patterns
|
|
364
|
+
|
|
365
|
+
```tsx
|
|
366
|
+
// Basic (modern items API)
|
|
367
|
+
const items: DescriptionsProps['items'] = [
|
|
368
|
+
{ key: '1', label: 'Name', children: 'John Doe' },
|
|
369
|
+
{ key: '2', label: 'Email', children: 'john@example.com' },
|
|
370
|
+
{ key: '3', label: 'Status', children: <Badge status="processing" text="Active" /> },
|
|
371
|
+
{ key: '4', label: 'Address', children: '123 Main St', span: 2 },
|
|
372
|
+
];
|
|
373
|
+
<Descriptions title="User Info" bordered items={items} />
|
|
374
|
+
|
|
375
|
+
// Responsive columns
|
|
376
|
+
<Descriptions
|
|
377
|
+
column={{ xs: 1, sm: 2, md: 3, lg: 4 }}
|
|
378
|
+
items={items}
|
|
379
|
+
/>
|
|
380
|
+
|
|
381
|
+
// Vertical layout (label above value)
|
|
382
|
+
<Descriptions layout="vertical" bordered items={items} />
|
|
383
|
+
|
|
384
|
+
// With extra action
|
|
385
|
+
<Descriptions title="Details" extra={<Button>Edit</Button>} bordered items={items} />
|
|
386
|
+
|
|
387
|
+
// Per-item label/content styling
|
|
388
|
+
const styledItems: DescriptionsProps['items'] = [
|
|
389
|
+
{
|
|
390
|
+
label: 'Priority',
|
|
391
|
+
children: 'High',
|
|
392
|
+
styles: {
|
|
393
|
+
label: { fontWeight: 'bold', color: '#1677ff' },
|
|
394
|
+
content: { color: 'red' },
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
];
|
|
398
|
+
|
|
399
|
+
// Span = 'filled' (takes remaining columns in row)
|
|
400
|
+
{ label: 'Notes', children: 'Long text...', span: 'filled' }
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Common Mistakes
|
|
404
|
+
- **`children` JSX approach is deprecated** (v5.8+) — use `items` prop
|
|
405
|
+
- **`labelStyle`/`contentStyle` props deprecated** — use `styles.label` / `styles.content`
|
|
406
|
+
- **`column` should use responsive object** for proper mobile behavior
|
|
407
|
+
- **`span` must not exceed `column` count** — excess span is ignored
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## Space
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
import { Space } from 'antd';
|
|
415
|
+
// Sub-components:
|
|
416
|
+
// Space.Compact — merge borders between inputs/buttons
|
|
417
|
+
// Space.Addon (v5.29+) — styled addon cell ($ sign, units, etc.)
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### SpaceProps
|
|
421
|
+
|
|
422
|
+
| Prop | Type | Default | Description |
|
|
423
|
+
|------|------|---------|-------------|
|
|
424
|
+
| `size` | `'small' \| 'medium' \| 'large' \| number \| [h, v]` | `'small'` | Gap size; tuple for [horizontal, vertical] |
|
|
425
|
+
| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Layout direction |
|
|
426
|
+
| `vertical` | `boolean` | — | Shorthand for `orientation="vertical"` |
|
|
427
|
+
| `align` | `'start' \| 'end' \| 'center' \| 'baseline'` | — | Cross-axis alignment |
|
|
428
|
+
| `wrap` | `boolean` | `false` | Wrap items to next line |
|
|
429
|
+
| `separator` | `ReactNode` | — | Separator between items |
|
|
430
|
+
| `classNames` | `{ root?, item?, separator? }` | — | Semantic class names |
|
|
431
|
+
| `styles` | `{ root?, item?, separator? }` | — | Semantic inline styles |
|
|
432
|
+
|
|
433
|
+
### Space.Compact — for input/button groups
|
|
434
|
+
|
|
435
|
+
Collapses borders between: Button, Input (all variants), Select, DatePicker, InputNumber, AutoComplete, Cascader, TimePicker, TreeSelect, ColorPicker.
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
interface SpaceCompactProps {
|
|
439
|
+
size?: 'small' | 'medium' | 'large';
|
|
440
|
+
orientation?: 'horizontal' | 'vertical';
|
|
441
|
+
vertical?: boolean;
|
|
442
|
+
block?: boolean; // full width
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Patterns
|
|
447
|
+
|
|
448
|
+
```tsx
|
|
449
|
+
// Inline button row
|
|
450
|
+
<Space>
|
|
451
|
+
<Button type="primary">Save</Button>
|
|
452
|
+
<Button>Cancel</Button>
|
|
453
|
+
<Button danger>Delete</Button>
|
|
454
|
+
</Space>
|
|
455
|
+
|
|
456
|
+
// Vertical form layout
|
|
457
|
+
<Space orientation="vertical" style={{ width: '100%' }}>
|
|
458
|
+
<Input placeholder="Name" />
|
|
459
|
+
<Input placeholder="Email" />
|
|
460
|
+
<Button type="primary" block>Submit</Button>
|
|
461
|
+
</Space>
|
|
462
|
+
|
|
463
|
+
// Custom gap
|
|
464
|
+
<Space size={[32, 16]} wrap>
|
|
465
|
+
{tags.map(t => <Tag key={t}>{t}</Tag>)}
|
|
466
|
+
</Space>
|
|
467
|
+
|
|
468
|
+
// With separator
|
|
469
|
+
<Space separator={<Divider type="vertical" />}>
|
|
470
|
+
<Link>Home</Link>
|
|
471
|
+
<Link>Products</Link>
|
|
472
|
+
<Link>About</Link>
|
|
473
|
+
</Space>
|
|
474
|
+
|
|
475
|
+
// Space.Compact — URL input + button
|
|
476
|
+
<Space.Compact block>
|
|
477
|
+
<Input defaultValue="https://" style={{ width: 'calc(100% - 100px)' }} />
|
|
478
|
+
<Button type="primary">Go</Button>
|
|
479
|
+
</Space.Compact>
|
|
480
|
+
|
|
481
|
+
// Space.Compact — Select + Input
|
|
482
|
+
<Space.Compact block>
|
|
483
|
+
<Select defaultValue="CN" options={[{ value: 'CN', label: '+86' }]} style={{ width: 80 }} />
|
|
484
|
+
<Input placeholder="Phone number" />
|
|
485
|
+
</Space.Compact>
|
|
486
|
+
|
|
487
|
+
// Space.Compact — with Addon (currency input)
|
|
488
|
+
<Space.Compact>
|
|
489
|
+
<Space.Addon>$</Space.Addon>
|
|
490
|
+
<InputNumber placeholder="Amount" style={{ width: '100%' }} />
|
|
491
|
+
<Space.Addon>USD</Space.Addon>
|
|
492
|
+
</Space.Compact>
|
|
493
|
+
|
|
494
|
+
// Vertical compact (button group)
|
|
495
|
+
<Space.Compact orientation="vertical">
|
|
496
|
+
<Button>Top</Button>
|
|
497
|
+
<Button>Middle</Button>
|
|
498
|
+
<Button>Bottom</Button>
|
|
499
|
+
</Space.Compact>
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Common Mistakes
|
|
503
|
+
- **`direction` prop is deprecated** — use `orientation` or `vertical`
|
|
504
|
+
- **`split` prop is deprecated** — use `separator`
|
|
505
|
+
- **`Space` wraps each child in a `<span>`** — use `Flex` for block-level elements without wrappers
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## Flex
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
import { Flex } from 'antd';
|
|
513
|
+
import type { FlexProps } from 'antd';
|
|
514
|
+
// Available from antd v5.10.0
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
**Space vs. Flex:**
|
|
518
|
+
- **Space** — wraps children in `<span>`; best for inline button/tag rows
|
|
519
|
+
- **Flex** — no wrapper elements; full CSS flexbox control; block-level
|
|
520
|
+
|
|
521
|
+
### FlexProps
|
|
522
|
+
|
|
523
|
+
| Prop | Type | Default | Description |
|
|
524
|
+
|------|------|---------|-------------|
|
|
525
|
+
| `vertical` | `boolean` | `false` | Column direction (`flex-direction: column`) |
|
|
526
|
+
| `justify` | CSS `justifyContent` | — | `flex-start`, `center`, `flex-end`, `space-between`, `space-around`, `space-evenly` |
|
|
527
|
+
| `align` | CSS `alignItems` | — | `flex-start`, `center`, `flex-end`, `baseline`, `stretch` |
|
|
528
|
+
| `gap` | `'small' \| 'medium' \| 'large' \| number \| string` | — | Gap between children |
|
|
529
|
+
| `wrap` | `boolean \| CSS flexWrap` | — | Enable wrapping |
|
|
530
|
+
| `flex` | CSS `flex` | — | Flex shorthand on the container |
|
|
531
|
+
| `component` | `keyof JSX.IntrinsicElements` | `'div'` | Underlying element |
|
|
532
|
+
|
|
533
|
+
### Patterns
|
|
534
|
+
|
|
535
|
+
```tsx
|
|
536
|
+
// Horizontal row, center-aligned
|
|
537
|
+
<Flex justify="space-between" align="center">
|
|
538
|
+
<h2>Title</h2>
|
|
539
|
+
<Button type="primary">Add</Button>
|
|
540
|
+
</Flex>
|
|
541
|
+
|
|
542
|
+
// Vertical stack (form layout)
|
|
543
|
+
<Flex vertical gap="middle" style={{ width: '100%' }}>
|
|
544
|
+
<Input placeholder="Name" />
|
|
545
|
+
<Input placeholder="Email" />
|
|
546
|
+
<Button type="primary" block>Submit</Button>
|
|
547
|
+
</Flex>
|
|
548
|
+
|
|
549
|
+
// Responsive card grid
|
|
550
|
+
<Flex wrap gap="middle">
|
|
551
|
+
{items.map(i => (
|
|
552
|
+
<div key={i.id} style={{ flex: '0 0 calc(25% - 12px)' }}>
|
|
553
|
+
<Card title={i.title}>Content</Card>
|
|
554
|
+
</div>
|
|
555
|
+
))}
|
|
556
|
+
</Flex>
|
|
557
|
+
|
|
558
|
+
// Full-width sidebar + main layout
|
|
559
|
+
<Flex style={{ height: '100vh' }}>
|
|
560
|
+
<Flex vertical style={{ width: 240, borderRight: '1px solid #f0f0f0' }}>
|
|
561
|
+
<Menu mode="inline" items={menuItems} />
|
|
562
|
+
</Flex>
|
|
563
|
+
<Flex vertical flex="1" style={{ padding: 24 }}>
|
|
564
|
+
{children}
|
|
565
|
+
</Flex>
|
|
566
|
+
</Flex>
|
|
567
|
+
|
|
568
|
+
// Card with image + content side by side
|
|
569
|
+
<Card styles={{ body: { padding: 0 } }}>
|
|
570
|
+
<Flex>
|
|
571
|
+
<img src={imageUrl} style={{ width: 200, objectFit: 'cover' }} alt="cover" />
|
|
572
|
+
<Flex vertical justify="space-between" style={{ padding: 24, flex: 1 }}>
|
|
573
|
+
<Typography.Title level={4}>{title}</Typography.Title>
|
|
574
|
+
<Button type="primary">Learn more</Button>
|
|
575
|
+
</Flex>
|
|
576
|
+
</Flex>
|
|
577
|
+
</Card>
|
|
578
|
+
|
|
579
|
+
// Centered loading state
|
|
580
|
+
<Flex justify="center" align="center" style={{ height: 200 }}>
|
|
581
|
+
<Spin tip="Loading..." />
|
|
582
|
+
</Flex>
|
|
583
|
+
|
|
584
|
+
// As semantic HTML
|
|
585
|
+
<Flex component="section" vertical gap={16}>
|
|
586
|
+
<Flex component="article">Article 1</Flex>
|
|
587
|
+
<Flex component="article">Article 2</Flex>
|
|
588
|
+
</Flex>
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### Common Mistakes
|
|
592
|
+
- **Flex items with text can overflow** — add `minWidth: 0` on flex children containing long text
|
|
593
|
+
- **Flex doesn't fill height by default** — add `style={{ height: '100%' }}` or `flex="1"` as needed
|
|
594
|
+
- **`gap` with number is px** — `gap={16}` = `16px`; preset strings map to theme spacing tokens
|