Skip to content

Commit d3e2b79

Browse files
committed
add ContextMenu
1 parent 2f5ce3a commit d3e2b79

15 files changed

Lines changed: 463 additions & 1 deletion

apps/playground/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"@opendatacapture/runtime-core": "workspace:*",
2222
"@opendatacapture/runtime-v1": "workspace:*",
2323
"@opendatacapture/schemas": "workspace:*",
24+
"@radix-ui/react-context-menu": "^2.2.6",
2425
"axios": "catalog:",
2526
"cmdk": "^1.1.1",
2627
"esbuild-wasm": "catalog:",
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type { Meta, StoryObj } from '@storybook/react-vite';
2+
3+
import { ContextMenu } from './ContextMenu';
4+
5+
type Story = StoryObj<typeof ContextMenu>;
6+
7+
export default { component: ContextMenu } as Meta<typeof ContextMenu>;
8+
9+
export const Default: Story = {
10+
args: {
11+
children: (
12+
<>
13+
<ContextMenu.Trigger className="flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm">
14+
Right click here
15+
</ContextMenu.Trigger>
16+
<ContextMenu.Content className="w-64">
17+
<ContextMenu.Item inset>
18+
Back
19+
<ContextMenu.Shortcut>⌘[</ContextMenu.Shortcut>
20+
</ContextMenu.Item>
21+
<ContextMenu.Item disabled inset>
22+
Forward
23+
<ContextMenu.Shortcut>⌘]</ContextMenu.Shortcut>
24+
</ContextMenu.Item>
25+
<ContextMenu.Item inset>
26+
Reload
27+
<ContextMenu.Shortcut>⌘R</ContextMenu.Shortcut>
28+
</ContextMenu.Item>
29+
<ContextMenu.Sub>
30+
<ContextMenu.SubTrigger inset>More Tools</ContextMenu.SubTrigger>
31+
<ContextMenu.SubContent className="w-48">
32+
<ContextMenu.Item>
33+
Save Page As...
34+
<ContextMenu.Shortcut>⇧⌘S</ContextMenu.Shortcut>
35+
</ContextMenu.Item>
36+
<ContextMenu.Item>Create Shortcut...</ContextMenu.Item>
37+
<ContextMenu.Item>Name Window...</ContextMenu.Item>
38+
<ContextMenu.Separator />
39+
<ContextMenu.Item>Developer Tools</ContextMenu.Item>
40+
</ContextMenu.SubContent>
41+
</ContextMenu.Sub>
42+
<ContextMenu.Separator />
43+
<ContextMenu.CheckboxItem checked>
44+
Show Bookmarks Bar
45+
<ContextMenu.Shortcut>⌘⇧B</ContextMenu.Shortcut>
46+
</ContextMenu.CheckboxItem>
47+
<ContextMenu.CheckboxItem>Show Full URLs</ContextMenu.CheckboxItem>
48+
<ContextMenu.Separator />
49+
<ContextMenu.RadioGroup value="pedro">
50+
<ContextMenu.Label inset>People</ContextMenu.Label>
51+
<ContextMenu.Separator />
52+
<ContextMenu.RadioItem value="pedro">Pedro Duarte</ContextMenu.RadioItem>
53+
<ContextMenu.RadioItem value="colm">Colm Tuite</ContextMenu.RadioItem>
54+
</ContextMenu.RadioGroup>
55+
</ContextMenu.Content>
56+
</>
57+
)
58+
}
59+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Group, Portal, RadioGroup, Root, Sub, Trigger } from '@radix-ui/react-context-menu';
2+
3+
import { ContextMenuCheckboxItem } from './ContextMenuCheckboxItem';
4+
import { ContextMenuContent } from './ContextMenuContent';
5+
import { ContextMenuItem } from './ContextMenuItem';
6+
import { ContextMenuLabel } from './ContextMenuLabel';
7+
import { ContextMenuRadioItem } from './ContextMenuRadioItem';
8+
import { ContextMenuSeparator } from './ContextMenuSeparator';
9+
import { ContextMenuShortcut } from './ContextMenuShortcut';
10+
import { ContextMenuSubContent } from './ContextMenuSubContent';
11+
import { ContextMenuSubTrigger } from './ContextMenuSubTrigger';
12+
13+
export const ContextMenu = Object.assign(Root.bind(null), {
14+
CheckboxItem: ContextMenuCheckboxItem,
15+
Content: ContextMenuContent,
16+
Group,
17+
Item: ContextMenuItem,
18+
Label: ContextMenuLabel,
19+
Portal,
20+
RadioGroup,
21+
RadioItem: ContextMenuRadioItem,
22+
Separator: ContextMenuSeparator,
23+
Shortcut: ContextMenuShortcut,
24+
Sub,
25+
SubContent: ContextMenuSubContent,
26+
SubTrigger: ContextMenuSubTrigger,
27+
Trigger
28+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React, { forwardRef } from 'react';
2+
3+
import { cn } from '@douglasneuroinformatics/libui/utils';
4+
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
5+
import { CheckIcon } from 'lucide-react';
6+
7+
export const ContextMenuCheckboxItem = forwardRef<
8+
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
9+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
10+
>(function ContextMenuCheckboxItem({ checked, children, className, ...props }, ref) {
11+
return (
12+
<ContextMenuPrimitive.CheckboxItem
13+
checked={checked}
14+
className={cn(
15+
'focus:bg-accent focus:text-accent-foreground rounded-xs outline-hidden relative flex cursor-default select-none items-center py-1.5 pl-8 pr-2 text-sm data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
16+
className
17+
)}
18+
ref={ref}
19+
{...props}
20+
>
21+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
22+
<ContextMenuPrimitive.ItemIndicator>
23+
<CheckIcon className="h-4 w-4" />
24+
</ContextMenuPrimitive.ItemIndicator>
25+
</span>
26+
{children}
27+
</ContextMenuPrimitive.CheckboxItem>
28+
);
29+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React, { forwardRef } from 'react';
2+
3+
import { cn } from '@douglasneuroinformatics/libui/utils';
4+
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
5+
6+
export const ContextMenuContent = forwardRef<
7+
React.ElementRef<typeof ContextMenuPrimitive.Content>,
8+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
9+
>(function ContextMenuContent({ className, ...props }, ref) {
10+
return (
11+
<ContextMenuPrimitive.Portal>
12+
<ContextMenuPrimitive.Content
13+
className={cn(
14+
'bg-popover text-popover-foreground 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 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md',
15+
className
16+
)}
17+
ref={ref}
18+
{...props}
19+
/>
20+
</ContextMenuPrimitive.Portal>
21+
);
22+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import React, { forwardRef } from 'react';
2+
3+
import { cn } from '@douglasneuroinformatics/libui/utils';
4+
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
5+
6+
export const ContextMenuItem = forwardRef<
7+
React.ElementRef<typeof ContextMenuPrimitive.Item>,
8+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
9+
inset?: boolean;
10+
}
11+
>(function ContextMenuItem({ className, inset, ...props }, ref) {
12+
return (
13+
<ContextMenuPrimitive.Item
14+
className={cn(
15+
'focus:bg-accent focus:text-accent-foreground rounded-xs outline-hidden relative flex cursor-default select-none items-center px-2 py-1.5 text-sm data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
16+
inset && 'pl-8',
17+
className
18+
)}
19+
ref={ref}
20+
{...props}
21+
/>
22+
);
23+
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React, { forwardRef } from 'react';
2+
3+
import { cn } from '@douglasneuroinformatics/libui/utils';
4+
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
5+
6+
export const ContextMenuLabel = forwardRef<
7+
React.ElementRef<typeof ContextMenuPrimitive.Label>,
8+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
9+
inset?: boolean;
10+
}
11+
>(function ContextMenuLabel({ className, inset, ...props }, ref) {
12+
return (
13+
<ContextMenuPrimitive.Label
14+
className={cn('text-foreground px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
15+
ref={ref}
16+
{...props}
17+
/>
18+
);
19+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import React, { forwardRef } from 'react';
2+
3+
import { cn } from '@douglasneuroinformatics/libui/utils';
4+
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
5+
import { CircleIcon } from 'lucide-react';
6+
7+
export const ContextMenuRadioItem = forwardRef<
8+
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
9+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
10+
>(function ContextMenuRadioItem({ children, className, ...props }, ref) {
11+
return (
12+
<ContextMenuPrimitive.RadioItem
13+
className={cn(
14+
'focus:bg-accent focus:text-accent-foreground rounded-xs outline-hidden relative flex cursor-default select-none items-center py-1.5 pl-8 pr-2 text-sm data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
15+
className
16+
)}
17+
ref={ref}
18+
{...props}
19+
>
20+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
21+
<ContextMenuPrimitive.ItemIndicator>
22+
<CircleIcon className="fill-current" style={{ height: 8, width: 8 }} />
23+
</ContextMenuPrimitive.ItemIndicator>
24+
</span>
25+
{children}
26+
</ContextMenuPrimitive.RadioItem>
27+
);
28+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React, { forwardRef } from 'react';
2+
3+
import { cn } from '@douglasneuroinformatics/libui/utils';
4+
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
5+
6+
export const ContextMenuSeparator = forwardRef<
7+
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
8+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
9+
>(function ContextMenuSeparator({ className, ...props }, ref) {
10+
return <ContextMenuPrimitive.Separator className={cn('bg-border -mx-1 my-1 h-px', className)} ref={ref} {...props} />;
11+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import * as React from 'react';
2+
3+
import { cn } from '@douglasneuroinformatics/libui/utils';
4+
5+
export const ContextMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
6+
return <span className={cn('text-muted-foreground ml-auto text-xs tracking-widest', className)} {...props} />;
7+
};

0 commit comments

Comments
 (0)