primitives

Context Menu

Context Menu — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).

Загрузка превью…

src/ui/context-menu/ContextMenu.vue

<script setup lang="ts">
/**
 * Порт базового радикс-слоя (registry/bases/radix/ui/context-menu.tsx,
 * MIT). Radix ContextMenuPrimitive -> reka-ui ContextMenu*, один в один.
 * Позиционирующие CSS-переменные Radix (`--radix-context-menu-*`)
 * оставлены буквально как в оригинале (правило 1), хотя reka-ui
 * выставляет содержательно те же переменные под своим префиксом. См.
 * docs/PORTING.md.
 */
import { ContextMenuRoot } from "reka-ui"
</script>

<template>
  <ContextMenuRoot data-slot="context-menu">
    <slot />
  </ContextMenuRoot>
</template>

src/ui/context-menu/ContextMenuCheckboxItem.vue

<script setup lang="ts">
/**
 * `checked` в оригинале — проп Radix CheckboxItem; у reka-ui тот же
 * примитив принимает `modelValue`/`v-model`, см. docs/PORTING.md.
 */
import type { HTMLAttributes } from "vue"
import { ContextMenuCheckboxItem, ContextMenuItemIndicator } from "reka-ui"
import { cn } from "@/lib/utils"

const props = defineProps<{
  class?: HTMLAttributes["class"]
  inset?: boolean
}>()
</script>

<template>
  <ContextMenuCheckboxItem
    data-slot="context-menu-checkbox-item"
    :data-inset="props.inset"
    :class="
      cn(
        'cn-context-menu-checkbox-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
        props.class
      )
    "
  >
    <span class="cn-context-menu-item-indicator pointer-events-none">
      <ContextMenuItemIndicator>
        <svg
          xmlns="http://www.w3.org/2000/svg"
          width="14"
          height="14"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
        >
          <polyline points="20 6 9 17 4 12" />
        </svg>
      </ContextMenuItemIndicator>
    </span>
    <slot />
  </ContextMenuCheckboxItem>
</template>

src/ui/context-menu/ContextMenuContent.vue

<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { ContextMenuContent, ContextMenuPortal } from "reka-ui"
import { cn } from "@/lib/utils"

const props = defineProps<{
  class?: HTMLAttributes["class"]
}>()
</script>

<template>
  <ContextMenuPortal>
    <ContextMenuContent
      data-slot="context-menu-content"
      :class="
        cn(
          'cn-context-menu-content cn-menu-target cn-menu-translucent z-50 max-h-(--radix-context-menu-content-available-height) origin-(--radix-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto',
          props.class
        )
      "
    >
      <slot />
    </ContextMenuContent>
  </ContextMenuPortal>
</template>

src/ui/context-menu/ContextMenuGroup.vue

<script setup lang="ts">
import { ContextMenuGroup } from "reka-ui"
</script>

<template>
  <ContextMenuGroup data-slot="context-menu-group">
    <slot />
  </ContextMenuGroup>
</template>

src/ui/context-menu/ContextMenuItem.vue

<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { ContextMenuItem } from "reka-ui"
import { cn } from "@/lib/utils"

const props = withDefaults(
  defineProps<{
    class?: HTMLAttributes["class"]
    inset?: boolean
    variant?: "default" | "destructive"
  }>(),
  {
    variant: "default",
  }
)
</script>

<template>
  <ContextMenuItem
    data-slot="context-menu-item"
    :data-inset="props.inset"
    :data-variant="props.variant"
    :class="
      cn(
        'cn-context-menu-item group/context-menu-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
        props.class
      )
    "
  >
    <slot />
  </ContextMenuItem>
</template>

src/ui/context-menu/ContextMenuLabel.vue

<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { ContextMenuLabel } from "reka-ui"
import { cn } from "@/lib/utils"

const props = defineProps<{
  class?: HTMLAttributes["class"]
  inset?: boolean
}>()
</script>

<template>
  <ContextMenuLabel
    data-slot="context-menu-label"
    :data-inset="props.inset"
    :class="cn('cn-context-menu-label', props.class)"
  >
    <slot />
  </ContextMenuLabel>
</template>

src/ui/context-menu/ContextMenuPortal.vue

<script setup lang="ts">
import { ContextMenuPortal } from "reka-ui"
</script>

<template>
  <ContextMenuPortal data-slot="context-menu-portal">
    <slot />
  </ContextMenuPortal>
</template>

src/ui/context-menu/ContextMenuRadioGroup.vue

<script setup lang="ts">
import { ContextMenuRadioGroup } from "reka-ui"
</script>

<template>
  <ContextMenuRadioGroup data-slot="context-menu-radio-group">
    <slot />
  </ContextMenuRadioGroup>
</template>

src/ui/context-menu/ContextMenuRadioItem.vue

<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import type { AcceptableValue } from "reka-ui"
import { ContextMenuItemIndicator, ContextMenuRadioItem } from "reka-ui"
import { cn } from "@/lib/utils"

const props = defineProps<{
  class?: HTMLAttributes["class"]
  inset?: boolean
  value: AcceptableValue
}>()
</script>

