@xyhp915/slack-base-ui 0.0.1 → 0.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.
Files changed (54) hide show
  1. package/README.md +220 -4
  2. package/agents/slack-base-ui/SKILL.md +137 -0
  3. package/agents/slack-base-ui/checklists/style-review.md +56 -0
  4. package/agents/slack-base-ui/templates/consumer-setup.md +109 -0
  5. package/agents/slack-base-ui/templates/slack-theme.css +152 -0
  6. package/libs/Dialog.d.ts +73 -0
  7. package/libs/Dialog.d.ts.map +1 -1
  8. package/libs/Popover.d.ts +69 -0
  9. package/libs/Popover.d.ts.map +1 -1
  10. package/libs/index.d.ts +4 -4
  11. package/libs/index.d.ts.map +1 -1
  12. package/libs/index.js +2885 -2718
  13. package/package.json +1 -1
  14. package/src/App.css +7 -0
  15. package/src/App.tsx +18 -0
  16. package/src/assets/react.svg +1 -0
  17. package/src/components/AlertDialog.tsx +185 -0
  18. package/src/components/AutoComplete.tsx +311 -0
  19. package/src/components/Avatar.tsx +70 -0
  20. package/src/components/Badge.tsx +48 -0
  21. package/src/components/Button.tsx +53 -0
  22. package/src/components/Checkbox.tsx +109 -0
  23. package/src/components/ContextMenu.tsx +393 -0
  24. package/src/components/Dialog.tsx +371 -0
  25. package/src/components/Form.tsx +409 -0
  26. package/src/components/IconButton.tsx +49 -0
  27. package/src/components/Input.tsx +56 -0
  28. package/src/components/Loading.tsx +123 -0
  29. package/src/components/Menu.tsx +368 -0
  30. package/src/components/Popover.tsx +367 -0
  31. package/src/components/Progress.tsx +89 -0
  32. package/src/components/Radio.tsx +137 -0
  33. package/src/components/Select.tsx +177 -0
  34. package/src/components/Switch.tsx +116 -0
  35. package/src/components/Tabs.tsx +128 -0
  36. package/src/components/Toast.tsx +149 -0
  37. package/src/components/Tooltip.tsx +46 -0
  38. package/src/components/index.ts +186 -0
  39. package/src/context/ThemeContext.tsx +53 -0
  40. package/src/context/useTheme.ts +11 -0
  41. package/src/examples/slack-clone/SlackApp.tsx +94 -0
  42. package/src/examples/slack-clone/components/ChannelHeader.tsx +34 -0
  43. package/src/examples/slack-clone/components/Composer.tsx +42 -0
  44. package/src/examples/slack-clone/components/Message.tsx +97 -0
  45. package/src/examples/slack-clone/components/UserProfile.tsx +78 -0
  46. package/src/examples/slack-clone/layout/Layout.tsx +27 -0
  47. package/src/examples/slack-clone/layout/Sidebar.tsx +67 -0
  48. package/src/examples/slack-clone/layout/SidebarItem.tsx +57 -0
  49. package/src/examples/slack-clone/layout/TopBar.tsx +30 -0
  50. package/src/index.css +240 -0
  51. package/src/main.tsx +22 -0
  52. package/src/pages/ComponentShowcase.tsx +1964 -0
  53. package/src/pages/Dashboard.tsx +87 -0
  54. package/src/pages/QuickStartDemo.tsx +262 -0
