primitives
Sheet
Sheet — базовый примитив RevueUI (Reka UI + shadcn-совместимый API).
Загрузка превью…
src/ui/sheet/Sheet.vue
<script setup lang="ts">
/**
* Порт базового радикс-слоя (registry/bases/radix/ui/sheet.tsx, MIT).
* Radix Dialog (переиспользуется как Sheet) -> reka-ui Dialog, один в один.
* Наш примитив dialog уже портирован (packages/ui/src/ui/dialog) —
* Sheet переиспользует те же reka-ui Dialog* компоненты.
*/
import { DialogRoot } from "reka-ui"
</script>
<template>
<DialogRoot data-slot="sheet">
<slot />
</DialogRoot>
</template>
src/ui/sheet/SheetClose.vue
<script setup lang="ts">
import { DialogClose } from "reka-ui"
</script>
<template>
<DialogClose data-slot="sheet-close">
<slot />
</DialogClose>
</template>
src/ui/sheet/SheetContent.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { DialogClose, DialogContent } from "reka-ui"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import SheetOverlay from "./SheetOverlay.vue"
import SheetPortal from "./SheetPortal.vue"
const props = withDefaults(
defineProps<{
class?: HTMLAttributes["class"]
side?: "top" | "right" | "bottom" | "left"
showCloseButton?: boolean
}>(),
{
side: "right",
showCloseButton: true,
}
)
</script>
<template>
<SheetPortal>
<SheetOverlay />
<DialogContent
data-slot="sheet-content"
:data-side="props.side"
:class="
cn(
'cn-sheet-content data-open:animate-in data-open:fade-in-0 data-[side=bottom]:data-open:slide-in-from-bottom-10 data-[side=left]:data-open:slide-in-from-left-10 data-[side=right]:data-open:slide-in-from-right-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:animate-out data-closed:fade-out-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=left]:data-closed:slide-out-to-left-10 data-[side=right]:data-closed:slide-out-to-right-10 data-[side=top]:data-closed:slide-out-to-top-10',
props.class
)
"
>
<slot />
<DialogClose v-if="props.showCloseButton" data-slot="sheet-close" as-child>
<Button variant="ghost" class="cn-sheet-close" size="icon-sm">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M18 6 6 18" />
<path d="m6 6 12 12" />
</svg>
<span class="sr-only">Close</span>
</Button>
</DialogClose>
</DialogContent>
</SheetPortal>
</template>
src/ui/sheet/SheetDescription.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { DialogDescription } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<DialogDescription
data-slot="sheet-description"
:class="cn('cn-sheet-description', props.class)"
>
<slot />
</DialogDescription>
</template>
src/ui/sheet/SheetFooter.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<div
data-slot="sheet-footer"
:class="cn('cn-sheet-footer mt-auto flex flex-col', props.class)"
>
<slot />
</div>
</template>
src/ui/sheet/SheetHeader.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<div data-slot="sheet-header" :class="cn('cn-sheet-header flex flex-col', props.class)">
<slot />
</div>
</template>
src/ui/sheet/SheetOverlay.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { DialogOverlay } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<DialogOverlay
data-slot="sheet-overlay"
:class="
cn(
'cn-sheet-overlay fixed inset-0 z-50 duration-100 data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0',
props.class
)
"
/>
</template>
src/ui/sheet/SheetPortal.vue
<script setup lang="ts">
import { DialogPortal } from "reka-ui"
</script>
<template>
<DialogPortal data-slot="sheet-portal">
<slot />
</DialogPortal>
</template>
src/ui/sheet/SheetTitle.vue
<script setup lang="ts">
import type { HTMLAttributes } from "vue"
import { DialogTitle } from "reka-ui"
import { cn } from "@/lib/utils"
const props = defineProps<{
class?: HTMLAttributes["class"]
}>()
</script>
<template>
<DialogTitle
data-slot="sheet-title"
:class="cn('cn-sheet-title cn-font-heading', props.class)"
>
<slot />
</DialogTitle>
</template>
src/ui/sheet/SheetTrigger.vue
<script setup lang="ts">
import { DialogTrigger } from "reka-ui"
</script>
<template>
<DialogTrigger data-slot="sheet-trigger">
<slot />
</DialogTrigger>
</template>
src/ui/sheet/index.ts
export { default as Sheet } from "./Sheet.vue"
export { default as SheetTrigger } from "./SheetTrigger.vue"
export { default as SheetClose } from "./SheetClose.vue"
export { default as SheetPortal } from "./SheetPortal.vue"
export { default as SheetOverlay } from "./SheetOverlay.vue"
export { default as SheetContent } from "./SheetContent.vue"
export { default as SheetHeader } from "./SheetHeader.vue"
export { default as SheetFooter } from "./SheetFooter.vue"
export { default as SheetTitle } from "./SheetTitle.vue"
export { default as SheetDescription } from "./SheetDescription.vue"