primitives
Menubar
Menubar — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).
Загрузка превью…
src/ui/menubar/Menubar.vue
<script setup lang="ts">
/**
* Порт базового радикс-слоя (registry/bases/radix/ui/menubar.tsx, MIT).
* Radix MenubarPrimitive -> reka-ui Menubar*, один в один: те же
* подкомпоненты (Root/Menu/Trigger/Portal/Content/Group/Item/CheckboxItem/
* RadioGroup/RadioItem/Label/Separator/Sub/SubTrigger/SubContent),
* `data-slot` и Tailwind-строки как в оригинале.
*
* Позиционирующая CSS-переменная Radix (`--radix-menubar-content-transform-
* origin`) оставлена буквально как в оригинале (правило 1) — см.
* docs/PORTING.md, тот же выбор, что уже сделан для dropdown-menu/select.
*/
import type { HTMLAttributes } from "vue"
import { MenubarRoot } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<MenubarRoot data-slot="menubar" :class="cn('cn-menubar flex items-center', props.class)">
<slot />
</MenubarRoot>
</template>
src/ui/menubar/MenubarCheckboxItem.vue
<script setup lang="ts">
/**
* `checked` в оригинале — проп Radix CheckboxItem. У reka-ui тот же
* примитив принимает `modelValue`/`v-model` (см. docs/PORTING.md,
* тот же выбор, что уже сделан для dropdown-menu/context-menu) — это
* стандартное расхождение именования между Radix (React) и reka-ui (Vue),
* а не решение порта.
*/
import type { HTMLAttributes } from "vue"
import { MenubarCheckboxItem, MenubarItemIndicator } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
inset?: boolean
}>()
</script>
<template>
<MenubarCheckboxItem
data-slot="menubar-checkbox-item"
:data-inset="props.inset"
:class="
cn(
'cn-menubar-checkbox-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0',
props.class
)
"
>
<span
class="cn-menubar-checkbox-item-indicator pointer-events-none absolute flex items-center justify-center"
data-slot="menubar-checkbox-item-indicator"
>
<MenubarItemIndicator>
<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>
</MenubarItemIndicator>
</span>
<slot />
</MenubarCheckboxItem>
</template>
src/ui/menubar/MenubarContent.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { MenubarContent, MenubarPortal } from "reka-ui"
import { cn } from "@/lib/utils"
const props = withDefaults(
defineProps<{
class?: HTMLAttributes["class"]
align?: "start" | "center" | "end"
alignOffset?: number
sideOffset?: number
}>(),
{
align: "start",
alignOffset: -4,
sideOffset: 8,
}
)
</script>
<template>
<MenubarPortal>
<MenubarContent
data-slot="menubar-content"
:align="props.align"
:align-offset="props.alignOffset"
:side-offset="props.sideOffset"
:class="
cn(
'cn-menubar-content cn-menu-target cn-menu-translucent z-50 origin-(--radix-menubar-content-transform-origin) overflow-hidden',
props.class
)
"
>
<slot />
</MenubarContent>
</MenubarPortal>
</template>
src/ui/menubar/MenubarGroup.vue
<script setup lang="ts">
import { MenubarGroup } from "reka-ui"
</script>
<template>
<MenubarGroup data-slot="menubar-group">
<slot />
</MenubarGroup>
</template>
src/ui/menubar/MenubarItem.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { MenubarItem } from "reka-ui"
import { cn } from "@/lib/utils"
const props = withDefaults(
defineProps<{
class?: HTMLAttributes["class"]
inset?: boolean
variant?: "default" | "destructive"
}>(),
{
variant: "default",
}
)
</script>
<template>
<MenubarItem
data-slot="menubar-item"
:data-inset="props.inset"
:data-variant="props.variant"
:class="
cn(
'cn-menubar-item group/menubar-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0',
props.class
)
"
>
<slot />
</MenubarItem>
</template>
src/ui/menubar/MenubarLabel.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { MenubarLabel } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
inset?: boolean
}>()
</script>
<template>
<MenubarLabel
data-slot="menubar-label"
:data-inset="props.inset"
:class="cn('cn-menubar-label', props.class)"
>
<slot />
</MenubarLabel>
</template>
src/ui/menubar/MenubarMenu.vue
<script setup lang="ts">
import { MenubarMenu } from "reka-ui"
</script>
<template>
<MenubarMenu data-slot="menubar-menu">
<slot />
</MenubarMenu>
</template>
src/ui/menubar/MenubarPortal.vue
<script setup lang="ts">
import { MenubarPortal } from "reka-ui"
</script>
<template>
<MenubarPortal data-slot="menubar-portal">
<slot />
</MenubarPortal>
</template>
src/ui/menubar/MenubarRadioGroup.vue
<script setup lang="ts">
import { MenubarRadioGroup } from "reka-ui"
</script>
<template>
<MenubarRadioGroup data-slot="menubar-radio-group">
<slot />
</MenubarRadioGroup>
</template>
src/ui/menubar/MenubarRadioItem.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import type { AcceptableValue } from "reka-ui"
import { MenubarItemIndicator, MenubarRadioItem } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
inset?: boolean
value: AcceptableValue
}>()
</script>
<template>
<MenubarRadioItem
data-slot="menubar-radio-item"
:value="props.value"
:data-inset="props.inset"
:class="
cn(
'cn-menubar-radio-item relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0',
props.class
)
"
>
<span
class="cn-menubar-radio-item-indicator pointer-events-none absolute flex items-center justify-center"
data-slot="menubar-radio-item-indicator"
>
<MenubarItemIndicator>
<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>
</MenubarItemIndicator>
</span>
<slot />
</MenubarRadioItem>
</template>
src/ui/menubar/MenubarSeparator.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { MenubarSeparator } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<MenubarSeparator
data-slot="menubar-separator"
:class="cn('cn-menubar-separator -mx-1 my-1 h-px', props.class)"
/>
</template>
src/ui/menubar/MenubarShortcut.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="menubar-shortcut" :class="cn('cn-menubar-shortcut ml-auto', props.class)"
><slot
/></span>
</template>
src/ui/menubar/MenubarSub.vue
<script setup lang="ts">
import { MenubarSub } from "reka-ui"
</script>
<template>
<MenubarSub data-slot="menubar-sub">
<slot />
</MenubarSub>
</template>
src/ui/menubar/MenubarSubContent.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { MenubarSubContent } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<MenubarSubContent
data-slot="menubar-sub-content"
:class="
cn(
'cn-menubar-sub-content cn-menu-target cn-menu-translucent z-50 origin-(--radix-menubar-content-transform-origin) overflow-hidden',
props.class
)
"
>
<slot />
</MenubarSubContent>
</template>
src/ui/menubar/MenubarSubTrigger.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { MenubarSubTrigger } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
inset?: boolean
}>()
</script>
<template>
<MenubarSubTrigger
data-slot="menubar-sub-trigger"
:data-inset="props.inset"
:class="
cn(
'cn-menubar-sub-trigger flex cursor-default items-center outline-none select-none',
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 size-4"
>
<path d="m9 18 6-6-6-6" />
</svg>
</MenubarSubTrigger>
</template>
src/ui/menubar/MenubarTrigger.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { MenubarTrigger } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<MenubarTrigger
data-slot="menubar-trigger"
:class="cn('cn-menubar-trigger flex items-center outline-hidden select-none', props.class)"
>
<slot />
</MenubarTrigger>
</template>
src/ui/menubar/index.ts
export { default as Menubar } from "./Menubar.vue"
export { default as MenubarMenu } from "./MenubarMenu.vue"
export { default as MenubarGroup } from "./MenubarGroup.vue"
export { default as MenubarPortal } from "./MenubarPortal.vue"
export { default as MenubarRadioGroup } from "./MenubarRadioGroup.vue"
export { default as MenubarTrigger } from "./MenubarTrigger.vue"
export { default as MenubarContent } from "./MenubarContent.vue"
export { default as MenubarItem } from "./MenubarItem.vue"
export { default as MenubarCheckboxItem } from "./MenubarCheckboxItem.vue"
export { default as MenubarRadioItem } from "./MenubarRadioItem.vue"
export { default as MenubarLabel } from "./MenubarLabel.vue"
export { default as MenubarSeparator } from "./MenubarSeparator.vue"
export { default as MenubarShortcut } from "./MenubarShortcut.vue"
export { default as MenubarSub } from "./MenubarSub.vue"
export { default as MenubarSubTrigger } from "./MenubarSubTrigger.vue"
export { default as MenubarSubContent } from "./MenubarSubContent.vue"
Установка
npx shadcn-vue@latest add https://revueui.rootapi.dev/r/menubar.jsonЗависимости реестра
npm-зависимости
- reka-ui
Источник: порт из ReUI (Keenthemes, MIT)