@@ -0,0 +1,1964 @@
1
+ import { Link } from 'react-router-dom'
2
+ import {
3
+ ArrowLeft,
4
+ Bell,
5
+ Home,
6
+ Settings,
7
+ Plus,
8
+ FileText,
9
+ Copy,
10
+ ClipboardPaste,
11
+ Trash2,
12
+ MoreVertical,
13
+ File,
14
+ Folder,
15
+ Save,
16
+ Scissors,
17
+ RefreshCw,
18
+ Download,
19
+ Share2,
20
+ Star,
21
+ StarOff,
22
+ Pencil,
23
+ FolderOpen,
24
+ Image,
25
+ Link as LinkIcon,
26
+ ExternalLink,
27
+ ZoomIn,
28
+ ZoomOut,
29
+ AlignLeft,
30
+ AlignCenter,
31
+ AlignRight,
32
+ MessageSquare,
33
+ UserCircle,
34
+ LogOut,
35
+ ShieldCheck,
36
+ Command,
37
+ } from 'lucide-react'
38
+ import { useState } from 'react'
39
+
40
+ import { Button } from '../components/Button'
41
+ import { Avatar } from '../components/Avatar'
42
+ import { Badge } from '../components/Badge'
43
+ import { Input } from '../components/Input'
44
+ import { IconButton } from '../components/IconButton'
45
+ import { Tooltip } from '../components/Tooltip'
46
+ import { Dialog, DialogFooter, DialogBody } from '../components/Dialog'
47
+ import { useDialog } from '../components/Dialog'
48
+ import { AlertDialog } from '../components/AlertDialog'
49
+ import { Form, FormInput, FormTextarea, FormSelect, FormCheckbox, FormActions } from '../components/Form'
50
+ import {
51
+ Popover,
52
+ PopoverTrigger,
53
+ PopoverContent,
54
+ PopoverHeader,
55
+ PopoverBody,
56
+ PopoverFooter,
57
+ PopoverClose,
58
+ useImperativePopover,
59
+ } from '../components'
60
+ import {
61
+ Menu,
62
+ MenuTrigger,
63
+ MenuContent,
64
+ MenuItem,
65
+ MenuItemWithIcon,
66
+ MenuCheckboxItem,
67
+ MenuRadioGroup,
68
+ MenuRadioItem,
69
+ MenuLabel,
70
+ MenuSeparator,
71
+ MenuSub,
72
+ MenuSubTrigger,
73
+ MenuSubContent,
74
+ } from '../components/Menu'
75
+ import {
76
+ ContextMenu,
77
+ ContextMenuTrigger,
78
+ ContextMenuContent,
79
+ ContextMenuItemWithIcon,
80
+ ContextMenuCheckboxItem,
81
+ ContextMenuRadioGroup,
82
+ ContextMenuRadioItem,
83
+ ContextMenuSeparator,
84
+ ContextMenuLabel,
85
+ ContextMenuSub,
86
+ ContextMenuSubTrigger,
87
+ ContextMenuSubContent,
88
+ } from '../components/ContextMenu'
89
+ import { Select } from '../components/Select'
90
+ import { Checkbox } from '../components/Checkbox'
91
+ import { Radio, RadioGroup } from '../components/Radio'
92
+ import { Switch } from '../components/Switch'
93
+ import { Tabs, TabList, Tab, TabPanel } from '../components/Tabs'
94
+ import { Progress } from '../components/Progress'
95
+ import { useToast } from '../components/Toast'
96
+ import { Loading } from '../components/Loading'
97
+ import { AutoComplete } from '../components/AutoComplete'
98
+
99
+ export const ComponentShowcase = () => {
100
+ const [simpleDialogOpen, setSimpleDialogOpen] = useState(false)
101
+ const [confirmDialogOpen, setConfirmDialogOpen] = useState(false)
102
+ const [customDialogOpen, setCustomDialogOpen] = useState(false)
103
+
104
+ // AlertDialog states
105
+ const [infoAlertOpen, setInfoAlertOpen] = useState(false)
106
+ const [successAlertOpen, setSuccessAlertOpen] = useState(false)
107
+ const [warningAlertOpen, setWarningAlertOpen] = useState(false)
108
+ const [dangerAlertOpen, setDangerAlertOpen] = useState(false)
109
+
110
+ // Menu states
111
+ const [showToolbar, setShowToolbar] = useState(true)
112
+ const [showSidebar, setShowSidebar] = useState(false)
113
+ const [theme, setTheme] = useState('light')
114
+
115
+ // ContextMenu states
116
+ const [showPreview, setShowPreview] = useState(true)
117
+ const [isPinned, setIsPinned] = useState(false)
118
+ const [textAlign, setTextAlign] = useState('left')
119
+ const [isStarred, setIsStarred] = useState(false)
120
+ const [zoom, setZoom] = useState('100')
121
+
122
+ // New component states
123
+ const [selectValue, setSelectValue] = useState<string | null>(null)
124
+ const [checkboxChecked, setCheckboxChecked] = useState(false)
125
+ const [radioValue, setRadioValue] = useState('email')
126
+ const [switchOn, setSwitchOn] = useState(false)
127
+ const [notificationsOn, setNotificationsOn] = useState(true)
128
+ const [progressValue, setProgressValue] = useState(60)
129
+ const { toast } = useToast()
130
+
131
+ // AutoComplete states
132
+ const [acValue, setAcValue] = useState('')
133
+ const [acAsync, setAcAsync] = useState('')
134
+
135
+ // Imperative Dialog
136
+ const { show: showDialog, confirm: confirmDialog, alert: alertDialog } = useDialog()
137
+ const [dialogResult, setDialogResult] = useState<string | null>(null)
138
+
139
+ // Imperative Popover
140
+ const imperativePopover = useImperativePopover()
141
+
142
+ return (
143
+ <div className="min-h-screen bg-(--bg-muted) pb-20 font-sans transition-colors duration-300">
144
+ {/* Header */}
145
+ <div
146
+ className="bg-(--bg-primary) border-b border-(--border-light) sticky top-0 z-10 px-8 py-4 flex items-center justify-between shadow-sm">
147
+ <div className="flex items-center gap-4">
148
+ <Link to="/"
149
+ className="p-2 hover:bg-(--bg-hover) rounded-full transition-colors text-(--text-secondary)">
150
+ <ArrowLeft className="w-5 h-5"/>
151
+ </Link>
152
+ <h1 className="text-xl font-bold text-(--text-primary)">Component Library</h1>
153
+ </div>
154
+ <div className="text-sm text-(--text-secondary)">
155
+ Base UI Primitives
156
+ </div>
157
+ </div>
158
+
159
+ <main className="max-w-5xl mx-auto px-8 py-12 space-y-16">
160
+
161
+ {/* Section: Buttons */}
162
+ <section>
163
+ <div className="mb-6 pb-2 border-b border-(--border-light)">
164
+ <h2 className="text-2xl font-bold text-(--text-primary)">Buttons</h2>
165
+ <p className="text-(--text-secondary) mt-1">Interactive elements for actions and navigation.</p>
166
+ </div>
167
+
168
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-10">
169
+ <div className="space-y-4">
170
+ <h3 className="font-semibold text-(--text-secondary)">Variants</h3>
171
+ <div className="flex flex-wrap gap-4 items-center">
172
+ <Button variant="primary">Primary</Button>
173
+ <Button variant="secondary">Secondary</Button>
174
+ <Button variant="danger">Danger</Button>
175
+ <Button variant="ghost">Ghost</Button>
176
+ </div>
177
+ </div>
178
+
179
+ <div className="space-y-4">
180
+ <h3 className="font-semibold text-(--text-secondary)">Sizes</h3>
181
+ <div className="flex flex-wrap gap-4 items-center">
182
+ <Button size="sm">Small</Button>
183
+ <Button size="md">Medium</Button>
184
+ <Button size="lg">Large</Button>
185
+ </div>
186
+ </div>
187
+
188
+ <div className="space-y-4">
189
+ <h3 className="font-semibold text-(--text-secondary)">States</h3>
190
+ <div className="flex flex-wrap gap-4 items-center">
191
+ <Button disabled>Disabled</Button>
192
+ <Button variant="primary" disabled>Disabled Primary</Button>
193
+ </div>
194
+ </div>
195
+
196
+ <div className="space-y-4">
197
+ <h3 className="font-semibold text-(--text-secondary)">Full Width</h3>
198
+ <Button fullWidth>Full Width Button</Button>
199
+ </div>
200
+ </div>
201
+ </section>
202
+
203
+ {/* Section: Avatars */}
204
+ <section>
205
+ <div className="mb-6 pb-2 border-b border-(--border-light)">
206
+ <h2 className="text-2xl font-bold text-(--text-primary)">Avatars</h2>
207
+ <p className="text-(--text-secondary) mt-1">User profile images with status indicators.</p>
208
+ </div>
209
+
210
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-10">
211
+ <div className="space-y-4">
212
+ <h3 className="font-semibold text-(--text-secondary)">Sizes</h3>
213
+ <div className="flex items-end gap-4">
214
+ <Avatar size="xs" fallback="XS"/>
215
+ <Avatar size="sm" fallback="SM"/>
216
+ <Avatar size="md" fallback="MD"/>
217
+ <Avatar size="lg" fallback="LG"/>
218
+ <Avatar size="xl" fallback="XL"/>
219
+ </div>
220
+ </div>
221
+
222
+ <div className="space-y-4">
223
+ <h3 className="font-semibold text-(--text-secondary)">Status Indicators</h3>
224
+ <div className="flex items-center gap-6">
225
+ <Avatar status="online" fallback="ON"/>
226
+ <Avatar status="away" fallback="AW"/>
227
+ <Avatar status="dnd" fallback="DND"/>
228
+ <Avatar status="offline" fallback="OFF"/>
229
+ </div>
230
+ </div>
231
+
232
+ <div className="space-y-4">
233
+ <h3 className="font-semibold text-(--text-secondary)">Image vs Fallback</h3>
234
+ <div className="flex items-center gap-6">
235
+ <Avatar src="https://i.pravatar.cc/150?u=12" alt="User"/>
236
+ <Avatar fallback="CD"/>
237
+ </div>
238
+ </div>
239
+
240
+ <div className="space-y-4">
241
+ <h3 className="font-semibold text-(--text-secondary)">Shape</h3>
242
+ <div className="flex items-center gap-6">
243
+ <Avatar rounded={true} fallback="R"/>
244
+ <Avatar rounded={false} fallback="C"/>
245
+ </div>
246
+ </div>
247
+ </div>
248
+ </section>
249
+
250
+ {/* Section: Badges */}
251
+ <section>
252
+ <div className="mb-6 pb-2 border-b border-(--border-light)">
253
+ <h2 className="text-2xl font-bold text-(--text-primary)">Badges</h2>
254
+ <p className="text-(--text-secondary) mt-1">Notification counts and status labels.</p>
255
+ </div>
256
+
257
+ <div className="flex gap-8">
258
+ <div className="space-y-2">
259
+ <h3 className="font-semibold text-(--text-secondary) text-sm">Neutral</h3>
260
+ <div className="flex gap-2">
261
+ <Badge count={1} variant="neutral"/>
262
+ <Badge count={99} variant="neutral"/>
263
+ </div>
264
+ </div>
265
+ <div className="space-y-2">
266
+ <h3 className="font-semibold text-(--text-secondary) text-sm">Danger</h3>
267
+ <div className="flex gap-2">
268
+ <Badge count={1} variant="danger"/>
269
+ <Badge count={5} variant="danger"/>
270
+ </div>
271
+ </div>
272
+ <div className="space-y-2">
273
+ <h3 className="font-semibold text-(--text-secondary) text-sm">Dot</h3>
274
+ <div className="flex gap-2">
275
+ <Badge dot variant="danger"/>
276
+ <Badge dot variant="neutral"/>
277
+ </div>
278
+ </div>
279
+ </div>
280
+ </section>
281
+
282
+ {/* Section: Inputs */}
283
+ <section>
284
+ <div className="mb-6 pb-2 border-b border-(--border-light)">
285
+ <h2 className="text-2xl font-bold text-(--text-primary)">Inputs</h2>
286
+ <p className="text-(--text-secondary) mt-1">Form controls for user input.</p>
287
+ </div>
288
+
289
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-2xl">
290
+ <div className="space-y-2">
291
+ <Input label="Email Address" placeholder="name@work-email.com" fullWidth/>
292
+ </div>
293
+ <div className="space-y-2">
294
+ <Input label="Password" type="password" placeholder="••••••••" error="Password is too short" fullWidth/>
295
+ </div>
296
+ </div>
297
+ </section>
298
+
299
+ {/* Section: Icon Buttons */}
300
+ <section>
301
+ <div className="mb-6 pb-2 border-b border-(--border-light)">
302
+ <h2 className="text-2xl font-bold text-(--text-primary)">Icon Buttons</h2>
303
+ <p className="text-(--text-secondary) mt-1">Compact buttons for toolbars and actions.</p>
304
+ </div>
305
+
306
+ <div className="flex flex-wrap gap-4">
307
+ <IconButton><Bell className="w-5 h-5"/></IconButton>
308
+ <IconButton variant="ghost"><Settings className="w-5 h-5"/></IconButton>
309
+ <IconButton active><Plus className="w-5 h-5"/></IconButton>
310
+ <IconButton size="sm"><Home className="w-4 h-4"/></IconButton>
311
+ </div>
312
+ </section>
313
+
314
+ {/* Section: Tooltips */}
315
+ <section>
316
+ <div className="mb-6 pb-2 border-b border-(--border-light)">
317
+ <h2 className="text-2xl font-bold text-(--text-primary)">Tooltips</h2>
318
+ <p className="text-(--text-secondary) mt-1">Contextual information on hover.</p>
319
+ </div>
320
+
321
+ <div className="flex gap-4">
322
+ <Tooltip content="This is a tooltip">
323
+ <Button>Hover Me</Button>
324
+ </Tooltip>
325
+
326
+ <Tooltip content="Tooltip on bottom" side="bottom">
327
+ <Button variant="secondary">Bottom Tooltip</Button>
328
+ </Tooltip>
329
+ </div>
330
+ </section>
331
+
332
+ {/* Section: Dialogs */}
333
+ <section>
334
+ <div className="mb-6 pb-2 border-b border-(--border-light)">
335
+ <h2 className="text-2xl font-bold text-(--text-primary)">Dialogs</h2>
336
+ <p className="text-(--text-secondary) mt-1">Modal dialogs for important interactions and
337
+ confirmations.</p>
338
+ </div>
339
+
340
+ <div className="flex flex-wrap gap-4">
341
+ <Button onClick={() => setSimpleDialogOpen(true)}>
342
+ Simple Dialog
343
+ </Button>
344
+
345
+ <Button variant="secondary" onClick={() => setConfirmDialogOpen(true)}>
346
+ Confirmation Dialog
347
+ </Button>
348
+
349
+ <Button variant="ghost" onClick={() => setCustomDialogOpen(true)}>
350
+ Custom Layout
351
+ </Button>
352
+ </div>
353
+
354
+ {/* Simple Dialog */}
355
+ <Dialog
356
+ open={simpleDialogOpen}
357
+ onOpenChange={setSimpleDialogOpen}
358
+ title="Welcome to Slack Base UI"
359
+ description="This is a simple dialog with a title and description."
360
+ size="md"
361
+ >
362
+ <DialogBody>
363
+ <p className="text-(--text-secondary)">
364
+ This dialog component is built on top of Base UI's Dialog primitive,
365
+ styled to match Slack's design system. It supports multiple sizes,
366
+ custom content, and follows accessibility best practices.
367
+ </p>
368
+ </DialogBody>
369
+ <DialogFooter>
370
+ <Button variant="secondary" onClick={() => setSimpleDialogOpen(false)}>
371
+ Close
372
+ </Button>
373
+ <Button variant="primary" onClick={() => setSimpleDialogOpen(false)}>
374
+ Got it
375
+ </Button>
376
+ </DialogFooter>
377
+ </Dialog>
378
+
379
+ {/* Confirmation Dialog */}
380
+ <Dialog
381
+ open={confirmDialogOpen}
382
+ onOpenChange={setConfirmDialogOpen}
383
+ title="Delete channel?"
384
+ description="Are you sure you want to delete this channel? This action cannot be undone."
385
+ size="sm"
386
+ >
387
+ <DialogBody>
388
+ <div className="bg-(--bg-secondary) border border-(--border-light) rounded-lg p-4 mb-4">
389
+ <p className="font-semibold text-(--text-primary) mb-1">#design-system</p>
390
+ <p className="text-sm text-(--text-secondary)">12 members · 234 messages</p>
391
+ </div>
392
+ <p className="text-(--text-secondary) text-sm">
393
+ All messages and files in this channel will be permanently deleted.
394
+ Members will no longer have access to this channel.
395
+ </p>
396
+ </DialogBody>
397
+ <DialogFooter>
398
+ <Button variant="secondary" onClick={() => setConfirmDialogOpen(false)}>
399
+ Cancel
400
+ </Button>
401
+ <Button variant="danger" onClick={() => setConfirmDialogOpen(false)}>
402
+ Delete Channel
403
+ </Button>
404
+ </DialogFooter>
405
+ </Dialog>
406
+
407
+ {/* Custom Layout Dialog */}
408
+ <Dialog
409
+ open={customDialogOpen}
410
+ onOpenChange={setCustomDialogOpen}
411
+ title="Invite people to workspace"
412
+ size="lg"
413
+ >
414
+ <DialogBody className="space-y-4">
415
+ <div>
416
+ <label className="block text-sm font-semibold text-(--text-primary) mb-2">
417
+ Email addresses
418
+ </label>
419
+ <Input
420
+ placeholder="name@example.com"
421
+ fullWidth
422
+ />
423
+ <p className="text-xs text-(--text-secondary) mt-1">
424
+ Separate multiple emails with commas
425
+ </p>
426
+ </div>
427
+
428
+ <div>
429
+ <label className="block text-sm font-semibold text-(--text-primary) mb-2">
430
+ Add to channels (optional)
431
+ </label>
432
+ <Input
433
+ placeholder="Search for channels..."
434
+ fullWidth
435
+ />
436
+ </div>
437
+
438
+ <div className="bg-(--bg-secondary) rounded-lg p-4 border border-(--border-light)">
439
+ <div className="flex items-start gap-3">
440
+ <div
441
+ className="w-10 h-10 rounded bg-(--slack-blue) flex items-center justify-center text-white font-bold shrink-0">
442
+ 💡
443
+ </div>
444
+ <div>
445
+ <p className="font-semibold text-(--text-primary) text-sm mb-1">
446
+ Pro tip
447
+ </p>
448
+ <p className="text-sm text-(--text-secondary)">
449
+ Invited members will receive an email with instructions to join your workspace.
450
+ They'll also be automatically added to #general.
451
+ </p>
452
+ </div>
453
+ </div>
454
+ </div>
455
+ </DialogBody>
456
+ <DialogFooter>
457
+ <Button variant="secondary" onClick={() => setCustomDialogOpen(false)}>
458
+ Cancel
459
+ </Button>
460
+ <Button variant="primary" onClick={() => setCustomDialogOpen(false)}>
461
+ Send Invitations
462
+ </Button>
463
+ </DialogFooter>
464
+ </Dialog>
465
+ </section>
466
+
467
+ {/* Section: Alert Dialogs */}
468
+ <section>
469
+ <div className="mb-6 pb-2 border-b border-(--border-light)">
470
+ <h2 className="text-2xl font-bold text-(--text-primary)">Alert Dialogs</h2>
471
+ <p className="text-(--text-secondary) mt-1">Focused alerts for important confirmations with semantic
472
+ variants.</p>
473
+ </div>
474
+
475
+ <div className="flex flex-wrap gap-4">
476
+ <Button onClick={() => setInfoAlertOpen(true)}>
477
+ Info Alert
478
+ </Button>
479
+
480
+ <Button variant="secondary" onClick={() => setSuccessAlertOpen(true)}>
481
+ Success Alert
482
+ </Button>
483
+
484
+ <Button onClick={() => setWarningAlertOpen(true)}>
485
+ Warning Alert
486
+ </Button>
487
+
488
+ <Button variant="danger" onClick={() => setDangerAlertOpen(true)}>
489
+ Danger Alert
490
+ </Button>
491
+ </div>
492
+
493
+ {/* Info Alert */}
494
+ <AlertDialog
495
+ open={infoAlertOpen}
496
+ onOpenChange={setInfoAlertOpen}
497
+ title="New feature available"
498
+ description="We've added a new way to organize your channels."
499
+ variant="info"
500
+ confirmText="Learn More"
501
+ onConfirm={() => {
502
+ console.log('Info confirmed')
503
+ }}
504
+ >
505
+ <p className="text-(--text-secondary)">
506
+ You can now create channel sections to group related channels together.
507
+ This helps keep your sidebar organized and easy to navigate.
508
+ </p>
509
+ </AlertDialog>
510
+
511
+ {/* Success Alert */}
512
+ <AlertDialog
513
+ open={successAlertOpen}
514
+ onOpenChange={setSuccessAlertOpen}
515
+ title="Channel created successfully"
516
+ description="Your new channel is ready to use."
517
+ variant="success"
518
+ confirmText="Go to Channel"
519
+ showCancel={false}
520
+ />
521
+
522
+ {/* Warning Alert */}
523
+ <AlertDialog
524
+ open={warningAlertOpen}
525
+ onOpenChange={setWarningAlertOpen}
526
+ title="Your workspace is almost full"
527
+ description="You're using 95% of your storage space."
528
+ variant="warning"
529
+ confirmText="Upgrade Plan"
530
+ cancelText="Dismiss"
531
+ onConfirm={async () => {
532
+ await new Promise(resolve => setTimeout(resolve, 1000))
533
+ console.log('Upgrade clicked')
534
+ }}
535
+ >
536
+ <p className="text-(--text-secondary)">
537
+ Consider upgrading to a paid plan to get more storage and unlock premium features.
538
+ </p>
539
+ </AlertDialog>
540
+
541
+ {/* Danger Alert */}
542
+ <AlertDialog
543
+ open={dangerAlertOpen}
544
+ onOpenChange={setDangerAlertOpen}
545
+ title="Delete workspace?"
546
+ description="This action cannot be undone and will permanently delete your workspace."
547
+ variant="danger"
548
+ confirmText="Delete Workspace"
549
+ cancelText="Cancel"
550
+ onConfirm={() => {
551
+ console.log('Workspace deleted')
552
+ }}
553
+ >
554
+ <div className="space-y-2">
555
+ <p className="text-(--text-secondary)">
556
+ This will permanently delete:
557
+ </p>
558
+ <ul className="list-disc list-inside text-(--text-secondary) space-y-1 ml-2">
559
+ <li>All channels and messages</li>
560
+ <li>All files and attachments</li>
561
+ <li>All workspace settings</li>
562
+ <li>All member data</li>
563
+ </ul>
564
+ </div>
565
+ </AlertDialog>
566
+ </section>
567
+
568
+ {/* Section: Imperative Dialog API */}
569
+ <section>
570
+ <div className="mb-6 pb-2 border-b border-(--border-light)">
571
+ <h2 className="text-2xl font-bold text-(--text-primary)">Imperative Dialog</h2>
572
+ <p className="text-(--text-secondary) mt-1">
573
+ 通过 <code
574
+ className="text-sm bg-(--bg-secondary) px-1.5 py-0.5 rounded border border-(--border-light) font-mono">useDialog()</code> 命令式调用对话框,无需自行管理{' '}
575
+ <code
576
+ className="text-sm bg-(--bg-secondary) px-1.5 py-0.5 rounded border border-(--border-light) font-mono">open</code> 状态。
577
+ </p>
578
+ </div>
579
+
580
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
581
+ {/* show() */}
582
+ <div className="bg-(--bg-primary) rounded-lg border border-(--border-light) p-5 space-y-4">
583
+ <div>
584
+ <h3 className="font-semibold text-(--text-primary) flex items-center gap-2">
585
+ <Command size={15} className="text-(--text-muted)"/>
586
+ show()
587
+ </h3>
588
+ <p className="text-sm text-(--text-secondary) mt-1">
589
+ 显示通用对话框,关闭时 Promise resolve。
590
+ </p>
591
+ </div>
592
+ <Button
593
+ variant="secondary"
594
+ fullWidth
595
+ onClick={async () => {
596
+ await showDialog({
597
+ title: '键盘快捷键',
598
+ description: '以下是常用操作的快捷键列表',
599
+ size: 'sm',
600
+ content: (
601
+ <div className="space-y-2 mt-1">
602
+ {([
603
+ ['⌘K', '打开命令面板'],
604
+ ['⌘/', '切换侧边栏'],
605
+ ['⌘N', '新建频道'],
606
+ ['⌘⇧M', '切换静音'],
607
+ ['⌘⇧Y', '设置状态'],
608
+ ] as [string, string][]).map(([key, desc]) => (
609
+ <div key={key} className="flex items-center justify-between text-sm py-1">
610
+ <span className="text-(--text-secondary)">{desc}</span>
611
+ <kbd
612
+ className="px-2 py-0.5 rounded bg-(--bg-secondary) border border-(--border-light) text-(--text-primary) font-mono text-xs">
613
+ {key}
614
+ </kbd>
615
+ </div>
616
+ ))}
617
+ </div>
618
+ ),
619
+ })
620
+ setDialogResult('对话框已关闭')
621
+ }}
622
+ >
623
+ Show Dialog
624
+ </Button>
625
+ </div>
626
+
627
+ {/* confirm() */}
628
+ <div className="bg-(--bg-primary) rounded-lg border border-(--border-light) p-5 space-y-4">
629
+ <div>
630
+ <h3 className="font-semibold text-(--text-primary) flex items-center gap-2">
631
+ <ShieldCheck size={15} className="text-(--text-muted)"/>
632
+ confirm()
633
+ </h3>
634
+ <p className="text-sm text-(--text-secondary) mt-1">
635
+ 返回{' '}
636
+ <code className="text-xs bg-(--bg-secondary) px-1 rounded font-mono">Promise&lt;boolean&gt;</code>
637
+ ,确认为 <code className="text-xs bg-(--bg-secondary) px-1 rounded font-mono">true</code>。
638
+ </p>
639
+ </div>
640
+ <Button
641
+ variant="danger"
642
+ fullWidth
643
+ onClick={async () => {
644
+ const ok = await confirmDialog({
645
+ title: '离开频道?',
646
+ description: '你将无法再收到此频道的消息,但随时可以重新加入。',
647
+ confirmLabel: '离开频道',
648
+ cancelLabel: '取消',
649
+ confirmVariant: 'danger',
650
+ })
651
+ setDialogResult(ok ? '✅ 已离开频道' : '❌ 操作已取消')
652
+ }}
653
+ >
654
+ Confirm Dialog
655
+ </Button>
656
+ </div>
657
+
658
+ {/* alert() */}
659
+ <div className="bg-(--bg-primary) rounded-lg border border-(--border-light) p-5 space-y-4">
660
+ <div>
661
+ <h3 className="font-semibold text-(--text-primary) flex items-center gap-2">
662
+ <Bell size={15} className="text-(--text-muted)"/>
663
+ alert()
664
+ </h3>
665
+ <p className="text-sm text-(--text-secondary) mt-1">
666
+ 仅有 OK 按钮,适合错误或提示信息场景。
667
+ </p>
668
+ </div>
669
+ <Button
670
+ fullWidth
671
+ onClick={async () => {
672
+ await alertDialog({
673
+ title: '无法发送消息',
674
+ description: '你没有在 #design-system 频道发送消息的权限,请联系工作区管理员。',
675
+ confirmLabel: '知道了',
676
+ })
677
+ setDialogResult('提示框已关闭')
678
+ }}
679
+ >
680
+ Alert Dialog
681
+ </Button>
682
+ </div>
683
+ </div>
684
+
685
+ {/* Result display */}
686
+ {dialogResult && (
687
+ <div
688
+ className="mt-4 flex items-center gap-3 text-sm bg-(--bg-secondary) border border-(--border-light) rounded-lg px-4 py-2.5">
689
+ <span className="text-(--text-secondary)">上次操作结果:</span>
690
+ <span className="font-semibold text-(--text-primary)">{dialogResult}</span>
691
+ <button
692
+ className="ml-auto text-xs text-(--text-muted) hover:text-(--text-secondary) underline"
693
+ onClick={() => setDialogResult(null)}
694
+ >
695
+ 清除
696
+ </button>
697
+ </div>
698
+ )}
699
+ </section>
700
+
701
+ {/* Section: Forms */}
702
+ <section>
703
+ <div className="mb-6 pb-2 border-b border-(--border-light)">
704
+ <h2 className="text-2xl font-bold text-(--text-primary)">Forms</h2>
705
+ <p className="text-(--text-secondary) mt-1">Complete form system with validation and various input
706
+ types.</p>
707
+ </div>
708
+
709
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
710
+ {/* Simple Form */}
711
+ <div className="bg-(--bg-primary) rounded-lg border border-(--border-light) p-6">
712
+ <h3 className="font-semibold text-(--text-primary) mb-4">Contact Form</h3>
713
+ <Form
714
+ initialValues={{
715
+ name: '',
716
+ email: '',
717
+ message: '',
718
+ }}
719
+ validate={(values) => {
720
+ const errors: Record<string, string> = {}
721
+ if (!values.name) {
722
+ errors.name = 'Name is required'
723
+ }
724
+ if (!values.email) {
725
+ errors.email = 'Email is required'
726
+ } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)) {
727
+ errors.email = 'Invalid email address'
728
+ }
729
+ if (!values.message) {
730
+ errors.message = 'Message is required'
731
+ }
732
+ return errors
733
+ }}
734
+ onSubmit={(values) => {
735
+ console.log('Form submitted:', values)
736
+ alert('Form submitted! Check console for values.')
737
+ }}
738
+ >
739
+ <FormInput
740
+ name="name"
741
+ label="Full Name"
742
+ placeholder="John Doe"
743
+ required
744
+ fullWidth
745
+ />
746
+ <FormInput
747
+ name="email"
748
+ label="Email Address"
749
+ type="email"
750
+ placeholder="john@example.com"
751
+ required
752
+ fullWidth
753
+ />
754
+ <FormTextarea
755
+ name="message"
756
+ label="Message"
757
+ placeholder="Your message here..."
758
+ required
759
+ fullWidth
760
+ rows={4}
761
+ />
762
+ <FormActions>
763
+ <Button type="button" variant="secondary">
764
+ Cancel
765
+ </Button>
766
+ <Button type="submit" variant="primary">
767
+ Send Message
768
+ </Button>
769
+ </FormActions>
770
+ </Form>
771
+ </div>
772
+
773
+ {/* Advanced Form */}
774
+ <div className="bg-(--bg-primary) rounded-lg border border-(--border-light) p-6">
775
+ <h3 className="font-semibold text-(--text-primary) mb-4">User Registration</h3>
776
+ <Form
777
+ initialValues={{
778
+ username: '',
779
+ email: '',
780
+ role: '',
781
+ bio: '',
782
+ newsletter: false,
783
+ }}
784
+ validate={(values) => {
785
+ const errors: Record<string, string> = {}
786
+ if (!values.username) {
787
+ errors.username = 'Username is required'
788
+ } else if (values.username.length < 3) {
789
+ errors.username = 'Username must be at least 3 characters'
790
+ }
791
+ if (!values.email) {
792
+ errors.email = 'Email is required'
793
+ }
794
+ if (!values.role) {
795
+ errors.role = 'Please select a role'
796
+ }
797
+ return errors
798
+ }}
799
+ onSubmit={(values) => {
800
+ console.log('Registration form submitted:', values)
801
+ alert('Registration form submitted! Check console.')
802
+ }}
803
+ >
804
+ <FormInput
805
+ name="username"
806
+ label="Username"
807
+ placeholder="johndoe"
808
+ required
809
+ fullWidth
810
+ />
811
+ <FormInput
812
+ name="email"
813
+ label="Email"
814
+ type="email"
815
+ placeholder="john@company.com"
816
+ required
817
+ fullWidth
818
+ />
819
+ <FormSelect
820
+ name="role"
821
+ label="Role"
822
+ required
823
+ fullWidth
824
+ options={[
825
+ { value: 'admin', label: 'Administrator' },
826
+ { value: 'member', label: 'Member' },
827
+ { value: 'guest', label: 'Guest' },
828
+ ]}
829
+ />
830
+ <FormTextarea
831
+ name="bio"
832
+ label="Bio (Optional)"
833
+ placeholder="Tell us about yourself..."
834
+ fullWidth
835
+ rows={3}
836
+ />
837
+ <FormCheckbox
838
+ name="newsletter"
839
+ label="Subscribe to newsletter"
840
+ />
841
+ <FormActions>
842
+ <Button type="button" variant="secondary">
843
+ Reset
844
+ </Button>
845
+ <Button type="submit" variant="primary">
846
+ Register
847
+ </Button>
848
+ </FormActions>
849
+ </Form>
850
+ </div>
851
+ </div>
852
+ </section>
853
+
854
+ {/* Section: Popover */}
855
+ <section>
856
+ <div className="mb-6 pb-2 border-b border-(--border-light)">
857
+ <h2 className="text-2xl font-bold text-(--text-primary)">Popover</h2>
858
+ <p className="text-(--text-secondary) mt-1">Display temporary content and interactive interfaces.</p>
859
+ </div>
860
+
861
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-10">
862
+ <div className="space-y-4">
863
+ <h3 className="font-semibold text-(--text-secondary)">Basic Popover</h3>
864
+ <Popover>
865
+ <PopoverTrigger render={<Button variant="secondary">
866
+ Open Popover</Button>}
867
+ />
868
+ <PopoverContent>
869
+ <div className="p-4">
870
+ <h3 className="font-bold mb-2 text-(--text-primary)">Popover Title</h3>
871
+ <p className="text-sm text-(--text-secondary)">
872
+ This is a simple popover with custom content.
873
+ </p>
874
+ </div>
875
+ </PopoverContent>
876
+ </Popover>
877
+ </div>
878
+
879
+ <div className="space-y-4">
880
+ <h3 className="font-semibold text-(--text-secondary)">With Header & Footer</h3>
881
+ <Popover>
882
+ <PopoverTrigger render={<Button variant="secondary">Settings</Button>}/>
883
+ <PopoverContent>
884
+ <PopoverHeader>User Settings</PopoverHeader>
885
+ <PopoverBody>
886
+ <p className="text-sm text-(--text-secondary)">
887
+ Configure your preferences and account settings here.
888
+ </p>
889
+ </PopoverBody>
890
+ <PopoverFooter>
891
+ <PopoverClose render={<Button variant="secondary" size="sm">Cancel</Button>}/>
892
+ <Button variant="primary" size="sm">Save</Button>
893
+ </PopoverFooter>
894
+ </PopoverContent>
895
+ </Popover>
896
+ </div>
897
+
898
+ <div className="space-y-4">
899
+ <h3 className="font-semibold text-(--text-secondary)">Different Sides</h3>
900
+ <div className="flex gap-2">
901
+ <Popover>
902
+ <PopoverTrigger render={<Button variant="ghost" size="sm">Top</Button>}/>
903
+ <PopoverContent side="top">
904
+ <div className="p-3">
905
+ <p className="text-sm">Top popover</p>
906
+ </div>
907
+ </PopoverContent>
908
+ </Popover>
909
+ <Popover>
910
+ <PopoverTrigger render={<Button variant="ghost" size="sm">Bottom</Button>}/>
911
+ <PopoverContent side="bottom">
912
+ <div className="p-3">
913
+ <p className="text-sm">Bottom popover</p>
914
+ </div>
915
+ </PopoverContent>
916
+ </Popover>
917
+ <Popover>
918
+ <PopoverTrigger render={<Button variant="ghost" size="sm">Left</Button>}/>
919
+ <PopoverContent side="left">
920
+ <div className="p-3">
921
+ <p className="text-sm">Left popover</p>
922
+ </div>
923
+ </PopoverContent>
924
+ </Popover>
925
+ <Popover>
926
+ <PopoverTrigger render={<Button variant="ghost" size="sm">Right</Button>}/>
927
+ <PopoverContent side="right">
928
+ <div className="p-3">
929
+ <p className="text-sm">Right popover</p>
930
+ </div>
931
+ </PopoverContent>
932
+ </Popover>
933
+ </div>
934
+ </div>
935
+
936
+ {/* Imperative — user card */}
937
+ <div className="space-y-4 md:col-span-2">
938
+ <h3 className="font-semibold text-(--text-secondary)">
939
+ Imperative{' '}
940
+ <code
941
+ className="text-xs bg-(--bg-secondary) px-1.5 py-0.5 rounded border border-(--border-light) font-mono font-normal">
942
+ useImperativePopover()
943
+ </code>
944
+ </h3>
945
+ <p className="text-sm text-(--text-secondary)">
946
+ 命令式调用,将 Popover 锚定到任意 DOM 元素;同一按钮再次点击自动关闭(toggle)。
947
+ </p>
948
+
949
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
950
+ {/* User list — click to show profile card */}
951
+ <div className="space-y-2">
952
+ <p className="text-xs font-medium text-(--text-muted) uppercase tracking-wide">成员列表(点击查看卡片)</p>
953
+ <div
954
+ className="rounded-lg border border-(--border-light) overflow-hidden divide-y divide-(--border-light)">
955
+ {([
956
+ {
957
+ name: 'Alice Johnson',
958
+ role: 'Design Lead',
959
+ status: 'online' as const,
960
+ tz: 'UTC+8 · 下午 3:24',
961
+ },
962
+ {
963
+ name: 'Bob Smith',
964
+ role: 'Senior Engineer',
965
+ status: 'away' as const,
966
+ tz: 'UTC-5 · 上午 2:24',
967
+ },
968
+ {
969
+ name: 'Charlie Wang',
970
+ role: 'Product Manager',
971
+ status: 'dnd' as const,
972
+ tz: 'UTC+8 · 下午 3:24',
973
+ },
974
+ ]).map(user => (
975
+ <button
976
+ key={user.name}
977
+ className="w-full flex items-center gap-3 px-4 py-2.5 hover:bg-(--bg-hover) transition-colors text-left"
978
+ onClick={e =>
979
+ imperativePopover.toggle(e.currentTarget, {
980
+ content: (
981
+ <div className="w-56">
982
+ <div className="px-4 pt-4 pb-3 space-y-3">
983
+ <div className="flex items-center gap-3">
984
+ <Avatar
985
+ fallback={user.name.slice(0, 2)}
986
+ status={user.status}
987
+ size="lg"
988
+ />
989
+ <div className="min-w-0">
990
+ <p className="font-bold text-(--text-primary) text-sm truncate">{user.name}</p>
991
+ <p className="text-xs text-(--text-secondary) truncate">{user.role}</p>
992
+ </div>
993
+ </div>
994
+ <p className="text-xs text-(--text-muted)">{user.tz}</p>
995
+ </div>
996
+ <div
997
+ className="border-t border-(--border-light) px-3 py-2 flex flex-col gap-1">
998
+ <button
999
+ className="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-(--bg-hover) text-sm text-(--text-primary) w-full text-left transition-colors">
1000
+ <MessageSquare size={14} className="text-(--text-muted)"/>
1001
+ 发送消息
1002
+ </button>
1003
+ <button
1004
+ className="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-(--bg-hover) text-sm text-(--text-primary) w-full text-left transition-colors">
1005
+ <UserCircle size={14} className="text-(--text-muted)"/>
1006
+ 查看个人资料
1007
+ </button>
1008
+ </div>
1009
+ </div>
1010
+ ),
1011
+ side: 'right',
1012
+ align: 'start',
1013
+ sideOffset: 6,
1014
+ })
1015
+ }
1016
+ >
1017
+ <Avatar fallback={user.name.slice(0, 2)} status={user.status} size="sm"/>
1018
+ <span className="text-sm text-(--text-primary)">{user.name}</span>
1019
+ </button>
1020
+ ))}
1021
+ </div>
1022
+ </div>
1023
+
1024
+ {/* Toolbar — toggle settings popover */}
1025
+ <div className="space-y-2">
1026
+ <p className="text-xs font-medium text-(--text-muted) uppercase tracking-wide">工具栏(Toggle
1027
+ 用法)</p>
1028
+ <div
1029
+ className="flex items-center gap-1 p-2 rounded-lg border border-(--border-light) bg-(--bg-secondary)">
1030
+ <Tooltip content="消息">
1031
+ <IconButton size="sm">
1032
+ <MessageSquare size={15}/>
1033
+ </IconButton>
1034
+ </Tooltip>
1035
+ <Tooltip content="成员">
1036
+ <IconButton size="sm">
1037
+ <UserCircle size={15}/>
1038
+ </IconButton>
1039
+ </Tooltip>
1040
+ <div className="flex-1"/>
1041
+ <Tooltip content="设置">
1042
+ <IconButton
1043
+ size="sm"
1044
+ active={imperativePopover.isOpen}
1045
+ onClick={e =>
1046
+ imperativePopover.toggle(e.currentTarget, {
1047
+ content: (
1048
+ <div className="w-52">
1049
+ <div className="px-4 py-3 border-b border-(--border-light)">
1050
+ <p className="font-bold text-[15px] text-(--text-primary)">频道设置</p>
1051
+ </div>
1052
+ <div className="px-3 py-2 flex flex-col gap-1">
1053
+ {([
1054
+ [Settings, '通知偏好'],
1055
+ [Star, '加入收藏'],
1056
+ [UserCircle, '查看成员'],
1057
+ ] as const).map(([Icon, label]) => (
1058
+ <button
1059
+ key={label}
1060
+ className="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-(--bg-hover) text-sm text-(--text-primary) w-full text-left transition-colors"
1061
+ onClick={() => imperativePopover.hide()}
1062
+ >
1063
+ <Icon size={14} className="text-(--text-muted)"/>
1064
+ {label}
1065
+ </button>
1066
+ ))}
1067
+ <div className="h-px bg-(--border-light) my-1"/>
1068
+ <button
1069
+ className="flex items-center gap-2 px-2 py-1.5 rounded hover:bg-red-50 dark:hover:bg-red-950/30 text-sm text-(--danger) w-full text-left transition-colors"
1070
+ onClick={() => imperativePopover.hide()}
1071
+ >
1072
+ <LogOut size={14}/>
1073
+ 离开频道
1074
+ </button>
1075
+ </div>
1076
+ </div>
1077
+ ),
1078
+ side: 'bottom',
1079
+ align: 'end',
1080
+ sideOffset: 6,
1081
+ })
1082
+ }
1083
+ >
1084
+ <Settings size={15}/>
1085
+ </IconButton>
1086
+ </Tooltip>
1087
+ </div>
1088
+ <p className="text-xs text-(--text-muted)">
1089
+ 点击设置图标打开/关闭浮层;
1090
+ <code className="text-[11px] bg-(--bg-secondary) px-1 rounded font-mono">isOpen</code>
1091
+ {' = '}
1092
+ <span className="font-medium text-(--text-primary)">{imperativePopover.isOpen
1093
+ ? 'true'
1094
+ : 'false'}</span>
1095
+ </p>
1096
+ </div>
1097
+ </div>
1098
+ </div>
1099
+ </div>
1100
+ </section>
1101
+
1102
+ {/* Section: Menu */}
1103
+ <section>
1104
+ <div className="mb-6 pb-2 border-b border-(--border-light)">
1105
+ <h2 className="text-2xl font-bold text-(--text-primary)">Menu</h2>
1106
+ <p className="text-(--text-secondary) mt-1">Dropdown menus with various item types and interactions.</p>
1107
+ </div>
1108
+
1109
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-10">
1110
+ <div className="space-y-4">
1111
+ <h3 className="font-semibold text-(--text-secondary)">Basic Menu</h3>
1112
+ <Menu>
1113
+ <MenuTrigger render={<Button variant="secondary">File</Button>}/>
1114
+ <MenuContent>
1115
+ <MenuItem onSelect={() => console.log('New')}>New File</MenuItem>
1116
+ <MenuItem onSelect={() => console.log('Open')}>Open...</MenuItem>
1117
+ <MenuSeparator/>
1118
+ <MenuItem onSelect={() => console.log('Save')}>Save</MenuItem>
1119
+ <MenuItem onSelect={() => console.log('Save As')}>Save As...</MenuItem>
1120
+ </MenuContent>
1121
+ </Menu>
1122
+ </div>
1123
+
1124
+ <div className="space-y-4">
1125
+ <h3 className="font-semibold text-(--text-secondary)">With Icons & Shortcuts</h3>
1126
+ <Menu>
1127
+ <MenuTrigger render={
1128
+ <Button variant="secondary">
1129
+ <FileText className="w-4 h-4 mr-2"/>
1130
+ Actions
1131
+ </Button>
1132
+ }/>
1133
+ <MenuContent>
1134
+ <MenuItemWithIcon icon={<File size={16}/>} shortcut="⌘N">
1135
+ New File
1136
+ </MenuItemWithIcon>
1137
+ <MenuItemWithIcon icon={<Folder size={16}/>} shortcut="⌘O">
1138
+ Open Folder
1139
+ </MenuItemWithIcon>
1140
+ <MenuSeparator/>
1141
+ <MenuItemWithIcon icon={<Save size={16}/>} shortcut="⌘S">
1142
+ Save
1143
+ </MenuItemWithIcon>
1144
+ </MenuContent>
1145
+ </Menu>
1146
+ </div>
1147
+
1148
+ <div className="space-y-4">
1149
+ <h3 className="font-semibold text-(--text-secondary)">Checkbox Items</h3>
1150
+ <Menu>
1151
+ <MenuTrigger render={<Button variant="secondary">View</Button>}/>
1152
+ <MenuContent>
1153
+ <MenuLabel>Options</MenuLabel>
1154
+ <MenuCheckboxItem
1155
+ checked={showToolbar}
1156
+ onCheckedChange={setShowToolbar}
1157
+ >
1158
+ Show Toolbar
1159
+ </MenuCheckboxItem>
1160
+ <MenuCheckboxItem
1161
+ checked={showSidebar}
1162
+ onCheckedChange={setShowSidebar}
1163
+ >
1164
+ Show Sidebar
1165
+ </MenuCheckboxItem>
1166
+ </MenuContent>
1167
+ </Menu>
1168
+ <p className="text-xs text-(--text-muted)">
1169
+ Toolbar: {showToolbar ? 'On' : 'Off'}, Sidebar: {showSidebar ? 'On' : 'Off'}
1170
+ </p>
1171
+ </div>
1172
+
1173
+ <div className="space-y-4">
1174
+ <h3 className="font-semibold text-(--text-secondary)">Radio Group</h3>
1175
+ <Menu>
1176
+ <MenuTrigger render={<Button variant="secondary">Theme</Button>}/>
1177
+ <MenuContent>
1178
+ <MenuLabel>Select Theme</MenuLabel>
1179
+ <MenuRadioGroup value={theme} onValueChange={setTheme}>
1180
+ <MenuRadioItem value="light">Light</MenuRadioItem>
1181
+ <MenuRadioItem value="dark">Dark</MenuRadioItem>
1182
+ <MenuRadioItem value="system">System</MenuRadioItem>
1183
+ </MenuRadioGroup>
1184
+ </MenuContent>
1185
+ </Menu>
1186
+ <p className="text-xs text-(--text-muted)">Current: {theme}</p>
1187
+ </div>
1188
+
1189
+ <div className="space-y-4">
1190
+ <h3 className="font-semibold text-(--text-secondary)">Submenu</h3>
1191
+ <Menu>
1192
+ <MenuTrigger render={
1193
+ <Button variant="secondary">
1194
+ <MoreVertical className="w-4 h-4"/>
1195
+ </Button>
1196
+ }/>
1197
+ <MenuContent>
1198
+ <MenuItem>Copy</MenuItem>
1199
+ <MenuItem>Paste</MenuItem>
1200
+ <MenuSub>
1201
+ <MenuSubTrigger>More Options</MenuSubTrigger>
1202
+ <MenuSubContent>
1203
+ <MenuItem>Cut</MenuItem>
1204
+ <MenuItem>Duplicate</MenuItem>
1205
+ <MenuItem>Rename</MenuItem>
1206
+ </MenuSubContent>
1207
+ </MenuSub>
1208
+ <MenuSeparator/>
1209
+ <MenuItem destructive>Delete</MenuItem>
1210
+ </MenuContent>
1211
+ </Menu>
1212
+ </div>
1213
+
1214
+ <div className="space-y-4">
1215
+ <h3 className="font-semibold text-(--text-secondary)">With Danger Action</h3>
1216
+ <Menu>
1217
+ <MenuTrigger render={<Button variant="secondary">Edit</Button>}/>
1218
+ <MenuContent>
1219
+ <MenuItem>Edit Details</MenuItem>
1220
+ <MenuItem>Duplicate</MenuItem>
1221
+ <MenuSeparator/>
1222
+ <MenuItem destructive onSelect={() => alert('Delete confirmed!')}>
1223
+ Delete Item
1224
+ </MenuItem>
1225
+ </MenuContent>
1226
+ </Menu>
1227
+ </div>
1228
+ </div>
1229
+ </section>
1230
+
1231
+ {/* Section: Context Menu */}
1232
+ <section>
1233
+ <div className="mb-6 pb-2 border-b border-(--border-light)">
1234
+ <h2 className="text-2xl font-bold text-(--text-primary)">Context Menu</h2>
1235
+ <p className="text-(--text-secondary) mt-1">Right-click menus for contextual actions. <strong
1236
+ className="text-(--text-primary)">右键点击</strong>下方区域即可打开菜单。</p>
1237
+ </div>
1238
+
1239
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-10">
1240
+
1241
+ {/* Basic clipboard */}
1242
+ <div className="space-y-3">
1243
+ <h3 className="font-semibold text-(--text-secondary)">Basic</h3>
1244
+ <ContextMenu>
1245
+ <ContextMenuTrigger>
1246
+ <div
1247
+ className="w-full h-28 flex items-center justify-center gap-2 rounded-lg bg-(--bg-secondary) border border-(--border-light) cursor-context-menu select-none hover:bg-(--bg-hover) transition-colors">
1248
+ <Copy size={16} className="text-(--text-muted)"/>
1249
+ <span className="text-(--text-secondary) text-sm font-medium">Right-click here</span>
1250
+ </div>
1251
+ </ContextMenuTrigger>
1252
+ <ContextMenuContent>
1253
+ <ContextMenuItemWithIcon icon={<Copy size={15}/>} shortcut="⌘C">Copy</ContextMenuItemWithIcon>
1254
+ <ContextMenuItemWithIcon icon={<Scissors size={15}/>} shortcut="⌘X">Cut</ContextMenuItemWithIcon>
1255
+ <ContextMenuItemWithIcon icon={<ClipboardPaste size={15}/>}
1256
+ shortcut="⌘V">Paste</ContextMenuItemWithIcon>
1257
+ <ContextMenuSeparator/>
1258
+ <ContextMenuItemWithIcon icon={<RefreshCw size={15}/>}
1259
+ shortcut="⌘R">Refresh</ContextMenuItemWithIcon>
1260
+ </ContextMenuContent>
1261
+ </ContextMenu>
1262
+ </div>
1263
+
1264
+ {/* File list — realistic file items */}
1265
+ <div className="space-y-3">
1266
+ <h3 className="font-semibold text-(--text-secondary)">File List (with Submenu)</h3>
1267
+ <div
1268
+ className="rounded-lg border border-(--border-light) bg-(--bg-primary) overflow-hidden divide-y divide-(--border-light)">
1269
+ {[
1270
+ { name: 'design-system.fig', icon: <File size={15} className="text-purple-500"/>, size: '4.2 MB' },
1271
+ { name: 'meeting-notes.md', icon: <FileText size={15} className="text-blue-500"/>, size: '12 KB' },
1272
+ { name: 'assets', icon: <Folder size={15} className="text-yellow-500"/>, size: '128 MB' },
1273
+ ].map(item => (
1274
+ <ContextMenu key={item.name}>
1275
+ <ContextMenuTrigger>
1276
+ <div
1277
+ className="flex items-center gap-3 px-4 py-2.5 cursor-context-menu select-none hover:bg-(--bg-hover) transition-colors">
1278
+ {item.icon}
1279
+ <span className="flex-1 text-sm text-(--text-primary)">{item.name}</span>
1280
+ <span className="text-xs text-(--text-muted)">{item.size}</span>
1281
+ </div>
1282
+ </ContextMenuTrigger>
1283
+ <ContextMenuContent>
1284
+ <ContextMenuItemWithIcon icon={<FolderOpen size={15}/>}>Open</ContextMenuItemWithIcon>
1285
+ <ContextMenuItemWithIcon icon={<Pencil size={15}/>}>Rename</ContextMenuItemWithIcon>
1286
+ <ContextMenuItemWithIcon icon={<Copy size={15}/>}>Duplicate</ContextMenuItemWithIcon>
1287
+ <ContextMenuSub>
1288
+ <ContextMenuSubTrigger>
1289
+ <Share2 size={15}/>
1290
+ <span>Share</span>
1291
+ </ContextMenuSubTrigger>
1292
+ <ContextMenuSubContent>
1293
+ <ContextMenuItemWithIcon icon={<LinkIcon size={15}/>}>Copy Link</ContextMenuItemWithIcon>
1294
+ <ContextMenuItemWithIcon icon={<ExternalLink size={15}/>}>Open in
1295
+ Browser</ContextMenuItemWithIcon>
1296
+ <ContextMenuSeparator/>
1297
+ <ContextMenuItemWithIcon icon={<Download size={15}/>}>Download</ContextMenuItemWithIcon>
1298
+ </ContextMenuSubContent>
1299
+ </ContextMenuSub>
1300
+ <ContextMenuSeparator/>
1301
+ <ContextMenuItemWithIcon icon={<Trash2 size={15}/>} destructive shortcut="⌫">Move to
1302
+ Trash</ContextMenuItemWithIcon>
1303
+ </ContextMenuContent>
1304
+ </ContextMenu>
1305
+ ))}
1306
+ </div>
1307
+ <p className="text-xs text-(--text-muted)">Right-click any file row</p>
1308
+ </div>
1309
+
1310
+ {/* Image viewer — RadioGroup zoom */}
1311
+ <div className="space-y-3">
1312
+ <h3 className="font-semibold text-(--text-secondary)">Image Viewer (Radio Group)</h3>
1313
+ <ContextMenu>
1314
+ <ContextMenuTrigger>
1315
+ <div
1316
+ className="w-full h-36 flex flex-col items-center justify-center gap-2 rounded-lg bg-gradient-to-br from-violet-50 to-indigo-100 dark:from-violet-950/30 dark:to-indigo-950/30 border border-(--border-light) cursor-context-menu select-none overflow-hidden relative"
1317
+ style={{ fontSize: `${Number(zoom) / 100}em` }}
1318
+ >
1319
+ <Image size={32} className="text-indigo-400"/>
1320
+ <span className="text-xs text-(--text-muted)">photo_{zoom}pct.jpg</span>
1321
+ <span
1322
+ className="absolute top-2 right-3 text-xs font-mono font-medium text-(--text-muted) bg-(--bg-primary)/60 px-1.5 py-0.5 rounded">{zoom}%</span>
1323
+ </div>
1324
+ </ContextMenuTrigger>
1325
+ <ContextMenuContent>
1326
+ <ContextMenuLabel>Zoom Level</ContextMenuLabel>
1327
+ <ContextMenuRadioGroup value={zoom} onValueChange={setZoom}>
1328
+ <ContextMenuRadioItem value="50">50%</ContextMenuRadioItem>
1329
+ <ContextMenuRadioItem value="100">100% (Original)</ContextMenuRadioItem>
1330
+ <ContextMenuRadioItem value="150">150%</ContextMenuRadioItem>
1331
+ <ContextMenuRadioItem value="200">200%</ContextMenuRadioItem>
1332
+ </ContextMenuRadioGroup>
1333
+ <ContextMenuSeparator/>
1334
+ <ContextMenuItemWithIcon icon={<ZoomIn size={15}/>} shortcut="⌘+">Zoom In</ContextMenuItemWithIcon>
1335
+ <ContextMenuItemWithIcon icon={<ZoomOut size={15}/>} shortcut="⌘−">Zoom
1336
+ Out</ContextMenuItemWithIcon>
1337
+ <ContextMenuSeparator/>
1338
+ <ContextMenuItemWithIcon icon={<Download size={15}/>}>Save Image As…</ContextMenuItemWithIcon>
1339
+ </ContextMenuContent>
1340
+ </ContextMenu>
1341
+ <p className="text-xs text-(--text-muted)">Current zoom: <span
1342
+ className="font-medium text-(--text-primary)">{zoom}%</span></p>
1343
+ </div>
1344
+
1345
+ {/* Document — full featured */}
1346
+ <div className="space-y-3">
1347
+ <h3 className="font-semibold text-(--text-secondary)">Document (Full Featured)</h3>
1348
+ <ContextMenu>
1349
+ <ContextMenuTrigger>
1350
+ <div
1351
+ className="w-full h-36 rounded-lg border border-(--border-light) bg-(--bg-primary) cursor-context-menu select-none hover:bg-(--bg-hover) transition-colors overflow-hidden">
1352
+ <div className="px-4 py-2.5 border-b border-(--border-light) flex items-center gap-2">
1353
+ <FileText size={14} className="text-(--text-muted)"/>
1354
+ <span className="text-xs font-medium text-(--text-secondary)">quarterly-report.md</span>
1355
+ {isStarred && <Star size={12} className="text-yellow-500 fill-yellow-400 ml-auto"/>}
1356
+ {isPinned && <span
1357
+ className="text-[10px] bg-blue-100 dark:bg-blue-950/40 text-blue-600 dark:text-blue-400 px-1.5 py-0.5 rounded font-medium ml-auto">Pinned</span>}
1358
+ </div>
1359
+ <div
1360
+ className="px-4 py-3 space-y-1.5"
1361
+ style={{ textAlign: textAlign as 'left' | 'center' | 'right' }}
1362
+ >
1363
+ <div className="h-2.5 rounded bg-(--bg-hover) w-full"/>
1364
+ <div className="h-2.5 rounded bg-(--bg-hover) w-4/5"/>
1365
+ <div className="h-2.5 rounded bg-(--bg-hover) w-3/5"/>
1366
+ </div>
1367
+ </div>
1368
+ </ContextMenuTrigger>
1369
+ <ContextMenuContent>
1370
+ <ContextMenuLabel>Edit</ContextMenuLabel>
1371
+ <ContextMenuItemWithIcon icon={<Copy size={15}/>} shortcut="⌘C">Copy</ContextMenuItemWithIcon>
1372
+ <ContextMenuItemWithIcon icon={<Scissors size={15}/>} shortcut="⌘X">Cut</ContextMenuItemWithIcon>
1373
+ <ContextMenuItemWithIcon icon={<ClipboardPaste size={15}/>}
1374
+ shortcut="⌘V">Paste</ContextMenuItemWithIcon>
1375
+ <ContextMenuSeparator/>
1376
+ <ContextMenuLabel>Text Alignment</ContextMenuLabel>
1377
+ <ContextMenuRadioGroup value={textAlign} onValueChange={setTextAlign}>
1378
+ <ContextMenuRadioItem value="left">
1379
+ <AlignLeft size={14} className="mr-1.5"/>Left
1380
+ </ContextMenuRadioItem>
1381
+ <ContextMenuRadioItem value="center">
1382
+ <AlignCenter size={14} className="mr-1.5"/>Center
1383
+ </ContextMenuRadioItem>
1384
+ <ContextMenuRadioItem value="right">
1385
+ <AlignRight size={14} className="mr-1.5"/>Right
1386
+ </ContextMenuRadioItem>
1387
+ </ContextMenuRadioGroup>
1388
+ <ContextMenuSeparator/>
1389
+ <ContextMenuLabel>Options</ContextMenuLabel>
1390
+ <ContextMenuCheckboxItem checked={showPreview} onCheckedChange={setShowPreview}>
1391
+ Show Preview
1392
+ </ContextMenuCheckboxItem>
1393
+ <ContextMenuCheckboxItem checked={isPinned} onCheckedChange={setIsPinned}>
1394
+ Pin to Top
1395
+ </ContextMenuCheckboxItem>
1396
+ <ContextMenuSeparator/>
1397
+ <ContextMenuItemWithIcon
1398
+ icon={isStarred ? <StarOff size={15}/> : <Star size={15}/>}
1399
+ onSelect={() => setIsStarred(v => !v)}
1400
+ >
1401
+ {isStarred ? 'Remove from Starred' : 'Add to Starred'}
1402
+ </ContextMenuItemWithIcon>
1403
+ <ContextMenuItemWithIcon icon={<Save size={15}/>} shortcut="⌘S">Save</ContextMenuItemWithIcon>
1404
+ <ContextMenuSeparator/>
1405
+ <ContextMenuItemWithIcon icon={<Trash2 size={15}/>} destructive shortcut="⌘⌫">
1406
+ Delete Document
1407
+ </ContextMenuItemWithIcon>
1408
+ </ContextMenuContent>
1409
+ </ContextMenu>
1410
+ <p className="text-xs text-(--text-muted)">
1411
+ Align: <span className="font-medium text-(--text-primary)">{textAlign}</span>
1412
+ {' · '}Preview: <span className="font-medium text-(--text-primary)">{showPreview
1413
+ ? 'On'
1414
+ : 'Off'}</span>
1415
+ {' · '}Pinned: <span className="font-medium text-(--text-primary)">{isPinned ? 'Yes' : 'No'}</span>
1416
+ {' · '}Starred: <span className="font-medium text-(--text-primary)">{isStarred ? '★' : '☆'}</span>
1417
+ </p>
1418
+ </div>
1419
+
1420
+ </div>
1421
+ </section>
1422
+
1423
+ </main>
1424
+
1425
+ {/* ── New Component Sections ─────────────────────────────────────── */}
1426
+
1427
+ {/* Section: Select */}
1428
+ <section className="max-w-5xl mx-auto px-8 pb-16 space-y-6">
1429
+ <div className="pb-2 border-b border-(--border-light)">
1430
+ <h2 className="text-2xl font-bold text-(--text-primary)">Select</h2>
1431
+ <p className="text-(--text-secondary) mt-1">Dropdown selection with keyboard navigation and grouped
1432
+ options.</p>
1433
+ </div>
1434
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-10">
1435
+ <div className="space-y-4">
1436
+ <h3 className="font-semibold text-(--text-secondary)">Basic</h3>
1437
+ <Select
1438
+ label="Channel type"
1439
+ placeholder="Choose a type"
1440
+ options={[
1441
+ { value: 'public', label: 'Public channel' },
1442
+ { value: 'private', label: 'Private channel' },
1443
+ { value: 'dm', label: 'Direct message' },
1444
+ ]}
1445
+ value={selectValue ?? undefined}
1446
+ onValueChange={(v) => setSelectValue(v)}
1447
+ fullWidth
1448
+ />
1449
+ {selectValue && (
1450
+ <p className="text-sm text-(--text-muted)">Selected: <strong>{selectValue}</strong></p>
1451
+ )}
1452
+ </div>
1453
+ <div className="space-y-4">
1454
+ <h3 className="font-semibold text-(--text-secondary)">Grouped options</h3>
1455
+ <Select
1456
+ label="Notification sound"
1457
+ placeholder="Pick a sound"
1458
+ groups={[
1459
+ {
1460
+ label: 'Slack sounds',
1461
+ options: [
1462
+ { value: 'knock', label: 'Knock Brush' },
1463
+ { value: 'ding', label: 'Ding' },
1464
+ ],
1465
+ },
1466
+ {
1467
+ label: 'System sounds',
1468
+ options: [
1469
+ { value: 'none', label: 'None' },
1470
+ { value: 'default', label: 'Default' },
1471
+ ],
1472
+ },
1473
+ ]}
1474
+ fullWidth
1475
+ />
1476
+ </div>
1477
+ <div className="space-y-4">
1478
+ <h3 className="font-semibold text-(--text-secondary)">With error</h3>
1479
+ <Select
1480
+ label="Status"
1481
+ placeholder="Select status"
1482
+ options={[
1483
+ { value: 'active', label: 'Active' },
1484
+ { value: 'away', label: 'Away' },
1485
+ ]}
1486
+ error="Please select a status"
1487
+ fullWidth
1488
+ />
1489
+ </div>
1490
+ <div className="space-y-4">
1491
+ <h3 className="font-semibold text-(--text-secondary)">Disabled</h3>
1492
+ <Select
1493
+ label="Region"
1494
+ placeholder="Disabled"
1495
+ options={[{ value: 'us', label: 'United States' }]}
1496
+ disabled
1497
+ fullWidth
1498
+ />
1499
+ </div>
1500
+ </div>
1501
+ </section>
1502
+
1503
+ {/* Section: Checkbox */}
1504
+ <section className="max-w-5xl mx-auto px-8 pb-16 space-y-6">
1505
+ <div className="pb-2 border-b border-(--border-light)">
1506
+ <h2 className="text-2xl font-bold text-(--text-primary)">Checkbox</h2>
1507
+ <p className="text-(--text-secondary) mt-1">Standalone checkbox with label, description and indeterminate
1508
+ state.</p>
1509
+ </div>
1510
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-10">
1511
+ <div className="space-y-4">
1512
+ <h3 className="font-semibold text-(--text-secondary)">With label</h3>
1513
+ <div className="space-y-3">
1514
+ <Checkbox
1515
+ label="Email notifications"
1516
+ description="Receive digest emails for missed activity"
1517
+ checked={checkboxChecked}
1518
+ onCheckedChange={setCheckboxChecked}
1519
+ />
1520
+ <Checkbox label="Desktop notifications" defaultChecked/>
1521
+ <Checkbox label="Mobile push notifications"/>
1522
+ </div>
1523
+ </div>
1524
+ <div className="space-y-4">
1525
+ <h3 className="font-semibold text-(--text-secondary)">States</h3>
1526
+ <div className="space-y-3">
1527
+ <Checkbox label="Checked" checked/>
1528
+ <Checkbox label="Unchecked"/>
1529
+ <Checkbox label="Indeterminate" indeterminate/>
1530
+ <Checkbox label="Disabled" disabled/>
1531
+ <Checkbox label="Disabled checked" disabled checked/>
1532
+ </div>
1533
+ </div>
1534
+ </div>
1535
+ </section>
1536
+
1537
+ {/* Section: Radio */}
1538
+ <section className="max-w-5xl mx-auto px-8 pb-16 space-y-6">
1539
+ <div className="pb-2 border-b border-(--border-light)">
1540
+ <h2 className="text-2xl font-bold text-(--text-primary)">Radio</h2>
1541
+ <p className="text-(--text-secondary) mt-1">Single-selection radio button group.</p>
1542
+ </div>
1543
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-10">
1544
+ <div className="space-y-4">
1545
+ <h3 className="font-semibold text-(--text-secondary)">Vertical (default)</h3>
1546
+ <RadioGroup
1547
+ label="Preferred contact"
1548
+ value={radioValue}
1549
+ onValueChange={setRadioValue}
1550
+ >
1551
+ <Radio value="email" label="Email" description="Receive messages via email"/>
1552
+ <Radio value="slack" label="Slack" description="Receive messages in Slack"/>
1553
+ <Radio value="sms" label="SMS" description="Receive text messages"/>
1554
+ </RadioGroup>
1555
+ <p className="text-sm text-(--text-muted)">Selected: <strong>{radioValue}</strong></p>
1556
+ </div>
1557
+ <div className="space-y-4">
1558
+ <h3 className="font-semibold text-(--text-secondary)">Horizontal</h3>
1559
+ <RadioGroup label="Theme" defaultValue="light" orientation="horizontal">
1560
+ <Radio value="light" label="Light"/>
1561
+ <Radio value="dark" label="Dark"/>
1562
+ <Radio value="auto" label="Auto"/>
1563
+ </RadioGroup>
1564
+ <div className="space-y-2 mt-4">
1565
+ <h3 className="font-semibold text-(--text-secondary)">With error</h3>
1566
+ <RadioGroup label="Required field" error="Please select an option">
1567
+ <Radio value="a" label="Option A"/>
1568
+ <Radio value="b" label="Option B"/>
1569
+ </RadioGroup>
1570
+ </div>
1571
+ </div>
1572
+ </div>
1573
+ </section>
1574
+
1575
+ {/* Section: Switch */}
1576
+ <section className="max-w-5xl mx-auto px-8 pb-16 space-y-6">
1577
+ <div className="pb-2 border-b border-(--border-light)">
1578
+ <h2 className="text-2xl font-bold text-(--text-primary)">Switch</h2>
1579
+ <p className="text-(--text-secondary) mt-1">Toggle switch for binary on/off settings.</p>
1580
+ </div>
1581
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-10">
1582
+ <div className="space-y-4">
1583
+ <h3 className="font-semibold text-(--text-secondary)">With label</h3>
1584
+ <div className="space-y-4">
1585
+ <Switch
1586
+ label="Enable notifications"
1587
+ description="You'll receive alerts for new messages"
1588
+ checked={notificationsOn}
1589
+ onCheckedChange={setNotificationsOn}
1590
+ />
1591
+ <Switch
1592
+ label="Dark mode"
1593
+ checked={switchOn}
1594
+ onCheckedChange={setSwitchOn}
1595
+ />
1596
+ <Switch label="Disabled switch" disabled/>
1597
+ <Switch label="Disabled on" disabled checked/>
1598
+ </div>
1599
+ </div>
1600
+ <div className="space-y-4">
1601
+ <h3 className="font-semibold text-(--text-secondary)">Sizes</h3>
1602
+ <div className="flex flex-col gap-4">
1603
+ <div className="flex items-center gap-4">
1604
+ <Switch size="sm" defaultChecked/>
1605
+ <span className="text-sm text-(--text-secondary)">Small</span>
1606
+ </div>
1607
+ <div className="flex items-center gap-4">
1608
+ <Switch size="md" defaultChecked/>
1609
+ <span className="text-sm text-(--text-secondary)">Medium (default)</span>
1610
+ </div>
1611
+ <div className="flex items-center gap-4">
1612
+ <Switch size="lg" defaultChecked/>
1613
+ <span className="text-sm text-(--text-secondary)">Large</span>
1614
+ </div>
1615
+ </div>
1616
+ </div>
1617
+ </div>
1618
+ </section>
1619
+
1620
+ {/* Section: Tabs */}
1621
+ <section className="max-w-5xl mx-auto px-8 pb-16 space-y-6">
1622
+ <div className="pb-2 border-b border-(--border-light)">
1623
+ <h2 className="text-2xl font-bold text-(--text-primary)">Tabs</h2>
1624
+ <p className="text-(--text-secondary) mt-1">Animated tab navigation with indicator bar.</p>
1625
+ </div>
1626
+ <div className="grid grid-cols-1 gap-10">
1627
+ <div className="space-y-4">
1628
+ <h3 className="font-semibold text-(--text-secondary)">Horizontal tabs</h3>
1629
+ <Tabs defaultValue="messages">
1630
+ <TabList>
1631
+ <Tab value="messages">Messages</Tab>
1632
+ <Tab value="threads">Threads</Tab>
1633
+ <Tab value="mentions">Mentions & reactions</Tab>
1634
+ <Tab value="drafts" disabled>Drafts (disabled)</Tab>
1635
+ </TabList>
1636
+ <TabPanel value="messages">
1637
+ <div className="space-y-2 text-(--text-primary)">
1638
+ <p className="text-[15px] font-semibold">📬 Messages</p>
1639
+ <p className="text-[14px] text-(--text-secondary)">Your direct messages and channel messages appear
1640
+ here.</p>
1641
+ </div>
1642
+ </TabPanel>
1643
+ <TabPanel value="threads">
1644
+ <div className="space-y-2 text-(--text-primary)">
1645
+ <p className="text-[15px] font-semibold">🧵 Threads</p>
1646
+ <p className="text-[14px] text-(--text-secondary)">Conversations you're participating in are shown
1647
+ here.</p>
1648
+ </div>
1649
+ </TabPanel>
1650
+ <TabPanel value="mentions">
1651
+ <div className="space-y-2 text-(--text-primary)">
1652
+ <p className="text-[15px] font-semibold">🔔 Mentions & reactions</p>
1653
+ <p className="text-[14px] text-(--text-secondary)">See when someone mentions you or reacts to your
1654
+ messages.</p>
1655
+ </div>
1656
+ </TabPanel>
1657
+ </Tabs>
1658
+ </div>
1659
+ </div>
1660
+ </section>
1661
+
1662
+ {/* Section: Progress */}
1663
+ <section className="max-w-5xl mx-auto px-8 pb-16 space-y-6">
1664
+ <div className="pb-2 border-b border-(--border-light)">
1665
+ <h2 className="text-2xl font-bold text-(--text-primary)">Progress</h2>
1666
+ <p className="text-(--text-secondary) mt-1">Progress bar with variants, sizes and indeterminate state.</p>
1667
+ </div>
1668
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-10">
1669
+ <div className="space-y-4">
1670
+ <h3 className="font-semibold text-(--text-secondary)">Variants</h3>
1671
+ <div className="space-y-4">
1672
+ <Progress value={progressValue} label="Uploading file…" showValue/>
1673
+ <Progress value={progressValue} variant="success" label="Storage used" showValue/>
1674
+ <Progress value={progressValue} variant="warning" label="Rate limit" showValue/>
1675
+ <Progress value={progressValue} variant="danger" label="Disk usage" showValue/>
1676
+ </div>
1677
+ <div className="flex gap-2 mt-2">
1678
+ <Button size="sm" onClick={() => setProgressValue(v => Math.max(0, v - 10))}>-10%</Button>
1679
+ <Button size="sm" onClick={() => setProgressValue(v => Math.min(100, v + 10))}>+10%</Button>
1680
+ </div>
1681
+ </div>
1682
+ <div className="space-y-4">
1683
+ <h3 className="font-semibold text-(--text-secondary)">Sizes &amp; indeterminate</h3>
1684
+ <div className="space-y-4">
1685
+ <Progress value={40} size="sm" label="Small"/>
1686
+ <Progress value={60} size="md" label="Medium (default)"/>
1687
+ <Progress value={80} size="lg" label="Large"/>
1688
+ <Progress label="Indeterminate (loading…)"/>
1689
+ </div>
1690
+ </div>
1691
+ </div>
1692
+ </section>
1693
+
1694
+ {/* Section: Loading */}
1695
+ <section className="max-w-5xl mx-auto px-8 pb-16 space-y-6">
1696
+ <div className="pb-2 border-b border-(--border-light)">
1697
+ <h2 className="text-2xl font-bold text-(--text-primary)">Loading</h2>
1698
+ <p className="text-(--text-secondary) mt-1">Spinner、dots 和 bar 三种加载指示器。</p>
1699
+ </div>
1700
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-10">
1701
+ {/* Variants */}
1702
+ <div className="space-y-4">
1703
+ <h3 className="font-semibold text-(--text-secondary)">Variants</h3>
1704
+ <div className="flex flex-wrap gap-6 items-center">
1705
+ <Loading variant="spinner" label="Spinner"/>
1706
+ <Loading variant="dots" label="Dots"/>
1707
+ </div>
1708
+ <div className="pt-2">
1709
+ <Loading variant="bar" label="Bar"/>
1710
+ </div>
1711
+ </div>
1712
+
1713
+ {/* Sizes */}
1714
+ <div className="space-y-4">
1715
+ <h3 className="font-semibold text-(--text-secondary)">Sizes</h3>
1716
+ <div className="flex flex-wrap gap-6 items-center">
1717
+ <Loading variant="spinner" size="sm" label="Small"/>
1718
+ <Loading variant="spinner" size="md" label="Medium"/>
1719
+ <Loading variant="spinner" size="lg" label="Large"/>
1720
+ </div>
1721
+ <div className="flex flex-wrap gap-6 items-center pt-2">
1722
+ <Loading variant="dots" size="sm"/>
1723
+ <Loading variant="dots" size="md"/>
1724
+ <Loading variant="dots" size="lg"/>
1725
+ </div>
1726
+ </div>
1727
+
1728
+ {/* Centered */}
1729
+ <div className="space-y-4">
1730
+ <h3 className="font-semibold text-(--text-secondary)">Centered</h3>
1731
+ <div className="border border-(--border-light) rounded-lg p-6 bg-(--bg-secondary)">
1732
+ <Loading variant="spinner" size="lg" label="Loading data…" centered/>
1733
+ </div>
1734
+ </div>
1735
+
1736
+ {/* Colors */}
1737
+ <div className="space-y-4">
1738
+ <h3 className="font-semibold text-(--text-secondary)">Custom colors</h3>
1739
+ <div className="flex flex-wrap gap-6 items-center">
1740
+ <Loading variant="spinner" color="text-(--slack-green)" label="Green"/>
1741
+ <Loading variant="spinner" color="text-(--danger)" label="Red"/>
1742
+ <Loading variant="dots" color="bg-amber-400" label="Amber"/>
1743
+ </div>
1744
+ </div>
1745
+ </div>
1746
+ </section>
1747
+
1748
+ {/* Section: AutoComplete */}
1749
+ <section className="max-w-5xl mx-auto px-8 pb-16 space-y-6">
1750
+ <div className="pb-2 border-b border-(--border-light)">
1751
+ <h2 className="text-2xl font-bold text-(--text-primary)">AutoComplete</h2>
1752
+ <p className="text-(--text-secondary) mt-1">带下拉过滤的输入框,支持受控模式与键盘导航。</p>
1753
+ </div>
1754
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-10">
1755
+ {/* Basic */}
1756
+ <div className="space-y-4">
1757
+ <h3 className="font-semibold text-(--text-secondary)">基础用法</h3>
1758
+ <AutoComplete
1759
+ label="选择频道"
1760
+ placeholder="搜索频道…"
1761
+ options={[
1762
+ { value: 'general', label: '#general' },
1763
+ { value: 'random', label: '#random' },
1764
+ { value: 'design', label: '#design' },
1765
+ { value: 'engineering', label: '#engineering' },
1766
+ { value: 'marketing', label: '#marketing' },
1767
+ { value: 'product', label: '#product' },
1768
+ ]}
1769
+ value={acValue}
1770
+ onValueChange={setAcValue}
1771
+ fullWidth
1772
+ />
1773
+ {acValue && (
1774
+ <p className="text-[13px] text-(--text-secondary)">
1775
+ 已选:<span className="font-semibold text-(--text-primary)">{acValue}</span>
1776
+ </p>
1777
+ )}
1778
+ </div>
1779
+
1780
+ {/* Required + error */}
1781
+ <div className="space-y-4">
1782
+ <h3 className="font-semibold text-(--text-secondary)">Required & Error</h3>
1783
+ <AutoComplete
1784
+ label="所在城市"
1785
+ placeholder="请选择城市…"
1786
+ required
1787
+ error="请选择一个城市"
1788
+ options={[
1789
+ { value: 'beijing', label: '北京' },
1790
+ { value: 'shanghai', label: '上海' },
1791
+ { value: 'guangzhou', label: '广州' },
1792
+ { value: 'shenzhen', label: '深圳' },
1793
+ { value: 'hangzhou', label: '杭州' },
1794
+ { value: 'chengdu', label: '成都' },
1795
+ ]}
1796
+ fullWidth
1797
+ />
1798
+ </div>
1799
+
1800
+ {/* Disabled options */}
1801
+ <div className="space-y-4">
1802
+ <h3 className="font-semibold text-(--text-secondary)">禁用选项</h3>
1803
+ <AutoComplete
1804
+ label="编程语言"
1805
+ placeholder="选择语言…"
1806
+ options={[
1807
+ { value: 'ts', label: 'TypeScript' },
1808
+ { value: 'js', label: 'JavaScript' },
1809
+ { value: 'py', label: 'Python' },
1810
+ { value: 'rs', label: 'Rust' },
1811
+ { value: 'java', label: 'Java', disabled: true },
1812
+ { value: 'php', label: 'PHP', disabled: true },
1813
+ ]}
1814
+ fullWidth
1815
+ />
1816
+ </div>
1817
+
1818
+ {/* Disabled whole */}
1819
+ <div className="space-y-4">
1820
+ <h3 className="font-semibold text-(--text-secondary)">Disabled</h3>
1821
+ <AutoComplete
1822
+ label="项目"
1823
+ placeholder="已禁用"
1824
+ disabled
1825
+ options={[{ value: 'a', label: 'Project Alpha' }]}
1826
+ defaultValue="a"
1827
+ fullWidth
1828
+ />
1829
+ </div>
1830
+
1831
+ {/* No clear + async hint */}
1832
+ <div className="space-y-4 md:col-span-2">
1833
+ <h3 className="font-semibold text-(--text-secondary)">onInputChange(模拟异步搜索)</h3>
1834
+ <div className="flex items-center gap-4">
1835
+ <AutoComplete
1836
+ placeholder="输入关键词…"
1837
+ clearable={false}
1838
+ options={[
1839
+ { value: 'apple', label: 'Apple' },
1840
+ { value: 'apricot', label: 'Apricot' },
1841
+ { value: 'banana', label: 'Banana' },
1842
+ { value: 'blueberry', label: 'Blueberry' },
1843
+ { value: 'cherry', label: 'Cherry' },
1844
+ { value: 'grape', label: 'Grape' },
1845
+ { value: 'mango', label: 'Mango' },
1846
+ { value: 'orange', label: 'Orange' },
1847
+ { value: 'peach', label: 'Peach' },
1848
+ { value: 'pear', label: 'Pear' },
1849
+ { value: 'pineapple', label: 'Pineapple' },
1850
+ { value: 'strawberry', label: 'Strawberry' },
1851
+ ]}
1852
+ value={acAsync}
1853
+ onValueChange={setAcAsync}
1854
+ onInputChange={(v) => console.log('input:', v)}
1855
+ className="w-64"
1856
+ />
1857
+ {acAsync && (
1858
+ <span className="text-[13px] text-(--text-secondary)">
1859
+ 已选:<span className="font-semibold text-(--text-primary)">{acAsync}</span>
1860
+ </span>
1861
+ )}
1862
+ </div>
1863
+ </div>
1864
+ </div>
1865
+ </section>
1866
+
1867
+ {/* Section: Toast */}
1868
+ <section className="max-w-5xl mx-auto px-8 pb-16 space-y-6">
1869
+ <div className="pb-2 border-b border-(--border-light)">
1870
+ <h2 className="text-2xl font-bold text-(--text-primary)">Toast</h2>
1871
+ <p className="text-(--text-secondary) mt-1">Lightweight notification toasts with five semantic types.</p>
1872
+ </div>
1873
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-10">
1874
+ <div className="space-y-4">
1875
+ <h3 className="font-semibold text-(--text-secondary)">Types</h3>
1876
+ <div className="flex flex-wrap gap-3">
1877
+ <Button
1878
+ size="sm"
1879
+ onClick={() => toast(
1880
+ { title: 'Default notification', description: 'This is a default toast message.' })}
1881
+ >
1882
+ Default
1883
+ </Button>
1884
+ <Button
1885
+ size="sm"
1886
+ onClick={() => toast(
1887
+ { type: 'info', title: 'Info', description: 'Your workspace is being updated.' })}
1888
+ >
1889
+ Info
1890
+ </Button>
1891
+ <Button
1892
+ size="sm"
1893
+ variant="primary"
1894
+ onClick={() => toast({
1895
+ type: 'success',
1896
+ title: 'Message sent!',
1897
+ description: 'Your message was delivered to #general.',
1898
+ })}
1899
+ >
1900
+ Success
1901
+ </Button>
1902
+ <Button
1903
+ size="sm"
1904
+ onClick={() => toast({
1905
+ type: 'warning',
1906
+ title: 'Storage almost full',
1907
+ description: 'You have used 90% of your storage.',
1908
+ })}
1909
+ >
1910
+ Warning
1911
+ </Button>
1912
+ <Button
1913
+ size="sm"
1914
+ variant="danger"
1915
+ onClick={() => toast({
1916
+ type: 'error',
1917
+ title: 'Failed to send',
1918
+ description: 'Could not deliver the message. Try again.',
1919
+ })}
1920
+ >
1921
+ Error
1922
+ </Button>
1923
+ </div>
1924
+ </div>
1925
+ <div className="space-y-4">
1926
+ <h3 className="font-semibold text-(--text-secondary)">With action</h3>
1927
+ <div className="flex flex-wrap gap-3">
1928
+ <Button
1929
+ size="sm"
1930
+ onClick={() =>
1931
+ toast({
1932
+ type: 'info',
1933
+ title: 'New message in #general',
1934
+ description: 'Alice: "Hey everyone, quick update…"',
1935
+ action: {
1936
+ label: 'View',
1937
+ onClick: () => {},
1938
+ },
1939
+ })
1940
+ }
1941
+ >
1942
+ Notification with action
1943
+ </Button>
1944
+ <Button
1945
+ size="sm"
1946
+ onClick={() =>
1947
+ toast({
1948
+ type: 'success',
1949
+ title: 'Invite sent',
1950
+ description: 'bob@example.com will receive an invitation.',
1951
+ timeout: 8000,
1952
+ })
1953
+ }
1954
+ >
1955
+ Custom timeout (8s)
1956
+ </Button>
1957
+ </div>
1958
+ </div>
1959
+ </div>
1960
+ </section>
1961
+
1962
+ </div>
1963
+ )
1964
+ }