Dropdown Menu
Dropdown Menu — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).
Загрузка превью…
src/ui/dropdown-menu/DropdownMenu.vue
<script setup lang="ts">
/**
* Порт базового радикс-слоя (registry/bases/radix/ui/dropdown-menu.tsx, MIT).
* Radix DropdownMenuPrimitive -> reka-ui DropdownMenu*, один в один: те же
* подкомпоненты (Root/Portal/Trigger/Content/Group/Item/CheckboxItem/
* RadioGroup/RadioItem/Label/Separator/Sub/SubTrigger/SubContent),
* `data-slot` и Tailwind-строки как в оригинале.
*
* Позиционирующие CSS-переменные Radix (`--radix-dropdown-menu-*`)
* оставлены буквально как в оригинале (правило 1: cva/Tailwind-строки
* переносятся дословно, без правок), хотя reka-ui выставляет
* содержательно те же переменные под своим префиксом
* (`--reka-dropdown-menu-*`). В открытом состоянии эти конкретные
* `max-h-()`/`w-()`/`origin-()` поэтому не резолвятся — тот же выбор,
* что уже сделан в `select`/`phone-input`; гейт проверяет закрытое
* состояние, где Content не рендерится вовсе. См. docs/PORTING.md.
*
* Никаких собственных пропсов: все атрибуты (`open`, `defaultOpen`,
* `modal`, `dir`, `@update:open` и т.д.) идут автоматическим fallthrough
* на корневой reka-ui компонент.
*/
import { DropdownMenuRoot } from "reka-ui"
</script>
<template>
<DropdownMenuRoot data-slot="dropdown-menu">
<slot />
</DropdownMenuRoot>
</template>
src/ui/dropdown-menu/DropdownMenuCheckboxItem.vue
<script setup lang="ts">
/**
* `checked` в оригинале — проп Radix CheckboxItem. У reka-ui тот же
* примитив принимает `modelValue`/`v-model` (см. docs/PORTING.md) — это
* стандартное расхождение именования между Radix (React) и reka-ui (Vue),
* а не решение порта; шаблон ниже не переименовывает его обратно, чтобы
* остаться совместимым с shadcn-vue.
*/
import type { HTMLAttributes } from "vue"
import { DropdownMenuCheckboxItem, DropdownMenuItemIndicator } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
inset?: boolean
}>()
</script>
<template>
<DropdownMenuCheckboxItem
data-slot="dropdown-menu-checkbox-item"
:data-inset="props.inset"
:class="
cn(
'cn-dropdown-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-dropdown-menu-item-indicator pointer-events-none"
data-slot="dropdown-menu-checkbox-item-indicator"
>
<DropdownMenuItemIndicator>
<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>
</DropdownMenuItemIndicator>
</span>
<slot />
</DropdownMenuCheckboxItem>
</template>
src/ui/dropdown-menu/DropdownMenuContent.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { DropdownMenuContent, DropdownMenuPortal } from "reka-ui"
import { cn } from "@/lib/utils"
const props = withDefaults(
defineProps<{
class?: HTMLAttributes["class"]
align?: "start" | "center" | "end"
sideOffset?: number
}>(),
{
align: "start",
sideOffset: 4,
}
)
</script>
<template>
<DropdownMenuPortal>
<DropdownMenuContent
data-slot="dropdown-menu-content"
:side-offset="props.sideOffset"
:align="props.align"
:class="
cn(
'cn-dropdown-menu-content cn-menu-target cn-menu-translucent z-50 max-h-(--radix-dropdown-menu-content-available-height) w-(--radix-dropdown-menu-trigger-width) origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto data-[state=closed]:overflow-hidden',
props.class
)
"
>
<slot />
</DropdownMenuContent>
</DropdownMenuPortal>
</template>
src/ui/dropdown-menu/DropdownMenuGroup.vue
<script setup lang="ts">
import { DropdownMenuGroup } from "reka-ui"
</script>
<template>
<DropdownMenuGroup data-slot="dropdown-menu-group">
<slot />
</DropdownMenuGroup>
</template>
src/ui/dropdown-menu/DropdownMenuItem.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { DropdownMenuItem } from "reka-ui"
import { cn } from "@/lib/utils"
const props = withDefaults(
defineProps<{
class?: HTMLAttributes["class"]
inset?: boolean
variant?: "default" | "destructive"
}>(),
{
variant: "default",
}
)
</script>
<template>
<DropdownMenuItem
data-slot="dropdown-menu-item"
:data-inset="props.inset"
:data-variant="props.variant"
:class="
cn(
'cn-dropdown-menu-item group/dropdown-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 />
</DropdownMenuItem>
</template>
src/ui/dropdown-menu/DropdownMenuLabel.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { DropdownMenuLabel } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
inset?: boolean
}>()
</script>
<template>
<DropdownMenuLabel
data-slot="dropdown-menu-label"
:data-inset="props.inset"
:class="cn('cn-dropdown-menu-label', props.class)"
>
<slot />
</DropdownMenuLabel>
</template>
src/ui/dropdown-menu/DropdownMenuPortal.vue
<script setup lang="ts">
import { DropdownMenuPortal } from "reka-ui"
</script>
<template>
<DropdownMenuPortal data-slot="dropdown-menu-portal">
<slot />
</DropdownMenuPortal>
</template>
src/ui/dropdown-menu/DropdownMenuRadioGroup.vue
<script setup lang="ts">
import { DropdownMenuRadioGroup } from "reka-ui"
</script>
<template>
<DropdownMenuRadioGroup data-slot="dropdown-menu-radio-group">
<slot />
</DropdownMenuRadioGroup>
</template>
src/ui/dropdown-menu/DropdownMenuRadioItem.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import type { AcceptableValue } from "reka-ui"
import { DropdownMenuItemIndicator, DropdownMenuRadioItem } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
inset?: boolean
value: AcceptableValue
}>()
</script>
<template>
<DropdownMenuRadioItem
data-slot="dropdown-menu-radio-item"
:value="props.value"
:data-inset="props.inset"
:class="
cn(
'cn-dropdown-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-dropdown-menu-item-indicator pointer-events-none"
data-slot="dropdown-menu-radio-item-indicator"
>
<DropdownMenuItemIndicator>
<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>
</DropdownMenuItemIndicator>
</span>
<slot />
</DropdownMenuRadioItem>
</template>
src/ui/dropdown-menu/DropdownMenuSeparator.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { DropdownMenuSeparator } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<DropdownMenuSeparator
data-slot="dropdown-menu-separator"
:class="cn('cn-dropdown-menu-separator', props.class)"
/>
</template>
src/ui/dropdown-menu/DropdownMenuShortcut.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="dropdown-menu-shortcut"
:class="cn('cn-dropdown-menu-shortcut', props.class)"
><slot /></span>
</template>
src/ui/dropdown-menu/DropdownMenuSub.vue
<script setup lang="ts">
import { DropdownMenuSub } from "reka-ui"
</script>
<template>
<DropdownMenuSub data-slot="dropdown-menu-sub">
<slot />
</DropdownMenuSub>
</template>
src/ui/dropdown-menu/DropdownMenuSubContent.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { DropdownMenuSubContent } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<DropdownMenuSubContent
data-slot="dropdown-menu-sub-content"
:class="
cn(
'cn-dropdown-menu-sub-content cn-menu-target cn-menu-translucent z-50 origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden',
props.class
)
"
>
<slot />
</DropdownMenuSubContent>
</template>
src/ui/dropdown-menu/DropdownMenuSubTrigger.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { DropdownMenuSubTrigger } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
inset?: boolean
}>()
</script>
<template>
<DropdownMenuSubTrigger
data-slot="dropdown-menu-sub-trigger"
:data-inset="props.inset"
:class="
cn(
'cn-dropdown-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>
</DropdownMenuSubTrigger>
</template>
src/ui/dropdown-menu/DropdownMenuTrigger.vue
<script setup lang="ts">
import { DropdownMenuTrigger } from "reka-ui"
</script>
<template>
<DropdownMenuTrigger data-slot="dropdown-menu-trigger">
<slot />
</DropdownMenuTrigger>
</template>
src/ui/dropdown-menu/index.ts
export { default as DropdownMenu } from "./DropdownMenu.vue"
export { default as DropdownMenuPortal } from "./DropdownMenuPortal.vue"
export { default as DropdownMenuTrigger } from "./DropdownMenuTrigger.vue"
export { default as DropdownMenuContent } from "./DropdownMenuContent.vue"
export { default as DropdownMenuGroup } from "./DropdownMenuGroup.vue"
export { default as DropdownMenuItem } from "./DropdownMenuItem.vue"
export { default as DropdownMenuCheckboxItem } from "./DropdownMenuCheckboxItem.vue"
export { default as DropdownMenuRadioGroup } from "./DropdownMenuRadioGroup.vue"
export { default as DropdownMenuRadioItem } from "./DropdownMenuRadioItem.vue"
export { default as DropdownMenuLabel } from "./DropdownMenuLabel.vue"
export { default as DropdownMenuSeparator } from "./DropdownMenuSeparator.vue"
export { default as DropdownMenuShortcut } from "./DropdownMenuShortcut.vue"
export { default as DropdownMenuSub } from "./DropdownMenuSub.vue"
export { default as DropdownMenuSubTrigger } from "./DropdownMenuSubTrigger.vue"
export { default as DropdownMenuSubContent } from "./DropdownMenuSubContent.vue"
Установка
npx shadcn-vue@latest add https://revueui.rootapi.dev/r/dropdown-menu.jsonЗависимости реестра
npm-зависимости
- reka-ui
Источник: порт из ReUI (Keenthemes, MIT)