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)