@lmy54321/design-system 1.1.3 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1049 @@
1
+ import React, { useState } from "react";
2
+ import { cn } from "../../components/ui/utils";
3
+ import { Btn } from "../../components/ui/Btn";
4
+ import { Switch } from "../../components/ui/Switch";
5
+ import { Tag } from "../../components/ui/Tag";
6
+ import { Loading } from "../../components/ui/Loading";
7
+ import { SegmentedControl } from "../../components/ui/SegmentedControl";
8
+ import { SearchBox } from "../../components/ui/SearchBox";
9
+ import { Dialog } from "../../components/ui/Dialog";
10
+ import { Toast } from "../../components/ui/Toast";
11
+ import { NotificationBar } from "../../components/ui/NotificationBar";
12
+ import { ActionSheet } from "../../components/ui/ActionSheet";
13
+ import { TopToolbar } from "../../components/ui/TopToolbar";
14
+ import { BottomNavigationBar } from "../../components/ui/BottomNavigationBar";
15
+ import { BottomActionButtons } from "../../components/ui/BottomActions/BottomActionButtons";
16
+ import { DraggablePanel, DRAWER_STATES } from "../../components/ui/DraggablePanel";
17
+ import { TypographyDocs } from "../../components/ui/TypographyDocs";
18
+ import { GridSystemDocs } from "../../components/ui/GridSystemDocs";
19
+ import { BubbleTip } from "../../components/ui/BubbleTip";
20
+ import { Push } from "../../components/ui/Push";
21
+ import { EmptyState } from "../../components/ui/EmptyState";
22
+ import { StatGrid } from "../../components/ui/StatGrid";
23
+ import { IconGallery } from "../../components/ui/IconGallery";
24
+ import { CardDemo } from "../../components/ui/CardDemo";
25
+
26
+ // ============ Color System Data ============
27
+ type ColorToken = {
28
+ variable: string;
29
+ group: string;
30
+ light: string;
31
+ lightLabel: string;
32
+ dark: string;
33
+ darkLabel: string;
34
+ description: string;
35
+ };
36
+
37
+ const TEXT_COLORS: ColorToken[] = [
38
+ { variable: "text_primary_black", group: "Map", light: "rgba(0,0,0,0.9)", lightLabel: "#000000 90%", dark: "rgba(255,255,255,0.9)", darkLabel: "#FFFFFF 90%", description: "默认的一级文本颜色" },
39
+ { variable: "text_secondary_black", group: "Map", light: "rgba(0,0,0,0.7)", lightLabel: "#000000 70%", dark: "rgba(255,255,255,0.7)", darkLabel: "#FFFFFF 70%", description: "第二层级的文本颜色,例如标签文本等" },
40
+ { variable: "text_tertiary_black", group: "Map", light: "rgba(0,0,0,0.42)", lightLabel: "#000000 42%", dark: "rgba(255,255,255,0.42)", darkLabel: "#FFFFFF 42%", description: "第三层级的文本颜色,例如表单的补充说明文本等" },
41
+ { variable: "text_primary_white", group: "Map", light: "#FFFFFF", lightLabel: "#FFFFFF 100%", dark: "#000000", darkLabel: "#000000", description: "默认的一级文本颜色" },
42
+ { variable: "text_secondary_white", group: "Map", light: "rgba(255,255,255,0.7)", lightLabel: "#FFFFFF 70%", dark: "rgba(0,0,0,0.7)", darkLabel: "#000000 70%", description: "第二层级的文本颜色" },
43
+ { variable: "text_tertiary_white", group: "Map", light: "rgba(255,255,255,0.35)", lightLabel: "#FFFFFF 35%", dark: "rgba(0,0,0,0.35)", darkLabel: "#000000 35%", description: "第三层级的文本颜色" },
44
+ { variable: "text_warning", group: "Map", light: "#FF293B", lightLabel: "#FF293B", dark: "#FF293B", darkLabel: "#FF293B", description: "用于警告信息的默认文本颜色" },
45
+ { variable: "text_link", group: "Map", light: "#4C81EE", lightLabel: "#4C81EE", dark: "#4C81EE", darkLabel: "#4C81EE", description: "链接文本的默认颜色" },
46
+ ];
47
+
48
+ const BUTTON_COLORS: ColorToken[] = [
49
+ { variable: "button_primary", group: "Map", light: "#4B526B", lightLabel: "#4B526B", dark: "#DEE0E8", darkLabel: "#DEE0E8", description: "一级按钮颜色" },
50
+ { variable: "button_secondary", group: "Map", light: "rgba(75,82,107,0.08)", lightLabel: "#4B526B 8%", dark: "rgba(204,214,255,0.16)", darkLabel: "#CCD6FF 16%", description: "二级按钮颜色" },
51
+ { variable: "button_warning", group: "Alias", light: "#FFD1D5", lightLabel: "#FFD1D5", dark: "#FF293B", darkLabel: "#FF293B", description: "警示按钮颜色" },
52
+ { variable: "button_blue_solid", group: "Alias", light: "#367BF6", lightLabel: "#367BF6", dark: "#367BF6", darkLabel: "#367BF6", description: "蓝色实心按钮" },
53
+ ];
54
+
55
+ const BG_COLORS: ColorToken[] = [
56
+ { variable: "card_background_primary", group: "Map", light: "#FFFFFF", lightLabel: "#FFFFFF", dark: "#000000", darkLabel: "#000000", description: "卡片主背景" },
57
+ { variable: "card_background_secondary", group: "Map", light: "rgba(255,255,255,0.9)", lightLabel: "#FFFFFF 90%", dark: "rgba(0,0,0,0.9)", darkLabel: "#000000 90%", description: "卡片次级背景" },
58
+ { variable: "container_bg_secondary", group: "Map", light: "rgba(75,82,107,0.04)", lightLabel: "#4B526B 4%", dark: "rgba(224,229,255,0.1)", darkLabel: "#E0E5FF 10%", description: "容器灰色背景" },
59
+ { variable: "overlay_background", group: "Map", light: "rgba(0,0,0,0.6)", lightLabel: "#000000 60%", dark: "rgba(0,0,0,0.6)", darkLabel: "#000000 60%", description: "蒙层颜色" },
60
+ ];
61
+
62
+ // ============ Section Definitions ============
63
+ type SectionId =
64
+ | "color-system" | "typography" | "grid-system" | "iconography"
65
+ | "button" | "search-box" | "switch" | "tag" | "segmented-control" | "loading" | "stat-grid"
66
+ | "card-containers" | "draggable-panel"
67
+ | "top-toolbar" | "navigation-bar" | "bottom-actions"
68
+ | "toast" | "dialog" | "notification-bar" | "action-sheet" | "bubble-tip" | "push" | "empty-state";
69
+
70
+ interface NavSection {
71
+ title: string;
72
+ items: { id: SectionId; label: string; sublabel: string }[];
73
+ }
74
+
75
+ const NAV_SECTIONS: NavSection[] = [
76
+ {
77
+ title: "FOUNDATIONS 基础样式",
78
+ items: [
79
+ { id: "color-system", label: "Color System", sublabel: "颜色系统" },
80
+ { id: "typography", label: "Typography", sublabel: "字体" },
81
+ { id: "grid-system", label: "Grid System", sublabel: "栅格系统" },
82
+ { id: "iconography", label: "Iconography", sublabel: "图标库" },
83
+ ],
84
+ },
85
+ {
86
+ title: "BASIC COMPONENTS 基础组件",
87
+ items: [
88
+ { id: "button", label: "Button", sublabel: "按钮" },
89
+ { id: "search-box", label: "Search Box", sublabel: "搜索框" },
90
+ { id: "switch", label: "Switch", sublabel: "开关" },
91
+ { id: "tag", label: "Tag", sublabel: "标签" },
92
+ { id: "segmented-control", label: "Segmented Control", sublabel: "分段控制" },
93
+ { id: "loading", label: "Loading", sublabel: "加载" },
94
+ { id: "stat-grid", label: "Stat Grid", sublabel: "数据统计" },
95
+ ],
96
+ },
97
+ {
98
+ title: "LAYOUT 布局组件",
99
+ items: [
100
+ { id: "card-containers", label: "Card Containers", sublabel: "卡片容器" },
101
+ { id: "draggable-panel", label: "Draggable Panel", sublabel: "三段式面板" },
102
+ ],
103
+ },
104
+ {
105
+ title: "NAVIGATION 导航组件",
106
+ items: [
107
+ { id: "top-toolbar", label: "Top Toolbar", sublabel: "顶部工具栏" },
108
+ { id: "navigation-bar", label: "Navigation Bar", sublabel: "底部导航栏" },
109
+ { id: "bottom-actions", label: "Bottom Actions", sublabel: "底部操作栏" },
110
+ ],
111
+ },
112
+ {
113
+ title: "FEEDBACK 反馈组件",
114
+ items: [
115
+ { id: "toast", label: "Toast", sublabel: "轻提示" },
116
+ { id: "dialog", label: "Dialog", sublabel: "对话框" },
117
+ { id: "notification-bar", label: "NotificationBar", sublabel: "通知栏" },
118
+ { id: "action-sheet", label: "ActionSheet", sublabel: "操作菜单" },
119
+ { id: "bubble-tip", label: "BubbleTip", sublabel: "气泡提示" },
120
+ { id: "push", label: "Push", sublabel: "推送通知" },
121
+ { id: "empty-state", label: "EmptyState", sublabel: "空状态" },
122
+ ],
123
+ },
124
+ ];
125
+
126
+ // ============ Color Table Component ============
127
+ function ColorTable({ title, data, isDark }: { title: string; data: ColorToken[]; isDark: boolean }) {
128
+ return (
129
+ <div className="w-full space-y-4">
130
+ <h3 className="text-[18px] font-semibold text-foreground">{title}</h3>
131
+ <div className="w-full overflow-x-auto rounded-xl border border-border bg-card">
132
+ <table className="w-full text-left text-sm">
133
+ <thead className="bg-card-muted text-muted-foreground">
134
+ <tr>
135
+ <th className="px-4 py-3 font-medium whitespace-nowrap text-[12px]">Variable</th>
136
+ <th className="px-4 py-3 font-medium whitespace-nowrap text-[12px]">Group</th>
137
+ <th className="px-4 py-3 font-medium whitespace-nowrap text-[12px]">Light Value</th>
138
+ <th className="px-4 py-3 font-medium whitespace-nowrap text-[12px]">Dark Value</th>
139
+ <th className="px-4 py-3 font-medium whitespace-nowrap text-[12px]">Preview</th>
140
+ <th className="px-4 py-3 font-medium text-[12px]">Description</th>
141
+ </tr>
142
+ </thead>
143
+ <tbody className="divide-y divide-border">
144
+ {data.map((token) => (
145
+ <tr key={token.variable} className="hover:bg-card-muted/50 transition-colors">
146
+ <td className="px-4 py-3 font-mono text-[11px] text-muted-foreground whitespace-nowrap">{token.variable}</td>
147
+ <td className="px-4 py-3 font-semibold text-foreground whitespace-nowrap text-[12px]">{token.group}</td>
148
+ <td className="px-4 py-3">
149
+ <div className="flex items-center gap-2">
150
+ <div className="size-7 rounded-lg border border-border shrink-0" style={{ backgroundColor: token.light }} />
151
+ <span className="text-muted-foreground text-[11px] whitespace-nowrap">{token.lightLabel}</span>
152
+ </div>
153
+ </td>
154
+ <td className="px-4 py-3">
155
+ <div className="flex items-center gap-2">
156
+ <div className="size-7 rounded-lg border border-border shrink-0" style={{ backgroundColor: token.dark }} />
157
+ <span className="text-muted-foreground text-[11px] whitespace-nowrap">{token.darkLabel}</span>
158
+ </div>
159
+ </td>
160
+ <td className="px-4 py-3">
161
+ <div className="flex items-center">
162
+ {title.includes("Text") ? (
163
+ <span className="font-medium text-[14px]" style={{ color: isDark ? token.dark : token.light }}>Aa</span>
164
+ ) : (
165
+ <div className="h-7 w-14 rounded-lg border border-border" style={{ backgroundColor: isDark ? token.dark : token.light }} />
166
+ )}
167
+ </div>
168
+ </td>
169
+ <td className="px-4 py-3 text-muted-foreground text-[12px] min-w-[200px]">{token.description}</td>
170
+ </tr>
171
+ ))}
172
+ </tbody>
173
+ </table>
174
+ </div>
175
+ </div>
176
+ );
177
+ }
178
+
179
+ // ============ Phone Frame Component ============
180
+ function PhoneFrame({ children, className }: { children: React.ReactNode; className?: string }) {
181
+ return (
182
+ <div className={cn("relative w-[390px] h-[680px] bg-card-muted rounded-[40px] border-[3px] border-border overflow-hidden shadow-lg", className)}>
183
+ <div className="absolute top-0 left-1/2 -translate-x-1/2 w-[120px] h-[28px] bg-foreground/90 rounded-b-[14px] z-50" />
184
+ <div className="w-full h-full overflow-hidden relative pt-[4px]">
185
+ {children}
186
+ </div>
187
+ </div>
188
+ );
189
+ }
190
+
191
+ // ============ Config Panel Component ============
192
+ function ConfigPanel({ title, subtitle, children }: { title: string; subtitle?: string; children: React.ReactNode }) {
193
+ return (
194
+ <div className="bg-card rounded-[16px] border border-border p-5 space-y-4">
195
+ <div>
196
+ <h3 className="text-[18px] font-semibold text-foreground">{title}</h3>
197
+ {subtitle && <p className="text-[12px] text-muted-foreground mt-1">{subtitle}</p>}
198
+ </div>
199
+ {children}
200
+ </div>
201
+ );
202
+ }
203
+
204
+ function ConfigRow({ label, children }: { label: string; children: React.ReactNode }) {
205
+ return (
206
+ <div className="space-y-2">
207
+ <label className="text-[13px] font-medium text-foreground">{label}</label>
208
+ <div>{children}</div>
209
+ </div>
210
+ );
211
+ }
212
+
213
+ function OptionGroup({ options, value, onChange }: { options: string[]; value: string; onChange: (v: string) => void }) {
214
+ return (
215
+ <div className="flex flex-wrap gap-2">
216
+ {options.map((opt) => (
217
+ <button
218
+ key={opt}
219
+ onClick={() => onChange(opt)}
220
+ className={cn(
221
+ "px-3 py-1.5 rounded-[20px] text-[12px] font-medium transition-all",
222
+ value === opt ? "bg-primary text-primary-foreground" : "bg-secondary text-secondary-foreground hover:bg-secondary/80"
223
+ )}
224
+ >
225
+ {opt}
226
+ </button>
227
+ ))}
228
+ </div>
229
+ );
230
+ }
231
+
232
+ // ============ Section Renderers ============
233
+
234
+ function ColorSystemSection() {
235
+ const [isDark, setIsDark] = useState(false);
236
+ return (
237
+ <div className={cn("flex flex-col gap-6 transition-colors", isDark && "dark")}>
238
+ <div className="flex items-center justify-between">
239
+ <div>
240
+ <h2 className="text-[24px] font-semibold text-foreground">Color System</h2>
241
+ <p className="text-muted-foreground text-[14px] mt-1">Design system color tokens with light/dark mode mappings.</p>
242
+ </div>
243
+ <button
244
+ onClick={() => setIsDark(!isDark)}
245
+ className="flex items-center gap-2 px-4 py-2 rounded-[20px] border border-border text-[13px] font-medium hover:bg-card-muted transition-colors"
246
+ >
247
+ {isDark ? "☀️" : "🌙"} {isDark ? "Light Mode" : "Dark Mode"}
248
+ </button>
249
+ </div>
250
+ <ColorTable title="Text Colors / 文本" data={TEXT_COLORS} isDark={isDark} />
251
+ <ColorTable title="Button Colors / 按钮色彩" data={BUTTON_COLORS} isDark={isDark} />
252
+ <ColorTable title="Background Colors / 背景色彩" data={BG_COLORS} isDark={isDark} />
253
+ </div>
254
+ );
255
+ }
256
+
257
+ function ButtonSection() {
258
+ const [size, setSize] = useState<string>("large");
259
+ const [variant, setVariant] = useState<string>("primary");
260
+ return (
261
+ <div className="flex gap-6 items-start">
262
+ <div className="flex-1 flex items-center justify-center min-h-[400px] bg-card-muted rounded-[24px] p-8">
263
+ <div className="space-y-6">
264
+ <div className="text-[11px] text-muted-foreground uppercase tracking-wider text-center mb-4">Button Preview</div>
265
+ <div className="flex flex-col items-center gap-4">
266
+ <Btn size={size as any} variant={variant as any} label="操作项" />
267
+ <div className="flex gap-3 flex-wrap justify-center">
268
+ {(["primary", "secondary", "outline", "ghost"] as const).map((v) => (
269
+ <Btn key={v} size={size as any} variant={v} label={v} />
270
+ ))}
271
+ </div>
272
+ </div>
273
+ <div className="border-t border-border pt-4 mt-4">
274
+ <div className="text-[11px] text-muted-foreground uppercase tracking-wider text-center mb-3">All Sizes</div>
275
+ <div className="flex flex-col items-center gap-3">
276
+ {(["large", "middle", "small", "xsmall"] as const).map((s) => (
277
+ <Btn key={s} size={s} variant={variant as any} label={`${s} (${s === 'large' ? '48px' : s === 'middle' ? '40px' : s === 'small' ? '32px' : '28px'})`} />
278
+ ))}
279
+ </div>
280
+ </div>
281
+ </div>
282
+ </div>
283
+ <div className="w-[280px] shrink-0 space-y-4">
284
+ <ConfigPanel title="Button" subtitle="按钮组件">
285
+ <ConfigRow label="Size">
286
+ <OptionGroup options={["large", "middle", "small", "xsmall"]} value={size} onChange={setSize} />
287
+ </ConfigRow>
288
+ <ConfigRow label="Variant">
289
+ <OptionGroup options={["primary", "secondary", "outline", "ghost"]} value={variant} onChange={setVariant} />
290
+ </ConfigRow>
291
+ </ConfigPanel>
292
+ </div>
293
+ </div>
294
+ );
295
+ }
296
+
297
+ function SearchBoxSection() {
298
+ const [searchValue, setSearchValue] = useState("");
299
+ const [variant, setVariant] = useState<string>("card");
300
+ const [mode, setMode] = useState<string>("default");
301
+ const [placeholder, setPlaceholder] = useState("底纹词");
302
+ return (
303
+ <div className="flex gap-6 items-start">
304
+ <div className="flex-1">
305
+ <PhoneFrame>
306
+ <div className="w-full h-full bg-card-muted flex flex-col">
307
+ <div className="p-4 pt-[40px] space-y-4">
308
+ <div className="bg-[rgba(54,123,246,0.1)] rounded-[12px] p-3 text-[12px] text-accent space-y-1">
309
+ <p className="font-medium">Interaction Guide:</p>
310
+ <ul className="list-disc pl-4 space-y-0.5">
311
+ <li><strong>Default:</strong> Clear input & blur.</li>
312
+ <li><strong>Inputting:</strong> Click input (Focus). Button becomes Primary. Close icon appears.</li>
313
+ <li><strong>Result:</strong> Type text & blur (or hit Search). Button becomes Cancel (Secondary). Mic icon returns.</li>
314
+ </ul>
315
+ </div>
316
+ <SearchBox
317
+ value={searchValue}
318
+ onChange={setSearchValue}
319
+ variant={variant as any}
320
+ mode={mode as any}
321
+ placeholder={placeholder}
322
+ onBack={() => setSearchValue("")}
323
+ onSearch={() => {}}
324
+ onMic={() => {}}
325
+ onClear={() => setSearchValue("")}
326
+ />
327
+ <div className="space-y-3 mt-6">
328
+ {[1,2,3,4,5].map(i => (
329
+ <div key={i} className="h-[56px] bg-border/30 rounded-[12px]" />
330
+ ))}
331
+ </div>
332
+ </div>
333
+ </div>
334
+ </PhoneFrame>
335
+ </div>
336
+ <div className="w-[280px] shrink-0 space-y-4">
337
+ <ConfigPanel title="Search Box" subtitle="Context & Content">
338
+ <ConfigRow label="Variant / Context">
339
+ <OptionGroup options={["card", "map"]} value={variant} onChange={setVariant} />
340
+ </ConfigRow>
341
+ <ConfigRow label="Scenic Mode (New)">
342
+ <OptionGroup options={["default", "scenic", "scenic-date"]} value={mode} onChange={setMode} />
343
+ </ConfigRow>
344
+ <ConfigRow label="Placeholder Text">
345
+ <input
346
+ type="text"
347
+ value={placeholder}
348
+ onChange={(e) => setPlaceholder(e.target.value)}
349
+ className="w-full h-[36px] px-3 rounded-[8px] bg-input-background border-none text-[13px] outline-none focus:ring-2 focus:ring-accent"
350
+ />
351
+ </ConfigRow>
352
+ <ConfigRow label="Current Value">
353
+ <div className="flex items-center justify-between">
354
+ <input
355
+ type="text"
356
+ value={searchValue}
357
+ onChange={(e) => setSearchValue(e.target.value)}
358
+ className="w-full h-[36px] px-3 rounded-[8px] bg-input-background border-none text-[13px] outline-none"
359
+ />
360
+ <button onClick={() => setSearchValue("")} className="text-[11px] text-muted-foreground ml-2 shrink-0 hover:text-foreground">Clear</button>
361
+ </div>
362
+ </ConfigRow>
363
+ <div className="text-[12px] text-muted-foreground pt-2 border-t border-border">
364
+ <p className="font-medium mb-1">Events</p>
365
+ <p>Check console for interaction logs</p>
366
+ </div>
367
+ </ConfigPanel>
368
+ </div>
369
+ </div>
370
+ );
371
+ }
372
+
373
+ function SwitchSection() {
374
+ const [checked, setChecked] = useState(true);
375
+ const [disabled, setDisabled] = useState(false);
376
+ const [interactiveChecked, setInteractiveChecked] = useState(true);
377
+ return (
378
+ <div className="flex gap-6 items-start">
379
+ <div className="flex-1 bg-card-muted rounded-[24px] p-8 min-h-[400px]">
380
+ <div className="space-y-8">
381
+ <div>
382
+ <div className="text-[11px] text-muted-foreground uppercase tracking-wider mb-4">Segmented Control</div>
383
+ <div className="flex items-center justify-center gap-8">
384
+ <SegmentedControl value="静音" options={["静音", "简洁"]} onChange={() => {}} size="large" />
385
+ </div>
386
+ </div>
387
+ <div className="border-t border-border pt-6">
388
+ <div className="text-[11px] text-muted-foreground uppercase tracking-wider mb-4">Boolean Switch</div>
389
+ <div className="flex items-center justify-between bg-card rounded-[12px] p-4">
390
+ <span className="text-[14px] text-foreground">Interactive Demo</span>
391
+ <Switch checked={interactiveChecked} onCheckedChange={setInteractiveChecked} />
392
+ </div>
393
+ <div className="flex items-center justify-center gap-6 mt-4">
394
+ <div className="flex flex-col items-center gap-2">
395
+ <Switch checked={true} onCheckedChange={() => {}} />
396
+ <span className="text-[11px] text-muted-foreground">Active</span>
397
+ </div>
398
+ <div className="flex flex-col items-center gap-2">
399
+ <Switch checked={false} onCheckedChange={() => {}} />
400
+ <span className="text-[11px] text-muted-foreground">Inactive</span>
401
+ </div>
402
+ <div className="flex flex-col items-center gap-2">
403
+ <Switch checked={false} onCheckedChange={() => {}} disabled />
404
+ <span className="text-[11px] text-muted-foreground">Disabled</span>
405
+ </div>
406
+ </div>
407
+ </div>
408
+ </div>
409
+ </div>
410
+ <div className="w-[280px] shrink-0 space-y-4">
411
+ <ConfigPanel title="Segmented Control" subtitle="Size Variant">
412
+ <ConfigRow label="Size">
413
+ <SegmentedControlSizeDemo />
414
+ </ConfigRow>
415
+ </ConfigPanel>
416
+ <ConfigPanel title="Boolean Switch" subtitle="Interactive State">
417
+ <div className="flex items-center justify-between">
418
+ <span className="text-[13px] text-foreground">Disabled State</span>
419
+ <Switch checked={disabled} onCheckedChange={setDisabled} />
420
+ </div>
421
+ </ConfigPanel>
422
+ </div>
423
+ </div>
424
+ );
425
+ }
426
+
427
+ function SegmentedControlSizeDemo() {
428
+ const [size, setSize] = useState<string>("large");
429
+ const sizeMap: Record<string, string> = { large: "Large (48px)", middle: "Middle (40px)", small: "Small (32px)" };
430
+ return (
431
+ <OptionGroup options={["large", "middle", "small"]} value={size} onChange={setSize} />
432
+ );
433
+ }
434
+
435
+ function TagSection() {
436
+ const [variant, setVariant] = useState<string>("filled");
437
+ const [size, setSize] = useState<string>("lg");
438
+ const [showArrow, setShowArrow] = useState(false);
439
+ return (
440
+ <div className="flex gap-6 items-start">
441
+ <div className="flex-1 flex items-center justify-center min-h-[300px] bg-card-muted rounded-[24px] p-8">
442
+ <div className="space-y-6 text-center">
443
+ <Tag label="标签文字" variant={variant as any} size={size as any} showArrow={showArrow} />
444
+ <div className="border-t border-border pt-4 flex flex-wrap gap-2 justify-center">
445
+ {(["lg", "md", "sm", "xs"] as const).map((s) => (
446
+ <Tag key={s} label={`Size ${s}`} variant={variant as any} size={s} showArrow={showArrow} />
447
+ ))}
448
+ </div>
449
+ </div>
450
+ </div>
451
+ <div className="w-[280px] shrink-0">
452
+ <ConfigPanel title="Tag" subtitle="标签组件">
453
+ <ConfigRow label="Variant">
454
+ <OptionGroup options={["filled", "outlined"]} value={variant} onChange={setVariant} />
455
+ </ConfigRow>
456
+ <ConfigRow label="Size">
457
+ <OptionGroup options={["lg", "md", "sm", "xs"]} value={size} onChange={setSize} />
458
+ </ConfigRow>
459
+ <ConfigRow label="Show Arrow">
460
+ <Switch checked={showArrow} onCheckedChange={setShowArrow} />
461
+ </ConfigRow>
462
+ </ConfigPanel>
463
+ </div>
464
+ </div>
465
+ );
466
+ }
467
+
468
+ function CardContainersSection() {
469
+ return (
470
+ <div className="flex gap-6 items-start">
471
+ <div className="flex-1">
472
+ <div className="max-w-[390px] mx-auto space-y-4">
473
+ <h3 className="mb-4 text-foreground">卡片容器示例</h3>
474
+ <div className="bg-card border-[0.5px] border-card-border rounded-[16px] p-4 space-y-3" style={{ boxShadow: "var(--elevation-sm)" }}>
475
+ <div className="flex items-center justify-between">
476
+ <div className="flex items-center gap-2">
477
+ <span className="text-[16px]">📍</span>
478
+ <h4 className="text-foreground">当前位置</h4>
479
+ </div>
480
+ <span className="text-xs text-accent">强调卡</span>
481
+ </div>
482
+ <div className="space-y-2">
483
+ <div className="text-foreground text-[14px]">北京市东城区东长安街</div>
484
+ <div className="text-sm text-muted-foreground">天安门广场附近 · 距离您 80米</div>
485
+ </div>
486
+ <div className="grid grid-cols-3 gap-3 pt-2 border-t border-border">
487
+ {["导航", "分享", "收藏"].map((action) => (
488
+ <button key={action} className="flex flex-col items-center gap-1 py-2 hover:bg-muted/50 rounded-lg transition-colors">
489
+ <span className="text-[16px]">{action === "导航" ? "🧭" : action === "分享" ? "📤" : "⭐"}</span>
490
+ <span className="text-xs text-foreground">{action}</span>
491
+ </button>
492
+ ))}
493
+ </div>
494
+ </div>
495
+
496
+ <div className="bg-card-muted rounded-[16px] p-4 space-y-3">
497
+ <div className="flex items-center justify-between">
498
+ <div className="flex items-center gap-2">
499
+ <span className="text-[16px]">📍</span>
500
+ <h4 className="text-foreground">附近地点</h4>
501
+ </div>
502
+ <span className="text-xs text-muted-foreground">普通卡</span>
503
+ </div>
504
+ <div className="space-y-2">
505
+ {[
506
+ { name: "故宫博物院", distance: "1.2km", type: "景点" },
507
+ { name: "王府井大街", distance: "850m", type: "商圈" },
508
+ { name: "国家博物馆", distance: "450m", type: "博物馆" },
509
+ ].map((poi, i) => (
510
+ <div key={i} className="flex items-center gap-3 py-2">
511
+ <span className="text-muted-foreground">📍</span>
512
+ <div className="flex-1 min-w-0">
513
+ <div className="text-sm text-foreground truncate">{poi.name}</div>
514
+ <div className="text-xs text-muted-foreground">{poi.type}</div>
515
+ </div>
516
+ <div className="text-xs text-muted-foreground shrink-0">{poi.distance}</div>
517
+ </div>
518
+ ))}
519
+ </div>
520
+ </div>
521
+
522
+ <div className="bg-card-muted rounded-[16px] p-4 space-y-3">
523
+ <div className="flex items-center justify-between">
524
+ <h4 className="text-foreground">搜索历史</h4>
525
+ <span className="text-xs text-muted-foreground">普通卡</span>
526
+ </div>
527
+ <div className="space-y-2">
528
+ {[
529
+ { keyword: "北京南站", time: "今天 14:30" },
530
+ { keyword: "三里屯", time: "昨天" },
531
+ { keyword: "首都机场", time: "2天前" },
532
+ ].map((item, i) => (
533
+ <div key={i} className="flex items-center justify-between py-2">
534
+ <div className="text-sm text-foreground">{item.keyword}</div>
535
+ <div className="text-xs text-muted-foreground">{item.time}</div>
536
+ </div>
537
+ ))}
538
+ </div>
539
+ </div>
540
+ </div>
541
+ </div>
542
+ <div className="w-[280px] shrink-0 space-y-4">
543
+ <ConfigPanel title="📝 使用规则">
544
+ <div className="space-y-3 text-[13px]">
545
+ <div className="flex items-start gap-2"><span>✅</span><div><div className="text-foreground font-medium">强调卡(白色)</div><div className="text-muted-foreground">一屏仅使用一次,用于突出最重要的模块</div></div></div>
546
+ <div className="flex items-start gap-2"><span>✅</span><div><div className="text-foreground font-medium">普通卡(灰色)</div><div className="text-muted-foreground">无使用限制,适用于大多数模块内容</div></div></div>
547
+ <div className="flex items-start gap-2"><span>❌</span><div><div className="text-foreground font-medium">禁止</div><div className="text-muted-foreground">在同一视口内使用多个强调卡</div></div></div>
548
+ <div className="flex items-start gap-2"><span>💡</span><div><div className="text-foreground font-medium">推荐</div><div className="text-muted-foreground">1个强调卡 + 多个普通卡的混合使用</div></div></div>
549
+ </div>
550
+ </ConfigPanel>
551
+ <ConfigPanel title="💻 代码示例">
552
+ <div className="space-y-3">
553
+ <div>
554
+ <div className="text-[11px] text-muted-foreground mb-1">强调卡(白色)</div>
555
+ <pre className="bg-card-muted rounded-[8px] p-3 text-[11px] text-foreground overflow-x-auto whitespace-pre-wrap">{`<div className=
556
+ "bg-card
557
+ border-[0.5px]
558
+ border-card-border
559
+ rounded-[16px]
560
+ p-4"
561
+ style={{
562
+ boxShadow:
563
+ "var(--elevation-sm)"
564
+ }}
565
+ >`}</pre>
566
+ </div>
567
+ <div>
568
+ <div className="text-[11px] text-muted-foreground mb-1">普通卡(灰色)</div>
569
+ <pre className="bg-card-muted rounded-[8px] p-3 text-[11px] text-foreground overflow-x-auto whitespace-pre-wrap">{`<div className=
570
+ "bg-card-muted
571
+ rounded-[16px]
572
+ p-4"
573
+ >`}</pre>
574
+ </div>
575
+ </div>
576
+ </ConfigPanel>
577
+ </div>
578
+ </div>
579
+ );
580
+ }
581
+
582
+ function DraggablePanelSection() {
583
+ const [panelState, setPanelState] = useState(DRAWER_STATES.MEDIUM);
584
+ const [showTopToolbar, setShowTopToolbar] = useState(true);
585
+ const [toolbarMode, setToolbarMode] = useState<string>("left-title");
586
+ const [showBottomToolbar, setShowBottomToolbar] = useState(true);
587
+ const [leftContent, setLeftContent] = useState<string>("search");
588
+ const [rightMode, setRightMode] = useState<string>("dual");
589
+ const stateLabels = ["Small", "Medium", "Large", "Full"];
590
+ return (
591
+ <div className="flex gap-6 items-start">
592
+ <div className="flex-1 flex justify-center">
593
+ <PhoneFrame>
594
+ <div className="w-full h-full relative bg-gradient-to-b from-[#e8e8e8] to-[#d0d0d0]">
595
+ {showTopToolbar && panelState !== DRAWER_STATES.FULL && (
596
+ <div className="absolute top-[63px] left-0 w-full z-[1000]">
597
+ <TopToolbar
598
+ mode={toolbarMode as any}
599
+ appearance={panelState === DRAWER_STATES.LARGE ? 'gray' : 'glass'}
600
+ title="地点详情"
601
+ />
602
+ </div>
603
+ )}
604
+ <DraggablePanel
605
+ state={panelState}
606
+ onStateChange={setPanelState}
607
+ topToolbar={showTopToolbar ? { mode: toolbarMode as any, title: "地点详情" } : undefined}
608
+ showTopToolbarInStates={[DRAWER_STATES.SMALL, DRAWER_STATES.MEDIUM, DRAWER_STATES.LARGE, DRAWER_STATES.FULL]}
609
+ bottomBar={showBottomToolbar ? (
610
+ <BottomActionButtons
611
+ mode={rightMode as any === "single" ? "single" : "split"}
612
+ leftContent={leftContent as any}
613
+ mainText="开始"
614
+ secondaryText="路线"
615
+ />
616
+ ) : undefined}
617
+ fullScreenContent={
618
+ <div className="p-4 space-y-3">
619
+ <h3 className="text-foreground">全屏内容</h3>
620
+ {[1,2,3,4,5,6].map(i => (
621
+ <div key={i} className="h-[80px] bg-card-muted rounded-[12px]" />
622
+ ))}
623
+ </div>
624
+ }
625
+ fullScreenTrigger={(onTrigger) => (
626
+ <div className="px-4">
627
+ <Btn variant="primary" size="large" label="进入全屏模式" onClick={onTrigger} className="w-full" />
628
+ </div>
629
+ )}
630
+ >
631
+ <div className="px-4 space-y-3 pb-4">
632
+ {[1,2,3,4].map(i => (
633
+ <div key={i} className="h-[60px] bg-border/20 rounded-[12px]" />
634
+ ))}
635
+ </div>
636
+ </DraggablePanel>
637
+ </div>
638
+ </PhoneFrame>
639
+ </div>
640
+ <div className="w-[280px] shrink-0 space-y-4">
641
+ <ConfigPanel title="Draggable Panel" subtitle="三段式面板">
642
+ <ConfigRow label="Panel State">
643
+ <OptionGroup options={stateLabels} value={stateLabels[panelState]} onChange={(v) => setPanelState(stateLabels.indexOf(v))} />
644
+ </ConfigRow>
645
+ <ConfigRow label="Show Top Toolbar">
646
+ <Switch checked={showTopToolbar} onCheckedChange={setShowTopToolbar} />
647
+ </ConfigRow>
648
+ <ConfigRow label="Toolbar Mode">
649
+ <OptionGroup options={["left-title", "center-title", "tabs"]} value={toolbarMode} onChange={setToolbarMode} />
650
+ </ConfigRow>
651
+ <ConfigRow label="Show Bottom Toolbar">
652
+ <Switch checked={showBottomToolbar} onCheckedChange={setShowBottomToolbar} />
653
+ </ConfigRow>
654
+ <ConfigRow label="Left Content">
655
+ <OptionGroup options={["search", "capsule", "capsule-more", "none"]} value={leftContent} onChange={setLeftContent} />
656
+ </ConfigRow>
657
+ <ConfigRow label="Right Action">
658
+ <OptionGroup options={["dual", "single"]} value={rightMode} onChange={setRightMode} />
659
+ </ConfigRow>
660
+ </ConfigPanel>
661
+ <div className="bg-card-muted rounded-[12px] p-4 text-[12px] text-muted-foreground space-y-1">
662
+ <p className="font-medium text-foreground mb-2">特性说明</p>
663
+ <p>· 三段式页卡:Small、Medium、Large 三种状态</p>
664
+ <p>· 全屏模式:Full 状态占据整个屏幕</p>
665
+ <p>· Top Toolbar:可选配置顶部工具栏</p>
666
+ <p>· Bottom Toolbar:底部操作栏,支持搜索、胶囊等多种形态</p>
667
+ <p>· 拖拽交互:支持向上/向下拖动切换状态</p>
668
+ <p>· 背景模糊:不同状态有不同的背景效果</p>
669
+ </div>
670
+ </div>
671
+ </div>
672
+ );
673
+ }
674
+
675
+ function TopToolbarSection() {
676
+ const [mode, setMode] = useState<string>("left-title");
677
+ const [appearance, setAppearance] = useState<string>("glass");
678
+ const [actionCount, setActionCount] = useState(2);
679
+ return (
680
+ <div className="flex gap-6 items-start">
681
+ <div className="flex-1 flex items-center justify-center min-h-[300px] bg-card-muted rounded-[24px] p-8">
682
+ <div className="w-[390px] bg-gradient-to-b from-[#e0e0e0] to-[#d0d0d0] rounded-[16px] overflow-hidden p-4">
683
+ <TopToolbar mode={mode as any} appearance={appearance as any} title="页面主标题" rightActionCount={actionCount} />
684
+ </div>
685
+ </div>
686
+ <div className="w-[280px] shrink-0">
687
+ <ConfigPanel title="Top Toolbar" subtitle="顶部工具栏">
688
+ <ConfigRow label="Mode">
689
+ <OptionGroup options={["left-title", "center-title", "tabs"]} value={mode} onChange={setMode} />
690
+ </ConfigRow>
691
+ <ConfigRow label="Appearance">
692
+ <OptionGroup options={["glass", "gray"]} value={appearance} onChange={setAppearance} />
693
+ </ConfigRow>
694
+ <ConfigRow label="Action Count">
695
+ <OptionGroup options={["0", "1", "2"]} value={String(actionCount)} onChange={(v) => setActionCount(Number(v))} />
696
+ </ConfigRow>
697
+ </ConfigPanel>
698
+ </div>
699
+ </div>
700
+ );
701
+ }
702
+
703
+ function NavigationBarSection() {
704
+ const [activeTab, setActiveTab] = useState<string>("home");
705
+ return (
706
+ <div className="flex gap-6 items-start">
707
+ <div className="flex-1 flex items-center justify-center min-h-[300px] bg-card-muted rounded-[24px] p-8">
708
+ <BottomNavigationBar activeTab={activeTab as any} onTabChange={(t) => setActiveTab(t)} />
709
+ </div>
710
+ <div className="w-[280px] shrink-0">
711
+ <ConfigPanel title="Navigation Bar" subtitle="底部导航栏">
712
+ <ConfigRow label="Active Tab">
713
+ <OptionGroup options={["home", "explore", "plan", "me"]} value={activeTab} onChange={setActiveTab} />
714
+ </ConfigRow>
715
+ </ConfigPanel>
716
+ </div>
717
+ </div>
718
+ );
719
+ }
720
+
721
+ function BottomActionsSection() {
722
+ const [mode, setMode] = useState<string>("split");
723
+ const [leftContent, setLeftContent] = useState<string>("none");
724
+ return (
725
+ <div className="flex gap-6 items-start">
726
+ <div className="flex-1 flex items-center justify-center min-h-[300px] bg-card-muted rounded-[24px] p-8">
727
+ <div className="w-[390px]">
728
+ <BottomActionButtons mode={mode as any} leftContent={leftContent as any} mainText="开始" secondaryText="路线" />
729
+ </div>
730
+ </div>
731
+ <div className="w-[280px] shrink-0">
732
+ <ConfigPanel title="Bottom Actions" subtitle="底部操作按钮">
733
+ <ConfigRow label="Mode">
734
+ <OptionGroup options={["single", "split", "weighted"]} value={mode} onChange={setMode} />
735
+ </ConfigRow>
736
+ <ConfigRow label="Left Content">
737
+ <OptionGroup options={["none", "search", "capsule", "capsule-more"]} value={leftContent} onChange={setLeftContent} />
738
+ </ConfigRow>
739
+ </ConfigPanel>
740
+ </div>
741
+ </div>
742
+ );
743
+ }
744
+
745
+ function ToastSection() {
746
+ const [showIcon, setShowIcon] = useState(true);
747
+ const [showClose, setShowClose] = useState(true);
748
+ const [variant, setVariant] = useState<string>("default");
749
+ return (
750
+ <div className="flex gap-6 items-start">
751
+ <div className="flex-1 flex flex-col items-center justify-center min-h-[300px] bg-card-muted rounded-[24px] p-8 gap-4">
752
+ <Toast lines={["操作成功"]} showIcon={showIcon} showClose={showClose} variant={variant as any} actionText={variant === 'action' ? "查看详情" : undefined} />
753
+ <Toast lines={["第一行文本", "第二行文本"]} showIcon={showIcon} showClose={showClose} variant={variant as any} actionText={variant === 'action' ? "查看" : undefined} />
754
+ <Toast lines={["纯文本提示"]} showIcon={false} showClose={false} variant="default" />
755
+ </div>
756
+ <div className="w-[280px] shrink-0">
757
+ <ConfigPanel title="Toast" subtitle="轻提示">
758
+ <ConfigRow label="Variant">
759
+ <OptionGroup options={["default", "action"]} value={variant} onChange={setVariant} />
760
+ </ConfigRow>
761
+ <ConfigRow label="Show Icon">
762
+ <Switch checked={showIcon} onCheckedChange={setShowIcon} />
763
+ </ConfigRow>
764
+ <ConfigRow label="Show Close">
765
+ <Switch checked={showClose} onCheckedChange={setShowClose} />
766
+ </ConfigRow>
767
+ </ConfigPanel>
768
+ </div>
769
+ </div>
770
+ );
771
+ }
772
+
773
+ function DialogSection() {
774
+ const [variant, setVariant] = useState<string>("default");
775
+ const [open, setOpen] = useState(true);
776
+ return (
777
+ <div className="flex gap-6 items-start">
778
+ <div className="flex-1 flex items-center justify-center min-h-[400px] bg-card-muted rounded-[24px] p-8 relative">
779
+ <Dialog
780
+ open={open}
781
+ onOpenChange={setOpen}
782
+ variant={variant as any}
783
+ demoMode={true}
784
+ title="标题最好是简短的"
785
+ description="请求授权确认文案要简短,可以居中"
786
+ mainActionText="主操作"
787
+ secondaryActionText="副操作"
788
+ />
789
+ {!open && (
790
+ <Btn variant="primary" label="Show Dialog" onClick={() => setOpen(true)} />
791
+ )}
792
+ </div>
793
+ <div className="w-[280px] shrink-0">
794
+ <ConfigPanel title="Dialog" subtitle="对话框">
795
+ <ConfigRow label="Variant">
796
+ <OptionGroup options={["default", "warning", "image"]} value={variant} onChange={setVariant} />
797
+ </ConfigRow>
798
+ </ConfigPanel>
799
+ </div>
800
+ </div>
801
+ );
802
+ }
803
+
804
+ function NotificationBarSection() {
805
+ const [variant, setVariant] = useState<string>("info");
806
+ return (
807
+ <div className="flex gap-6 items-start">
808
+ <div className="flex-1 flex flex-col items-center justify-center min-h-[300px] bg-card-muted rounded-[24px] p-8 gap-4">
809
+ <div className="w-[360px] space-y-3">
810
+ <NotificationBar variant={variant as any} message="这是一条通知消息" onClose={() => {}} />
811
+ <NotificationBar variant="success" message="操作成功完成" onClose={() => {}} actions={[{ label: "查看" }]} />
812
+ <NotificationBar variant="warning" message="请注意安全信息" onClose={() => {}} />
813
+ <NotificationBar variant="attention" message="有新消息需要处理" onClose={() => {}} actions={[{ label: "处理" }]} />
814
+ </div>
815
+ </div>
816
+ <div className="w-[280px] shrink-0">
817
+ <ConfigPanel title="NotificationBar" subtitle="通知栏">
818
+ <ConfigRow label="Variant">
819
+ <OptionGroup options={["info", "success", "warning", "attention"]} value={variant} onChange={setVariant} />
820
+ </ConfigRow>
821
+ </ConfigPanel>
822
+ </div>
823
+ </div>
824
+ );
825
+ }
826
+
827
+ function ActionSheetSection() {
828
+ const [variant, setVariant] = useState<string>("default");
829
+ return (
830
+ <div className="flex gap-6 items-start">
831
+ <div className="flex-1 flex items-center justify-center min-h-[400px] bg-card-muted rounded-[24px] p-8">
832
+ <div className="flex gap-6 flex-wrap justify-center">
833
+ {(["default", "edit", "title", "selectable"] as const).map((v) => (
834
+ <div key={v} className="flex flex-col items-center gap-2">
835
+ <div className="relative h-[300px] w-[170px]">
836
+ <ActionSheet isOpen={true} variant={v} className="!relative !static" />
837
+ </div>
838
+ <span className="text-[11px] text-muted-foreground capitalize">{v}</span>
839
+ </div>
840
+ ))}
841
+ </div>
842
+ </div>
843
+ <div className="w-[280px] shrink-0">
844
+ <ConfigPanel title="ActionSheet" subtitle="操作菜单">
845
+ <ConfigRow label="Variant">
846
+ <OptionGroup options={["default", "edit", "title", "selectable"]} value={variant} onChange={setVariant} />
847
+ </ConfigRow>
848
+ </ConfigPanel>
849
+ </div>
850
+ </div>
851
+ );
852
+ }
853
+
854
+ function LoadingSection() {
855
+ return (
856
+ <div className="flex items-center justify-center min-h-[200px] bg-card-muted rounded-[24px] p-8">
857
+ <div className="flex flex-col items-center gap-4">
858
+ <Loading showClose={true} />
859
+ <Loading showClose={false} />
860
+ </div>
861
+ </div>
862
+ );
863
+ }
864
+
865
+ function StatGridSection() {
866
+ return (
867
+ <div className="flex items-center justify-center min-h-[200px] bg-card-muted rounded-[24px] p-8">
868
+ <div className="w-[360px]">
869
+ <StatGrid items={[
870
+ { value: "1,248", label: "今日访问", variant: "primary" },
871
+ { value: "356", label: "新增用户", variant: "accent" },
872
+ { value: "96.8%", label: "转化率", variant: "default" },
873
+ ]} />
874
+ </div>
875
+ </div>
876
+ );
877
+ }
878
+
879
+ function BubbleTipSection() {
880
+ const [placement, setPlacement] = useState<string>("top");
881
+ return (
882
+ <div className="flex gap-6 items-start">
883
+ <div className="flex-1 flex items-center justify-center min-h-[300px] bg-card-muted rounded-[24px] p-8">
884
+ <div className="flex flex-col items-center gap-6">
885
+ <BubbleTip message="气泡提示文本" placement={placement as any} />
886
+ <div className="flex gap-4 flex-wrap justify-center">
887
+ {(["top", "bottom", "left", "right"] as const).map((p) => (
888
+ <BubbleTip key={p} message={p} placement={p} closable={false} showIcon={false} />
889
+ ))}
890
+ </div>
891
+ </div>
892
+ </div>
893
+ <div className="w-[280px] shrink-0">
894
+ <ConfigPanel title="BubbleTip" subtitle="气泡提示">
895
+ <ConfigRow label="Placement">
896
+ <OptionGroup options={["top", "bottom", "left", "right"]} value={placement} onChange={setPlacement} />
897
+ </ConfigRow>
898
+ </ConfigPanel>
899
+ </div>
900
+ </div>
901
+ );
902
+ }
903
+
904
+ function PushSection() {
905
+ return (
906
+ <div className="flex items-center justify-center min-h-[200px] bg-card-muted rounded-[24px] p-8">
907
+ <div className="w-[360px] space-y-3">
908
+ <Push title="新消息通知" description="您有一条新的推送消息,请点击查看详情" />
909
+ <Push title="地图应用" description="附近有新的优惠活动" actionText="查看" />
910
+ </div>
911
+ </div>
912
+ );
913
+ }
914
+
915
+ function EmptyStateSection() {
916
+ const [variant, setVariant] = useState<string>("generic");
917
+ return (
918
+ <div className="flex gap-6 items-start">
919
+ <div className="flex-1 flex items-center justify-center min-h-[400px] bg-card-muted rounded-[24px] p-8">
920
+ <EmptyState variant={variant as any} />
921
+ </div>
922
+ <div className="w-[280px] shrink-0">
923
+ <ConfigPanel title="EmptyState" subtitle="空状态">
924
+ <ConfigRow label="Variant">
925
+ <OptionGroup options={["generic", "not-logged-in", "network-error", "no-plan", "no-comment", "location-related"]} value={variant} onChange={setVariant} />
926
+ </ConfigRow>
927
+ </ConfigPanel>
928
+ </div>
929
+ </div>
930
+ );
931
+ }
932
+
933
+ function SegmentedControlSection() {
934
+ const [size, setSize] = useState<string>("large");
935
+ const [value, setValue] = useState("静音");
936
+ return (
937
+ <div className="flex gap-6 items-start">
938
+ <div className="flex-1 flex items-center justify-center min-h-[200px] bg-card-muted rounded-[24px] p-8">
939
+ <div className="space-y-4 flex flex-col items-center">
940
+ <SegmentedControl value={value} options={["静音", "简洁"]} onChange={setValue} size={size as any} />
941
+ <div className="flex gap-4">
942
+ {(["large", "middle", "small"] as const).map((s) => (
943
+ <SegmentedControl key={s} value="Tab1" options={["Tab1", "Tab2"]} onChange={() => {}} size={s} />
944
+ ))}
945
+ </div>
946
+ </div>
947
+ </div>
948
+ <div className="w-[280px] shrink-0">
949
+ <ConfigPanel title="Segmented Control" subtitle="分段控制器">
950
+ <ConfigRow label="Size">
951
+ <OptionGroup options={["large", "middle", "small"]} value={size} onChange={setSize} />
952
+ </ConfigRow>
953
+ </ConfigPanel>
954
+ </div>
955
+ </div>
956
+ );
957
+ }
958
+
959
+ // ============ Main Component ============
960
+ export function ComponentLibrary() {
961
+ const [activeSection, setActiveSection] = useState<SectionId>("color-system");
962
+ const [isDark, setIsDark] = useState(false);
963
+
964
+ const renderContent = () => {
965
+ switch (activeSection) {
966
+ case "color-system": return <ColorSystemSection />;
967
+ case "typography": return <TypographyDocs />;
968
+ case "grid-system": return <GridSystemDocs />;
969
+ case "iconography": return <IconGallery />;
970
+ case "button": return <ButtonSection />;
971
+ case "search-box": return <SearchBoxSection />;
972
+ case "switch": return <SwitchSection />;
973
+ case "tag": return <TagSection />;
974
+ case "segmented-control": return <SegmentedControlSection />;
975
+ case "loading": return <LoadingSection />;
976
+ case "stat-grid": return <StatGridSection />;
977
+ case "card-containers": return <CardContainersSection />;
978
+ case "draggable-panel": return <DraggablePanelSection />;
979
+ case "top-toolbar": return <TopToolbarSection />;
980
+ case "navigation-bar": return <NavigationBarSection />;
981
+ case "bottom-actions": return <BottomActionsSection />;
982
+ case "toast": return <ToastSection />;
983
+ case "dialog": return <DialogSection />;
984
+ case "notification-bar": return <NotificationBarSection />;
985
+ case "action-sheet": return <ActionSheetSection />;
986
+ case "bubble-tip": return <BubbleTipSection />;
987
+ case "push": return <PushSection />;
988
+ case "empty-state": return <EmptyStateSection />;
989
+ default: return <div className="text-muted-foreground">Select a component</div>;
990
+ }
991
+ };
992
+
993
+ return (
994
+ <div className={cn("flex flex-col h-screen bg-background overflow-hidden", isDark && "dark")}>
995
+ {/* Header */}
996
+ <header className="shrink-0 border-b border-border px-8 py-5 flex items-center justify-between bg-card">
997
+ <div>
998
+ <h1 className="text-[24px] font-semibold text-foreground">Design Component Library</h1>
999
+ <p className="text-[13px] text-muted-foreground mt-0.5">Interactive playground for system components</p>
1000
+ </div>
1001
+ <button
1002
+ onClick={() => setIsDark(!isDark)}
1003
+ className="flex items-center gap-2 px-4 py-2 rounded-[20px] border border-border text-[13px] font-medium hover:bg-card-muted transition-colors"
1004
+ >
1005
+ {isDark ? "☀️" : "🌙"} Dark Mode
1006
+ </button>
1007
+ </header>
1008
+
1009
+ {/* Body */}
1010
+ <div className="flex flex-1 overflow-hidden">
1011
+ {/* Sidebar */}
1012
+ <nav className="w-[200px] shrink-0 border-r border-border overflow-y-auto py-4 px-3 bg-card">
1013
+ {NAV_SECTIONS.map((section) => (
1014
+ <div key={section.title} className="mb-5">
1015
+ <div className="text-[10px] font-semibold text-muted-foreground uppercase tracking-wider px-2 mb-2">
1016
+ {section.title}
1017
+ </div>
1018
+ {section.items.map((item) => (
1019
+ <button
1020
+ key={item.id}
1021
+ onClick={() => setActiveSection(item.id)}
1022
+ className={cn(
1023
+ "w-full text-left px-3 py-2 rounded-[8px] transition-colors mb-0.5",
1024
+ activeSection === item.id
1025
+ ? "bg-primary/8 border-l-[3px] border-primary"
1026
+ : "hover:bg-card-muted"
1027
+ )}
1028
+ >
1029
+ <div className={cn(
1030
+ "text-[13px] font-medium",
1031
+ activeSection === item.id ? "text-foreground" : "text-foreground/80"
1032
+ )}>
1033
+ {item.label}
1034
+ </div>
1035
+ <div className="text-[11px] text-muted-foreground">{item.sublabel}</div>
1036
+ </button>
1037
+ ))}
1038
+ </div>
1039
+ ))}
1040
+ </nav>
1041
+
1042
+ {/* Main Content */}
1043
+ <main className="flex-1 overflow-y-auto p-8">
1044
+ {renderContent()}
1045
+ </main>
1046
+ </div>
1047
+ </div>
1048
+ );
1049
+ }