@teamix-evo/ui 0.1.1
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/LICENSE +21 -0
- package/README.md +336 -0
- package/_data.json +12 -0
- package/manifest.json +1688 -0
- package/package.json +90 -0
- package/src/components/accordion/accordion.meta.md +87 -0
- package/src/components/accordion/accordion.stories.tsx +67 -0
- package/src/components/accordion/accordion.tsx +58 -0
- package/src/components/affix/affix.meta.md +80 -0
- package/src/components/affix/affix.stories.tsx +57 -0
- package/src/components/affix/affix.tsx +97 -0
- package/src/components/alert/alert.meta.md +101 -0
- package/src/components/alert/alert.stories.tsx +93 -0
- package/src/components/alert/alert.tsx +132 -0
- package/src/components/alert-dialog/alert-dialog.meta.md +107 -0
- package/src/components/alert-dialog/alert-dialog.stories.tsx +81 -0
- package/src/components/alert-dialog/alert-dialog.tsx +136 -0
- package/src/components/anchor/anchor.meta.md +87 -0
- package/src/components/anchor/anchor.stories.tsx +74 -0
- package/src/components/anchor/anchor.tsx +130 -0
- package/src/components/app/app.meta.md +86 -0
- package/src/components/app/app.stories.tsx +62 -0
- package/src/components/app/app.tsx +58 -0
- package/src/components/aspect-ratio/aspect-ratio.meta.md +81 -0
- package/src/components/aspect-ratio/aspect-ratio.stories.tsx +59 -0
- package/src/components/aspect-ratio/aspect-ratio.tsx +22 -0
- package/src/components/auto-complete/auto-complete.meta.md +102 -0
- package/src/components/auto-complete/auto-complete.stories.tsx +93 -0
- package/src/components/auto-complete/auto-complete.tsx +205 -0
- package/src/components/avatar/avatar.meta.md +94 -0
- package/src/components/avatar/avatar.stories.tsx +80 -0
- package/src/components/avatar/avatar.tsx +126 -0
- package/src/components/badge/badge.meta.md +119 -0
- package/src/components/badge/badge.stories.tsx +153 -0
- package/src/components/badge/badge.tsx +210 -0
- package/src/components/breadcrumb/breadcrumb.meta.md +107 -0
- package/src/components/breadcrumb/breadcrumb.stories.tsx +84 -0
- package/src/components/breadcrumb/breadcrumb.tsx +122 -0
- package/src/components/button/button.meta.md +98 -0
- package/src/components/button/button.stories.tsx +235 -0
- package/src/components/button/button.tsx +160 -0
- package/src/components/button-group/button-group.meta.md +92 -0
- package/src/components/button-group/button-group.stories.tsx +90 -0
- package/src/components/button-group/button-group.tsx +75 -0
- package/src/components/calendar/calendar.meta.md +118 -0
- package/src/components/calendar/calendar.stories.tsx +68 -0
- package/src/components/calendar/calendar.tsx +107 -0
- package/src/components/card/card.meta.md +117 -0
- package/src/components/card/card.stories.tsx +112 -0
- package/src/components/card/card.tsx +222 -0
- package/src/components/carousel/carousel.meta.md +117 -0
- package/src/components/carousel/carousel.stories.tsx +84 -0
- package/src/components/carousel/carousel.tsx +224 -0
- package/src/components/cascader/cascader.meta.md +110 -0
- package/src/components/cascader/cascader.stories.tsx +108 -0
- package/src/components/cascader/cascader.tsx +198 -0
- package/src/components/checkbox/checkbox.meta.md +99 -0
- package/src/components/checkbox/checkbox.stories.tsx +130 -0
- package/src/components/checkbox/checkbox.tsx +125 -0
- package/src/components/collapsible/collapsible.meta.md +80 -0
- package/src/components/collapsible/collapsible.stories.tsx +35 -0
- package/src/components/collapsible/collapsible.tsx +18 -0
- package/src/components/color-picker/color-picker.meta.md +84 -0
- package/src/components/color-picker/color-picker.stories.tsx +80 -0
- package/src/components/color-picker/color-picker.tsx +160 -0
- package/src/components/combobox/combobox.meta.md +93 -0
- package/src/components/combobox/combobox.stories.tsx +55 -0
- package/src/components/combobox/combobox.tsx +130 -0
- package/src/components/command/command.meta.md +104 -0
- package/src/components/command/command.stories.tsx +59 -0
- package/src/components/command/command.tsx +147 -0
- package/src/components/context-menu/context-menu.meta.md +90 -0
- package/src/components/context-menu/context-menu.stories.tsx +46 -0
- package/src/components/context-menu/context-menu.tsx +191 -0
- package/src/components/data-table/data-table.meta.md +149 -0
- package/src/components/data-table/data-table.stories.tsx +125 -0
- package/src/components/data-table/data-table.tsx +185 -0
- package/src/components/date-picker/date-picker.meta.md +106 -0
- package/src/components/date-picker/date-picker.stories.tsx +58 -0
- package/src/components/date-picker/date-picker.tsx +156 -0
- package/src/components/descriptions/descriptions.meta.md +78 -0
- package/src/components/descriptions/descriptions.stories.tsx +60 -0
- package/src/components/descriptions/descriptions.tsx +129 -0
- package/src/components/dialog/dialog.meta.md +105 -0
- package/src/components/dialog/dialog.stories.tsx +93 -0
- package/src/components/dialog/dialog.tsx +128 -0
- package/src/components/drawer/drawer.meta.md +96 -0
- package/src/components/drawer/drawer.stories.tsx +54 -0
- package/src/components/drawer/drawer.tsx +114 -0
- package/src/components/dropdown-menu/dropdown-menu.meta.md +103 -0
- package/src/components/dropdown-menu/dropdown-menu.stories.tsx +112 -0
- package/src/components/dropdown-menu/dropdown-menu.tsx +195 -0
- package/src/components/empty/empty.meta.md +81 -0
- package/src/components/empty/empty.stories.tsx +46 -0
- package/src/components/empty/empty.tsx +47 -0
- package/src/components/field/field.meta.md +116 -0
- package/src/components/field/field.stories.tsx +117 -0
- package/src/components/field/field.tsx +164 -0
- package/src/components/flex/flex.meta.md +94 -0
- package/src/components/flex/flex.stories.tsx +112 -0
- package/src/components/flex/flex.tsx +122 -0
- package/src/components/float-button/float-button.meta.md +87 -0
- package/src/components/float-button/float-button.stories.tsx +78 -0
- package/src/components/float-button/float-button.tsx +143 -0
- package/src/components/form/form.meta.md +131 -0
- package/src/components/form/form.stories.tsx +122 -0
- package/src/components/form/form.tsx +194 -0
- package/src/components/grid/grid.meta.md +87 -0
- package/src/components/grid/grid.stories.tsx +99 -0
- package/src/components/grid/grid.tsx +130 -0
- package/src/components/hover-card/hover-card.meta.md +92 -0
- package/src/components/hover-card/hover-card.stories.tsx +68 -0
- package/src/components/hover-card/hover-card.tsx +29 -0
- package/src/components/image/image.meta.md +94 -0
- package/src/components/image/image.stories.tsx +55 -0
- package/src/components/image/image.tsx +138 -0
- package/src/components/input/input.meta.md +109 -0
- package/src/components/input/input.stories.tsx +117 -0
- package/src/components/input/input.tsx +213 -0
- package/src/components/input-group/input-group.meta.md +92 -0
- package/src/components/input-group/input-group.stories.tsx +88 -0
- package/src/components/input-group/input-group.tsx +107 -0
- package/src/components/input-number/input-number.meta.md +91 -0
- package/src/components/input-number/input-number.stories.tsx +87 -0
- package/src/components/input-number/input-number.tsx +210 -0
- package/src/components/input-otp/input-otp.meta.md +105 -0
- package/src/components/input-otp/input-otp.stories.tsx +65 -0
- package/src/components/input-otp/input-otp.tsx +97 -0
- package/src/components/item/item.meta.md +116 -0
- package/src/components/item/item.stories.tsx +113 -0
- package/src/components/item/item.tsx +171 -0
- package/src/components/kbd/kbd.meta.md +85 -0
- package/src/components/kbd/kbd.stories.tsx +70 -0
- package/src/components/kbd/kbd.tsx +81 -0
- package/src/components/label/label.meta.md +91 -0
- package/src/components/label/label.stories.tsx +87 -0
- package/src/components/label/label.tsx +66 -0
- package/src/components/masonry/masonry.meta.md +85 -0
- package/src/components/masonry/masonry.stories.tsx +66 -0
- package/src/components/masonry/masonry.tsx +59 -0
- package/src/components/mentions/mentions.meta.md +89 -0
- package/src/components/mentions/mentions.stories.tsx +75 -0
- package/src/components/mentions/mentions.tsx +237 -0
- package/src/components/menubar/menubar.meta.md +100 -0
- package/src/components/menubar/menubar.stories.tsx +81 -0
- package/src/components/menubar/menubar.tsx +232 -0
- package/src/components/native-select/native-select.meta.md +88 -0
- package/src/components/native-select/native-select.stories.tsx +80 -0
- package/src/components/native-select/native-select.tsx +54 -0
- package/src/components/navigation-menu/navigation-menu.meta.md +108 -0
- package/src/components/navigation-menu/navigation-menu.stories.tsx +112 -0
- package/src/components/navigation-menu/navigation-menu.tsx +125 -0
- package/src/components/notification/notification.meta.md +91 -0
- package/src/components/notification/notification.stories.tsx +96 -0
- package/src/components/notification/notification.tsx +84 -0
- package/src/components/pagination/pagination.meta.md +127 -0
- package/src/components/pagination/pagination.stories.tsx +62 -0
- package/src/components/pagination/pagination.tsx +285 -0
- package/src/components/popconfirm/popconfirm.meta.md +109 -0
- package/src/components/popconfirm/popconfirm.stories.tsx +76 -0
- package/src/components/popconfirm/popconfirm.tsx +134 -0
- package/src/components/popover/popover.meta.md +97 -0
- package/src/components/popover/popover.stories.tsx +82 -0
- package/src/components/popover/popover.tsx +55 -0
- package/src/components/progress/progress.meta.md +86 -0
- package/src/components/progress/progress.stories.tsx +75 -0
- package/src/components/progress/progress.tsx +195 -0
- package/src/components/radio-group/radio-group.meta.md +103 -0
- package/src/components/radio-group/radio-group.stories.tsx +77 -0
- package/src/components/radio-group/radio-group.tsx +78 -0
- package/src/components/rate/rate.meta.md +87 -0
- package/src/components/rate/rate.stories.tsx +81 -0
- package/src/components/rate/rate.tsx +153 -0
- package/src/components/resizable/resizable.meta.md +92 -0
- package/src/components/resizable/resizable.stories.tsx +104 -0
- package/src/components/resizable/resizable.tsx +56 -0
- package/src/components/result/result.meta.md +90 -0
- package/src/components/result/result.stories.tsx +71 -0
- package/src/components/result/result.tsx +91 -0
- package/src/components/scroll-area/scroll-area.meta.md +84 -0
- package/src/components/scroll-area/scroll-area.stories.tsx +41 -0
- package/src/components/scroll-area/scroll-area.tsx +51 -0
- package/src/components/segmented/segmented.meta.md +103 -0
- package/src/components/segmented/segmented.stories.tsx +101 -0
- package/src/components/segmented/segmented.tsx +138 -0
- package/src/components/select/select.meta.md +110 -0
- package/src/components/select/select.stories.tsx +100 -0
- package/src/components/select/select.tsx +188 -0
- package/src/components/separator/separator.meta.md +74 -0
- package/src/components/separator/separator.stories.tsx +71 -0
- package/src/components/separator/separator.tsx +104 -0
- package/src/components/sheet/sheet.meta.md +97 -0
- package/src/components/sheet/sheet.stories.tsx +82 -0
- package/src/components/sheet/sheet.tsx +139 -0
- package/src/components/sidebar/sidebar.meta.md +131 -0
- package/src/components/sidebar/sidebar.stories.tsx +82 -0
- package/src/components/sidebar/sidebar.tsx +351 -0
- package/src/components/skeleton/skeleton.meta.md +95 -0
- package/src/components/skeleton/skeleton.stories.tsx +79 -0
- package/src/components/skeleton/skeleton.tsx +144 -0
- package/src/components/slider/slider.meta.md +94 -0
- package/src/components/slider/slider.stories.tsx +69 -0
- package/src/components/slider/slider.tsx +86 -0
- package/src/components/sonner/sonner.meta.md +96 -0
- package/src/components/sonner/sonner.stories.tsx +91 -0
- package/src/components/sonner/sonner.tsx +40 -0
- package/src/components/space/space.meta.md +94 -0
- package/src/components/space/space.stories.tsx +94 -0
- package/src/components/space/space.tsx +106 -0
- package/src/components/spinner/spinner.meta.md +76 -0
- package/src/components/spinner/spinner.stories.tsx +71 -0
- package/src/components/spinner/spinner.tsx +64 -0
- package/src/components/statistic/statistic.meta.md +99 -0
- package/src/components/statistic/statistic.stories.tsx +71 -0
- package/src/components/statistic/statistic.tsx +197 -0
- package/src/components/steps/steps.meta.md +102 -0
- package/src/components/steps/steps.stories.tsx +75 -0
- package/src/components/steps/steps.tsx +170 -0
- package/src/components/switch/switch.meta.md +92 -0
- package/src/components/switch/switch.stories.tsx +75 -0
- package/src/components/switch/switch.tsx +101 -0
- package/src/components/table/table.meta.md +95 -0
- package/src/components/table/table.stories.tsx +75 -0
- package/src/components/table/table.tsx +122 -0
- package/src/components/tabs/tabs.meta.md +98 -0
- package/src/components/tabs/tabs.stories.tsx +70 -0
- package/src/components/tabs/tabs.tsx +119 -0
- package/src/components/tag/tag.meta.md +94 -0
- package/src/components/tag/tag.stories.tsx +77 -0
- package/src/components/tag/tag.tsx +185 -0
- package/src/components/textarea/textarea.meta.md +83 -0
- package/src/components/textarea/textarea.stories.tsx +63 -0
- package/src/components/textarea/textarea.tsx +113 -0
- package/src/components/time-picker/time-picker.meta.md +83 -0
- package/src/components/time-picker/time-picker.stories.tsx +59 -0
- package/src/components/time-picker/time-picker.tsx +94 -0
- package/src/components/timeline/timeline.meta.md +102 -0
- package/src/components/timeline/timeline.stories.tsx +104 -0
- package/src/components/timeline/timeline.tsx +147 -0
- package/src/components/toggle/toggle.meta.md +88 -0
- package/src/components/toggle/toggle.stories.tsx +66 -0
- package/src/components/toggle/toggle.tsx +53 -0
- package/src/components/toggle-group/toggle-group.meta.md +90 -0
- package/src/components/toggle-group/toggle-group.stories.tsx +83 -0
- package/src/components/toggle-group/toggle-group.tsx +78 -0
- package/src/components/tooltip/tooltip.meta.md +99 -0
- package/src/components/tooltip/tooltip.stories.tsx +71 -0
- package/src/components/tooltip/tooltip.tsx +93 -0
- package/src/components/tour/tour.meta.md +116 -0
- package/src/components/tour/tour.stories.tsx +66 -0
- package/src/components/tour/tour.tsx +242 -0
- package/src/components/transfer/transfer.meta.md +90 -0
- package/src/components/transfer/transfer.stories.tsx +68 -0
- package/src/components/transfer/transfer.tsx +251 -0
- package/src/components/tree/tree.meta.md +111 -0
- package/src/components/tree/tree.stories.tsx +109 -0
- package/src/components/tree/tree.tsx +367 -0
- package/src/components/tree-select/tree-select.meta.md +100 -0
- package/src/components/tree-select/tree-select.stories.tsx +80 -0
- package/src/components/tree-select/tree-select.tsx +171 -0
- package/src/components/typography/typography.meta.md +102 -0
- package/src/components/typography/typography.stories.tsx +115 -0
- package/src/components/typography/typography.tsx +245 -0
- package/src/components/upload/upload.meta.md +111 -0
- package/src/components/upload/upload.stories.tsx +75 -0
- package/src/components/upload/upload.tsx +265 -0
- package/src/components/watermark/watermark.meta.md +95 -0
- package/src/components/watermark/watermark.stories.tsx +78 -0
- package/src/components/watermark/watermark.tsx +165 -0
- package/src/utils/cn.ts +6 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/utils/cn';
|
|
4
|
+
import { Input } from '@/components/input/input';
|
|
5
|
+
import {
|
|
6
|
+
Popover,
|
|
7
|
+
PopoverContent,
|
|
8
|
+
PopoverTrigger,
|
|
9
|
+
} from '@/components/popover/popover';
|
|
10
|
+
|
|
11
|
+
export interface ColorPickerProps {
|
|
12
|
+
/** 受控值 — `#RRGGBB` 或 `#RRGGBBAA`(开 `allowAlpha` 时)。 */
|
|
13
|
+
value?: string;
|
|
14
|
+
/** uncontrolled 初值。 @default "#000000" */
|
|
15
|
+
defaultValue?: string;
|
|
16
|
+
/** 值变化回调。 */
|
|
17
|
+
onChange?: (value: string) => void;
|
|
18
|
+
/**
|
|
19
|
+
* 是否允许透明度(antd `format=hex8` 类似行为) — 启用后展示 alpha 滑块,value 变为 8 位 hex。
|
|
20
|
+
* @default false
|
|
21
|
+
*/
|
|
22
|
+
allowAlpha?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* 预设色块(antd `presets` 并集) — 一组常用颜色快捷选择。
|
|
25
|
+
*/
|
|
26
|
+
presets?: string[];
|
|
27
|
+
/** 整体禁用。 */
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
/** 触发器尺寸。 @default "default" */
|
|
30
|
+
size?: 'sm' | 'default' | 'lg';
|
|
31
|
+
/** 触发器 className。 */
|
|
32
|
+
className?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function clampHex(hex: string, allowAlpha: boolean): string {
|
|
36
|
+
const cleaned = hex.replace(/[^0-9a-fA-F#]/g, '');
|
|
37
|
+
const withHash = cleaned.startsWith('#') ? cleaned : `#${cleaned}`;
|
|
38
|
+
const expected = allowAlpha ? 9 : 7;
|
|
39
|
+
return withHash.slice(0, expected);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 颜色选择 — antd 独有补足。**等价 antd `ColorPicker`**(v5.5+)。基于原生
|
|
44
|
+
* `<input type="color">` + alpha range 输入 + hex 文本输入 + 预设色块,
|
|
45
|
+
* 提供"触发器(色块)→ 弹出面板"的标准交互。
|
|
46
|
+
*/
|
|
47
|
+
const ColorPicker = React.forwardRef<HTMLButtonElement, ColorPickerProps>(
|
|
48
|
+
(
|
|
49
|
+
{
|
|
50
|
+
value,
|
|
51
|
+
defaultValue = '#000000',
|
|
52
|
+
onChange,
|
|
53
|
+
allowAlpha = false,
|
|
54
|
+
presets,
|
|
55
|
+
disabled = false,
|
|
56
|
+
size = 'default',
|
|
57
|
+
className,
|
|
58
|
+
},
|
|
59
|
+
ref,
|
|
60
|
+
) => {
|
|
61
|
+
const isControlled = value !== undefined;
|
|
62
|
+
const [internal, setInternal] = React.useState<string>(defaultValue);
|
|
63
|
+
const current = isControlled ? value! : internal;
|
|
64
|
+
|
|
65
|
+
const update = (next: string) => {
|
|
66
|
+
const v = clampHex(next, allowAlpha);
|
|
67
|
+
if (!isControlled) setInternal(v);
|
|
68
|
+
onChange?.(v);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const baseHex = current.slice(0, 7);
|
|
72
|
+
const alphaHex = allowAlpha && current.length >= 9 ? current.slice(7, 9) : 'ff';
|
|
73
|
+
const alphaInt = parseInt(alphaHex, 16);
|
|
74
|
+
|
|
75
|
+
const swatchSize = size === 'sm' ? 'size-7' : size === 'lg' ? 'size-11' : 'size-9';
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<Popover>
|
|
79
|
+
<PopoverTrigger asChild>
|
|
80
|
+
<button
|
|
81
|
+
ref={ref}
|
|
82
|
+
type="button"
|
|
83
|
+
disabled={disabled}
|
|
84
|
+
className={cn(
|
|
85
|
+
'inline-flex items-center justify-center rounded-md border border-input bg-background shadow-sm transition-colors',
|
|
86
|
+
'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
|
|
87
|
+
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
88
|
+
swatchSize,
|
|
89
|
+
className,
|
|
90
|
+
)}
|
|
91
|
+
aria-label="选择颜色"
|
|
92
|
+
style={{ padding: 2 }}
|
|
93
|
+
>
|
|
94
|
+
<span
|
|
95
|
+
className="block size-full rounded-sm"
|
|
96
|
+
style={{ backgroundColor: current }}
|
|
97
|
+
/>
|
|
98
|
+
</button>
|
|
99
|
+
</PopoverTrigger>
|
|
100
|
+
<PopoverContent className="w-64 p-3" align="start">
|
|
101
|
+
<div className="flex flex-col gap-3">
|
|
102
|
+
<input
|
|
103
|
+
type="color"
|
|
104
|
+
value={baseHex}
|
|
105
|
+
disabled={disabled}
|
|
106
|
+
onChange={(e) => update(allowAlpha ? `${e.target.value}${alphaHex}` : e.target.value)}
|
|
107
|
+
className="h-10 w-full cursor-pointer rounded-md border border-input bg-transparent"
|
|
108
|
+
/>
|
|
109
|
+
{allowAlpha ? (
|
|
110
|
+
<div className="flex items-center gap-2">
|
|
111
|
+
<span className="w-12 text-xs text-muted-foreground">Alpha</span>
|
|
112
|
+
<input
|
|
113
|
+
type="range"
|
|
114
|
+
min={0}
|
|
115
|
+
max={255}
|
|
116
|
+
value={alphaInt}
|
|
117
|
+
disabled={disabled}
|
|
118
|
+
onChange={(e) => {
|
|
119
|
+
const a = Number(e.target.value).toString(16).padStart(2, '0');
|
|
120
|
+
update(`${baseHex}${a}`);
|
|
121
|
+
}}
|
|
122
|
+
className="flex-1"
|
|
123
|
+
/>
|
|
124
|
+
<span className="w-10 text-right text-xs tabular-nums">
|
|
125
|
+
{Math.round((alphaInt / 255) * 100)}%
|
|
126
|
+
</span>
|
|
127
|
+
</div>
|
|
128
|
+
) : null}
|
|
129
|
+
<Input
|
|
130
|
+
value={current}
|
|
131
|
+
disabled={disabled}
|
|
132
|
+
onChange={(e) => update(e.target.value)}
|
|
133
|
+
placeholder={allowAlpha ? '#RRGGBBAA' : '#RRGGBB'}
|
|
134
|
+
/>
|
|
135
|
+
{presets && presets.length > 0 ? (
|
|
136
|
+
<div className="flex flex-wrap gap-1.5">
|
|
137
|
+
{presets.map((p) => (
|
|
138
|
+
<button
|
|
139
|
+
key={p}
|
|
140
|
+
type="button"
|
|
141
|
+
aria-label={p}
|
|
142
|
+
onClick={() => update(p)}
|
|
143
|
+
className={cn(
|
|
144
|
+
'size-6 rounded-sm border border-border ring-offset-background transition-all',
|
|
145
|
+
'hover:scale-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
146
|
+
)}
|
|
147
|
+
style={{ backgroundColor: p }}
|
|
148
|
+
/>
|
|
149
|
+
))}
|
|
150
|
+
</div>
|
|
151
|
+
) : null}
|
|
152
|
+
</div>
|
|
153
|
+
</PopoverContent>
|
|
154
|
+
</Popover>
|
|
155
|
+
);
|
|
156
|
+
},
|
|
157
|
+
);
|
|
158
|
+
ColorPicker.displayName = 'ColorPicker';
|
|
159
|
+
|
|
160
|
+
export { ColorPicker };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: combobox
|
|
3
|
+
name: Combobox
|
|
4
|
+
type: component
|
|
5
|
+
category: form
|
|
6
|
+
since: 0.1.0
|
|
7
|
+
package: "@teamix-evo/ui"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Combobox
|
|
11
|
+
|
|
12
|
+
可搜索单选下拉 — `Command + Popover` 复合,等价 antd `AutoComplete` / `Select showSearch` 的核心场景。
|
|
13
|
+
**单选**(刻意不做多选,保持语义简单);多选请直接用 `Command` + 自管 selected 数组。
|
|
14
|
+
|
|
15
|
+
## When to use
|
|
16
|
+
|
|
17
|
+
- 选项较多需要搜索筛选(国家 / 时区 / 用户列表 / 标签)
|
|
18
|
+
- 选项动态加载(异步 — 在父组件准备数据后传给 `options`)
|
|
19
|
+
|
|
20
|
+
## When NOT to use
|
|
21
|
+
|
|
22
|
+
- 选项 ≤ 7 个 → `Select`(无需搜索)
|
|
23
|
+
- 多选 → `Command` 自定义,或 `CheckboxGroup`
|
|
24
|
+
- 自由文本输入 → `Input` + 自定义 suggestion(超出本组件范围)
|
|
25
|
+
|
|
26
|
+
<!-- auto:props:begin -->
|
|
27
|
+
| 名称 | 类型 | 默认值 | 必填 | 说明 |
|
|
28
|
+
| --- | --- | --- | --- | --- |
|
|
29
|
+
| `options` | `ComboboxOption[]` | – | ✓ | 选项列表(antd `AutoComplete.options` 并集)。 |
|
|
30
|
+
| `value` | `string` | – | – | 受控 value(单选)。 |
|
|
31
|
+
| `onChange` | `(value: string) => void` | – | – | value 变化回调。 |
|
|
32
|
+
| `placeholder` | `string` | `"请选择..."` | – | 触发器占位文本(未选)。 |
|
|
33
|
+
| `searchPlaceholder` | `string` | `"搜索..."` | – | 搜索框占位文本。 |
|
|
34
|
+
| `emptyText` | `string` | `"无匹配项"` | – | 无匹配时的提示文本。 |
|
|
35
|
+
| `className` | `string` | `"w-[200px]"` | – | 触发器宽度。 |
|
|
36
|
+
| `disabled` | `boolean` | – | – | 是否禁用。 |
|
|
37
|
+
| `size` | `'sm' \| 'default' \| 'lg'` | `"default"` | – | 触发器尺寸。 |
|
|
38
|
+
<!-- auto:props:end -->
|
|
39
|
+
|
|
40
|
+
<!-- auto:deps:begin -->
|
|
41
|
+
### 同库依赖
|
|
42
|
+
|
|
43
|
+
> `teamix-evo ui add combobox` 时,以下 entry 会被自动连带安装(无需手动 add)。
|
|
44
|
+
|
|
45
|
+
| Entry | 类型 | 描述 |
|
|
46
|
+
| --- | --- | --- |
|
|
47
|
+
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
48
|
+
| `button` | component | 通用按钮 — shadcn 实现 + antd 功能扩展(loading / icon / shape / block / dashed variant) |
|
|
49
|
+
| `command` | component | 命令面板 — cmdk(Linear / Raycast 风格),全局搜索 / 命令执行;Combobox 的底座 |
|
|
50
|
+
| `popover` | component | 可交互浮层 — Radix Popover + antd arrow 并集 |
|
|
51
|
+
|
|
52
|
+
### npm 依赖
|
|
53
|
+
|
|
54
|
+
> 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pnpm add lucide-react@^0.460.0
|
|
58
|
+
```
|
|
59
|
+
<!-- auto:deps:end -->
|
|
60
|
+
|
|
61
|
+
> 选项类型 `ComboboxOption`:`{ value: string; label: ReactNode; disabled?: boolean }`。
|
|
62
|
+
|
|
63
|
+
## AI 生成纪律
|
|
64
|
+
|
|
65
|
+
- **`value` 稳定**:用业务 ID,不要用 index
|
|
66
|
+
- **`label` 可富节点**:支持图标 + 文本,不止 string
|
|
67
|
+
- **第二次点击当前 value 会清空**:`onChange('')` 触发,业务侧据此实现"取消选择"
|
|
68
|
+
- **大量选项(>1000) → 自定义虚拟滚动**:本组件未集成 virtualizer
|
|
69
|
+
- **异步加载选项**:父组件 fetch 后传 `options`,加 loading 占位符在 options 列表外
|
|
70
|
+
|
|
71
|
+
## Examples
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { Combobox, type ComboboxOption } from '@/components/ui/combobox';
|
|
75
|
+
import * as React from 'react';
|
|
76
|
+
|
|
77
|
+
const frameworks: ComboboxOption[] = [
|
|
78
|
+
{ value: 'next', label: 'Next.js' },
|
|
79
|
+
{ value: 'sveltekit', label: 'SvelteKit' },
|
|
80
|
+
{ value: 'nuxt', label: 'Nuxt' },
|
|
81
|
+
{ value: 'remix', label: 'Remix' },
|
|
82
|
+
{ value: 'astro', label: 'Astro' },
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
const [v, setV] = React.useState('');
|
|
86
|
+
<Combobox
|
|
87
|
+
options={frameworks}
|
|
88
|
+
value={v}
|
|
89
|
+
onChange={setV}
|
|
90
|
+
placeholder="选择框架"
|
|
91
|
+
searchPlaceholder="搜索框架..."
|
|
92
|
+
/>
|
|
93
|
+
```
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { Combobox, type ComboboxOption } from './combobox';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Combobox> = {
|
|
6
|
+
title: '表单与输入 · Form/Combobox',
|
|
7
|
+
component: Combobox,
|
|
8
|
+
tags: ['autodocs'],
|
|
9
|
+
parameters: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component:
|
|
13
|
+
'可搜索单选下拉 — Command + Popover 复合,对标 antd AutoComplete / Select showSearch。键盘可达、模糊搜索、空态、再次选中清空,适合选项数 > 7 的单选场景。OpenTrek tokens 适配。',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default meta;
|
|
20
|
+
type Story = StoryObj<typeof Combobox>;
|
|
21
|
+
|
|
22
|
+
const frameworks: ComboboxOption[] = [
|
|
23
|
+
{ value: 'next', label: 'Next.js' },
|
|
24
|
+
{ value: 'sveltekit', label: 'SvelteKit' },
|
|
25
|
+
{ value: 'nuxt', label: 'Nuxt' },
|
|
26
|
+
{ value: 'remix', label: 'Remix' },
|
|
27
|
+
{ value: 'astro', label: 'Astro' },
|
|
28
|
+
{ value: 'qwik', label: 'Qwik', disabled: true },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
export const Default: Story = {
|
|
32
|
+
render: () => {
|
|
33
|
+
const [v, setV] = React.useState('');
|
|
34
|
+
return (
|
|
35
|
+
<Combobox
|
|
36
|
+
options={frameworks}
|
|
37
|
+
value={v}
|
|
38
|
+
onChange={setV}
|
|
39
|
+
placeholder="选择框架"
|
|
40
|
+
searchPlaceholder="搜索框架..."
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const Sizes: Story = {
|
|
47
|
+
parameters: { controls: { disable: true } },
|
|
48
|
+
render: () => (
|
|
49
|
+
<div className="flex flex-col gap-3">
|
|
50
|
+
<Combobox options={frameworks} placeholder="sm" size="sm" />
|
|
51
|
+
<Combobox options={frameworks} placeholder="default" />
|
|
52
|
+
<Combobox options={frameworks} placeholder="lg" size="lg" />
|
|
53
|
+
</div>
|
|
54
|
+
),
|
|
55
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { Check, ChevronsUpDown } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
import { cn } from '@/utils/cn';
|
|
5
|
+
import { Button } from '@/components/button/button';
|
|
6
|
+
import {
|
|
7
|
+
Command,
|
|
8
|
+
CommandEmpty,
|
|
9
|
+
CommandGroup,
|
|
10
|
+
CommandInput,
|
|
11
|
+
CommandItem,
|
|
12
|
+
CommandList,
|
|
13
|
+
} from '@/components/command/command';
|
|
14
|
+
import {
|
|
15
|
+
Popover,
|
|
16
|
+
PopoverContent,
|
|
17
|
+
PopoverTrigger,
|
|
18
|
+
} from '@/components/popover/popover';
|
|
19
|
+
|
|
20
|
+
export interface ComboboxOption {
|
|
21
|
+
/** 选项值(受控 value 比对的依据,稳定 ID)。 */
|
|
22
|
+
value: string;
|
|
23
|
+
/** 显示文本。 */
|
|
24
|
+
label: React.ReactNode;
|
|
25
|
+
/** 禁用此选项。 */
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ComboboxProps {
|
|
30
|
+
/** 选项列表(antd `AutoComplete.options` 并集)。 */
|
|
31
|
+
options: ComboboxOption[];
|
|
32
|
+
/** 受控 value(单选)。 */
|
|
33
|
+
value?: string;
|
|
34
|
+
/** value 变化回调。 */
|
|
35
|
+
onChange?: (value: string) => void;
|
|
36
|
+
/** 触发器占位文本(未选)。 @default "请选择..." */
|
|
37
|
+
placeholder?: string;
|
|
38
|
+
/** 搜索框占位文本。 @default "搜索..." */
|
|
39
|
+
searchPlaceholder?: string;
|
|
40
|
+
/** 无匹配时的提示文本。 @default "无匹配项" */
|
|
41
|
+
emptyText?: string;
|
|
42
|
+
/** 触发器宽度。 @default "w-[200px]" */
|
|
43
|
+
className?: string;
|
|
44
|
+
/** 是否禁用。 */
|
|
45
|
+
disabled?: boolean;
|
|
46
|
+
/** 触发器尺寸。 @default "default" */
|
|
47
|
+
size?: 'sm' | 'default' | 'lg';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 可搜索单选下拉 — Command + Popover 复合,等价 antd `AutoComplete` /
|
|
52
|
+
* `Select showSearch` 的核心场景。
|
|
53
|
+
*
|
|
54
|
+
* 多选场景请直接用 Command + 自管 selected 数组(本组件刻意不做多选,保持
|
|
55
|
+
* 单选语义清晰)。
|
|
56
|
+
*/
|
|
57
|
+
const Combobox = React.forwardRef<HTMLButtonElement, ComboboxProps>(
|
|
58
|
+
(
|
|
59
|
+
{
|
|
60
|
+
options,
|
|
61
|
+
value,
|
|
62
|
+
onChange,
|
|
63
|
+
placeholder = '请选择...',
|
|
64
|
+
searchPlaceholder = '搜索...',
|
|
65
|
+
emptyText = '无匹配项',
|
|
66
|
+
className,
|
|
67
|
+
disabled,
|
|
68
|
+
size = 'default',
|
|
69
|
+
},
|
|
70
|
+
ref,
|
|
71
|
+
) => {
|
|
72
|
+
const [open, setOpen] = React.useState(false);
|
|
73
|
+
const selected = options.find((o) => o.value === value);
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<Popover open={open} onOpenChange={setOpen}>
|
|
77
|
+
<PopoverTrigger asChild>
|
|
78
|
+
<Button
|
|
79
|
+
ref={ref}
|
|
80
|
+
variant="outline"
|
|
81
|
+
size={size}
|
|
82
|
+
disabled={disabled}
|
|
83
|
+
role="combobox"
|
|
84
|
+
aria-expanded={open}
|
|
85
|
+
className={cn(
|
|
86
|
+
'w-[200px] justify-between font-normal',
|
|
87
|
+
!value && 'text-muted-foreground',
|
|
88
|
+
className,
|
|
89
|
+
)}
|
|
90
|
+
>
|
|
91
|
+
{selected ? selected.label : placeholder}
|
|
92
|
+
<ChevronsUpDown className="ml-2 size-4 shrink-0 opacity-50" />
|
|
93
|
+
</Button>
|
|
94
|
+
</PopoverTrigger>
|
|
95
|
+
<PopoverContent className="w-[200px] p-0">
|
|
96
|
+
<Command>
|
|
97
|
+
<CommandInput placeholder={searchPlaceholder} />
|
|
98
|
+
<CommandList>
|
|
99
|
+
<CommandEmpty>{emptyText}</CommandEmpty>
|
|
100
|
+
<CommandGroup>
|
|
101
|
+
{options.map((opt) => (
|
|
102
|
+
<CommandItem
|
|
103
|
+
key={opt.value}
|
|
104
|
+
value={opt.value}
|
|
105
|
+
disabled={opt.disabled}
|
|
106
|
+
onSelect={(v) => {
|
|
107
|
+
onChange?.(v === value ? '' : v);
|
|
108
|
+
setOpen(false);
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
<Check
|
|
112
|
+
className={cn(
|
|
113
|
+
'mr-2 size-4',
|
|
114
|
+
opt.value === value ? 'opacity-100' : 'opacity-0',
|
|
115
|
+
)}
|
|
116
|
+
/>
|
|
117
|
+
{opt.label}
|
|
118
|
+
</CommandItem>
|
|
119
|
+
))}
|
|
120
|
+
</CommandGroup>
|
|
121
|
+
</CommandList>
|
|
122
|
+
</Command>
|
|
123
|
+
</PopoverContent>
|
|
124
|
+
</Popover>
|
|
125
|
+
);
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
Combobox.displayName = 'Combobox';
|
|
129
|
+
|
|
130
|
+
export { Combobox };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: command
|
|
3
|
+
name: Command
|
|
4
|
+
type: component
|
|
5
|
+
category: shell
|
|
6
|
+
since: 0.1.0
|
|
7
|
+
package: "@teamix-evo/ui"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Command
|
|
11
|
+
|
|
12
|
+
命令面板 — 基于 [`cmdk`](https://cmdk.paco.me/),提供"按 ⌘K 打开"的全局搜索 / 命令执行体验(Linear / Raycast / Vercel 风格)。
|
|
13
|
+
**shadcn-only**(antd 无对标)。是 `Combobox` 的底座(Combobox = Command + Popover)。
|
|
14
|
+
|
|
15
|
+
## When to use
|
|
16
|
+
|
|
17
|
+
- 全局命令面板(⌘K 搜索 / 跳转 / 执行任务)
|
|
18
|
+
- 复杂筛选 / 多条件搜索
|
|
19
|
+
- 编辑器命令(quick action)
|
|
20
|
+
- 配 `Popover` 做 Combobox(可搜索下拉)
|
|
21
|
+
- 配 `CommandDialog` 做模态命令面板
|
|
22
|
+
|
|
23
|
+
## When NOT to use
|
|
24
|
+
|
|
25
|
+
- 简单单选下拉 → `Select`
|
|
26
|
+
- 静态菜单 → `DropdownMenu`
|
|
27
|
+
- 需要表单语义 → `Combobox`(基于本组件)
|
|
28
|
+
|
|
29
|
+
<!-- auto:props:begin -->
|
|
30
|
+
_(no props)_
|
|
31
|
+
<!-- auto:props:end -->
|
|
32
|
+
|
|
33
|
+
<!-- auto:deps:begin -->
|
|
34
|
+
### 同库依赖
|
|
35
|
+
|
|
36
|
+
> `teamix-evo ui add command` 时,以下 entry 会被自动连带安装(无需手动 add)。
|
|
37
|
+
|
|
38
|
+
| Entry | 类型 | 描述 |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
41
|
+
| `dialog` | component | 模态对话框 — Radix Dialog + antd Modal 并集(组合式 Header/Footer/Title/Description) |
|
|
42
|
+
|
|
43
|
+
### npm 依赖
|
|
44
|
+
|
|
45
|
+
> 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm add cmdk@^1.0.0 lucide-react@^0.460.0
|
|
49
|
+
```
|
|
50
|
+
<!-- auto:deps:end -->
|
|
51
|
+
|
|
52
|
+
> 子组件:`Command`(Root 容器)/ `CommandDialog`(模态版,内部组合 Dialog)/ `CommandInput`(自带搜索图标)/ `CommandList`(可滚动列表)/ `CommandEmpty`(无匹配空态)/ `CommandGroup`(分组,带 heading)/ `CommandItem`(选项,支持 `onSelect`)/ `CommandSeparator` / `CommandShortcut`(右对齐快捷键)。
|
|
53
|
+
|
|
54
|
+
## AI 生成纪律
|
|
55
|
+
|
|
56
|
+
- **`CommandList` 内必有 `CommandEmpty`**:无搜索结果时不渲染会让 UI 空白
|
|
57
|
+
- **`CommandItem` 用 `onSelect` 不是 `onClick`**:cmdk 通过 keyboard 导航触发,需要 `onSelect`
|
|
58
|
+
- **value 必稳定**:`<CommandItem value="...">` 用稳定 ID(决定搜索匹配)
|
|
59
|
+
- **CommandDialog 全局监听 ⌘K**:业务侧加一个 `useEffect` 监听键盘事件 toggle open
|
|
60
|
+
- **不嵌套 Command**:嵌套是反模式
|
|
61
|
+
|
|
62
|
+
## Examples
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
import {
|
|
66
|
+
Command, CommandInput, CommandList, CommandEmpty,
|
|
67
|
+
CommandGroup, CommandItem, CommandShortcut,
|
|
68
|
+
} from '@/components/ui/command';
|
|
69
|
+
import { Calendar, Mail, User } from 'lucide-react';
|
|
70
|
+
|
|
71
|
+
// 内联
|
|
72
|
+
<Command className="rounded-lg border">
|
|
73
|
+
<CommandInput placeholder="输入命令或搜索..." />
|
|
74
|
+
<CommandList>
|
|
75
|
+
<CommandEmpty>无结果。</CommandEmpty>
|
|
76
|
+
<CommandGroup heading="建议">
|
|
77
|
+
<CommandItem><Calendar /> 日历 <CommandShortcut>⌘K</CommandShortcut></CommandItem>
|
|
78
|
+
<CommandItem><Mail /> 邮件</CommandItem>
|
|
79
|
+
</CommandGroup>
|
|
80
|
+
<CommandGroup heading="设置">
|
|
81
|
+
<CommandItem><User /> 个人资料</CommandItem>
|
|
82
|
+
</CommandGroup>
|
|
83
|
+
</CommandList>
|
|
84
|
+
</Command>
|
|
85
|
+
|
|
86
|
+
// 模态(全局 ⌘K)
|
|
87
|
+
import { CommandDialog } from '@/components/ui/command';
|
|
88
|
+
const [open, setOpen] = React.useState(false);
|
|
89
|
+
React.useEffect(() => {
|
|
90
|
+
const onKey = (e: KeyboardEvent) => {
|
|
91
|
+
if (e.key === 'k' && (e.metaKey || e.ctrlKey)) {
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
setOpen((o) => !o);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
document.addEventListener('keydown', onKey);
|
|
97
|
+
return () => document.removeEventListener('keydown', onKey);
|
|
98
|
+
}, []);
|
|
99
|
+
|
|
100
|
+
<CommandDialog open={open} onOpenChange={setOpen}>
|
|
101
|
+
<CommandInput placeholder="..." />
|
|
102
|
+
<CommandList>...</CommandList>
|
|
103
|
+
</CommandDialog>
|
|
104
|
+
```
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Calendar, Mail, Settings, User } from 'lucide-react';
|
|
3
|
+
import {
|
|
4
|
+
Command,
|
|
5
|
+
CommandInput,
|
|
6
|
+
CommandList,
|
|
7
|
+
CommandEmpty,
|
|
8
|
+
CommandGroup,
|
|
9
|
+
CommandItem,
|
|
10
|
+
CommandShortcut,
|
|
11
|
+
CommandSeparator,
|
|
12
|
+
} from './command';
|
|
13
|
+
|
|
14
|
+
const meta: Meta<typeof Command> = {
|
|
15
|
+
title: '应用壳 · Shell/Command',
|
|
16
|
+
component: Command,
|
|
17
|
+
tags: ['autodocs'],
|
|
18
|
+
parameters: {
|
|
19
|
+
docs: {
|
|
20
|
+
description: {
|
|
21
|
+
component:
|
|
22
|
+
'命令面板 — 全局搜索 / 命令执行(Linear / Raycast / Vercel 风格)。基于 cmdk,提供键盘导航、模糊搜索、分组、空态、快捷键提示;模态版用 CommandDialog 监听 ⌘K 全局触发。OpenTrek tokens 适配,无 mock。',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default meta;
|
|
29
|
+
type Story = StoryObj<typeof Command>;
|
|
30
|
+
|
|
31
|
+
export const Inline: Story = {
|
|
32
|
+
render: () => (
|
|
33
|
+
<Command className="w-80 rounded-lg border">
|
|
34
|
+
<CommandInput placeholder="输入命令或搜索..." />
|
|
35
|
+
<CommandList>
|
|
36
|
+
<CommandEmpty>无结果。</CommandEmpty>
|
|
37
|
+
<CommandGroup heading="建议">
|
|
38
|
+
<CommandItem>
|
|
39
|
+
<Calendar /> 日历
|
|
40
|
+
<CommandShortcut>⌘K</CommandShortcut>
|
|
41
|
+
</CommandItem>
|
|
42
|
+
<CommandItem>
|
|
43
|
+
<Mail /> 邮件
|
|
44
|
+
<CommandShortcut>⌘E</CommandShortcut>
|
|
45
|
+
</CommandItem>
|
|
46
|
+
</CommandGroup>
|
|
47
|
+
<CommandSeparator />
|
|
48
|
+
<CommandGroup heading="设置">
|
|
49
|
+
<CommandItem>
|
|
50
|
+
<User /> 个人资料
|
|
51
|
+
</CommandItem>
|
|
52
|
+
<CommandItem>
|
|
53
|
+
<Settings /> 偏好设置
|
|
54
|
+
</CommandItem>
|
|
55
|
+
</CommandGroup>
|
|
56
|
+
</CommandList>
|
|
57
|
+
</Command>
|
|
58
|
+
),
|
|
59
|
+
};
|