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.

@@ -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