@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,237 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/utils/cn';
|
|
4
|
+
import { Textarea } from '@/components/textarea/textarea';
|
|
5
|
+
|
|
6
|
+
export interface MentionsOption {
|
|
7
|
+
/** 真实插入到文本中的 value(不含前缀)。 */
|
|
8
|
+
value: string;
|
|
9
|
+
/** 候选列表显示文本(不传则用 value)。 */
|
|
10
|
+
label?: React.ReactNode;
|
|
11
|
+
/** 禁用此选项。 */
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MentionsProps
|
|
16
|
+
extends Omit<
|
|
17
|
+
React.TextareaHTMLAttributes<HTMLTextAreaElement>,
|
|
18
|
+
'onChange' | 'value' | 'defaultValue' | 'onSelect'
|
|
19
|
+
> {
|
|
20
|
+
/** 候选项(antd `options` 并集)。 */
|
|
21
|
+
options: MentionsOption[];
|
|
22
|
+
/** 受控值。 */
|
|
23
|
+
value?: string;
|
|
24
|
+
/** uncontrolled 初值。 */
|
|
25
|
+
defaultValue?: string;
|
|
26
|
+
/** 文本变化回调。 */
|
|
27
|
+
onChange?: (value: string) => void;
|
|
28
|
+
/**
|
|
29
|
+
* 触发符(antd `prefix` 并集) — 通常是 `@` 或 `#`。
|
|
30
|
+
* @default "@"
|
|
31
|
+
*/
|
|
32
|
+
prefix?: string;
|
|
33
|
+
/**
|
|
34
|
+
* 选中候选项后插入文本的尾随分隔(对齐 antd `split` 默认空格)。
|
|
35
|
+
* @default " "
|
|
36
|
+
*/
|
|
37
|
+
split?: string;
|
|
38
|
+
/**
|
|
39
|
+
* 用户在 prefix 后输入时的搜索回调,用于业务侧异步刷新 `options`。
|
|
40
|
+
*/
|
|
41
|
+
onSearch?: (query: string, prefix: string) => void;
|
|
42
|
+
/**
|
|
43
|
+
* 选中候选项时回调。
|
|
44
|
+
*/
|
|
45
|
+
onSelect?: (option: MentionsOption, prefix: string) => void;
|
|
46
|
+
/**
|
|
47
|
+
* 文本框尺寸传透传 Textarea(行数)。
|
|
48
|
+
* @default 3
|
|
49
|
+
*/
|
|
50
|
+
rows?: number;
|
|
51
|
+
/** 禁用。 */
|
|
52
|
+
disabled?: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface TriggerCtx {
|
|
56
|
+
/** prefix 在 value 中的索引(`@` 的位置)。 */
|
|
57
|
+
start: number;
|
|
58
|
+
/** prefix 后到光标位置的查询字符串。 */
|
|
59
|
+
query: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getTriggerContext(
|
|
63
|
+
value: string,
|
|
64
|
+
caret: number,
|
|
65
|
+
prefix: string,
|
|
66
|
+
): TriggerCtx | null {
|
|
67
|
+
if (caret <= 0) return null;
|
|
68
|
+
// Walk backwards from caret until we hit a whitespace, newline, or `prefix`.
|
|
69
|
+
for (let i = caret - 1; i >= 0; i--) {
|
|
70
|
+
const ch = value[i];
|
|
71
|
+
if (ch === prefix) {
|
|
72
|
+
// The char immediately before prefix must be start-of-string / whitespace / newline,
|
|
73
|
+
// otherwise we'd trigger on emails like a@b.
|
|
74
|
+
const prev = i === 0 ? '' : value[i - 1];
|
|
75
|
+
if (prev === '' || /\s/.test(prev)) {
|
|
76
|
+
return { start: i, query: value.slice(i + 1, caret) };
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
if (/\s/.test(ch)) return null;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const Mentions = React.forwardRef<HTMLTextAreaElement, MentionsProps>(
|
|
86
|
+
(
|
|
87
|
+
{
|
|
88
|
+
options,
|
|
89
|
+
value,
|
|
90
|
+
defaultValue,
|
|
91
|
+
onChange,
|
|
92
|
+
prefix = '@',
|
|
93
|
+
split = ' ',
|
|
94
|
+
onSearch,
|
|
95
|
+
onSelect,
|
|
96
|
+
rows = 3,
|
|
97
|
+
disabled,
|
|
98
|
+
className,
|
|
99
|
+
onKeyDown,
|
|
100
|
+
...props
|
|
101
|
+
},
|
|
102
|
+
ref,
|
|
103
|
+
) => {
|
|
104
|
+
const isControlled = value !== undefined;
|
|
105
|
+
const [internal, setInternal] = React.useState<string>(defaultValue ?? '');
|
|
106
|
+
const current = isControlled ? value! : internal;
|
|
107
|
+
|
|
108
|
+
const innerRef = React.useRef<HTMLTextAreaElement | null>(null);
|
|
109
|
+
React.useImperativeHandle(ref, () => innerRef.current as HTMLTextAreaElement);
|
|
110
|
+
|
|
111
|
+
const [ctx, setCtx] = React.useState<TriggerCtx | null>(null);
|
|
112
|
+
const [activeIdx, setActiveIdx] = React.useState(0);
|
|
113
|
+
|
|
114
|
+
const filtered = React.useMemo(() => {
|
|
115
|
+
if (!ctx) return [];
|
|
116
|
+
const q = ctx.query.toLowerCase();
|
|
117
|
+
return options.filter((o) =>
|
|
118
|
+
q === '' ? true : o.value.toLowerCase().includes(q),
|
|
119
|
+
);
|
|
120
|
+
}, [ctx, options]);
|
|
121
|
+
|
|
122
|
+
React.useEffect(() => {
|
|
123
|
+
setActiveIdx(0);
|
|
124
|
+
}, [ctx?.query]);
|
|
125
|
+
|
|
126
|
+
const update = (next: string) => {
|
|
127
|
+
if (!isControlled) setInternal(next);
|
|
128
|
+
onChange?.(next);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
132
|
+
const next = e.target.value;
|
|
133
|
+
update(next);
|
|
134
|
+
const caret = e.target.selectionStart ?? next.length;
|
|
135
|
+
const t = getTriggerContext(next, caret, prefix);
|
|
136
|
+
setCtx(t);
|
|
137
|
+
if (t) onSearch?.(t.query, prefix);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const insertOption = (opt: MentionsOption) => {
|
|
141
|
+
if (!ctx || opt.disabled) return;
|
|
142
|
+
const el = innerRef.current;
|
|
143
|
+
const before = current.slice(0, ctx.start);
|
|
144
|
+
const after = current.slice(
|
|
145
|
+
(el?.selectionStart ?? ctx.start + 1 + ctx.query.length),
|
|
146
|
+
);
|
|
147
|
+
const inserted = `${prefix}${opt.value}${split}`;
|
|
148
|
+
const next = `${before}${inserted}${after}`;
|
|
149
|
+
update(next);
|
|
150
|
+
onSelect?.(opt, prefix);
|
|
151
|
+
setCtx(null);
|
|
152
|
+
// Restore caret right after the inserted mention.
|
|
153
|
+
window.requestAnimationFrame(() => {
|
|
154
|
+
if (!el) return;
|
|
155
|
+
const pos = (before + inserted).length;
|
|
156
|
+
el.focus();
|
|
157
|
+
el.setSelectionRange(pos, pos);
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
|
162
|
+
if (ctx && filtered.length > 0) {
|
|
163
|
+
if (e.key === 'ArrowDown') {
|
|
164
|
+
e.preventDefault();
|
|
165
|
+
setActiveIdx((i) => (i + 1) % filtered.length);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (e.key === 'ArrowUp') {
|
|
169
|
+
e.preventDefault();
|
|
170
|
+
setActiveIdx((i) => (i - 1 + filtered.length) % filtered.length);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (e.key === 'Enter' || e.key === 'Tab') {
|
|
174
|
+
const opt = filtered[activeIdx];
|
|
175
|
+
if (opt && !opt.disabled) {
|
|
176
|
+
e.preventDefault();
|
|
177
|
+
insertOption(opt);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (e.key === 'Escape') {
|
|
182
|
+
e.preventDefault();
|
|
183
|
+
setCtx(null);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
onKeyDown?.(e);
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<div className={cn('relative', className)}>
|
|
192
|
+
<Textarea
|
|
193
|
+
ref={innerRef}
|
|
194
|
+
value={current}
|
|
195
|
+
rows={rows}
|
|
196
|
+
disabled={disabled}
|
|
197
|
+
onChange={handleChange}
|
|
198
|
+
onKeyDown={handleKeyDown}
|
|
199
|
+
onBlur={() => {
|
|
200
|
+
// Delay so click-on-option can fire first.
|
|
201
|
+
window.setTimeout(() => setCtx(null), 120);
|
|
202
|
+
}}
|
|
203
|
+
{...props}
|
|
204
|
+
/>
|
|
205
|
+
{ctx && filtered.length > 0 ? (
|
|
206
|
+
<ul
|
|
207
|
+
role="listbox"
|
|
208
|
+
className="absolute left-0 top-full z-50 mt-1 max-h-60 w-56 overflow-auto rounded-md border bg-popover p-1 text-sm text-popover-foreground shadow-md"
|
|
209
|
+
>
|
|
210
|
+
{filtered.map((opt, i) => (
|
|
211
|
+
<li
|
|
212
|
+
key={opt.value}
|
|
213
|
+
role="option"
|
|
214
|
+
aria-selected={i === activeIdx}
|
|
215
|
+
onMouseDown={(e) => {
|
|
216
|
+
e.preventDefault();
|
|
217
|
+
insertOption(opt);
|
|
218
|
+
}}
|
|
219
|
+
onMouseEnter={() => setActiveIdx(i)}
|
|
220
|
+
className={cn(
|
|
221
|
+
'cursor-pointer rounded-sm px-2 py-1.5',
|
|
222
|
+
i === activeIdx && !opt.disabled && 'bg-accent text-accent-foreground',
|
|
223
|
+
opt.disabled && 'cursor-not-allowed opacity-50',
|
|
224
|
+
)}
|
|
225
|
+
>
|
|
226
|
+
{opt.label ?? opt.value}
|
|
227
|
+
</li>
|
|
228
|
+
))}
|
|
229
|
+
</ul>
|
|
230
|
+
) : null}
|
|
231
|
+
</div>
|
|
232
|
+
);
|
|
233
|
+
},
|
|
234
|
+
);
|
|
235
|
+
Mentions.displayName = 'Mentions';
|
|
236
|
+
|
|
237
|
+
export { Mentions };
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: menubar
|
|
3
|
+
name: Menubar
|
|
4
|
+
type: component
|
|
5
|
+
category: navigation
|
|
6
|
+
since: 0.1.0
|
|
7
|
+
package: "@teamix-evo/ui"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Menubar
|
|
11
|
+
|
|
12
|
+
应用顶部菜单栏 — Radix Menubar(典型 macOS 风格,文件 / 编辑 / 视图 / 帮助)。
|
|
13
|
+
**与 DropdownMenu 区别**:Menubar 是**横排多个一级菜单**,鼠标 hover 切换;DropdownMenu 是单个独立下拉。
|
|
14
|
+
|
|
15
|
+
## When to use
|
|
16
|
+
|
|
17
|
+
- 桌面应用 / 编辑器顶部菜单(VS Code / Figma 风格)
|
|
18
|
+
- 多组关联操作的横向汇总
|
|
19
|
+
|
|
20
|
+
## When NOT to use
|
|
21
|
+
|
|
22
|
+
- 单按钮触发 → `DropdownMenu`
|
|
23
|
+
- 右键 → `ContextMenu`
|
|
24
|
+
- 主导航(分类页面入口)→ `NavigationMenu`
|
|
25
|
+
|
|
26
|
+
## Props
|
|
27
|
+
|
|
28
|
+
> 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成。下表是 `Menubar`(Root) 的 props。
|
|
29
|
+
|
|
30
|
+
<!-- auto:props:begin -->
|
|
31
|
+
_(组件无 `<Name>Props` interface — props 详见 [`menubar.tsx`](./menubar.tsx))_
|
|
32
|
+
<!-- auto:props:end -->
|
|
33
|
+
|
|
34
|
+
## 依赖
|
|
35
|
+
|
|
36
|
+
> 以下表格由 `pnpm --filter @teamix-evo/ui gen:meta` 自动生成,数据源是 [`manifest.json`](../../../manifest.json)。**手工编辑 marker 之间的内容会在下次生成时被覆盖**。
|
|
37
|
+
|
|
38
|
+
<!-- auto:deps:begin -->
|
|
39
|
+
### 同库依赖
|
|
40
|
+
|
|
41
|
+
> `teamix-evo ui add menubar` 时,以下 entry 会被自动连带安装(无需手动 add)。
|
|
42
|
+
|
|
43
|
+
| Entry | 类型 | 描述 |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| `cn` | util | Tailwind className 合并工具(clsx + tailwind-merge) |
|
|
46
|
+
|
|
47
|
+
### npm 依赖
|
|
48
|
+
|
|
49
|
+
> 业务侧需要先 `pnpm add` / `npm install` 这些包。CLI 在 `ui add` 完成后会列出此提示。
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pnpm add @radix-ui/react-menubar@^1.1.0 lucide-react@^0.460.0
|
|
53
|
+
```
|
|
54
|
+
<!-- auto:deps:end -->
|
|
55
|
+
|
|
56
|
+
> 完整子组件:`Menubar` / `MenubarMenu` / `MenubarTrigger` / `MenubarContent` / `MenubarItem` / `MenubarCheckboxItem` / `MenubarRadioItem` / `MenubarLabel` / `MenubarSeparator` / `MenubarShortcut` / `MenubarSub` / `MenubarSubTrigger` / `MenubarSubContent` / `MenubarGroup` / `MenubarRadioGroup` / `MenubarPortal`。
|
|
57
|
+
|
|
58
|
+
## AI 生成纪律
|
|
59
|
+
|
|
60
|
+
- **Menubar 内必有 MenubarMenu**:每个一级菜单一个 `<MenubarMenu>`
|
|
61
|
+
- **行业惯例顺序**:文件 / 编辑 / 视图 / 工具 / 帮助 — 不要打乱
|
|
62
|
+
- **快捷键用 `<MenubarShortcut>`**:右对齐,与平台风格一致(macOS ⌘ / Windows Ctrl)
|
|
63
|
+
- **避免太深的 Sub 嵌套**:超过 2 级的子菜单影响可用性
|
|
64
|
+
|
|
65
|
+
## Examples
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
import {
|
|
69
|
+
Menubar, MenubarMenu, MenubarTrigger, MenubarContent,
|
|
70
|
+
MenubarItem, MenubarSeparator, MenubarShortcut,
|
|
71
|
+
MenubarSub, MenubarSubTrigger, MenubarSubContent,
|
|
72
|
+
} from '@/components/ui/menubar';
|
|
73
|
+
|
|
74
|
+
<Menubar>
|
|
75
|
+
<MenubarMenu>
|
|
76
|
+
<MenubarTrigger>文件</MenubarTrigger>
|
|
77
|
+
<MenubarContent>
|
|
78
|
+
<MenubarItem>新建文件 <MenubarShortcut>⌘N</MenubarShortcut></MenubarItem>
|
|
79
|
+
<MenubarItem>打开 <MenubarShortcut>⌘O</MenubarShortcut></MenubarItem>
|
|
80
|
+
<MenubarSeparator />
|
|
81
|
+
<MenubarSub>
|
|
82
|
+
<MenubarSubTrigger>分享</MenubarSubTrigger>
|
|
83
|
+
<MenubarSubContent>
|
|
84
|
+
<MenubarItem>邮件</MenubarItem>
|
|
85
|
+
<MenubarItem>消息</MenubarItem>
|
|
86
|
+
</MenubarSubContent>
|
|
87
|
+
</MenubarSub>
|
|
88
|
+
<MenubarSeparator />
|
|
89
|
+
<MenubarItem>退出 <MenubarShortcut>⌘Q</MenubarShortcut></MenubarItem>
|
|
90
|
+
</MenubarContent>
|
|
91
|
+
</MenubarMenu>
|
|
92
|
+
<MenubarMenu>
|
|
93
|
+
<MenubarTrigger>编辑</MenubarTrigger>
|
|
94
|
+
<MenubarContent>
|
|
95
|
+
<MenubarItem>撤销 <MenubarShortcut>⌘Z</MenubarShortcut></MenubarItem>
|
|
96
|
+
<MenubarItem>重做 <MenubarShortcut>⇧⌘Z</MenubarShortcut></MenubarItem>
|
|
97
|
+
</MenubarContent>
|
|
98
|
+
</MenubarMenu>
|
|
99
|
+
</Menubar>
|
|
100
|
+
```
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import {
|
|
3
|
+
Menubar,
|
|
4
|
+
MenubarMenu,
|
|
5
|
+
MenubarTrigger,
|
|
6
|
+
MenubarContent,
|
|
7
|
+
MenubarItem,
|
|
8
|
+
MenubarSeparator,
|
|
9
|
+
MenubarShortcut,
|
|
10
|
+
MenubarSub,
|
|
11
|
+
MenubarSubTrigger,
|
|
12
|
+
MenubarSubContent,
|
|
13
|
+
} from './menubar';
|
|
14
|
+
|
|
15
|
+
const meta: Meta<typeof Menubar> = {
|
|
16
|
+
title: '导航 · Navigation/Menubar',
|
|
17
|
+
component: Menubar,
|
|
18
|
+
tags: ['autodocs'],
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default meta;
|
|
22
|
+
type Story = StoryObj<typeof Menubar>;
|
|
23
|
+
|
|
24
|
+
export const Default: Story = {
|
|
25
|
+
render: () => (
|
|
26
|
+
<Menubar>
|
|
27
|
+
<MenubarMenu>
|
|
28
|
+
<MenubarTrigger>文件</MenubarTrigger>
|
|
29
|
+
<MenubarContent>
|
|
30
|
+
<MenubarItem>
|
|
31
|
+
新建文件 <MenubarShortcut>⌘N</MenubarShortcut>
|
|
32
|
+
</MenubarItem>
|
|
33
|
+
<MenubarItem>
|
|
34
|
+
打开 <MenubarShortcut>⌘O</MenubarShortcut>
|
|
35
|
+
</MenubarItem>
|
|
36
|
+
<MenubarSeparator />
|
|
37
|
+
<MenubarSub>
|
|
38
|
+
<MenubarSubTrigger>分享</MenubarSubTrigger>
|
|
39
|
+
<MenubarSubContent>
|
|
40
|
+
<MenubarItem>邮件</MenubarItem>
|
|
41
|
+
<MenubarItem>消息</MenubarItem>
|
|
42
|
+
<MenubarItem>复制链接</MenubarItem>
|
|
43
|
+
</MenubarSubContent>
|
|
44
|
+
</MenubarSub>
|
|
45
|
+
<MenubarSeparator />
|
|
46
|
+
<MenubarItem>
|
|
47
|
+
退出 <MenubarShortcut>⌘Q</MenubarShortcut>
|
|
48
|
+
</MenubarItem>
|
|
49
|
+
</MenubarContent>
|
|
50
|
+
</MenubarMenu>
|
|
51
|
+
<MenubarMenu>
|
|
52
|
+
<MenubarTrigger>编辑</MenubarTrigger>
|
|
53
|
+
<MenubarContent>
|
|
54
|
+
<MenubarItem>
|
|
55
|
+
撤销 <MenubarShortcut>⌘Z</MenubarShortcut>
|
|
56
|
+
</MenubarItem>
|
|
57
|
+
<MenubarItem>
|
|
58
|
+
重做 <MenubarShortcut>⇧⌘Z</MenubarShortcut>
|
|
59
|
+
</MenubarItem>
|
|
60
|
+
<MenubarSeparator />
|
|
61
|
+
<MenubarItem>
|
|
62
|
+
剪切 <MenubarShortcut>⌘X</MenubarShortcut>
|
|
63
|
+
</MenubarItem>
|
|
64
|
+
<MenubarItem>
|
|
65
|
+
复制 <MenubarShortcut>⌘C</MenubarShortcut>
|
|
66
|
+
</MenubarItem>
|
|
67
|
+
<MenubarItem>
|
|
68
|
+
粘贴 <MenubarShortcut>⌘V</MenubarShortcut>
|
|
69
|
+
</MenubarItem>
|
|
70
|
+
</MenubarContent>
|
|
71
|
+
</MenubarMenu>
|
|
72
|
+
<MenubarMenu>
|
|
73
|
+
<MenubarTrigger>视图</MenubarTrigger>
|
|
74
|
+
<MenubarContent>
|
|
75
|
+
<MenubarItem>侧边栏</MenubarItem>
|
|
76
|
+
<MenubarItem>状态栏</MenubarItem>
|
|
77
|
+
</MenubarContent>
|
|
78
|
+
</MenubarMenu>
|
|
79
|
+
</Menubar>
|
|
80
|
+
),
|
|
81
|
+
};
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import * as MenubarPrimitive from '@radix-ui/react-menubar';
|
|
3
|
+
import { Check, ChevronRight, Circle } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
import { cn } from '@/utils/cn';
|
|
6
|
+
|
|
7
|
+
// Explicit `typeof` annotations sidestep TS2742 — without them the inferred
|
|
8
|
+
// types reference deep `.pnpm/@radix-ui/react-context/...` paths which are
|
|
9
|
+
// "not portable" and break declaration emit / consumer typechecks.
|
|
10
|
+
const MenubarMenu: typeof MenubarPrimitive.Menu = MenubarPrimitive.Menu;
|
|
11
|
+
const MenubarGroup: typeof MenubarPrimitive.Group = MenubarPrimitive.Group;
|
|
12
|
+
const MenubarPortal: typeof MenubarPrimitive.Portal = MenubarPrimitive.Portal;
|
|
13
|
+
const MenubarSub: typeof MenubarPrimitive.Sub = MenubarPrimitive.Sub;
|
|
14
|
+
const MenubarRadioGroup: typeof MenubarPrimitive.RadioGroup =
|
|
15
|
+
MenubarPrimitive.RadioGroup;
|
|
16
|
+
|
|
17
|
+
const Menubar = React.forwardRef<
|
|
18
|
+
React.ElementRef<typeof MenubarPrimitive.Root>,
|
|
19
|
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
|
|
20
|
+
>(({ className, ...props }, ref) => (
|
|
21
|
+
<MenubarPrimitive.Root
|
|
22
|
+
ref={ref}
|
|
23
|
+
className={cn(
|
|
24
|
+
'flex h-9 items-center gap-1 rounded-md border bg-background p-1 shadow-sm',
|
|
25
|
+
className,
|
|
26
|
+
)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
));
|
|
30
|
+
Menubar.displayName = MenubarPrimitive.Root.displayName;
|
|
31
|
+
|
|
32
|
+
const MenubarTrigger = React.forwardRef<
|
|
33
|
+
React.ElementRef<typeof MenubarPrimitive.Trigger>,
|
|
34
|
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
|
|
35
|
+
>(({ className, ...props }, ref) => (
|
|
36
|
+
<MenubarPrimitive.Trigger
|
|
37
|
+
ref={ref}
|
|
38
|
+
className={cn(
|
|
39
|
+
'flex cursor-default select-none items-center rounded-sm px-3 py-1 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
));
|
|
45
|
+
MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName;
|
|
46
|
+
|
|
47
|
+
const MenubarSubTrigger = React.forwardRef<
|
|
48
|
+
React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
|
|
49
|
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
|
|
50
|
+
inset?: boolean;
|
|
51
|
+
}
|
|
52
|
+
>(({ className, inset, children, ...props }, ref) => (
|
|
53
|
+
<MenubarPrimitive.SubTrigger
|
|
54
|
+
ref={ref}
|
|
55
|
+
className={cn(
|
|
56
|
+
'flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
|
|
57
|
+
inset && 'pl-8',
|
|
58
|
+
className,
|
|
59
|
+
)}
|
|
60
|
+
{...props}
|
|
61
|
+
>
|
|
62
|
+
{children}
|
|
63
|
+
<ChevronRight className="ml-auto size-4" />
|
|
64
|
+
</MenubarPrimitive.SubTrigger>
|
|
65
|
+
));
|
|
66
|
+
MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName;
|
|
67
|
+
|
|
68
|
+
const MenubarSubContent = React.forwardRef<
|
|
69
|
+
React.ElementRef<typeof MenubarPrimitive.SubContent>,
|
|
70
|
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
|
|
71
|
+
>(({ className, ...props }, ref) => (
|
|
72
|
+
<MenubarPrimitive.SubContent
|
|
73
|
+
ref={ref}
|
|
74
|
+
className={cn(
|
|
75
|
+
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
|
76
|
+
className,
|
|
77
|
+
)}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
));
|
|
81
|
+
MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName;
|
|
82
|
+
|
|
83
|
+
const MenubarContent = React.forwardRef<
|
|
84
|
+
React.ElementRef<typeof MenubarPrimitive.Content>,
|
|
85
|
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
|
|
86
|
+
>(
|
|
87
|
+
(
|
|
88
|
+
{ className, align = 'start', alignOffset = -4, sideOffset = 8, ...props },
|
|
89
|
+
ref,
|
|
90
|
+
) => (
|
|
91
|
+
<MenubarPrimitive.Portal>
|
|
92
|
+
<MenubarPrimitive.Content
|
|
93
|
+
ref={ref}
|
|
94
|
+
align={align}
|
|
95
|
+
alignOffset={alignOffset}
|
|
96
|
+
sideOffset={sideOffset}
|
|
97
|
+
className={cn(
|
|
98
|
+
'z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
|
99
|
+
className,
|
|
100
|
+
)}
|
|
101
|
+
{...props}
|
|
102
|
+
/>
|
|
103
|
+
</MenubarPrimitive.Portal>
|
|
104
|
+
),
|
|
105
|
+
);
|
|
106
|
+
MenubarContent.displayName = MenubarPrimitive.Content.displayName;
|
|
107
|
+
|
|
108
|
+
const MenubarItem = React.forwardRef<
|
|
109
|
+
React.ElementRef<typeof MenubarPrimitive.Item>,
|
|
110
|
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
|
|
111
|
+
inset?: boolean;
|
|
112
|
+
}
|
|
113
|
+
>(({ className, inset, ...props }, ref) => (
|
|
114
|
+
<MenubarPrimitive.Item
|
|
115
|
+
ref={ref}
|
|
116
|
+
className={cn(
|
|
117
|
+
'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
118
|
+
inset && 'pl-8',
|
|
119
|
+
className,
|
|
120
|
+
)}
|
|
121
|
+
{...props}
|
|
122
|
+
/>
|
|
123
|
+
));
|
|
124
|
+
MenubarItem.displayName = MenubarPrimitive.Item.displayName;
|
|
125
|
+
|
|
126
|
+
const MenubarCheckboxItem = React.forwardRef<
|
|
127
|
+
React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
|
|
128
|
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
|
|
129
|
+
>(({ className, children, checked, ...props }, ref) => (
|
|
130
|
+
<MenubarPrimitive.CheckboxItem
|
|
131
|
+
ref={ref}
|
|
132
|
+
className={cn(
|
|
133
|
+
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
134
|
+
className,
|
|
135
|
+
)}
|
|
136
|
+
checked={checked}
|
|
137
|
+
{...props}
|
|
138
|
+
>
|
|
139
|
+
<span className="absolute left-2 flex size-3.5 items-center justify-center">
|
|
140
|
+
<MenubarPrimitive.ItemIndicator>
|
|
141
|
+
<Check className="size-4" />
|
|
142
|
+
</MenubarPrimitive.ItemIndicator>
|
|
143
|
+
</span>
|
|
144
|
+
{children}
|
|
145
|
+
</MenubarPrimitive.CheckboxItem>
|
|
146
|
+
));
|
|
147
|
+
MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName;
|
|
148
|
+
|
|
149
|
+
const MenubarRadioItem = React.forwardRef<
|
|
150
|
+
React.ElementRef<typeof MenubarPrimitive.RadioItem>,
|
|
151
|
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
|
|
152
|
+
>(({ className, children, ...props }, ref) => (
|
|
153
|
+
<MenubarPrimitive.RadioItem
|
|
154
|
+
ref={ref}
|
|
155
|
+
className={cn(
|
|
156
|
+
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
157
|
+
className,
|
|
158
|
+
)}
|
|
159
|
+
{...props}
|
|
160
|
+
>
|
|
161
|
+
<span className="absolute left-2 flex size-3.5 items-center justify-center">
|
|
162
|
+
<MenubarPrimitive.ItemIndicator>
|
|
163
|
+
<Circle className="size-2 fill-current" />
|
|
164
|
+
</MenubarPrimitive.ItemIndicator>
|
|
165
|
+
</span>
|
|
166
|
+
{children}
|
|
167
|
+
</MenubarPrimitive.RadioItem>
|
|
168
|
+
));
|
|
169
|
+
MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName;
|
|
170
|
+
|
|
171
|
+
const MenubarLabel = React.forwardRef<
|
|
172
|
+
React.ElementRef<typeof MenubarPrimitive.Label>,
|
|
173
|
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
|
|
174
|
+
inset?: boolean;
|
|
175
|
+
}
|
|
176
|
+
>(({ className, inset, ...props }, ref) => (
|
|
177
|
+
<MenubarPrimitive.Label
|
|
178
|
+
ref={ref}
|
|
179
|
+
className={cn(
|
|
180
|
+
'px-2 py-1.5 text-sm font-semibold',
|
|
181
|
+
inset && 'pl-8',
|
|
182
|
+
className,
|
|
183
|
+
)}
|
|
184
|
+
{...props}
|
|
185
|
+
/>
|
|
186
|
+
));
|
|
187
|
+
MenubarLabel.displayName = MenubarPrimitive.Label.displayName;
|
|
188
|
+
|
|
189
|
+
const MenubarSeparator = React.forwardRef<
|
|
190
|
+
React.ElementRef<typeof MenubarPrimitive.Separator>,
|
|
191
|
+
React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
|
|
192
|
+
>(({ className, ...props }, ref) => (
|
|
193
|
+
<MenubarPrimitive.Separator
|
|
194
|
+
ref={ref}
|
|
195
|
+
className={cn('-mx-1 my-1 h-px bg-muted', className)}
|
|
196
|
+
{...props}
|
|
197
|
+
/>
|
|
198
|
+
));
|
|
199
|
+
MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName;
|
|
200
|
+
|
|
201
|
+
const MenubarShortcut = ({
|
|
202
|
+
className,
|
|
203
|
+
...props
|
|
204
|
+
}: React.HTMLAttributes<HTMLSpanElement>) => (
|
|
205
|
+
<span
|
|
206
|
+
className={cn(
|
|
207
|
+
'ml-auto text-xs tracking-widest text-muted-foreground',
|
|
208
|
+
className,
|
|
209
|
+
)}
|
|
210
|
+
{...props}
|
|
211
|
+
/>
|
|
212
|
+
);
|
|
213
|
+
MenubarShortcut.displayName = 'MenubarShortcut';
|
|
214
|
+
|
|
215
|
+
export {
|
|
216
|
+
Menubar,
|
|
217
|
+
MenubarMenu,
|
|
218
|
+
MenubarTrigger,
|
|
219
|
+
MenubarContent,
|
|
220
|
+
MenubarItem,
|
|
221
|
+
MenubarSeparator,
|
|
222
|
+
MenubarLabel,
|
|
223
|
+
MenubarCheckboxItem,
|
|
224
|
+
MenubarRadioGroup,
|
|
225
|
+
MenubarRadioItem,
|
|
226
|
+
MenubarPortal,
|
|
227
|
+
MenubarSubContent,
|
|
228
|
+
MenubarSubTrigger,
|
|
229
|
+
MenubarGroup,
|
|
230
|
+
MenubarSub,
|
|
231
|
+
MenubarShortcut,
|
|
232
|
+
};
|