<template>
  <ContextMenuRadioItem
    data-slot="context-menu-radio-item"
    :value="props.value"
    :data-inset="props.inset"
    :class="
      cn(
        'cn-context-menu-radio-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
        props.class
      )
    "
  >
    <span class="cn-context-menu-item-indicator pointer-events-none">
      <ContextMenuItemIndicator>
        <svg
          xmlns="http://www.w3.org/2000/svg"
          width="14"
          height="14"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
          stroke-linecap="round"
          stroke-linejoin="round"
        >
          <polyline points="20 6 9 17 4 12" />
        </svg>
      </ContextMenuItemIndicator>
    </span>
    <slot />
  </ContextMenuRadioItem>
</template>

src/ui/context-menu/ContextMenuSeparator.vue

<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { ContextMenuSeparator } from "reka-ui"
import { cn } from "@/lib/utils"

const props = defineProps<{
  class?: HTMLAttributes["class"]
}>()
</script>

<template>
  <ContextMenuSeparator
    data-slot="context-menu-separator"
    :class="cn('cn-context-menu-separator', props.class)"
  />
</template>

src/ui/context-menu/ContextMenuShortcut.vue

<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"

const props = defineProps<{
  class?: HTMLAttributes["class"]
}>()
</script>

<template>
  <span
    data-slot="context-menu-shortcut"
    :class="cn('cn-context-menu-shortcut', props.class)"
  ><slot /></span>
</template>

src/ui/context-menu/ContextMenuSub.vue

<script setup lang="ts">
import { ContextMenuSub } from "reka-ui"
</script>

<template>
  <ContextMenuSub data-slot="context-menu-sub">
    <slot />
  </ContextMenuSub>
</template>

src/ui/context-menu/ContextMenuSubContent.vue

<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { ContextMenuSubContent } from "reka-ui"
import { cn } from "@/lib/utils"

const props = defineProps<{
  class?: HTMLAttributes["class"]
}>()
</script>

<template>
  <ContextMenuSubContent
    data-slot="context-menu-sub-content"
    :class="
      cn(
        'cn-context-menu-sub-content cn-menu-target cn-menu-translucent z-50 origin-(--radix-context-menu-content-transform-origin) overflow-hidden',
        props.class
      )
    "
  >
    <slot />
  </ContextMenuSubContent>
</template>

src/ui/context-menu/ContextMenuSubTrigger.vue

<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { ContextMenuSubTrigger } from "reka-ui"
import { cn } from "@/lib/utils"

const props = defineProps<{
  class?: HTMLAttributes["class"]
  inset?: boolean
}>()
</script>

<template>
  <ContextMenuSubTrigger
    data-slot="context-menu-sub-trigger"
    :data-inset="props.inset"
    :class="
      cn(
        'cn-context-menu-sub-trigger flex cursor-default items-center outline-hidden select-none [&_svg]:pointer-events-none [&_svg]:shrink-0',
        props.class
      )
    "
  >
    <slot />
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width="14"
      height="14"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      stroke-width="2"
      stroke-linecap="round"
      stroke-linejoin="round"
      class="cn-rtl-flip ml-auto"
    >
      <path d="m9 18 6-6-6-6" />
    </svg>
  </ContextMenuSubTrigger>
</template>

src/ui/context-menu/ContextMenuTrigger.vue

<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { ContextMenuTrigger } from "reka-ui"
import { cn } from "@/lib/utils"

const props = defineProps<{
  class?: HTMLAttributes["class"]
}>()
</script>

<template>
  <ContextMenuTrigger
    data-slot="context-menu-trigger"
    :class="cn('cn-context-menu-trigger select-none', props.class)"
  >
    <slot />
  </ContextMenuTrigger>
</template>

src/ui/context-menu/index.ts

export { default as ContextMenu } from "./ContextMenu.vue"
export { default as ContextMenuTrigger } from "./ContextMenuTrigger.vue"
export { default as ContextMenuContent } from "./ContextMenuContent.vue"
export { default as ContextMenuItem } from "./ContextMenuItem.vue"
export { default as ContextMenuCheckboxItem } from "./ContextMenuCheckboxItem.vue"
export { default as ContextMenuRadioItem } from "./ContextMenuRadioItem.vue"
export { default as ContextMenuLabel } from "./ContextMenuLabel.vue"
export { default as ContextMenuSeparator } from "./ContextMenuSeparator.vue"
export { default as ContextMenuShortcut } from "./ContextMenuShortcut.vue"
export { default as ContextMenuGroup } from "./ContextMenuGroup.vue"
export { default as ContextMenuPortal } from "./ContextMenuPortal.vue"
export { default as ContextMenuSub } from "./ContextMenuSub.vue"
export { default as ContextMenuSubContent } from "./ContextMenuSubContent.vue"
export { default as ContextMenuSubTrigger } from "./ContextMenuSubTrigger.vue"
export { default as ContextMenuRadioGroup } from "./ContextMenuRadioGroup.vue"

Установка

npx shadcn-vue@latest add https://revueui.rootapi.dev/r/context-menu.json

Зависимости реестра

npm-зависимости

  • reka-ui

Источник: порт из ReUI (Keenthemes, MIT)