Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions app/docs/components/context-menu/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Metadata } from "next"

import { contextMenuMetaData } from "@/lib/metadata"
import { Button } from "@/components/ui/8bit/button"
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuTrigger,
} from "@/components/ui/8bit/context-menu"
import { Separator } from "@/components/ui/separator"

import CodeSnippet from "../code-snippet"
import CopyCommandButton from "../copy-command-button"
import InstallationCommands from "../installation-commands"
import { OpenInV0Button } from "../open-in-v0-button"

export const metadata: Metadata = {
title: "8-bit Context Menu",
description: "Displays an 8-bit context menu component.",
openGraph: {
images: contextMenuMetaData,
},
}

export default function ButtonPage() {
return (
<div className="flex flex-col gap-4">
<div className="flex flex-col md:flex-row items-center justify-between gap-2">
<h1 className="text-3xl font-bold">Context Menu</h1>
<CopyCommandButton
copyCommand={`pnpm dlx shadcn@canary add ${process.env.NEXT_PUBLIC_BASE_URL}/r/8bit-context-menu.json`}
command={"pnpm dlx shadcn@canary add 8bit-context-menu"}
/>
</div>
Comment on lines +26 to +35
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Component function name should reflect Context Menu
The default export is named ButtonPage, which appears copied from another page. For clarity and consistency, rename it to something like ContextMenuPage.

🤖 Prompt for AI Agents
In app/docs/components/context-menu/page.tsx around lines 26 to 35, the default
exported component function is named ButtonPage, which does not reflect the
content of the page. Rename the function from ButtonPage to ContextMenuPage to
improve clarity and maintain consistency with the page's purpose.


<p className="text-muted-foreground">
Displays an 8-bit context menu component.
</p>

<div className="flex flex-col gap-4 border rounded-lg p-4 min-h-[450px]">
<div className="flex items-center justify-between">
<h2 className="text-sm text-muted-foreground sm:pl-3">
8-bit context menu component
</h2>

<div className="flex items-center gap-2">
<OpenInV0Button name="8bit-context-menu" className="w-fit" />
</div>
</div>
<div className="flex items-center justify-center min-h-[400px] relative">
<ContextMenu>
<ContextMenuTrigger>
<Button>Right click me</Button>
</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem>Profile</ContextMenuItem>
<ContextMenuItem>Billing</ContextMenuItem>
<ContextMenuItem>Team</ContextMenuItem>
<ContextMenuItem>Subscription</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
</div>
</div>

<h3 className="text-lg font-bold">Installation</h3>

<Separator />

<InstallationCommands
packageUrl={`${process.env.NEXT_PUBLIC_BASE_URL}/r/8bit-context-menu.json`}
/>

<h3 className="text-lg font-bold mt-10">Usage</h3>

<Separator />

<CodeSnippet>{`import { ContextMenu } from "@/components/ui/8bit/context-menu"`}</CodeSnippet>

<CodeSnippet>{`<ContextMenu>
<ContextMenuTrigger>Right click</ContextMenuTrigger>
<ContextMenuContent>
<ContextMenuItem>Profile</ContextMenuItem>
<ContextMenuItem>Billing</ContextMenuItem>
<ContextMenuItem>Team</ContextMenuItem>
<ContextMenuItem>Subscription</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>`}</CodeSnippet>
</div>
)
}
288 changes: 288 additions & 0 deletions components/ui/8bit/context-menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
"use client"

import * as React from "react"
import { Press_Start_2P } from "next/font/google"
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
import { ChevronRight, Circle } from "lucide-react"

import { cn } from "@/lib/utils"

const pressStart = Press_Start_2P({
weight: ["400"],
subsets: ["latin"],
})

const ContextMenu = ContextMenuPrimitive.Root

const ContextMenuTrigger = ContextMenuPrimitive.Trigger

const ContextMenuGroup = ContextMenuPrimitive.Group

const ContextMenuPortal = ContextMenuPrimitive.Portal

const ContextMenuSub = ContextMenuPrimitive.Sub

const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup

const ContextMenuSubTrigger = React.forwardRef<
React.ComponentRef<typeof ContextMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<ContextMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:border-y-4 focus:border-foreground h-8 focus:dark:border-ring border-dashed focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto size-4" />
</ContextMenuPrimitive.SubTrigger>
))
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName

const ContextMenuSubContent = React.forwardRef<
React.ComponentRef<typeof ContextMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-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]",
className
)}
{...props}
/>
))
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName

const ContextMenuContent = React.forwardRef<
React.ComponentRef<typeof ContextMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Portal>
<div className="relative">
<ContextMenuPrimitive.Content
ref={ref}
className={cn(
"z-50 max-h-[--radix-context-menu-content-available-height] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border-x-4 border-y-6 border-foreground dark:border-ring bg-popover p-1 text-popover-foreground animate-in fade-in-80 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]",
pressStart.className,
className
)}
{...props}
/>
<div
className="absolute inset-0 border-x-6 -mx-1.5 border-foreground dark:border-ring pointer-events-none"
aria-hidden="true"
/>
</div>
</ContextMenuPrimitive.Portal>
))
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName

const ContextMenuItem = React.forwardRef<
React.ComponentRef<typeof ContextMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:border-y-4 focus:border-foreground h-8 focus:dark:border-ring border-dashed focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName

const ContextMenuCheckboxItem = React.forwardRef<
React.ComponentRef<typeof ContextMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<ContextMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:border-y-4 focus:border-foreground h-8 focus:dark:border-ring border-dashed focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<svg
width="50"
height="50"
viewBox="0 0 256 256"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
stroke="currentColor"
strokeWidth="0.25"
aria-label="check"
className="size-9"
>
<rect
x="80"
y="128"
width="14"
height="14"
rx="1"
transform="rotate(90 80 128)"
></rect>
<rect
x="96"
y="144"
width="14"
height="14"
rx="1"
transform="rotate(90 96 144)"
></rect>
<rect
x="112"
y="160"
width="14"
height="14"
rx="1"
transform="rotate(90 112 160)"
></rect>
<rect
x="128"
y="144"
width="14"
height="14"
rx="1"
transform="rotate(90 128 144)"
></rect>
<rect
x="144"
y="128"
width="14"
height="14"
rx="1"
transform="rotate(90 144 128)"
></rect>
<rect
x="160"
y="112"
width="14"
height="14"
rx="1"
transform="rotate(90 160 112)"
></rect>
<rect
x="176"
y="96"
width="14"
height="14"
rx="1"
transform="rotate(90 176 96)"
></rect>
<rect
x="192"
y="80"
width="14"
height="14"
rx="1"
transform="rotate(90 192 80)"
></rect>
</svg>
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.CheckboxItem>
))
ContextMenuCheckboxItem.displayName =
ContextMenuPrimitive.CheckboxItem.displayName

const ContextMenuRadioItem = React.forwardRef<
React.ComponentRef<typeof ContextMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<ContextMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:border-y-4 focus:border-foreground h-8 focus:dark:border-ring border-dashed focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<ContextMenuPrimitive.ItemIndicator>
<Circle className="size-2 fill-current" />
</ContextMenuPrimitive.ItemIndicator>
</span>
{children}
</ContextMenuPrimitive.RadioItem>
))
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName

const ContextMenuLabel = React.forwardRef<
React.ComponentRef<typeof ContextMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<ContextMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold text-foreground",
inset && "pl-8",
className
)}
{...props}
/>
))
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName

const ContextMenuSeparator = React.forwardRef<
React.ComponentRef<typeof ContextMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<ContextMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-border", className)}
{...props}
/>
))
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName

const ContextMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
)
}
ContextMenuShortcut.displayName = "ContextMenuShortcut"

export {
ContextMenu,
ContextMenuTrigger,
ContextMenuContent,
ContextMenuItem,
ContextMenuCheckboxItem,
ContextMenuRadioItem,
ContextMenuLabel,
ContextMenuSeparator,
ContextMenuShortcut,
ContextMenuGroup,
ContextMenuPortal,
ContextMenuSub,
ContextMenuSubContent,
ContextMenuSubTrigger,
ContextMenuRadioGroup,
}
5 changes: 5 additions & 0 deletions config/nav-items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ export const navItems = {
title: "Command",
url: "/docs/components/command",
},
{
title: "Context Menu",
url: "/docs/components/context-menu",
new: true,
},
{
title: "Collapsible",
url: "/docs/components/collapsible",
Expand Down
1 change: 1 addition & 0 deletions lib/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,4 @@ export const toggleGroupMetaData = "/assets/8bitcn-toggle-group-light.png"
export const accordionMetaData = "/assets/8bitcn-accordion-light.png"
export const sliderMetaData = "/assets/8bitcn-slider-light.png"
export const datePickerMetaData = "/assets/8bitcn-date-picker-light.png"
export const contextMenuMetaData = "/assets/8bitcn-context-menu-light.png"
Loading