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,451 @@
|
|
|
1
|
+
# Ant Design — Feedback Components (Drawer, Message, Notification, Spin, Skeleton, App)
|
|
2
|
+
|
|
3
|
+
## Drawer
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { Drawer } from 'antd';
|
|
7
|
+
import type { DrawerProps } from 'antd';
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
### DrawerProps
|
|
11
|
+
|
|
12
|
+
| Prop | Type | Default | Description |
|
|
13
|
+
|------|------|---------|-------------|
|
|
14
|
+
| `open` | `boolean` | `false` | Show/hide the drawer |
|
|
15
|
+
| `onClose` | `(e) => void` | — | Called when closing (mask click, ESC, close button) |
|
|
16
|
+
| `title` | `ReactNode` | — | Drawer header title |
|
|
17
|
+
| `placement` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'right'` | Which side slides in from |
|
|
18
|
+
| `width` | `string \| number` | `378` | Width (left/right placement) |
|
|
19
|
+
| `height` | `string \| number` | `378` | Height (top/bottom placement) |
|
|
20
|
+
| `size` | `'default' \| 'large'` | `'default'` | Preset size |
|
|
21
|
+
| `extra` | `ReactNode` | — | Extra content in header (top right) |
|
|
22
|
+
| `footer` | `ReactNode` | — | Footer content |
|
|
23
|
+
| `mask` | `boolean` | `true` | Show backdrop |
|
|
24
|
+
| `maskClosable` | `boolean` | `true` | Close on mask click |
|
|
25
|
+
| `keyboard` | `boolean` | `true` | Close on ESC key |
|
|
26
|
+
| `destroyOnClose` | `boolean` | `false` | Unmount content when closed |
|
|
27
|
+
| `push` | `boolean \| { distance }` | `{ distance: 180 }` | Push sibling content |
|
|
28
|
+
| `zIndex` | `number` | `1000` | Z-index |
|
|
29
|
+
| `afterOpenChange` | `(open) => void` | — | After open/close animation |
|
|
30
|
+
| `getContainer` | `HTMLElement \| (() => HTMLElement) \| false` | `document.body` | Portal container |
|
|
31
|
+
| `styles` | `{ header?, body?, footer?, mask? }` | — | Semantic inline styles |
|
|
32
|
+
| `classNames` | `{ header?, body?, footer? }` | — | Semantic CSS classes |
|
|
33
|
+
|
|
34
|
+
### Patterns
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
// Basic controlled drawer
|
|
38
|
+
const [open, setOpen] = useState(false);
|
|
39
|
+
<Button onClick={() => setOpen(true)}>Open</Button>
|
|
40
|
+
<Drawer title="Details" open={open} onClose={() => setOpen(false)} width={480}>
|
|
41
|
+
<p>Content here</p>
|
|
42
|
+
</Drawer>
|
|
43
|
+
|
|
44
|
+
// Form in drawer with footer actions
|
|
45
|
+
<Drawer
|
|
46
|
+
title="Add Record"
|
|
47
|
+
open={open}
|
|
48
|
+
onClose={() => setOpen(false)}
|
|
49
|
+
footer={
|
|
50
|
+
<Space>
|
|
51
|
+
<Button onClick={() => setOpen(false)}>Cancel</Button>
|
|
52
|
+
<Button type="primary" onClick={handleSubmit} loading={saving}>Submit</Button>
|
|
53
|
+
</Space>
|
|
54
|
+
}
|
|
55
|
+
destroyOnClose
|
|
56
|
+
>
|
|
57
|
+
<Form form={form} layout="vertical">
|
|
58
|
+
<Form.Item name="name" label="Name" rules={[{ required: true }]}>
|
|
59
|
+
<Input />
|
|
60
|
+
</Form.Item>
|
|
61
|
+
</Form>
|
|
62
|
+
</Drawer>
|
|
63
|
+
|
|
64
|
+
// Large drawer from left
|
|
65
|
+
<Drawer placement="left" size="large" title="Navigation" open={open} onClose={() => setOpen(false)}>
|
|
66
|
+
<Menu mode="inline" items={menuItems} />
|
|
67
|
+
</Drawer>
|
|
68
|
+
|
|
69
|
+
// Without close button (custom close)
|
|
70
|
+
<Drawer
|
|
71
|
+
title={<span>Title <CloseOutlined onClick={() => setOpen(false)} /></span>}
|
|
72
|
+
closable={false}
|
|
73
|
+
open={open}
|
|
74
|
+
onClose={() => setOpen(false)}
|
|
75
|
+
>
|
|
76
|
+
Content
|
|
77
|
+
</Drawer>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Common Mistakes
|
|
81
|
+
- **Always update state in `onClose`** — `onClose={() => setOpen(false)}`; forgetting this means the drawer won't close
|
|
82
|
+
- **Use `destroyOnClose` for forms** — otherwise form state persists from previous open
|
|
83
|
+
- **Long content doesn't scroll by default** — add `styles={{ body: { overflowY: 'auto' } }}` if needed
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Message
|
|
88
|
+
|
|
89
|
+
Imperative API — brief top-of-page notifications that auto-dismiss.
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { message } from 'antd';
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Methods
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
message.success(content, duration?, onClose?)
|
|
99
|
+
message.error(content, duration?, onClose?)
|
|
100
|
+
message.info(content, duration?, onClose?)
|
|
101
|
+
message.warning(content, duration?, onClose?)
|
|
102
|
+
message.loading(content, duration?, onClose?)
|
|
103
|
+
|
|
104
|
+
// Config object form
|
|
105
|
+
message.success({ content: 'Done!', duration: 2, key: 'myMsg', onClose: () => {} })
|
|
106
|
+
|
|
107
|
+
// Destroy all
|
|
108
|
+
message.destroy()
|
|
109
|
+
message.destroy('specificKey')
|
|
110
|
+
|
|
111
|
+
// Global config
|
|
112
|
+
message.config({ maxCount: 3, duration: 2, top: 8 })
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Key/Update pattern
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// Show loading, then update to success/error
|
|
119
|
+
const key = 'uploading';
|
|
120
|
+
message.loading({ content: 'Uploading...', key, duration: 0 });
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
await uploadFile();
|
|
124
|
+
message.success({ content: 'Done!', key, duration: 2 });
|
|
125
|
+
} catch {
|
|
126
|
+
message.error({ content: 'Failed!', key, duration: 2 });
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Patterns
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
// Inside click handlers
|
|
134
|
+
<Button onClick={() => message.success('Saved!')}>Save</Button>
|
|
135
|
+
|
|
136
|
+
// With duration 0 = never auto-close
|
|
137
|
+
message.loading({ content: 'Processing...', key, duration: 0 });
|
|
138
|
+
|
|
139
|
+
// Limit concurrent messages
|
|
140
|
+
message.config({ maxCount: 3 });
|
|
141
|
+
|
|
142
|
+
// Promise returned (resolves on close)
|
|
143
|
+
await message.success('Saved!');
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Common Mistakes
|
|
147
|
+
- **`message.xxx` is NOT affected by `ConfigProvider` theme/locale** — use `App.useApp()` for context-aware messages
|
|
148
|
+
- **Without `maxCount`, messages pile up** — always set `message.config({ maxCount: 3 })`
|
|
149
|
+
- **`key` update only works if the original message is still visible** — if it auto-closed, a new one appears
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Notification
|
|
154
|
+
|
|
155
|
+
Imperative API — corner notifications with title + description, longer content.
|
|
156
|
+
|
|
157
|
+
```typescript
|
|
158
|
+
import { notification } from 'antd';
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Methods
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
notification.success({ message, description?, placement?, duration?, btn?, key?, onClose?, icon? })
|
|
165
|
+
notification.error({...})
|
|
166
|
+
notification.info({...})
|
|
167
|
+
notification.warning({...})
|
|
168
|
+
notification.open({...})
|
|
169
|
+
|
|
170
|
+
notification.destroy() // close all
|
|
171
|
+
notification.close('key') // close by key
|
|
172
|
+
|
|
173
|
+
// Global config
|
|
174
|
+
notification.config({
|
|
175
|
+
placement: 'topRight', // 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight'
|
|
176
|
+
duration: 4.5,
|
|
177
|
+
maxCount: 5,
|
|
178
|
+
top: 24, bottom: 24,
|
|
179
|
+
})
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Key properties
|
|
183
|
+
|
|
184
|
+
| Prop | Type | Default | Description |
|
|
185
|
+
|------|------|---------|-------------|
|
|
186
|
+
| `message` | `ReactNode` | — | Title (required) |
|
|
187
|
+
| `description` | `ReactNode` | — | Body text |
|
|
188
|
+
| `placement` | `'topLeft' \| 'topRight' \| 'bottomLeft' \| 'bottomRight'` | `'topRight'` | Position |
|
|
189
|
+
| `duration` | `number` | `4.5` | Auto-close seconds; `0` = never |
|
|
190
|
+
| `btn` | `ReactNode` | — | Action button |
|
|
191
|
+
| `key` | `string` | — | Unique ID for updates |
|
|
192
|
+
| `icon` | `ReactNode` | — | Custom icon |
|
|
193
|
+
| `onClose` | `() => void` | — | Close callback |
|
|
194
|
+
| `onClick` | `() => void` | — | Click callback |
|
|
195
|
+
|
|
196
|
+
### Patterns
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
// Basic
|
|
200
|
+
notification.success({ message: 'Saved', description: 'Your changes were saved.' });
|
|
201
|
+
|
|
202
|
+
// With action button
|
|
203
|
+
notification.warning({
|
|
204
|
+
message: 'Session expiring',
|
|
205
|
+
description: 'Your session will expire in 5 minutes',
|
|
206
|
+
duration: 0,
|
|
207
|
+
key: 'session-warn',
|
|
208
|
+
btn: (
|
|
209
|
+
<Button size="small" type="primary"
|
|
210
|
+
onClick={() => { extendSession(); notification.close('session-warn'); }}>
|
|
211
|
+
Extend
|
|
212
|
+
</Button>
|
|
213
|
+
),
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Update by key
|
|
217
|
+
notification.open({ message: 'Processing...', key: 'progress', duration: 0 });
|
|
218
|
+
// ... later:
|
|
219
|
+
notification.success({ message: 'Done!', key: 'progress' });
|
|
220
|
+
|
|
221
|
+
// Placement options
|
|
222
|
+
notification.info({ message: 'Info', placement: 'bottomRight' });
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Common Mistakes
|
|
226
|
+
- **`notification.xxx` is NOT affected by `ConfigProvider`** — use `App.useApp()` for context-aware notifications
|
|
227
|
+
- **Without `maxCount`, old notifications accumulate** — set `notification.config({ maxCount: 5 })`
|
|
228
|
+
- **`duration: 4.5` may be too short for long descriptions** — increase or set to `0` for persistent
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Spin
|
|
233
|
+
|
|
234
|
+
Loading spinner that can wrap content.
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
import { Spin } from 'antd';
|
|
238
|
+
import type { SpinProps } from 'antd';
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### SpinProps
|
|
242
|
+
|
|
243
|
+
| Prop | Type | Default | Description |
|
|
244
|
+
|------|------|---------|-------------|
|
|
245
|
+
| `spinning` | `boolean` | `true` | Show spinner |
|
|
246
|
+
| `tip` | `string \| ReactNode` | — | Loading text below spinner |
|
|
247
|
+
| `size` | `'small' \| 'default' \| 'large'` | `'default'` | Spinner size |
|
|
248
|
+
| `delay` | `number` | `0` | Delay (ms) before showing |
|
|
249
|
+
| `indicator` | `ReactNode` | — | Custom spinner element |
|
|
250
|
+
| `fullscreen` | `boolean` | `false` | Full-page overlay |
|
|
251
|
+
| `children` | `ReactNode` | — | Content to overlay when spinning |
|
|
252
|
+
| `wrapperClassName` | `string` | — | Class for wrapper div |
|
|
253
|
+
|
|
254
|
+
### Patterns
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
// Standalone spinner
|
|
258
|
+
<Spin />
|
|
259
|
+
<Spin size="large" tip="Loading..." />
|
|
260
|
+
|
|
261
|
+
// Content overlay
|
|
262
|
+
<Spin spinning={isLoading}>
|
|
263
|
+
<Table dataSource={data} columns={columns} />
|
|
264
|
+
</Spin>
|
|
265
|
+
|
|
266
|
+
// Custom indicator
|
|
267
|
+
<Spin indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />} />
|
|
268
|
+
|
|
269
|
+
// Delayed (avoid flash for fast operations)
|
|
270
|
+
<Spin spinning={loading} delay={500}>
|
|
271
|
+
<Content />
|
|
272
|
+
</Spin>
|
|
273
|
+
|
|
274
|
+
// Fullscreen
|
|
275
|
+
<Spin fullscreen spinning={processing} tip="Please wait..." />
|
|
276
|
+
|
|
277
|
+
// Conditional (avoid empty wrapper)
|
|
278
|
+
{isLoading ? <Spin /> : <Content />}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Common Mistakes
|
|
282
|
+
- **`Spin` wrapping children blocks all interaction** while `spinning={true}` — intentional but verify UX
|
|
283
|
+
- **Custom icon needs `spin` CSS** — use `<LoadingOutlined spin />` from `@ant-design/icons`
|
|
284
|
+
- **`delay` causes flicker** on variable-speed operations — use judiciously (500ms is safe)
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Skeleton
|
|
289
|
+
|
|
290
|
+
Placeholder UI that mimics content structure during loading.
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
import { Skeleton } from 'antd';
|
|
294
|
+
import type { SkeletonProps } from 'antd';
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### SkeletonProps
|
|
298
|
+
|
|
299
|
+
| Prop | Type | Default | Description |
|
|
300
|
+
|------|------|---------|-------------|
|
|
301
|
+
| `loading` | `boolean` | `true` | Show skeleton vs content |
|
|
302
|
+
| `active` | `boolean` | `false` | Shimmer animation |
|
|
303
|
+
| `avatar` | `boolean \| SkeletonAvatarProps` | `false` | Show avatar placeholder |
|
|
304
|
+
| `title` | `boolean \| { width }` | `true` | Show title bar |
|
|
305
|
+
| `paragraph` | `boolean \| { rows, width }` | `true` | Show text lines |
|
|
306
|
+
| `round` | `boolean` | `false` | Rounded corners on all bars |
|
|
307
|
+
|
|
308
|
+
### Sub-components
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
// Standalone placeholders
|
|
312
|
+
<Skeleton.Avatar active size="large" shape="circle" />
|
|
313
|
+
<Skeleton.Button active size="large" block />
|
|
314
|
+
<Skeleton.Input active size="default" block />
|
|
315
|
+
<Skeleton.Image active />
|
|
316
|
+
|
|
317
|
+
// Avatar shapes: 'circle' | 'square'
|
|
318
|
+
// Button shapes: 'default' | 'circle' | 'round'
|
|
319
|
+
// Sizes: 'small' | 'default' | 'large' | number
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Patterns
|
|
323
|
+
|
|
324
|
+
```tsx
|
|
325
|
+
// Standard loading skeleton
|
|
326
|
+
<Skeleton loading={isLoading} active avatar paragraph={{ rows: 4 }}>
|
|
327
|
+
<ActualContent />
|
|
328
|
+
</Skeleton>
|
|
329
|
+
|
|
330
|
+
// Profile card skeleton
|
|
331
|
+
<Card>
|
|
332
|
+
<Skeleton active avatar={{ size: 'large', shape: 'circle' }} paragraph={{ rows: 2 }} />
|
|
333
|
+
</Card>
|
|
334
|
+
|
|
335
|
+
// List skeleton
|
|
336
|
+
{isLoading
|
|
337
|
+
? Array.from({ length: 5 }).map((_, i) => (
|
|
338
|
+
<List.Item key={i}>
|
|
339
|
+
<Skeleton active avatar loading />
|
|
340
|
+
</List.Item>
|
|
341
|
+
))
|
|
342
|
+
: <List dataSource={data} renderItem={...} />
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Mixed sub-components (custom layout)
|
|
346
|
+
<Space direction="vertical" style={{ width: '100%' }}>
|
|
347
|
+
<Skeleton.Avatar active size="large" />
|
|
348
|
+
<Skeleton.Button active block size="large" />
|
|
349
|
+
<Skeleton.Input active block />
|
|
350
|
+
</Space>
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Common Mistakes
|
|
354
|
+
- **`active` is `false` by default** — add `active` for shimmer animation
|
|
355
|
+
- **Match skeleton dimensions to content** — size mismatch causes layout shift when content loads
|
|
356
|
+
- **Paragraph rows default is 3** — set `paragraph={{ rows: 2 }}` to match shorter content
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## App
|
|
361
|
+
|
|
362
|
+
Context provider that makes `message`, `notification`, and `modal` context-aware (respects `ConfigProvider` theme and locale).
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
import { App } from 'antd';
|
|
366
|
+
// Must wrap the root of your app (or the subtree that needs context-aware imperative APIs)
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Why use App?
|
|
370
|
+
|
|
371
|
+
Static methods (`message.xxx`, `notification.xxx`, `Modal.confirm`) do NOT inherit `ConfigProvider` theme/locale. Wrapping with `App` and using `App.useApp()` inside components fixes this.
|
|
372
|
+
|
|
373
|
+
### Setup
|
|
374
|
+
|
|
375
|
+
```tsx
|
|
376
|
+
// Root layout — wrap once
|
|
377
|
+
<ConfigProvider theme={myTheme}>
|
|
378
|
+
<App message={{ maxCount: 3 }} notification={{ placement: 'bottomRight' }}>
|
|
379
|
+
<Router>
|
|
380
|
+
<Routes />
|
|
381
|
+
</Router>
|
|
382
|
+
</App>
|
|
383
|
+
</ConfigProvider>
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### App Props
|
|
387
|
+
|
|
388
|
+
| Prop | Type | Description |
|
|
389
|
+
|------|------|-------------|
|
|
390
|
+
| `message` | `MessageConfig` | Message instance config (maxCount, duration, etc.) |
|
|
391
|
+
| `notification` | `NotificationConfig` | Notification instance config |
|
|
392
|
+
| `modal` | `ModalConfig` | Modal instance config |
|
|
393
|
+
|
|
394
|
+
### useApp() hook
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
import { App } from 'antd';
|
|
398
|
+
|
|
399
|
+
const MyComponent = () => {
|
|
400
|
+
const { message, notification, modal } = App.useApp();
|
|
401
|
+
// These are context-aware — they respect ConfigProvider theme/locale
|
|
402
|
+
|
|
403
|
+
const handleSave = async () => {
|
|
404
|
+
try {
|
|
405
|
+
await saveData();
|
|
406
|
+
message.success('Saved!');
|
|
407
|
+
} catch {
|
|
408
|
+
message.error('Failed!');
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const handleDelete = () => {
|
|
413
|
+
modal.confirm({
|
|
414
|
+
title: 'Delete item?',
|
|
415
|
+
content: 'This action cannot be undone.',
|
|
416
|
+
okText: 'Delete',
|
|
417
|
+
okType: 'danger',
|
|
418
|
+
onOk: async () => {
|
|
419
|
+
await deleteItem();
|
|
420
|
+
notification.success({ message: 'Deleted', description: 'Item removed.' });
|
|
421
|
+
},
|
|
422
|
+
});
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
return (
|
|
426
|
+
<>
|
|
427
|
+
<Button onClick={handleSave}>Save</Button>
|
|
428
|
+
<Button danger onClick={handleDelete}>Delete</Button>
|
|
429
|
+
</>
|
|
430
|
+
);
|
|
431
|
+
};
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### Common Mistakes
|
|
435
|
+
- **`App.useApp()` must be called inside an `<App>` wrapper** — calling outside throws an error
|
|
436
|
+
- **Only one `<App>` at root level** — multiple `App` wrappers can cause unexpected behavior
|
|
437
|
+
- **For static method fallback** — if `App` setup is too complex, the static methods still work but won't respect theme
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## Comparison: When to Use What
|
|
442
|
+
|
|
443
|
+
| Feedback Type | Use | Duration | Position |
|
|
444
|
+
|---------------|-----|----------|----------|
|
|
445
|
+
| Success/error on user action | `message.success/error` | 3s auto | Top center |
|
|
446
|
+
| Important status update | `notification.success/info` | 4.5s auto | Corner |
|
|
447
|
+
| Warning needing action | `notification.warning + btn` | `duration: 0` | Corner |
|
|
448
|
+
| Async loading overlay | `<Spin spinning={loading}>` | — | Over content |
|
|
449
|
+
| Page loading placeholder | `<Skeleton loading={loading}>` | — | In place |
|
|
450
|
+
| Side panel form/content | `<Drawer>` | — | Side slide |
|
|
451
|
+
| Context-aware messages | `App.useApp()` | — | Respects theme |
|