Skip to content

Commit 94d14f4

Browse files
committed
feat(web): add copy, cut, paste and duplicate
1 parent a24b869 commit 94d14f4

9 files changed

Lines changed: 1232 additions & 181 deletions

File tree

apps/web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"@radix-ui/react-alert-dialog": "^1.1.11",
2929
"@radix-ui/react-avatar": "^1.1.7",
3030
"@radix-ui/react-checkbox": "^1.2.3",
31+
"@radix-ui/react-context-menu": "^2.2.15",
3132
"@radix-ui/react-dialog": "^1.1.11",
3233
"@radix-ui/react-dropdown-menu": "^2.1.12",
3334
"@radix-ui/react-label": "^2.1.4",

apps/web/src/components/app-header-breadcrumb.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export function AppHeaderBreadcrumb() {
2222

2323
return (
2424
<Breadcrumb className="ml-4 hidden md:flex">
25-
<BreadcrumbList>
25+
<BreadcrumbList className="flex-nowrap">
2626
{breadcrumbs.map((item, index) => {
2727
const isLast = index === breadcrumbs.length - 1;
2828
return (
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
2+
import { Check, ChevronRight, Circle } from "lucide-react";
3+
import * as React from "react";
4+
5+
import { cn } from "@/utils/utils";
6+
7+
const ContextMenu = ContextMenuPrimitive.Root;
8+
9+
const ContextMenuTrigger = ContextMenuPrimitive.Trigger;
10+
11+
const ContextMenuGroup = ContextMenuPrimitive.Group;
12+
13+
const ContextMenuPortal = ContextMenuPrimitive.Portal;
14+
15+
const ContextMenuSub = ContextMenuPrimitive.Sub;
16+
17+
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
18+
19+
const ContextMenuSubTrigger = React.forwardRef<
20+
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
21+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
22+
inset?: boolean;
23+
}
24+
>(({ className, inset, children, ...props }, ref) => (
25+
<ContextMenuPrimitive.SubTrigger
26+
ref={ref}
27+
className={cn(
28+
"flex cursor-default select-none items-center 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",
29+
inset && "pl-8",
30+
className
31+
)}
32+
{...props}
33+
>
34+
{children}
35+
<ChevronRight className="ml-auto h-4 w-4" />
36+
</ContextMenuPrimitive.SubTrigger>
37+
));
38+
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
39+
40+
const ContextMenuSubContent = React.forwardRef<
41+
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
42+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
43+
>(({ className, ...props }, ref) => (
44+
<ContextMenuPrimitive.SubContent
45+
ref={ref}
46+
className={cn(
47+
"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 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-context-menu-content-transform-origin]",
48+
className
49+
)}
50+
{...props}
51+
/>
52+
));
53+
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
54+
55+
const ContextMenuContent = React.forwardRef<
56+
React.ElementRef<typeof ContextMenuPrimitive.Content>,
57+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
58+
>(({ className, ...props }, ref) => (
59+
<ContextMenuPrimitive.Portal>
60+
<ContextMenuPrimitive.Content
61+
ref={ref}
62+
className={cn(
63+
"z-50 max-h-[--radix-context-menu-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md 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 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 origin-[--radix-context-menu-content-transform-origin]",
64+
className
65+
)}
66+
{...props}
67+
/>
68+
</ContextMenuPrimitive.Portal>
69+
));
70+
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
71+
72+
const ContextMenuItem = React.forwardRef<
73+
React.ElementRef<typeof ContextMenuPrimitive.Item>,
74+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
75+
inset?: boolean;
76+
}
77+
>(({ className, inset, ...props }, ref) => (
78+
<ContextMenuPrimitive.Item
79+
ref={ref}
80+
className={cn(
81+
"relative flex cursor-default select-none items-center 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",
82+
inset && "pl-8",
83+
className
84+
)}
85+
{...props}
86+
/>
87+
));
88+
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
89+
90+
const ContextMenuCheckboxItem = React.forwardRef<
91+
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
92+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
93+
>(({ className, children, checked, ...props }, ref) => (
94+
<ContextMenuPrimitive.CheckboxItem
95+
ref={ref}
96+
className={cn(
97+
"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",
98+
className
99+
)}
100+
checked={checked}
101+
{...props}
102+
>
103+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
104+
<ContextMenuPrimitive.ItemIndicator>
105+
<Check className="h-4 w-4" />
106+
</ContextMenuPrimitive.ItemIndicator>
107+
</span>
108+
{children}
109+
</ContextMenuPrimitive.CheckboxItem>
110+
));
111+
ContextMenuCheckboxItem.displayName =
112+
ContextMenuPrimitive.CheckboxItem.displayName;
113+
114+
const ContextMenuRadioItem = React.forwardRef<
115+
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
116+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
117+
>(({ className, children, ...props }, ref) => (
118+
<ContextMenuPrimitive.RadioItem
119+
ref={ref}
120+
className={cn(
121+
"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",
122+
className
123+
)}
124+
{...props}
125+
>
126+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
127+
<ContextMenuPrimitive.ItemIndicator>
128+
<Circle className="h-4 w-4 fill-current" />
129+
</ContextMenuPrimitive.ItemIndicator>
130+
</span>
131+
{children}
132+
</ContextMenuPrimitive.RadioItem>
133+
));
134+
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
135+
136+
const ContextMenuLabel = React.forwardRef<
137+
React.ElementRef<typeof ContextMenuPrimitive.Label>,
138+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
139+
inset?: boolean;
140+
}
141+
>(({ className, inset, ...props }, ref) => (
142+
<ContextMenuPrimitive.Label
143+
ref={ref}
144+
className={cn(
145+
"px-2 py-1.5 text-sm font-semibold text-foreground",
146+
inset && "pl-8",
147+
className
148+
)}
149+
{...props}
150+
/>
151+
));
152+
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
153+
154+
const ContextMenuSeparator = React.forwardRef<
155+
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
156+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
157+
>(({ className, ...props }, ref) => (
158+
<ContextMenuPrimitive.Separator
159+
ref={ref}
160+
className={cn("-mx-1 my-1 h-px bg-border", className)}
161+
{...props}
162+
/>
163+
));
164+
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
165+
166+
const ContextMenuShortcut = ({
167+
className,
168+
...props
169+
}: React.HTMLAttributes<HTMLSpanElement>) => {
170+
return (
171+
<span
172+
className={cn(
173+
"ml-auto text-xs tracking-widest text-muted-foreground",
174+
className
175+
)}
176+
{...props}
177+
/>
178+
);
179+
};
180+
ContextMenuShortcut.displayName = "ContextMenuShortcut";
181+
182+
export {
183+
ContextMenu,
184+
ContextMenuCheckboxItem,
185+
ContextMenuContent,
186+
ContextMenuGroup,
187+
ContextMenuItem,
188+
ContextMenuLabel,
189+
ContextMenuPortal,
190+
ContextMenuRadioGroup,
191+
ContextMenuRadioItem,
192+
ContextMenuSeparator,
193+
ContextMenuShortcut,
194+
ContextMenuSub,
195+
ContextMenuSubContent,
196+
ContextMenuSubTrigger,
197+
ContextMenuTrigger,
198+
};

0 commit comments

Comments
 (0)