@emara/ui 1.1.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.
- package/components/ui/.gitkeep +0 -0
- package/components/ui/accordion.stories.tsx +231 -0
- package/components/ui/accordion.tsx +250 -0
- package/components/ui/app-shell.stories.tsx +270 -0
- package/components/ui/app-shell.tsx +491 -0
- package/components/ui/avatar.stories.tsx +174 -0
- package/components/ui/avatar.tsx +257 -0
- package/components/ui/badge.stories.tsx +127 -0
- package/components/ui/badge.tsx +146 -0
- package/components/ui/breadcrumb.stories.tsx +92 -0
- package/components/ui/breadcrumb.tsx +302 -0
- package/components/ui/button.stories.tsx +186 -0
- package/components/ui/button.tsx +128 -0
- package/components/ui/card.stories.tsx +279 -0
- package/components/ui/card.tsx +250 -0
- package/components/ui/checkbox.stories.tsx +93 -0
- package/components/ui/checkbox.tsx +131 -0
- package/components/ui/combobox.stories.tsx +489 -0
- package/components/ui/combobox.tsx +874 -0
- package/components/ui/context-menu.stories.tsx +202 -0
- package/components/ui/context-menu.tsx +309 -0
- package/components/ui/data-table.stories.tsx +227 -0
- package/components/ui/data-table.tsx +539 -0
- package/components/ui/date-picker.stories.tsx +225 -0
- package/components/ui/date-picker.tsx +597 -0
- package/components/ui/dialog.stories.tsx +193 -0
- package/components/ui/dialog.tsx +262 -0
- package/components/ui/divider.stories.tsx +84 -0
- package/components/ui/divider.tsx +135 -0
- package/components/ui/drawer.stories.tsx +218 -0
- package/components/ui/drawer.tsx +329 -0
- package/components/ui/dropdown-menu.stories.tsx +270 -0
- package/components/ui/dropdown-menu.tsx +353 -0
- package/components/ui/empty-state.stories.tsx +121 -0
- package/components/ui/empty-state.tsx +289 -0
- package/components/ui/field-group.stories.tsx +201 -0
- package/components/ui/field-group.tsx +276 -0
- package/components/ui/form.stories.tsx +219 -0
- package/components/ui/form.tsx +542 -0
- package/components/ui/input.stories.tsx +154 -0
- package/components/ui/input.tsx +208 -0
- package/components/ui/label.stories.tsx +84 -0
- package/components/ui/label.tsx +98 -0
- package/components/ui/page-header.stories.tsx +136 -0
- package/components/ui/page-header.tsx +315 -0
- package/components/ui/pagination.stories.tsx +136 -0
- package/components/ui/pagination.tsx +427 -0
- package/components/ui/popover.stories.tsx +212 -0
- package/components/ui/popover.tsx +167 -0
- package/components/ui/radio-group.stories.tsx +96 -0
- package/components/ui/radio-group.tsx +250 -0
- package/components/ui/select.stories.tsx +203 -0
- package/components/ui/select.tsx +318 -0
- package/components/ui/sidebar.stories.tsx +186 -0
- package/components/ui/sidebar.tsx +623 -0
- package/components/ui/skeleton.stories.tsx +131 -0
- package/components/ui/skeleton.tsx +311 -0
- package/components/ui/switch.stories.tsx +74 -0
- package/components/ui/switch.tsx +186 -0
- package/components/ui/table.stories.tsx +107 -0
- package/components/ui/table.tsx +285 -0
- package/components/ui/tabs.stories.tsx +222 -0
- package/components/ui/tabs.tsx +287 -0
- package/components/ui/textarea.stories.tsx +96 -0
- package/components/ui/textarea.tsx +182 -0
- package/components/ui/toast.stories.tsx +169 -0
- package/components/ui/toast.tsx +250 -0
- package/components/ui/tooltip.stories.tsx +146 -0
- package/components/ui/tooltip.tsx +156 -0
- package/components/ui/top-bar.stories.tsx +182 -0
- package/components/ui/top-bar.tsx +155 -0
- package/dist/components/ui/accordion.d.ts +45 -0
- package/dist/components/ui/accordion.d.ts.map +1 -0
- package/dist/components/ui/accordion.js +99 -0
- package/dist/components/ui/accordion.js.map +1 -0
- package/dist/components/ui/app-shell.d.ts +70 -0
- package/dist/components/ui/app-shell.d.ts.map +1 -0
- package/dist/components/ui/app-shell.js +199 -0
- package/dist/components/ui/app-shell.js.map +1 -0
- package/dist/components/ui/avatar.d.ts +41 -0
- package/dist/components/ui/avatar.d.ts.map +1 -0
- package/dist/components/ui/avatar.js +104 -0
- package/dist/components/ui/avatar.js.map +1 -0
- package/dist/components/ui/badge.d.ts +27 -0
- package/dist/components/ui/badge.d.ts.map +1 -0
- package/dist/components/ui/badge.js +65 -0
- package/dist/components/ui/badge.js.map +1 -0
- package/dist/components/ui/breadcrumb.d.ts +35 -0
- package/dist/components/ui/breadcrumb.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb.js +88 -0
- package/dist/components/ui/breadcrumb.js.map +1 -0
- package/dist/components/ui/button.d.ts +26 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/button.js +73 -0
- package/dist/components/ui/button.js.map +1 -0
- package/dist/components/ui/card.d.ts +52 -0
- package/dist/components/ui/card.d.ts.map +1 -0
- package/dist/components/ui/card.js +96 -0
- package/dist/components/ui/card.js.map +1 -0
- package/dist/components/ui/checkbox.d.ts +18 -0
- package/dist/components/ui/checkbox.d.ts.map +1 -0
- package/dist/components/ui/checkbox.js +59 -0
- package/dist/components/ui/checkbox.js.map +1 -0
- package/dist/components/ui/combobox.d.ts +194 -0
- package/dist/components/ui/combobox.d.ts.map +1 -0
- package/dist/components/ui/combobox.js +361 -0
- package/dist/components/ui/combobox.js.map +1 -0
- package/dist/components/ui/context-menu.d.ts +46 -0
- package/dist/components/ui/context-menu.d.ts.map +1 -0
- package/dist/components/ui/context-menu.js +95 -0
- package/dist/components/ui/context-menu.js.map +1 -0
- package/dist/components/ui/data-table.d.ts +53 -0
- package/dist/components/ui/data-table.d.ts.map +1 -0
- package/dist/components/ui/data-table.js +163 -0
- package/dist/components/ui/data-table.js.map +1 -0
- package/dist/components/ui/date-picker.d.ts +103 -0
- package/dist/components/ui/date-picker.d.ts.map +1 -0
- package/dist/components/ui/date-picker.js +306 -0
- package/dist/components/ui/date-picker.js.map +1 -0
- package/dist/components/ui/dialog.d.ts +40 -0
- package/dist/components/ui/dialog.d.ts.map +1 -0
- package/dist/components/ui/dialog.js +110 -0
- package/dist/components/ui/dialog.js.map +1 -0
- package/dist/components/ui/divider.d.ts +30 -0
- package/dist/components/ui/divider.d.ts.map +1 -0
- package/dist/components/ui/divider.js +62 -0
- package/dist/components/ui/divider.js.map +1 -0
- package/dist/components/ui/drawer.d.ts +56 -0
- package/dist/components/ui/drawer.d.ts.map +1 -0
- package/dist/components/ui/drawer.js +147 -0
- package/dist/components/ui/drawer.js.map +1 -0
- package/dist/components/ui/dropdown-menu.d.ts +63 -0
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu.js +116 -0
- package/dist/components/ui/dropdown-menu.js.map +1 -0
- package/dist/components/ui/empty-state.d.ts +43 -0
- package/dist/components/ui/empty-state.d.ts.map +1 -0
- package/dist/components/ui/empty-state.js +128 -0
- package/dist/components/ui/empty-state.js.map +1 -0
- package/dist/components/ui/field-group.d.ts +38 -0
- package/dist/components/ui/field-group.d.ts.map +1 -0
- package/dist/components/ui/field-group.js +107 -0
- package/dist/components/ui/field-group.js.map +1 -0
- package/dist/components/ui/form.d.ts +67 -0
- package/dist/components/ui/form.d.ts.map +1 -0
- package/dist/components/ui/form.js +286 -0
- package/dist/components/ui/form.js.map +1 -0
- package/dist/components/ui/input.d.ts +36 -0
- package/dist/components/ui/input.d.ts.map +1 -0
- package/dist/components/ui/input.js +99 -0
- package/dist/components/ui/input.js.map +1 -0
- package/dist/components/ui/label.d.ts +37 -0
- package/dist/components/ui/label.d.ts.map +1 -0
- package/dist/components/ui/label.js +34 -0
- package/dist/components/ui/label.js.map +1 -0
- package/dist/components/ui/page-header.d.ts +65 -0
- package/dist/components/ui/page-header.d.ts.map +1 -0
- package/dist/components/ui/page-header.js +140 -0
- package/dist/components/ui/page-header.js.map +1 -0
- package/dist/components/ui/pagination.d.ts +67 -0
- package/dist/components/ui/pagination.d.ts.map +1 -0
- package/dist/components/ui/pagination.js +109 -0
- package/dist/components/ui/pagination.js.map +1 -0
- package/dist/components/ui/popover.d.ts +28 -0
- package/dist/components/ui/popover.d.ts.map +1 -0
- package/dist/components/ui/popover.js +85 -0
- package/dist/components/ui/popover.js.map +1 -0
- package/dist/components/ui/radio-group.d.ts +35 -0
- package/dist/components/ui/radio-group.d.ts.map +1 -0
- package/dist/components/ui/radio-group.js +103 -0
- package/dist/components/ui/radio-group.js.map +1 -0
- package/dist/components/ui/select.d.ts +42 -0
- package/dist/components/ui/select.d.ts.map +1 -0
- package/dist/components/ui/select.js +86 -0
- package/dist/components/ui/select.js.map +1 -0
- package/dist/components/ui/sidebar.d.ts +59 -0
- package/dist/components/ui/sidebar.d.ts.map +1 -0
- package/dist/components/ui/sidebar.js +189 -0
- package/dist/components/ui/sidebar.js.map +1 -0
- package/dist/components/ui/skeleton.d.ts +77 -0
- package/dist/components/ui/skeleton.d.ts.map +1 -0
- package/dist/components/ui/skeleton.js +115 -0
- package/dist/components/ui/skeleton.js.map +1 -0
- package/dist/components/ui/switch.d.ts +26 -0
- package/dist/components/ui/switch.d.ts.map +1 -0
- package/dist/components/ui/switch.js +84 -0
- package/dist/components/ui/switch.js.map +1 -0
- package/dist/components/ui/table.d.ts +52 -0
- package/dist/components/ui/table.d.ts.map +1 -0
- package/dist/components/ui/table.js +109 -0
- package/dist/components/ui/table.js.map +1 -0
- package/dist/components/ui/tabs.d.ts +42 -0
- package/dist/components/ui/tabs.d.ts.map +1 -0
- package/dist/components/ui/tabs.js +163 -0
- package/dist/components/ui/tabs.js.map +1 -0
- package/dist/components/ui/textarea.d.ts +26 -0
- package/dist/components/ui/textarea.d.ts.map +1 -0
- package/dist/components/ui/textarea.js +96 -0
- package/dist/components/ui/textarea.js.map +1 -0
- package/dist/components/ui/toast.d.ts +77 -0
- package/dist/components/ui/toast.d.ts.map +1 -0
- package/dist/components/ui/toast.js +141 -0
- package/dist/components/ui/toast.js.map +1 -0
- package/dist/components/ui/tooltip.d.ts +31 -0
- package/dist/components/ui/tooltip.d.ts.map +1 -0
- package/dist/components/ui/tooltip.js +71 -0
- package/dist/components/ui/tooltip.js.map +1 -0
- package/dist/components/ui/top-bar.d.ts +30 -0
- package/dist/components/ui/top-bar.d.ts.map +1 -0
- package/dist/components/ui/top-bar.js +64 -0
- package/dist/components/ui/top-bar.js.map +1 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +6 -0
- package/dist/lib/utils.js.map +1 -0
- package/lib/utils.ts +6 -0
- package/package.json +112 -0
- package/styles/globals.css +685 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import {
|
|
4
|
+
RiDeleteBinLine,
|
|
5
|
+
RiEditLine,
|
|
6
|
+
RiFileCopyLine,
|
|
7
|
+
RiShareLine,
|
|
8
|
+
} from "@remixicon/react";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
ContextMenu,
|
|
12
|
+
ContextMenuCheckboxItem,
|
|
13
|
+
ContextMenuContent,
|
|
14
|
+
ContextMenuItem,
|
|
15
|
+
ContextMenuLabel,
|
|
16
|
+
ContextMenuRadioGroup,
|
|
17
|
+
ContextMenuRadioItem,
|
|
18
|
+
ContextMenuSeparator,
|
|
19
|
+
ContextMenuShortcut,
|
|
20
|
+
ContextMenuSub,
|
|
21
|
+
ContextMenuSubContent,
|
|
22
|
+
ContextMenuSubTrigger,
|
|
23
|
+
ContextMenuTrigger,
|
|
24
|
+
} from "./context-menu";
|
|
25
|
+
|
|
26
|
+
const meta: Meta<typeof ContextMenu> = {
|
|
27
|
+
title: "Overlays/ContextMenu",
|
|
28
|
+
component: ContextMenu,
|
|
29
|
+
parameters: { layout: "centered" },
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default meta;
|
|
33
|
+
type Story = StoryObj<typeof ContextMenu>;
|
|
34
|
+
|
|
35
|
+
function Surface({ label }: { label: string }) {
|
|
36
|
+
return (
|
|
37
|
+
<div className="flex h-40 w-80 items-center justify-center rounded-md border border-dashed border-border text-sm text-muted-foreground">
|
|
38
|
+
{label}
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const Default: Story = {
|
|
44
|
+
render: () => (
|
|
45
|
+
<ContextMenu>
|
|
46
|
+
<ContextMenuTrigger>
|
|
47
|
+
<Surface label="Right-click anywhere in this area" />
|
|
48
|
+
</ContextMenuTrigger>
|
|
49
|
+
<ContextMenuContent>
|
|
50
|
+
<ContextMenuLabel>Actions</ContextMenuLabel>
|
|
51
|
+
<ContextMenuItem icon={<RiEditLine />}>Edit</ContextMenuItem>
|
|
52
|
+
<ContextMenuItem icon={<RiFileCopyLine />}>Duplicate</ContextMenuItem>
|
|
53
|
+
<ContextMenuItem icon={<RiShareLine />}>Share</ContextMenuItem>
|
|
54
|
+
<ContextMenuSeparator />
|
|
55
|
+
<ContextMenuItem variant="destructive" icon={<RiDeleteBinLine />}>
|
|
56
|
+
Delete
|
|
57
|
+
</ContextMenuItem>
|
|
58
|
+
</ContextMenuContent>
|
|
59
|
+
</ContextMenu>
|
|
60
|
+
),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const WithShortcuts: Story = {
|
|
64
|
+
render: () => (
|
|
65
|
+
<ContextMenu>
|
|
66
|
+
<ContextMenuTrigger>
|
|
67
|
+
<Surface label="Right-click for shortcuts" />
|
|
68
|
+
</ContextMenuTrigger>
|
|
69
|
+
<ContextMenuContent>
|
|
70
|
+
<ContextMenuItem icon={<RiFileCopyLine />}>
|
|
71
|
+
Copy
|
|
72
|
+
<ContextMenuShortcut keys={["⌘", "C"]} />
|
|
73
|
+
</ContextMenuItem>
|
|
74
|
+
<ContextMenuItem icon={<RiEditLine />}>
|
|
75
|
+
Paste
|
|
76
|
+
<ContextMenuShortcut keys={["⌘", "V"]} />
|
|
77
|
+
</ContextMenuItem>
|
|
78
|
+
<ContextMenuSeparator />
|
|
79
|
+
<ContextMenuItem variant="destructive" icon={<RiDeleteBinLine />}>
|
|
80
|
+
Delete
|
|
81
|
+
<ContextMenuShortcut keys={["⌫"]} />
|
|
82
|
+
</ContextMenuItem>
|
|
83
|
+
</ContextMenuContent>
|
|
84
|
+
</ContextMenu>
|
|
85
|
+
),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const CheckboxItems: Story = {
|
|
89
|
+
render: () => {
|
|
90
|
+
const Wrapper = () => {
|
|
91
|
+
const [grid, setGrid] = useState(true);
|
|
92
|
+
const [guides, setGuides] = useState(false);
|
|
93
|
+
const [snap, setSnap] = useState(false);
|
|
94
|
+
return (
|
|
95
|
+
<ContextMenu>
|
|
96
|
+
<ContextMenuTrigger>
|
|
97
|
+
<Surface label="Right-click — view toggles" />
|
|
98
|
+
</ContextMenuTrigger>
|
|
99
|
+
<ContextMenuContent>
|
|
100
|
+
<ContextMenuLabel>View</ContextMenuLabel>
|
|
101
|
+
<ContextMenuCheckboxItem checked={grid} onCheckedChange={setGrid}>
|
|
102
|
+
Show grid
|
|
103
|
+
</ContextMenuCheckboxItem>
|
|
104
|
+
<ContextMenuCheckboxItem checked={guides} onCheckedChange={setGuides}>
|
|
105
|
+
Show guides
|
|
106
|
+
</ContextMenuCheckboxItem>
|
|
107
|
+
<ContextMenuCheckboxItem checked={snap} onCheckedChange={setSnap}>
|
|
108
|
+
Snap to grid
|
|
109
|
+
</ContextMenuCheckboxItem>
|
|
110
|
+
</ContextMenuContent>
|
|
111
|
+
</ContextMenu>
|
|
112
|
+
);
|
|
113
|
+
};
|
|
114
|
+
return <Wrapper />;
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const RadioItems: Story = {
|
|
119
|
+
render: () => {
|
|
120
|
+
const Wrapper = () => {
|
|
121
|
+
const [zoom, setZoom] = useState("100");
|
|
122
|
+
return (
|
|
123
|
+
<ContextMenu>
|
|
124
|
+
<ContextMenuTrigger>
|
|
125
|
+
<Surface label={`Right-click — zoom ${zoom}%`} />
|
|
126
|
+
</ContextMenuTrigger>
|
|
127
|
+
<ContextMenuContent>
|
|
128
|
+
<ContextMenuLabel>Zoom</ContextMenuLabel>
|
|
129
|
+
<ContextMenuRadioGroup value={zoom} onValueChange={setZoom}>
|
|
130
|
+
<ContextMenuRadioItem value="50">50%</ContextMenuRadioItem>
|
|
131
|
+
<ContextMenuRadioItem value="100">100%</ContextMenuRadioItem>
|
|
132
|
+
<ContextMenuRadioItem value="150">150%</ContextMenuRadioItem>
|
|
133
|
+
<ContextMenuRadioItem value="200">200%</ContextMenuRadioItem>
|
|
134
|
+
</ContextMenuRadioGroup>
|
|
135
|
+
</ContextMenuContent>
|
|
136
|
+
</ContextMenu>
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
return <Wrapper />;
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export const Submenus: Story = {
|
|
144
|
+
render: () => (
|
|
145
|
+
<ContextMenu>
|
|
146
|
+
<ContextMenuTrigger>
|
|
147
|
+
<Surface label="Right-click for submenus" />
|
|
148
|
+
</ContextMenuTrigger>
|
|
149
|
+
<ContextMenuContent>
|
|
150
|
+
<ContextMenuItem>Open</ContextMenuItem>
|
|
151
|
+
<ContextMenuSub>
|
|
152
|
+
<ContextMenuSubTrigger>Open with…</ContextMenuSubTrigger>
|
|
153
|
+
<ContextMenuSubContent>
|
|
154
|
+
<ContextMenuItem>Visual Studio Code</ContextMenuItem>
|
|
155
|
+
<ContextMenuItem>Sublime Text</ContextMenuItem>
|
|
156
|
+
<ContextMenuItem>Vim</ContextMenuItem>
|
|
157
|
+
</ContextMenuSubContent>
|
|
158
|
+
</ContextMenuSub>
|
|
159
|
+
<ContextMenuSeparator />
|
|
160
|
+
<ContextMenuSub>
|
|
161
|
+
<ContextMenuSubTrigger>Move to</ContextMenuSubTrigger>
|
|
162
|
+
<ContextMenuSubContent>
|
|
163
|
+
<ContextMenuItem>Documents</ContextMenuItem>
|
|
164
|
+
<ContextMenuItem>Downloads</ContextMenuItem>
|
|
165
|
+
<ContextMenuItem>Trash</ContextMenuItem>
|
|
166
|
+
</ContextMenuSubContent>
|
|
167
|
+
</ContextMenuSub>
|
|
168
|
+
</ContextMenuContent>
|
|
169
|
+
</ContextMenu>
|
|
170
|
+
),
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export const WithDescriptions: Story = {
|
|
174
|
+
render: () => (
|
|
175
|
+
<ContextMenu>
|
|
176
|
+
<ContextMenuTrigger>
|
|
177
|
+
<Surface label="Right-click for descriptions" />
|
|
178
|
+
</ContextMenuTrigger>
|
|
179
|
+
<ContextMenuContent>
|
|
180
|
+
<ContextMenuItem
|
|
181
|
+
icon={<RiEditLine />}
|
|
182
|
+
description="Open the rich text editor."
|
|
183
|
+
>
|
|
184
|
+
Edit content
|
|
185
|
+
</ContextMenuItem>
|
|
186
|
+
<ContextMenuItem
|
|
187
|
+
icon={<RiShareLine />}
|
|
188
|
+
description="Generate a shareable link."
|
|
189
|
+
>
|
|
190
|
+
Share
|
|
191
|
+
</ContextMenuItem>
|
|
192
|
+
<ContextMenuItem
|
|
193
|
+
variant="destructive"
|
|
194
|
+
icon={<RiDeleteBinLine />}
|
|
195
|
+
description="Permanently remove."
|
|
196
|
+
>
|
|
197
|
+
Delete
|
|
198
|
+
</ContextMenuItem>
|
|
199
|
+
</ContextMenuContent>
|
|
200
|
+
</ContextMenu>
|
|
201
|
+
),
|
|
202
|
+
};
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { forwardRef } from "react";
|
|
4
|
+
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
|
|
5
|
+
import { RiArrowRightSLine, RiCheckLine, RiCircleFill, RiLoader2Line } from "@remixicon/react";
|
|
6
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
7
|
+
|
|
8
|
+
import { cn } from "@/lib/utils";
|
|
9
|
+
|
|
10
|
+
// Per docs/emara-ui-phase-3-components.md §6. Same API shape as DropdownMenu;
|
|
11
|
+
// the only difference is `Trigger` wraps any element and listens for the
|
|
12
|
+
// context-menu event (right-click / long-press) instead of being a button.
|
|
13
|
+
|
|
14
|
+
const ContextMenu = ContextMenuPrimitive.Root;
|
|
15
|
+
const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
|
|
16
|
+
const ContextMenuGroup = ContextMenuPrimitive.Group;
|
|
17
|
+
const ContextMenuPortal = ContextMenuPrimitive.Portal;
|
|
18
|
+
const ContextMenuSub = ContextMenuPrimitive.Sub;
|
|
19
|
+
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
|
|
20
|
+
|
|
21
|
+
// ----------------------------------------------------------------------------
|
|
22
|
+
// ContextMenuContent
|
|
23
|
+
// ----------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
const contextContentVariants = cva(
|
|
26
|
+
[
|
|
27
|
+
"z-popover min-w-32 overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md",
|
|
28
|
+
"data-[state=open]:animate-[scale-in_var(--duration-fast)_var(--ease-out)]",
|
|
29
|
+
"data-[state=closed]:animate-[scale-out_var(--duration-fast)_var(--ease-in)]",
|
|
30
|
+
].join(" "),
|
|
31
|
+
{
|
|
32
|
+
variants: {
|
|
33
|
+
size: {
|
|
34
|
+
sm: "py-1 text-xs",
|
|
35
|
+
md: "py-1 text-sm",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
defaultVariants: { size: "md" },
|
|
39
|
+
},
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
type ContextMenuContentProps = React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content> &
|
|
43
|
+
VariantProps<typeof contextContentVariants>;
|
|
44
|
+
|
|
45
|
+
const ContextMenuContent = forwardRef<
|
|
46
|
+
React.ElementRef<typeof ContextMenuPrimitive.Content>,
|
|
47
|
+
ContextMenuContentProps
|
|
48
|
+
>(function ContextMenuContent({ className, size, ...props }, ref) {
|
|
49
|
+
return (
|
|
50
|
+
<ContextMenuPortal>
|
|
51
|
+
<ContextMenuPrimitive.Content
|
|
52
|
+
ref={ref}
|
|
53
|
+
className={cn(contextContentVariants({ size }), className)}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
</ContextMenuPortal>
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
ContextMenuContent.displayName = "ContextMenuContent";
|
|
60
|
+
|
|
61
|
+
// ----------------------------------------------------------------------------
|
|
62
|
+
// Shared item base
|
|
63
|
+
// ----------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
const itemBase = [
|
|
66
|
+
"relative flex cursor-pointer select-none items-start gap-2 rounded-sm py-1.5 ps-8 pe-2 text-sm outline-none",
|
|
67
|
+
"transition-colors",
|
|
68
|
+
"focus:bg-accent focus:text-accent-foreground",
|
|
69
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
70
|
+
"aria-busy:cursor-progress",
|
|
71
|
+
].join(" ");
|
|
72
|
+
|
|
73
|
+
// ----------------------------------------------------------------------------
|
|
74
|
+
// ContextMenuItem (Emara additions: variant/icon/description/kbd/loading)
|
|
75
|
+
// ----------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
type ContextMenuItemProps = React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
|
|
78
|
+
variant?: "default" | "destructive";
|
|
79
|
+
icon?: React.ReactNode;
|
|
80
|
+
description?: React.ReactNode;
|
|
81
|
+
kbd?: string[];
|
|
82
|
+
loading?: boolean;
|
|
83
|
+
inset?: boolean;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const ContextMenuItem = forwardRef<
|
|
87
|
+
React.ElementRef<typeof ContextMenuPrimitive.Item>,
|
|
88
|
+
ContextMenuItemProps
|
|
89
|
+
>(function ContextMenuItem(
|
|
90
|
+
{
|
|
91
|
+
className,
|
|
92
|
+
variant = "default",
|
|
93
|
+
icon,
|
|
94
|
+
description,
|
|
95
|
+
kbd,
|
|
96
|
+
loading = false,
|
|
97
|
+
inset = false,
|
|
98
|
+
children,
|
|
99
|
+
onSelect,
|
|
100
|
+
...props
|
|
101
|
+
},
|
|
102
|
+
ref,
|
|
103
|
+
) {
|
|
104
|
+
return (
|
|
105
|
+
<ContextMenuPrimitive.Item
|
|
106
|
+
ref={ref}
|
|
107
|
+
aria-busy={loading || undefined}
|
|
108
|
+
onSelect={(e) => {
|
|
109
|
+
if (loading) {
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
onSelect?.(e);
|
|
114
|
+
}}
|
|
115
|
+
className={cn(
|
|
116
|
+
itemBase,
|
|
117
|
+
variant === "destructive" &&
|
|
118
|
+
"text-destructive focus:bg-destructive/10 focus:text-destructive",
|
|
119
|
+
inset && "ps-2",
|
|
120
|
+
className,
|
|
121
|
+
)}
|
|
122
|
+
{...props}
|
|
123
|
+
>
|
|
124
|
+
<span className="absolute start-2 inline-flex h-4 w-4 items-center justify-center text-current">
|
|
125
|
+
{loading ? <RiLoader2Line className="size-3.5 animate-spin" /> : (icon ?? null)}
|
|
126
|
+
</span>
|
|
127
|
+
<span className="flex flex-1 flex-col gap-0.5 leading-none">
|
|
128
|
+
<span>{children}</span>
|
|
129
|
+
{description ? <span className="text-muted-foreground text-xs">{description}</span> : null}
|
|
130
|
+
</span>
|
|
131
|
+
{kbd ? (
|
|
132
|
+
<span className="text-muted-foreground ms-auto inline-flex items-center gap-0.5 text-xs">
|
|
133
|
+
{kbd.map((k, i) => (
|
|
134
|
+
<kbd
|
|
135
|
+
key={`${k}-${i}`}
|
|
136
|
+
className="border-border bg-muted min-w-[1em] rounded border px-1 font-mono text-[10px] leading-tight"
|
|
137
|
+
>
|
|
138
|
+
{k}
|
|
139
|
+
</kbd>
|
|
140
|
+
))}
|
|
141
|
+
</span>
|
|
142
|
+
) : null}
|
|
143
|
+
</ContextMenuPrimitive.Item>
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
ContextMenuItem.displayName = "ContextMenuItem";
|
|
147
|
+
|
|
148
|
+
// ----------------------------------------------------------------------------
|
|
149
|
+
// CheckboxItem / RadioItem
|
|
150
|
+
// ----------------------------------------------------------------------------
|
|
151
|
+
|
|
152
|
+
const ContextMenuCheckboxItem = forwardRef<
|
|
153
|
+
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
|
|
154
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
|
|
155
|
+
>(function ContextMenuCheckboxItem({ className, children, ...props }, ref) {
|
|
156
|
+
return (
|
|
157
|
+
<ContextMenuPrimitive.CheckboxItem ref={ref} className={cn(itemBase, className)} {...props}>
|
|
158
|
+
<span className="absolute start-2 inline-flex h-4 w-4 items-center justify-center">
|
|
159
|
+
<ContextMenuPrimitive.ItemIndicator>
|
|
160
|
+
<RiCheckLine className="size-4" />
|
|
161
|
+
</ContextMenuPrimitive.ItemIndicator>
|
|
162
|
+
</span>
|
|
163
|
+
<span className="flex-1">{children}</span>
|
|
164
|
+
</ContextMenuPrimitive.CheckboxItem>
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
ContextMenuCheckboxItem.displayName = "ContextMenuCheckboxItem";
|
|
168
|
+
|
|
169
|
+
const ContextMenuRadioItem = forwardRef<
|
|
170
|
+
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
|
|
171
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
|
|
172
|
+
>(function ContextMenuRadioItem({ className, children, ...props }, ref) {
|
|
173
|
+
return (
|
|
174
|
+
<ContextMenuPrimitive.RadioItem ref={ref} className={cn(itemBase, className)} {...props}>
|
|
175
|
+
<span className="absolute start-2 inline-flex h-4 w-4 items-center justify-center">
|
|
176
|
+
<ContextMenuPrimitive.ItemIndicator>
|
|
177
|
+
<RiCircleFill className="size-2 fill-current" />
|
|
178
|
+
</ContextMenuPrimitive.ItemIndicator>
|
|
179
|
+
</span>
|
|
180
|
+
<span className="flex-1">{children}</span>
|
|
181
|
+
</ContextMenuPrimitive.RadioItem>
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
ContextMenuRadioItem.displayName = "ContextMenuRadioItem";
|
|
185
|
+
|
|
186
|
+
// ----------------------------------------------------------------------------
|
|
187
|
+
// Label / Separator / Shortcut
|
|
188
|
+
// ----------------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
const ContextMenuLabel = forwardRef<
|
|
191
|
+
React.ElementRef<typeof ContextMenuPrimitive.Label>,
|
|
192
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label>
|
|
193
|
+
>(function ContextMenuLabel({ className, ...props }, ref) {
|
|
194
|
+
return (
|
|
195
|
+
<ContextMenuPrimitive.Label
|
|
196
|
+
ref={ref}
|
|
197
|
+
className={cn(
|
|
198
|
+
"text-muted-foreground px-2 py-1.5 text-xs font-semibold tracking-wide uppercase",
|
|
199
|
+
className,
|
|
200
|
+
)}
|
|
201
|
+
{...props}
|
|
202
|
+
/>
|
|
203
|
+
);
|
|
204
|
+
});
|
|
205
|
+
ContextMenuLabel.displayName = "ContextMenuLabel";
|
|
206
|
+
|
|
207
|
+
const ContextMenuSeparator = forwardRef<
|
|
208
|
+
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
|
|
209
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
|
|
210
|
+
>(function ContextMenuSeparator({ className, ...props }, ref) {
|
|
211
|
+
return (
|
|
212
|
+
<ContextMenuPrimitive.Separator
|
|
213
|
+
ref={ref}
|
|
214
|
+
className={cn("bg-border -mx-1 my-1 h-px", className)}
|
|
215
|
+
{...props}
|
|
216
|
+
/>
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
ContextMenuSeparator.displayName = "ContextMenuSeparator";
|
|
220
|
+
|
|
221
|
+
function ContextMenuShortcut({ keys, className }: { keys: string[]; className?: string }) {
|
|
222
|
+
return (
|
|
223
|
+
<span
|
|
224
|
+
className={cn(
|
|
225
|
+
"text-muted-foreground ms-auto inline-flex items-center gap-0.5 text-xs",
|
|
226
|
+
className,
|
|
227
|
+
)}
|
|
228
|
+
dir="ltr"
|
|
229
|
+
>
|
|
230
|
+
{keys.map((k, i) => (
|
|
231
|
+
<span key={`${k}-${i}`} className="inline-flex items-center gap-0.5">
|
|
232
|
+
{i > 0 ? <span className="opacity-70">+</span> : null}
|
|
233
|
+
<kbd className="border-border bg-muted min-w-[1em] rounded border px-1 font-mono text-[10px] leading-tight">
|
|
234
|
+
{k}
|
|
235
|
+
</kbd>
|
|
236
|
+
</span>
|
|
237
|
+
))}
|
|
238
|
+
</span>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ----------------------------------------------------------------------------
|
|
243
|
+
// SubTrigger / SubContent
|
|
244
|
+
// ----------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
const ContextMenuSubTrigger = forwardRef<
|
|
247
|
+
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
|
|
248
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
|
|
249
|
+
icon?: React.ReactNode;
|
|
250
|
+
}
|
|
251
|
+
>(function ContextMenuSubTrigger({ className, icon, children, ...props }, ref) {
|
|
252
|
+
return (
|
|
253
|
+
<ContextMenuPrimitive.SubTrigger
|
|
254
|
+
ref={ref}
|
|
255
|
+
className={cn(
|
|
256
|
+
itemBase,
|
|
257
|
+
"data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
|
|
258
|
+
className,
|
|
259
|
+
)}
|
|
260
|
+
{...props}
|
|
261
|
+
>
|
|
262
|
+
<span className="absolute start-2 inline-flex h-4 w-4 items-center justify-center">
|
|
263
|
+
{icon ?? null}
|
|
264
|
+
</span>
|
|
265
|
+
<span className="flex-1">{children}</span>
|
|
266
|
+
<RiArrowRightSLine className="rtl-mirror ms-auto size-4" aria-hidden="true" />
|
|
267
|
+
</ContextMenuPrimitive.SubTrigger>
|
|
268
|
+
);
|
|
269
|
+
});
|
|
270
|
+
ContextMenuSubTrigger.displayName = "ContextMenuSubTrigger";
|
|
271
|
+
|
|
272
|
+
const ContextMenuSubContent = forwardRef<
|
|
273
|
+
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
|
|
274
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
|
|
275
|
+
>(function ContextMenuSubContent({ className, ...props }, ref) {
|
|
276
|
+
return (
|
|
277
|
+
<ContextMenuPrimitive.SubContent
|
|
278
|
+
ref={ref}
|
|
279
|
+
className={cn(
|
|
280
|
+
"border-border bg-popover text-popover-foreground z-popover min-w-32 overflow-hidden rounded-md border py-1 text-sm shadow-md",
|
|
281
|
+
"data-[state=open]:animate-[scale-in_var(--duration-fast)_var(--ease-out)]",
|
|
282
|
+
"data-[state=closed]:animate-[scale-out_var(--duration-fast)_var(--ease-in)]",
|
|
283
|
+
className,
|
|
284
|
+
)}
|
|
285
|
+
{...props}
|
|
286
|
+
/>
|
|
287
|
+
);
|
|
288
|
+
});
|
|
289
|
+
ContextMenuSubContent.displayName = "ContextMenuSubContent";
|
|
290
|
+
|
|
291
|
+
export {
|
|
292
|
+
ContextMenu,
|
|
293
|
+
ContextMenuTrigger,
|
|
294
|
+
ContextMenuContent,
|
|
295
|
+
ContextMenuItem,
|
|
296
|
+
ContextMenuCheckboxItem,
|
|
297
|
+
ContextMenuRadioGroup,
|
|
298
|
+
ContextMenuRadioItem,
|
|
299
|
+
ContextMenuLabel,
|
|
300
|
+
ContextMenuSeparator,
|
|
301
|
+
ContextMenuShortcut,
|
|
302
|
+
ContextMenuGroup,
|
|
303
|
+
ContextMenuPortal,
|
|
304
|
+
ContextMenuSub,
|
|
305
|
+
ContextMenuSubTrigger,
|
|
306
|
+
ContextMenuSubContent,
|
|
307
|
+
contextContentVariants,
|
|
308
|
+
};
|
|
309
|
+
export type { ContextMenuContentProps, ContextMenuItemProps };
|