{"name":"context-menu","type":"registry:component","title":"Context Menu","description":"Displays a menu to the user at the cursor position when right-clicking on a trigger area. Supports checkboxes, radio groups, nested submenus, keyboard navigation, and accessibility features.","categories":["ui","menu","context-menu","web-component"],"author":"Lloyd Richards <lloyd.d.richards@gmail.com>","registryDependencies":["@lit/popover"],"dependencies":["lucide-static","@floating-ui/dom"],"files":[{"path":"registry/ui/context-menu/context-menu.ts","type":"registry:ui","content":"import { css, html, LitElement, nothing, type PropertyValues } from \"lit\";\nimport {\n  customElement,\n  property,\n  query,\n  queryAssignedElements,\n  state,\n} from \"lit/decorators.js\";\nimport { unsafeSVG } from \"lit/directives/unsafe-svg.js\";\nimport { Check, ChevronRight, Circle } from \"lucide-static\";\n\nimport { TW } from \"@/registry/lib/tailwindMixin\";\nimport { cn } from \"@/registry/lib/utils\";\nimport \"@/registry/ui/popover/popover\";\n\nexport type ContextMenuProperties = {\n  open?: boolean;\n  modal?: boolean;\n  disabled?: boolean;\n};\n\ntype VirtualAnchor = {\n  getBoundingClientRect(): DOMRect;\n};\n\ntype AnchorElement = Element | VirtualAnchor;\n\nexport type MenuItemWithProperties = HTMLElement & {\n  disabled?: boolean;\n  highlighted?: boolean;\n  value?: string;\n  checked?: boolean;\n};\n\nexport function isMenuItemElement(\n  element: HTMLElement,\n): element is MenuItemWithProperties {\n  const validTags = [\n    \"UI-CONTEXT-MENU-ITEM\",\n    \"UI-CONTEXT-MENU-CHECKBOX-ITEM\",\n    \"UI-CONTEXT-MENU-RADIO-ITEM\",\n    \"UI-CONTEXT-MENU-SUB-TRIGGER\",\n  ];\n  return validTags.includes(element.tagName);\n}\n\nconst isNode = (value: EventTarget | null): value is Node => {\n  return value instanceof Node;\n};\n\n@customElement(\"ui-context-menu\")\nexport class ContextMenu\n  extends TW(LitElement)\n  implements ContextMenuProperties\n{\n  static styles = css`\n    :host {\n      display: contents;\n    }\n  `;\n\n  @property({ type: Boolean, reflect: true }) open = false;\n  @property({ type: Boolean }) modal = true;\n  @property({ type: Boolean }) disabled = false;\n\n  @state() private cursorX = 0;\n  @state() private cursorY = 0;\n  @state() private virtualAnchor?: AnchorElement;\n\n  @query(\"ui-popover\") popoverElement?: HTMLElement;\n\n  private clickAwayHandler = (e: MouseEvent) => {\n    if (!this.open) return;\n\n    if (!isNode(e.target)) return;\n\n    if (this.querySelector(\"ui-context-menu-content\")?.contains(e.target))\n      return;\n    if (this.querySelector(\"ui-popover\")?.contains(e.target)) return;\n\n    const triggerSlot = this.shadowRoot?.querySelector<HTMLSlotElement>(\n      'slot[name=\"trigger\"]',\n    );\n    if (triggerSlot) {\n      const assignedElements = triggerSlot.assignedElements({ flatten: true });\n      for (const el of assignedElements) {\n        if (el.contains(e.target)) return;\n      }\n    }\n\n    this.open = false;\n  };\n\n  private escapeHandler = (e: KeyboardEvent) => {\n    if (e.key === \"Escape\" && this.open) {\n      e.preventDefault();\n      this.open = false;\n    }\n  };\n\n  override connectedCallback() {\n    super.connectedCallback();\n    this.addEventListener(\"item-select\", this.handleItemSelect);\n  }\n\n  override disconnectedCallback() {\n    super.disconnectedCallback();\n    this.removeEventListener(\"item-select\", this.handleItemSelect);\n    document.removeEventListener(\"click\", this.clickAwayHandler, true);\n    document.removeEventListener(\"keydown\", this.escapeHandler);\n  }\n\n  override updated(changedProperties: PropertyValues) {\n    super.updated(changedProperties);\n\n    if (changedProperties.has(\"open\")) {\n      if (this.open) {\n        setTimeout(() => {\n          document.addEventListener(\"click\", this.clickAwayHandler, true);\n          document.addEventListener(\"keydown\", this.escapeHandler);\n        }, 100);\n        const content = this.querySelector(\"ui-context-menu-content\");\n        if (content) {\n          setTimeout(() => {\n            const menu =\n              content.shadowRoot?.querySelector<HTMLElement>('[role=\"menu\"]');\n            menu?.focus();\n          }, 0);\n        }\n      } else {\n        document.removeEventListener(\"click\", this.clickAwayHandler, true);\n        document.removeEventListener(\"keydown\", this.escapeHandler);\n        this.dispatchEvent(\n          new CustomEvent(\"context-menu-close\", {\n            bubbles: true,\n            composed: true,\n          }),\n        );\n      }\n    }\n  }\n\n  private handleContextMenu = (e: MouseEvent) => {\n    if (this.disabled) return;\n\n    e.preventDefault();\n    e.stopPropagation();\n\n    this.cursorX = e.clientX;\n    this.cursorY = e.clientY;\n\n    this.virtualAnchor = {\n      getBoundingClientRect: () => ({\n        width: 0,\n        height: 0,\n        x: this.cursorX,\n        y: this.cursorY,\n        top: this.cursorY,\n        left: this.cursorX,\n        right: this.cursorX,\n        bottom: this.cursorY,\n        toJSON: () => this,\n      }),\n    };\n\n    this.open = true;\n\n    this.dispatchEvent(\n      new CustomEvent(\"context-menu-open\", {\n        detail: { x: this.cursorX, y: this.cursorY },\n        bubbles: true,\n        composed: true,\n      }),\n    );\n  };\n\n  private handleItemSelect = () => {\n    this.open = false;\n  };\n\n  override render() {\n    return html`\n      <slot name=\"trigger\" @contextmenu=${this.handleContextMenu}></slot>\n\n      <ui-popover\n        .active=${this.open}\n        .anchor=${this.virtualAnchor as Element}\n        placement=\"bottom-start\"\n        strategy=\"fixed\"\n        .distance=${0}\n        .flip=${true}\n        .shift=${true}\n      >\n        <slot name=\"content\"></slot>\n      </ui-popover>\n    `;\n  }\n}\n\nexport interface ContextMenuContentProperties {\n  align?: \"start\" | \"center\" | \"end\";\n  sideOffset?: number;\n  alignOffset?: number;\n  avoidCollisions?: boolean;\n  collisionPadding?: number;\n}\n\n@customElement(\"ui-context-menu-content\")\nexport class ContextMenuContent\n  extends TW(LitElement)\n  implements ContextMenuContentProperties\n{\n  @property({ type: String }) align: \"start\" | \"center\" | \"end\" = \"start\";\n  @property({ type: Number }) sideOffset = 5;\n  @property({ type: Number }) alignOffset = 0;\n  @property({ type: Boolean }) avoidCollisions = true;\n  @property({ type: Number }) collisionPadding = 8;\n\n  @state() private isOpen = false;\n  @state() private highlightedIndex = -1;\n  @state() private typeaheadString = \"\";\n  private typeaheadTimeout?: number;\n\n  @queryAssignedElements({ flatten: true })\n  private items!: HTMLElement[];\n\n  override connectedCallback() {\n    super.connectedCallback();\n    const menu = this.closest(\"ui-context-menu\");\n    if (menu) {\n      this.isOpen = menu.open;\n      const observer = new MutationObserver(() => {\n        this.isOpen = menu.open;\n      });\n      observer.observe(menu, { attributes: true, attributeFilter: [\"open\"] });\n    }\n  }\n\n  private getNavigableItems(): MenuItemWithProperties[] {\n    return this.items.filter(\n      (item): item is MenuItemWithProperties =>\n        isMenuItemElement(item) && !item.disabled,\n    );\n  }\n\n  private handleKeyDown = (e: KeyboardEvent) => {\n    const navItems = this.getNavigableItems();\n    if (navItems.length === 0) return;\n\n    switch (e.key) {\n      case \"ArrowDown\":\n        e.preventDefault();\n        this.highlightedIndex = Math.min(\n          this.highlightedIndex + 1,\n          navItems.length - 1,\n        );\n        this.updateHighlighted(navItems);\n        break;\n      case \"ArrowUp\":\n        e.preventDefault();\n        this.highlightedIndex = Math.max(this.highlightedIndex - 1, 0);\n        this.updateHighlighted(navItems);\n        break;\n      case \"Home\":\n        e.preventDefault();\n        this.highlightedIndex = 0;\n        this.updateHighlighted(navItems);\n        break;\n      case \"End\":\n        e.preventDefault();\n        this.highlightedIndex = navItems.length - 1;\n        this.updateHighlighted(navItems);\n        break;\n      case \"Enter\":\n      case \" \":\n        e.preventDefault();\n        if (this.highlightedIndex >= 0) {\n          navItems[this.highlightedIndex]?.click();\n        }\n        break;\n      case \"Escape\": {\n        e.preventDefault();\n        const menu = this.closest(\"ui-context-menu\");\n        if (menu) menu.open = false;\n        break;\n      }\n      default:\n        if (e.key.length === 1) {\n          this.handleTypeahead(e.key, navItems);\n        }\n    }\n  };\n\n  private handleTypeahead(char: string, items: HTMLElement[]) {\n    clearTimeout(this.typeaheadTimeout);\n    this.typeaheadString += char.toLowerCase();\n\n    const matchIndex = items.findIndex((item) =>\n      item.textContent?.toLowerCase().startsWith(this.typeaheadString),\n    );\n\n    if (matchIndex >= 0) {\n      this.highlightedIndex = matchIndex;\n      this.updateHighlighted(items);\n    }\n\n    this.typeaheadTimeout = window.setTimeout(() => {\n      this.typeaheadString = \"\";\n    }, 500);\n  }\n\n  private updateHighlighted(items: MenuItemWithProperties[]) {\n    items.forEach((item, index) => {\n      item.highlighted = index === this.highlightedIndex;\n    });\n  }\n\n  override render() {\n    if (!this.isOpen) return nothing;\n\n    return html`\n      <div\n        role=\"menu\"\n        tabindex=\"0\"\n        aria-orientation=\"vertical\"\n        class=${cn(\n          \"min-w-[8rem] max-w-[20rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md\",\n          \"animate-in fade-in-80 zoom-in-95\",\n          this.className,\n        )}\n        @keydown=${this.handleKeyDown}\n      >\n        <slot></slot>\n      </div>\n    `;\n  }\n}\n\nexport interface ContextMenuItemProperties {\n  disabled?: boolean;\n  inset?: boolean;\n}\n\n@customElement(\"ui-context-menu-item\")\nexport class ContextMenuItem\n  extends TW(LitElement)\n  implements ContextMenuItemProperties\n{\n  @property({ type: Boolean }) disabled = false;\n  @property({ type: Boolean }) inset = false;\n\n  @state() highlighted = false;\n\n  private handleClick = () => {\n    if (!this.disabled) {\n      this.dispatchEvent(\n        new CustomEvent(\"select\", {\n          detail: { value: this.textContent },\n          bubbles: true,\n          composed: true,\n        }),\n      );\n      this.dispatchEvent(\n        new CustomEvent(\"item-select\", {\n          bubbles: true,\n          composed: true,\n        }),\n      );\n    }\n  };\n\n  private handleMouseEnter = () => {\n    if (!this.disabled) {\n      this.highlighted = true;\n    }\n  };\n\n  private handleMouseLeave = () => {\n    this.highlighted = false;\n  };\n\n  override render() {\n    return html`\n      <div\n        role=\"menuitem\"\n        tabindex=\"-1\"\n        aria-disabled=${this.disabled}\n        class=${cn(\n          \"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors\",\n          \"hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground\",\n          \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n          \"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground\",\n          this.inset && \"pl-8\",\n          this.className,\n        )}\n        ?data-disabled=${this.disabled}\n        ?data-highlighted=${this.highlighted}\n        @click=${this.handleClick}\n        @mouseenter=${this.handleMouseEnter}\n        @mouseleave=${this.handleMouseLeave}\n      >\n        <slot></slot>\n        <span class=\"ml-auto text-xs\">\n          <slot name=\"shortcut\"></slot>\n        </span>\n      </div>\n    `;\n  }\n}\n\nexport interface ContextMenuCheckboxItemProperties {\n  checked?: boolean;\n  disabled?: boolean;\n}\n\n@customElement(\"ui-context-menu-checkbox-item\")\nexport class ContextMenuCheckboxItem\n  extends TW(LitElement)\n  implements ContextMenuCheckboxItemProperties\n{\n  @property({ type: Boolean }) checked = false;\n  @property({ type: Boolean }) disabled = false;\n\n  @state() highlighted = false;\n\n  private handleClick = () => {\n    if (!this.disabled) {\n      this.checked = !this.checked;\n      this.dispatchEvent(\n        new CustomEvent(\"checked-change\", {\n          detail: { checked: this.checked },\n          bubbles: true,\n          composed: true,\n        }),\n      );\n      this.dispatchEvent(\n        new CustomEvent(\"item-select\", {\n          bubbles: true,\n          composed: true,\n        }),\n      );\n    }\n  };\n\n  override render() {\n    return html`\n      <div\n        role=\"menuitemcheckbox\"\n        aria-checked=${this.checked}\n        tabindex=\"-1\"\n        aria-disabled=${this.disabled}\n        class=${cn(\n          \"relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors\",\n          \"hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground\",\n          \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n          this.className,\n        )}\n        data-state=${this.checked ? \"checked\" : \"unchecked\"}\n        ?data-disabled=${this.disabled}\n        @click=${this.handleClick}\n      >\n        <span\n          class=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\"\n        >\n          ${this.checked ? unsafeSVG(Check) : nothing}\n        </span>\n        <slot></slot>\n      </div>\n    `;\n  }\n}\n\nexport interface ContextMenuRadioGroupProperties {\n  value?: string;\n}\n\n@customElement(\"ui-context-menu-radio-group\")\nexport class ContextMenuRadioGroup\n  extends TW(LitElement)\n  implements ContextMenuRadioGroupProperties\n{\n  @property({ type: String }) value = \"\";\n\n  override connectedCallback() {\n    super.connectedCallback();\n    this.addEventListener(\"radio-select\", this.handleRadioSelect);\n  }\n\n  override disconnectedCallback() {\n    super.disconnectedCallback();\n    this.removeEventListener(\"radio-select\", this.handleRadioSelect);\n  }\n\n  override updated(changedProperties: PropertyValues) {\n    super.updated(changedProperties);\n\n    if (changedProperties.has(\"value\")) {\n      this.updateRadioItems();\n    }\n  }\n\n  private updateRadioItems() {\n    const items = this.querySelectorAll(\"ui-context-menu-radio-item\");\n    items.forEach((item) => {\n      item.checked = item.value === this.value;\n    });\n  }\n\n  private handleRadioSelect = (e: Event) => {\n    if (!(e instanceof CustomEvent)) return;\n    e.stopPropagation();\n    this.value = e.detail.value;\n    this.dispatchEvent(\n      new CustomEvent(\"value-change\", {\n        detail: { value: this.value },\n        bubbles: true,\n        composed: true,\n      }),\n    );\n  };\n\n  override render() {\n    return html`\n      <div role=\"group\">\n        <slot></slot>\n      </div>\n    `;\n  }\n}\n\nexport interface ContextMenuRadioItemProperties {\n  value?: string;\n  checked?: boolean;\n  disabled?: boolean;\n}\n\n@customElement(\"ui-context-menu-radio-item\")\nexport class ContextMenuRadioItem\n  extends TW(LitElement)\n  implements ContextMenuRadioItemProperties\n{\n  @property({ type: String }) value = \"\";\n  @property({ type: Boolean }) checked = false;\n  @property({ type: Boolean }) disabled = false;\n\n  @state() highlighted = false;\n\n  private handleClick = () => {\n    if (!this.disabled) {\n      this.dispatchEvent(\n        new CustomEvent(\"radio-select\", {\n          detail: { value: this.value },\n          bubbles: true,\n          composed: true,\n        }),\n      );\n      this.dispatchEvent(\n        new CustomEvent(\"item-select\", {\n          bubbles: true,\n          composed: true,\n        }),\n      );\n    }\n  };\n\n  override render() {\n    return html`\n      <div\n        role=\"menuitemradio\"\n        aria-checked=${this.checked}\n        tabindex=\"-1\"\n        aria-disabled=${this.disabled}\n        class=${cn(\n          \"relative flex cursor-default select-none items-center gap-2 rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors\",\n          \"hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground\",\n          \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n          this.className,\n        )}\n        ?data-disabled=${this.disabled}\n        @click=${this.handleClick}\n      >\n        <span\n          class=\"absolute left-2 flex h-3.5 w-3.5 items-center justify-center\"\n        >\n          ${this.checked ? unsafeSVG(Circle) : nothing}\n        </span>\n        <slot></slot>\n      </div>\n    `;\n  }\n}\n\nexport interface ContextMenuSubProperties {\n  open?: boolean;\n}\n\n@customElement(\"ui-context-menu-sub\")\nexport class ContextMenuSub\n  extends TW(LitElement)\n  implements ContextMenuSubProperties\n{\n  static styles = css`\n    :host {\n      display: contents;\n    }\n  `;\n\n  @property({ type: Boolean, reflect: true }) open = false;\n\n  @query(\"ui-context-menu-sub-trigger\") triggerElement?: HTMLElement;\n\n  private hoverTimeout?: number;\n\n  override connectedCallback() {\n    super.connectedCallback();\n    this.addEventListener(\"sub-trigger-click\", this.handleTriggerClick);\n    this.addEventListener(\"sub-trigger-mouseenter\", this.handleTriggerHover);\n  }\n\n  override disconnectedCallback() {\n    super.disconnectedCallback();\n    clearTimeout(this.hoverTimeout);\n    this.removeEventListener(\"sub-trigger-click\", this.handleTriggerClick);\n    this.removeEventListener(\"sub-trigger-mouseenter\", this.handleTriggerHover);\n  }\n\n  private handleTriggerClick = (e: Event) => {\n    e.stopPropagation();\n    this.open = !this.open;\n  };\n\n  private handleTriggerHover = () => {\n    clearTimeout(this.hoverTimeout);\n    this.hoverTimeout = window.setTimeout(() => {\n      this.open = true;\n    }, 200);\n  };\n\n  override render() {\n    return html`\n      <ui-popover\n        .active=${this.open}\n        .anchor=${this.triggerElement}\n        placement=\"right-start\"\n        .distance=${0}\n        .skidding=${-4}\n        .flip=${true}\n        .shift=${true}\n      >\n        <slot name=\"trigger\" slot=\"anchor\"></slot>\n        <slot name=\"content\"></slot>\n      </ui-popover>\n    `;\n  }\n}\n\nexport interface ContextMenuSubTriggerProperties {\n  disabled?: boolean;\n}\n\n@customElement(\"ui-context-menu-sub-trigger\")\nexport class ContextMenuSubTrigger\n  extends TW(LitElement)\n  implements ContextMenuSubTriggerProperties\n{\n  @property({ type: Boolean }) disabled = false;\n\n  @state() highlighted = false;\n\n  private getSubMenuOpen() {\n    const sub = this.closest(\"ui-context-menu-sub\");\n    return sub?.open ? \"true\" : \"false\";\n  }\n\n  private handleClick = (e: Event) => {\n    if (!this.disabled) {\n      e.stopPropagation();\n      this.dispatchEvent(\n        new CustomEvent(\"sub-trigger-click\", {\n          bubbles: true,\n          composed: true,\n        }),\n      );\n    }\n  };\n\n  private handleMouseEnter = () => {\n    if (!this.disabled) {\n      this.highlighted = true;\n      this.dispatchEvent(\n        new CustomEvent(\"sub-trigger-mouseenter\", {\n          bubbles: true,\n          composed: true,\n        }),\n      );\n    }\n  };\n\n  private handleMouseLeave = () => {\n    this.highlighted = false;\n  };\n\n  override render() {\n    return html`\n      <div\n        role=\"menuitem\"\n        aria-haspopup=\"menu\"\n        aria-expanded=${this.getSubMenuOpen()}\n        tabindex=\"-1\"\n        aria-disabled=${this.disabled}\n        class=${cn(\n          \"flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors\",\n          \"hover:bg-accent focus:bg-accent data-[state=open]:bg-accent\",\n          \"data-[disabled]:pointer-events-none data-[disabled]:opacity-50\",\n          this.className,\n        )}\n        ?data-disabled=${this.disabled}\n        @click=${this.handleClick}\n        @mouseenter=${this.handleMouseEnter}\n        @mouseleave=${this.handleMouseLeave}\n      >\n        <slot></slot>\n        <span class=\"ml-auto\" aria-hidden=\"true\">\n          ${unsafeSVG(ChevronRight)}\n        </span>\n      </div>\n    `;\n  }\n}\n\n@customElement(\"ui-context-menu-sub-content\")\nexport class ContextMenuSubContent extends ContextMenuContent {}\n\n@customElement(\"ui-context-menu-separator\")\nexport class ContextMenuSeparator extends TW(LitElement) {\n  override render() {\n    return html`\n      <div\n        role=\"separator\"\n        aria-orientation=\"horizontal\"\n        class=\"-mx-1 my-1 h-px bg-muted\"\n      ></div>\n    `;\n  }\n}\n\nexport interface ContextMenuLabelProperties {\n  inset?: boolean;\n}\n\n@customElement(\"ui-context-menu-label\")\nexport class ContextMenuLabel\n  extends TW(LitElement)\n  implements ContextMenuLabelProperties\n{\n  @property({ type: Boolean }) inset = false;\n\n  override render() {\n    return html`\n      <div\n        class=${cn(\n          \"px-2 py-1.5 text-sm font-semibold\",\n          this.inset && \"pl-8\",\n          this.className,\n        )}\n      >\n        <slot></slot>\n      </div>\n    `;\n  }\n}\n\n@customElement(\"ui-context-menu-group\")\nexport class ContextMenuGroup extends TW(LitElement) {\n  override render() {\n    return html`\n      <div role=\"group\">\n        <slot></slot>\n      </div>\n    `;\n  }\n}\n\n@customElement(\"ui-context-menu-shortcut\")\nexport class ContextMenuShortcut extends TW(LitElement) {\n  override render() {\n    return html`\n      <span class=\"ml-auto text-xs tracking-widest text-muted-foreground\">\n        <slot></slot>\n      </span>\n    `;\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    \"ui-context-menu\": ContextMenu;\n    \"ui-context-menu-content\": ContextMenuContent;\n    \"ui-context-menu-item\": ContextMenuItem;\n    \"ui-context-menu-checkbox-item\": ContextMenuCheckboxItem;\n    \"ui-context-menu-radio-group\": ContextMenuRadioGroup;\n    \"ui-context-menu-radio-item\": ContextMenuRadioItem;\n    \"ui-context-menu-sub\": ContextMenuSub;\n    \"ui-context-menu-sub-trigger\": ContextMenuSubTrigger;\n    \"ui-context-menu-sub-content\": ContextMenuSubContent;\n    \"ui-context-menu-separator\": ContextMenuSeparator;\n    \"ui-context-menu-label\": ContextMenuLabel;\n    \"ui-context-menu-group\": ContextMenuGroup;\n    \"ui-context-menu-shortcut\": ContextMenuShortcut;\n  }\n}\n"},{"path":"registry/ui/context-menu/context-menu.stories.ts","type":"registry:ui","content":"import \"./context-menu\";\nimport type { Meta, StoryObj } from \"@storybook/web-components-vite\";\nimport { html } from \"lit\";\n\nconst meta: Meta = {\n  title: \"ui/Context Menu\",\n  component: \"ui-context-menu\",\n  tags: [\"autodocs\"],\n  render: () => html`\n    <ui-context-menu>\n      <div\n        slot=\"trigger\"\n        class=\"flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm\"\n      >\n        Right click here\n      </div>\n\n      <ui-context-menu-content slot=\"content\">\n        <ui-context-menu-item>\n          Back\n          <ui-context-menu-shortcut slot=\"shortcut\"\n            >⌘[</ui-context-menu-shortcut\n          >\n        </ui-context-menu-item>\n        <ui-context-menu-item disabled>\n          Forward\n          <ui-context-menu-shortcut slot=\"shortcut\"\n            >⌘]</ui-context-menu-shortcut\n          >\n        </ui-context-menu-item>\n        <ui-context-menu-item>\n          Reload\n          <ui-context-menu-shortcut slot=\"shortcut\"\n            >⌘R</ui-context-menu-shortcut\n          >\n        </ui-context-menu-item>\n        <ui-context-menu-separator></ui-context-menu-separator>\n        <ui-context-menu-item>Inspect</ui-context-menu-item>\n      </ui-context-menu-content>\n    </ui-context-menu>\n  `,\n};\n\nexport default meta;\ntype Story = StoryObj;\n\nexport const Default: Story = {};\n\nexport const WithCheckboxes: Story = {\n  render: () => html`\n    <ui-context-menu>\n      <div\n        slot=\"trigger\"\n        class=\"flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm\"\n      >\n        Right-click for options\n      </div>\n\n      <ui-context-menu-content slot=\"content\">\n        <ui-context-menu-checkbox-item checked>\n          Show Bookmarks Bar\n        </ui-context-menu-checkbox-item>\n        <ui-context-menu-checkbox-item>\n          Show Full URLs\n        </ui-context-menu-checkbox-item>\n        <ui-context-menu-separator></ui-context-menu-separator>\n        <ui-context-menu-label>People</ui-context-menu-label>\n        <ui-context-menu-radio-group value=\"pedro\">\n          <ui-context-menu-radio-item value=\"pedro\">\n            Pedro Duarte\n          </ui-context-menu-radio-item>\n          <ui-context-menu-radio-item value=\"colm\">\n            Colm Tuite\n          </ui-context-menu-radio-item>\n        </ui-context-menu-radio-group>\n      </ui-context-menu-content>\n    </ui-context-menu>\n  `,\n};\n\nexport const WithSubmenu: Story = {\n  render: () => html`\n    <ui-context-menu>\n      <div\n        slot=\"trigger\"\n        class=\"flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm\"\n      >\n        Right-click for menu\n      </div>\n\n      <ui-context-menu-content slot=\"content\">\n        <ui-context-menu-item>Back</ui-context-menu-item>\n        <ui-context-menu-item>Reload</ui-context-menu-item>\n\n        <ui-context-menu-sub>\n          <ui-context-menu-sub-trigger slot=\"trigger\">\n            More Tools\n          </ui-context-menu-sub-trigger>\n\n          <ui-context-menu-sub-content slot=\"content\">\n            <ui-context-menu-item>Save Page...</ui-context-menu-item>\n            <ui-context-menu-item>Create Shortcut...</ui-context-menu-item>\n            <ui-context-menu-separator></ui-context-menu-separator>\n            <ui-context-menu-item>Developer Tools</ui-context-menu-item>\n          </ui-context-menu-sub-content>\n        </ui-context-menu-sub>\n\n        <ui-context-menu-separator></ui-context-menu-separator>\n        <ui-context-menu-item>Inspect</ui-context-menu-item>\n      </ui-context-menu-content>\n    </ui-context-menu>\n  `,\n};\n\nexport const WithShortcuts: Story = {\n  render: () => html`\n    <ui-context-menu>\n      <div\n        slot=\"trigger\"\n        class=\"flex h-[150px] w-[300px] items-center justify-center rounded-md border border-dashed text-sm\"\n      >\n        Right-click here\n      </div>\n\n      <ui-context-menu-content slot=\"content\">\n        <ui-context-menu-item>\n          New Tab\n          <ui-context-menu-shortcut slot=\"shortcut\"\n            >⌘T</ui-context-menu-shortcut\n          >\n        </ui-context-menu-item>\n        <ui-context-menu-item>\n          New Window\n          <ui-context-menu-shortcut slot=\"shortcut\"\n            >⌘N</ui-context-menu-shortcut\n          >\n        </ui-context-menu-item>\n        <ui-context-menu-separator></ui-context-menu-separator>\n        <ui-context-menu-item>\n          Close Tab\n          <ui-context-menu-shortcut slot=\"shortcut\"\n            >⌘W</ui-context-menu-shortcut\n          >\n        </ui-context-menu-item>\n        <ui-context-menu-item>\n          Close Window\n          <ui-context-menu-shortcut slot=\"shortcut\"\n            >⌘⇧W</ui-context-menu-shortcut\n          >\n        </ui-context-menu-item>\n      </ui-context-menu-content>\n    </ui-context-menu>\n  `,\n};\n\nexport const Complex: Story = {\n  render: () => html`\n    <ui-context-menu>\n      <div\n        slot=\"trigger\"\n        class=\"flex h-[200px] w-[400px] items-center justify-center rounded-md border-2 border-dashed text-sm\"\n      >\n        Right-click for full menu\n      </div>\n\n      <ui-context-menu-content slot=\"content\">\n        <ui-context-menu-label>Actions</ui-context-menu-label>\n        <ui-context-menu-separator></ui-context-menu-separator>\n\n        <ui-context-menu-group>\n          <ui-context-menu-item>\n            Cut\n            <ui-context-menu-shortcut slot=\"shortcut\"\n              >⌘X</ui-context-menu-shortcut\n            >\n          </ui-context-menu-item>\n          <ui-context-menu-item>\n            Copy\n            <ui-context-menu-shortcut slot=\"shortcut\"\n              >⌘C</ui-context-menu-shortcut\n            >\n          </ui-context-menu-item>\n          <ui-context-menu-item>\n            Paste\n            <ui-context-menu-shortcut slot=\"shortcut\"\n              >⌘V</ui-context-menu-shortcut\n            >\n          </ui-context-menu-item>\n        </ui-context-menu-group>\n\n        <ui-context-menu-separator></ui-context-menu-separator>\n\n        <ui-context-menu-checkbox-item checked>\n          Show Grid\n        </ui-context-menu-checkbox-item>\n        <ui-context-menu-checkbox-item>\n          Show Rulers\n        </ui-context-menu-checkbox-item>\n\n        <ui-context-menu-separator></ui-context-menu-separator>\n\n        <ui-context-menu-sub>\n          <ui-context-menu-sub-trigger slot=\"trigger\">\n            Share\n          </ui-context-menu-sub-trigger>\n\n          <ui-context-menu-sub-content slot=\"content\">\n            <ui-context-menu-item>Email</ui-context-menu-item>\n            <ui-context-menu-item>Message</ui-context-menu-item>\n            <ui-context-menu-separator></ui-context-menu-separator>\n            <ui-context-menu-item>More...</ui-context-menu-item>\n          </ui-context-menu-sub-content>\n        </ui-context-menu-sub>\n\n        <ui-context-menu-separator></ui-context-menu-separator>\n\n        <ui-context-menu-item>Delete</ui-context-menu-item>\n      </ui-context-menu-content>\n    </ui-context-menu>\n  `,\n};\n"}]}