सामग्री पर जाएँ

संदर्भ मेनू

फ़ाइल पंक्तियों, कैनवस और डेटा ग्रिड्स के लिए राइट-क्लिक मेनू — संदर्भगत क्रियाएँ, नियोब्रूटलिस्ट शैली के बॉर्डर-और-छाया प्रभाव के साथ।

import {
  ContextMenu,
  ContextMenuCheckboxItem,

कॉन्टेक्स्ट मेनू राइट-क्लिक (या टच पर long-press) पर पॉइंटर पर एक्शन की फ़्लोटिंग सूची खोलता है — सबमेनू, चेकबॉक्स और रेडियो आइटम, और कीबोर्ड शॉर्टकट के साथ। यह Base UI Context Menu कंपोनेंट पर बना है और नियोब्रूटलिस्ट रेसिपी के अनुसार स्टाइल किया गया है: मोटे बॉर्डर, कठोर छायाएँ और बोल्ड टाइपोग्राफ़ी।

इसे इन स्थितियों में चुनें:

  • फ़ाइल और पंक्ति एक्शन — फ़ाइल-मैनेजर एंट्री या टेबल पंक्ति पर rename, duplicate, delete; हर पंक्ति पर बटन की भीड़ के बिना।
  • कैनवास सतह — व्हाइटबोर्ड, डिज़ाइन टूल, एडिटर जहाँ उपलब्ध एक्शन क्लिक की जगह पर निर्भर करें।
  • डेटा ग्रिड — कॉपी वैल्यू, फ़िल्टर बाय, पिन कॉलम जैसे प्रति-सेल ऑपरेशन, डेटा के पास।

इंस्टॉलेशन

pnpm dlx shadcn@latest add https://neobrutalism.com/r/base/context-menu.json
npx shadcn@latest add https://neobrutalism.com/r/base/context-menu.json
yarn dlx shadcn@latest add https://neobrutalism.com/r/base/context-menu.json
bunx --bun shadcn@latest add https://neobrutalism.com/r/base/context-menu.json

निम्नलिखित डिपेंडेंसी इंस्टॉल करें:

pnpm add @base-ui/react
npm install @base-ui/react
yarn add @base-ui/react
bun add @base-ui/react

निम्नलिखित कोड को अपने प्रोजेक्ट में कॉपी और पेस्ट करें.

components/ui/context-menu.tsx
"use client"

import * as React from "react"
import { CheckIcon, ChevronRightIcon } from "lucide-react"
import { ContextMenu as ContextMenuPrimitive } from "radix-ui"

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

function ContextMenu({
  ...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Root>) {
  return <ContextMenuPrimitive.Root data-slot="context-menu" {...props} />
}

function ContextMenuTrigger({
  className,
  ...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Trigger>) {
  return (
    <ContextMenuPrimitive.Trigger
      data-slot="context-menu-trigger"
      className={cn("select-none", className)}
      {...props}
    />
  )
}

function ContextMenuGroup({
  ...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Group>) {
  return (
    <ContextMenuPrimitive.Group data-slot="context-menu-group" {...props} />
  )
}

function ContextMenuPortal({
  ...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Portal>) {
  return (
    <ContextMenuPrimitive.Portal data-slot="context-menu-portal" {...props} />
  )
}

function ContextMenuSub({
  ...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Sub>) {
  return <ContextMenuPrimitive.Sub data-slot="context-menu-sub" {...props} />
}

function ContextMenuRadioGroup({
  ...props
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioGroup>) {
  return (
    <ContextMenuPrimitive.RadioGroup
      data-slot="context-menu-radio-group"
      {...props}
    />
  )
}

function ContextMenuContent({
  className,
  ...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Content> & {
  side?: "top" | "right" | "bottom" | "left"
}) {
  return (
    <ContextMenuPrimitive.Portal>
      <ContextMenuPrimitive.Content
        data-slot="context-menu-content"
        className={cn("z-50 max-h-(--radix-context-menu-content-available-height) min-w-36 origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded border-2 bg-popover p-1 text-popover-foreground shadow-md duration-100 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 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
        {...props}
      />
    </ContextMenuPrimitive.Portal>
  )
}

function ContextMenuItem({
  className,
  inset,
  variant = "default",
  ...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Item> & {
  inset?: boolean
  variant?: "default" | "destructive"
}) {
  return (
    <ContextMenuPrimitive.Item
      data-slot="context-menu-item"
      data-inset={inset}
      data-variant={variant}
      className={cn(
        "group/context-menu-item relative flex cursor-default items-center gap-1.5 rounded-sm px-2 py-1.5 text-sm outline-none select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 focus:*:[svg]:text-accent-foreground data-[variant=destructive]:*:[svg]:text-destructive",
        className
      )}
      {...props}
    />
  )
}

function ContextMenuSubTrigger({
  className,
  inset,
  children,
  ...props
}: React.ComponentProps<typeof ContextMenuPrimitive.SubTrigger> & {
  inset?: boolean
}) {
  return (
    <ContextMenuPrimitive.SubTrigger
      data-slot="context-menu-sub-trigger"
      data-inset={inset}
      className={cn(
        "flex cursor-default items-center gap-1.5 rounded-sm px-2 py-1.5 text-sm outline-none select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className
      )}
      {...props}
    >
      {children}
      <ChevronRightIcon className="cn-rtl-flip ml-auto" />
    </ContextMenuPrimitive.SubTrigger>
  )
}

function ContextMenuSubContent({
  className,
  ...props
}: React.ComponentProps<typeof ContextMenuPrimitive.SubContent>) {
  return (
    <ContextMenuPrimitive.SubContent
      data-slot="context-menu-sub-content"
      className={cn("z-50 min-w-32 origin-(--radix-context-menu-content-transform-origin) overflow-hidden rounded border-2 bg-popover p-1 text-popover-foreground shadow-md duration-100 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 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
      {...props}
    />
  )
}

function ContextMenuCheckboxItem({
  className,
  children,
  checked,
  inset,
  ...props
}: React.ComponentProps<typeof ContextMenuPrimitive.CheckboxItem> & {
  inset?: boolean
}) {
  return (
    <ContextMenuPrimitive.CheckboxItem
      data-slot="context-menu-checkbox-item"
      data-inset={inset}
      className={cn(
        "relative flex cursor-default items-center gap-1.5 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-none select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className
      )}
      checked={checked}
      {...props}
    >
      <span className="pointer-events-none absolute right-2">
        <ContextMenuPrimitive.ItemIndicator>
          <CheckIcon />
        </ContextMenuPrimitive.ItemIndicator>
      </span>
      {children}
    </ContextMenuPrimitive.CheckboxItem>
  )
}

function ContextMenuRadioItem({
  className,
  children,
  inset,
  ...props
}: React.ComponentProps<typeof ContextMenuPrimitive.RadioItem> & {
  inset?: boolean
}) {
  return (
    <ContextMenuPrimitive.RadioItem
      data-slot="context-menu-radio-item"
      data-inset={inset}
      className={cn(
        "relative flex cursor-default items-center gap-1.5 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-none select-none focus:bg-accent focus:text-accent-foreground data-inset:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className
      )}
      {...props}
    >
      <span className="pointer-events-none absolute right-2">
        <ContextMenuPrimitive.ItemIndicator>
          <CheckIcon />
        </ContextMenuPrimitive.ItemIndicator>
      </span>
      {children}
    </ContextMenuPrimitive.RadioItem>
  )
}

function ContextMenuLabel({
  className,
  inset,
  ...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Label> & {
  inset?: boolean
}) {
  return (
    <ContextMenuPrimitive.Label
      data-slot="context-menu-label"
      data-inset={inset}
      className={cn(
        "px-1.5 py-1 text-xs font-medium text-muted-foreground data-inset:pl-7",
        className
      )}
      {...props}
    />
  )
}

function ContextMenuSeparator({
  className,
  ...props
}: React.ComponentProps<typeof ContextMenuPrimitive.Separator>) {
  return (
    <ContextMenuPrimitive.Separator
      data-slot="context-menu-separator"
      className={cn("-mx-1 my-1 h-px bg-border", className)}
      {...props}
    />
  )
}

function ContextMenuShortcut({
  className,
  ...props
}: React.ComponentProps<"span">) {
  return (
    <span
      data-slot="context-menu-shortcut"
      className={cn(
        "ml-auto text-xs tracking-widest text-muted-foreground group-focus/context-menu-item:text-accent-foreground",
        className
      )}
      {...props}
    />
  )
}

export {
  ContextMenu,
  ContextMenuTrigger,
  ContextMenuContent,
  ContextMenuItem,
  ContextMenuCheckboxItem,
  ContextMenuRadioItem,
  ContextMenuLabel,
  ContextMenuSeparator,
  ContextMenuShortcut,
  ContextMenuGroup,
  ContextMenuPortal,
  ContextMenuSub,
  ContextMenuSubContent,
  ContextMenuSubTrigger,
  ContextMenuRadioGroup,
}

अपने प्रोजेक्ट सेटअप के अनुसार इंपोर्ट पाथ अपडेट करें.

उपयोग

import {
  ContextMenu,
  ContextMenuContent,
  ContextMenuItem,
  ContextMenuTrigger,
} from "@/components/ui/context-menu"
<ContextMenu>
  <ContextMenuTrigger>Right click here</ContextMenuTrigger>
  <ContextMenuContent>
    <ContextMenuItem>Profile</ContextMenuItem>
    <ContextMenuItem>Billing</ContextMenuItem>
    <ContextMenuItem>Team</ContextMenuItem>
    <ContextMenuItem>Subscription</ContextMenuItem>
  </ContextMenuContent>
</ContextMenu>

कंपोज़िशन

ContextMenu बनाने के लिए निम्नलिखित कंपोज़िशन का उपयोग करें:

ContextMenu
├── ContextMenuTrigger
└── ContextMenuContent
    ├── ContextMenuGroup
    │   ├── ContextMenuLabel
    │   ├── ContextMenuItem
    │   └── ContextMenuItem
    ├── ContextMenuSeparator
    ├── ContextMenuGroup
    │   ├── ContextMenuLabel
    │   ├── ContextMenuCheckboxItem
    │   └── ContextMenuCheckboxItem
    ├── ContextMenuSeparator
    ├── ContextMenuGroup
    │   ├── ContextMenuLabel
    │   └── ContextMenuRadioGroup
    │       ├── ContextMenuRadioItem
    │       └── ContextMenuRadioItem
    └── ContextMenuSub
        ├── ContextMenuSubTrigger
        └── ContextMenuSubContent
            └── ContextMenuGroup
                ├── ContextMenuItem
                └── ContextMenuItem

उदाहरण

बुनियादी

कुछ क्रियाओं वाला एक सरल कॉन्टेक्स्ट मेन्यू.

import {
  ContextMenu,
  ContextMenuContent,

सबमेन्यू

द्वितीयक क्रियाओं को नेस्ट करने के लिए ContextMenuSub का उपयोग करें.

import {
  ContextMenu,
  ContextMenuContent,

शॉर्टकट

कीबोर्ड संकेत दिखाने के लिए ContextMenuShortcut जोड़ें.

import {
  ContextMenu,
  ContextMenuContent,

समूह

संबंधित क्रियाओं को समूहित करें और उन्हें डिवाइडर से अलग करें.

import {
  ContextMenu,
  ContextMenuContent,

आइकन

त्वरित स्कैनिंग के लिए आइकन को लेबल के साथ मिलाएं.

import {
  ClipboardPasteIcon,
  CopyIcon,

चेकबॉक्स

टॉगल के लिए ContextMenuCheckboxItem का उपयोग करें.

import {
  ContextMenu,
  ContextMenuCheckboxItem,

रेडियो

अनन्य विकल्पों के लिए ContextMenuRadioItem का उपयोग करें.

"use client"

import * as React from "react"

विनाशकारी

मेन्यू आइटम को विनाशकारी शैली देने के लिए variant="destructive" का उपयोग करें.

import { ArchiveIcon, PencilIcon, ShareIcon, TrashIcon } from "lucide-react"

import {

साइड्स

side और align प्रॉप्स से सबमेन्यू की प्लेसमेंट नियंत्रित करें.

import {
  ContextMenu,
  ContextMenuContent,

RTL

Neobrutalism में RTL सपोर्ट सक्षम करने के लिए, RTL कॉन्फ़िगरेशन गाइड देखें.

"use client"

import * as React from "react"

मेन्यू को ट्रिगर के लॉजिकल दाईं ओर रखने के लिए side="inline-end" का उपयोग करें.

<ContextMenu>
  <ContextMenuTrigger>Right click here</ContextMenuTrigger>
  <ContextMenuContent side="inline-end">
    <ContextMenuItem>Profile</ContextMenuItem>
    <ContextMenuItem>Billing</ContextMenuItem>
    <ContextMenuItem>Team</ContextMenuItem>
    <ContextMenuItem>Subscription</ContextMenuItem>
  </ContextMenuContent>
</ContextMenu>

एक्सेसिबिलिटी

कॉन्टेक्स्ट मेनू WAI-ARIA Menu पैटर्न का पालन करता है: पॉपअप role="menu" रेंडर करता है, चिल्ड्रन menuitem, menuitemcheckbox और menuitemradio होते हैं, खुलने पर फ़ोकस मेनू में जाता है और बंद होने पर ट्रिगर क्षेत्र पर लौटता है। यह ब्राउज़र के contextmenu इवेंट से खुलता है, इसलिए जब ट्रिगर क्षेत्र फ़ोकस-योग्य हो तो Shift + F10 (या समर्पित मेनू कुंजी) काम करती है — अगर चाइल्ड नेटिव रूप से फ़ोकस-योग्य न हो तो tabIndex={0} जोड़ें।

कीबोर्ड इंटरैक्शन:

कुंजीक्रिया
Shift + F10 / menu keyट्रिगर क्षेत्र पर फ़ोकस होने पर मेनू खोलें
ArrowDown / ArrowUpफ़ोकस अगले / पिछले आइटम पर ले जाएँ
ArrowRight / ArrowLeftसबमेनू खोलें / बंद करें (RTL में उल्टा)
Enter / Spaceफ़ोकस किए गए आइटम को सक्रिय करें
Escमेनू बंद करें
कोई वर्ण टाइप करेंअगले मिलते आइटम पर फ़ोकस ले जाएँ (typeahead)

API रेफ़रेंस

अधिक जानकारी के लिए Base UI डॉक्युमेंटेशन देखें.