@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.
- package/README.md +220 -4
- package/agents/slack-base-ui/SKILL.md +137 -0
- package/agents/slack-base-ui/checklists/style-review.md +56 -0
- package/agents/slack-base-ui/templates/consumer-setup.md +109 -0
- package/agents/slack-base-ui/templates/slack-theme.css +152 -0
- package/libs/Dialog.d.ts +73 -0
- package/libs/Dialog.d.ts.map +1 -1
- package/libs/Popover.d.ts +69 -0
- package/libs/Popover.d.ts.map +1 -1
- package/libs/index.d.ts +4 -4
- package/libs/index.d.ts.map +1 -1
- package/libs/index.js +2885 -2718
- package/package.json +1 -1
- package/src/App.css +7 -0
- package/src/App.tsx +18 -0
- package/src/assets/react.svg +1 -0
- package/src/components/AlertDialog.tsx +185 -0
- package/src/components/AutoComplete.tsx +311 -0
- package/src/components/Avatar.tsx +70 -0
- package/src/components/Badge.tsx +48 -0
- package/src/components/Button.tsx +53 -0
- package/src/components/Checkbox.tsx +109 -0
- package/src/components/ContextMenu.tsx +393 -0
- package/src/components/Dialog.tsx +371 -0
- package/src/components/Form.tsx +409 -0
- package/src/components/IconButton.tsx +49 -0
- package/src/components/Input.tsx +56 -0
- package/src/components/Loading.tsx +123 -0
- package/src/components/Menu.tsx +368 -0
- package/src/components/Popover.tsx +367 -0
- package/src/components/Progress.tsx +89 -0
- package/src/components/Radio.tsx +137 -0
- package/src/components/Select.tsx +177 -0
- package/src/components/Switch.tsx +116 -0
- package/src/components/Tabs.tsx +128 -0
- package/src/components/Toast.tsx +149 -0
- package/src/components/Tooltip.tsx +46 -0
- package/src/components/index.ts +186 -0
- package/src/context/ThemeContext.tsx +53 -0
- package/src/context/useTheme.ts +11 -0
- package/src/examples/slack-clone/SlackApp.tsx +94 -0
- package/src/examples/slack-clone/components/ChannelHeader.tsx +34 -0
- package/src/examples/slack-clone/components/Composer.tsx +42 -0
- package/src/examples/slack-clone/components/Message.tsx +97 -0
- package/src/examples/slack-clone/components/UserProfile.tsx +78 -0
- package/src/examples/slack-clone/layout/Layout.tsx +27 -0
- package/src/examples/slack-clone/layout/Sidebar.tsx +67 -0
- package/src/examples/slack-clone/layout/SidebarItem.tsx +57 -0
- package/src/examples/slack-clone/layout/TopBar.tsx +30 -0
- package/src/index.css +240 -0
- package/src/main.tsx +22 -0
- package/src/pages/ComponentShowcase.tsx +1964 -0
- package/src/pages/Dashboard.tsx +87 -0
- 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<boolean></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 & 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
|
+
}
|