feat: add modal and drawer components and examples (#4229)
* feat: add modal component * feat: add drawer component * feat: apply new modal and drawer components to the layout * chore: typo * feat: add some unit tests
This commit is contained in:
@@ -1,62 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialog as AlertDialogRoot,
|
||||
AlertDialogTitle,
|
||||
} from '../ui/alert-dialog';
|
||||
|
||||
interface Props {
|
||||
cancelText?: string;
|
||||
content?: string;
|
||||
submitText?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
cancelText: '取消',
|
||||
submitText: '确认',
|
||||
});
|
||||
|
||||
const emits = defineEmits<{
|
||||
cancel: [];
|
||||
submit: [];
|
||||
}>();
|
||||
|
||||
const openModal = defineModel<boolean>('open');
|
||||
|
||||
function handleSubmit() {
|
||||
emits('submit');
|
||||
openModal.value = false;
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
emits('cancel');
|
||||
openModal.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogRoot v-model:open="openModal">
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{{ title }}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{{ content }}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel @click="handleCancel">
|
||||
{{ cancelText }}
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction @click="handleSubmit">
|
||||
{{ submitText }}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialogRoot>
|
||||
</template>
|
@@ -1 +0,0 @@
|
||||
export { default as VbenAlertDialog } from './alert-dialog.vue';
|
@@ -1,4 +1,3 @@
|
||||
export * from './alert-dialog';
|
||||
export * from './avatar';
|
||||
export * from './back-top';
|
||||
export * from './breadcrumb';
|
||||
@@ -20,11 +19,9 @@ export * from './popover';
|
||||
export * from './render-content';
|
||||
export * from './scrollbar';
|
||||
export * from './segmented';
|
||||
export * from './sheet';
|
||||
export * from './spinner';
|
||||
export * from './swap';
|
||||
export * from './tooltip';
|
||||
export * from './ui/alert-dialog';
|
||||
export * from './ui/avatar';
|
||||
export * from './ui/badge';
|
||||
export * from './ui/breadcrumb';
|
||||
|
@@ -1 +0,0 @@
|
||||
export { default as VbenSheet } from './sheet.vue';
|
@@ -1,113 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, useSlots } from 'vue';
|
||||
|
||||
import { X } from 'lucide-vue-next';
|
||||
|
||||
import { VbenButton, VbenIconButton } from '../button';
|
||||
import { VbenScrollbar } from '../scrollbar';
|
||||
import {
|
||||
Sheet,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetFooter,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from '../ui/sheet';
|
||||
|
||||
interface Props {
|
||||
cancelText?: string;
|
||||
description?: string;
|
||||
showFooter?: boolean;
|
||||
submitText?: string;
|
||||
title?: string;
|
||||
width?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
cancelText: '关闭',
|
||||
description: '',
|
||||
showFooter: false,
|
||||
submitText: '确认',
|
||||
title: '',
|
||||
width: 400,
|
||||
});
|
||||
|
||||
const emits = defineEmits<{
|
||||
cancel: [];
|
||||
submit: [];
|
||||
}>();
|
||||
|
||||
const openModal = defineModel<boolean>('open');
|
||||
|
||||
const slots = useSlots();
|
||||
|
||||
const contentStyle = computed(() => {
|
||||
return {
|
||||
width: `${props.width}px`,
|
||||
};
|
||||
});
|
||||
|
||||
function handlerSubmit() {
|
||||
emits('submit');
|
||||
openModal.value = false;
|
||||
}
|
||||
|
||||
// function handleCancel() {
|
||||
// emits('cancel');
|
||||
// openModal.value = false;
|
||||
// }
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Sheet v-model:open="openModal">
|
||||
<SheetTrigger>
|
||||
<slot name="trigger"></slot>
|
||||
</SheetTrigger>
|
||||
<SheetContent :style="contentStyle" class="!w-full pb-12 sm:rounded-l-lg">
|
||||
<SheetHeader
|
||||
:class="description ? 'h-16' : 'h-12'"
|
||||
class="border-border flex flex-row items-center justify-between border-b pl-3 pr-3"
|
||||
>
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<div>
|
||||
<SheetTitle class="text-left text-lg">{{ title }}</SheetTitle>
|
||||
<SheetDescription class="text-muted-foreground text-xs">
|
||||
{{ description }}
|
||||
</SheetDescription>
|
||||
</div>
|
||||
<slot v-if="slots.extra" name="extra"></slot>
|
||||
</div>
|
||||
<SheetClose
|
||||
as-child
|
||||
class="data-[state=open]:bg-secondary cursor-pointer rounded-full opacity-80 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none"
|
||||
>
|
||||
<VbenIconButton>
|
||||
<X class="size-4" />
|
||||
</VbenIconButton>
|
||||
</SheetClose>
|
||||
</SheetHeader>
|
||||
<div class="h-full pb-16">
|
||||
<VbenScrollbar class="h-full" shadow>
|
||||
<slot></slot>
|
||||
</VbenScrollbar>
|
||||
</div>
|
||||
<SheetFooter v-if="showFooter || slots.footer" as-child>
|
||||
<div
|
||||
class="border-border absolute bottom-0 flex h-12 w-full items-center justify-end border-t"
|
||||
>
|
||||
<slot v-if="slots.footer" name="footer"></slot>
|
||||
<template v-else>
|
||||
<SheetClose as-child>
|
||||
<VbenButton class="mr-2" variant="outline">
|
||||
{{ cancelText }}
|
||||
</VbenButton>
|
||||
</SheetClose>
|
||||
<VbenButton @click="handlerSubmit">{{ submitText }}</VbenButton>
|
||||
</template>
|
||||
</div>
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</template>
|
@@ -1 +1,2 @@
|
||||
export { default as VbenLoading } from './loading.vue';
|
||||
export { default as VbenSpinner } from './spinner.vue';
|
||||
|
@@ -0,0 +1,137 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared';
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
/**
|
||||
* @zh_CN 最小加载时间
|
||||
* @en_US Minimum loading time
|
||||
*/
|
||||
minLoadingTime?: number;
|
||||
|
||||
/**
|
||||
* @zh_CN loading状态开启
|
||||
*/
|
||||
spinning?: boolean;
|
||||
/**
|
||||
* @zh_CN 文字
|
||||
*/
|
||||
text?: string;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'VbenLoading',
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
minLoadingTime: 50,
|
||||
text: '',
|
||||
});
|
||||
// const startTime = ref(0);
|
||||
const showSpinner = ref(false);
|
||||
const renderSpinner = ref(true);
|
||||
const timer = ref<ReturnType<typeof setTimeout>>();
|
||||
|
||||
watch(
|
||||
() => props.spinning,
|
||||
(show) => {
|
||||
if (!show) {
|
||||
showSpinner.value = false;
|
||||
clearTimeout(timer.value);
|
||||
return;
|
||||
}
|
||||
|
||||
// startTime.value = performance.now();
|
||||
timer.value = setTimeout(() => {
|
||||
// const loadingTime = performance.now() - startTime.value;
|
||||
|
||||
showSpinner.value = true;
|
||||
if (showSpinner.value) {
|
||||
renderSpinner.value = true;
|
||||
}
|
||||
}, props.minLoadingTime);
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
);
|
||||
|
||||
function onTransitionEnd() {
|
||||
if (!showSpinner.value) {
|
||||
renderSpinner.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'bg-overlay z-100 pointer-events-none absolute left-0 top-0 flex size-full flex-col items-center justify-center backdrop-blur-sm transition-all duration-500',
|
||||
{
|
||||
'invisible opacity-0': !showSpinner,
|
||||
},
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
@transitionend="onTransitionEnd"
|
||||
>
|
||||
<span class="dot relative inline-block size-9 text-3xl">
|
||||
<i
|
||||
v-for="index in 4"
|
||||
:key="index"
|
||||
class="bg-primary absolute block size-4 origin-[50%_50%] scale-75 rounded-full opacity-30"
|
||||
></i>
|
||||
</span>
|
||||
|
||||
<div v-if="text" class="mt-4 text-xs">{{ text }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.dot {
|
||||
transform: rotate(45deg);
|
||||
animation: rotate-ani 1.2s infinite linear;
|
||||
}
|
||||
|
||||
.dot i {
|
||||
animation: spin-move-ani 1s infinite linear alternate;
|
||||
}
|
||||
|
||||
.dot i:nth-child(1) {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.dot i:nth-child(2) {
|
||||
top: 0;
|
||||
right: 0;
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
.dot i:nth-child(3) {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
animation-delay: 0.8s;
|
||||
}
|
||||
|
||||
.dot i:nth-child(4) {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
animation-delay: 1.2s;
|
||||
}
|
||||
|
||||
@keyframes rotate-ani {
|
||||
to {
|
||||
transform: rotate(405deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin-move-ani {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,7 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared';
|
||||
|
||||
interface Props {
|
||||
class?: string;
|
||||
/**
|
||||
* @zh_CN 最小加载时间
|
||||
* @en_US Minimum loading time
|
||||
@@ -14,7 +17,7 @@ interface Props {
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'Spinner',
|
||||
name: 'VbenSpinner',
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -58,19 +61,34 @@ function onTransitionEnd() {
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="{
|
||||
'invisible opacity-0': !showSpinner,
|
||||
}"
|
||||
class="flex-center bg-overlay z-100 absolute left-0 top-0 size-full backdrop-blur-sm transition-all duration-500"
|
||||
:class="
|
||||
cn(
|
||||
'flex-center bg-overlay z-100 absolute left-0 top-0 size-full backdrop-blur-sm transition-all duration-500',
|
||||
{
|
||||
'invisible opacity-0': !showSpinner,
|
||||
},
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
@transitionend="onTransitionEnd"
|
||||
>
|
||||
<div
|
||||
class="loader before:bg-primary/50 after:bg-primary relative h-12 w-12 before:absolute before:left-0 before:top-[60px] before:h-[5px] before:w-12 before:animate-[loader-shadow-ani_0.5s_linear_infinite] before:rounded-[50%] before:content-[''] after:absolute after:left-0 after:top-0 after:h-full after:w-full after:animate-[loader-jump-ani_0.5s_linear_infinite] after:rounded after:content-['']"
|
||||
class="loader before:bg-primary/50 after:bg-primary relative size-12 before:absolute before:left-0 before:top-[60px] before:h-[5px] before:w-12 before:rounded-[50%] before:content-[''] after:absolute after:left-0 after:top-0 after:h-full after:w-full after:rounded after:content-['']"
|
||||
></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
<style scoped>
|
||||
.loader {
|
||||
&::before {
|
||||
animation: loader-shadow-ani 0.5s linear infinite;
|
||||
}
|
||||
|
||||
&::after {
|
||||
animation: loader-jump-ani 0.5s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loader-jump-ani {
|
||||
15% {
|
||||
border-bottom-right-radius: 3px;
|
||||
|
@@ -1,19 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
type AlertDialogEmits,
|
||||
type AlertDialogProps,
|
||||
AlertDialogRoot,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue';
|
||||
|
||||
const props = defineProps<AlertDialogProps>();
|
||||
const emits = defineEmits<AlertDialogEmits>();
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogRoot v-bind="forwarded">
|
||||
<slot></slot>
|
||||
</AlertDialogRoot>
|
||||
</template>
|
@@ -1,28 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared';
|
||||
|
||||
import { AlertDialogAction, type AlertDialogActionProps } from 'radix-vue';
|
||||
|
||||
import { buttonVariants } from '../button';
|
||||
|
||||
const props = defineProps<
|
||||
{ class?: HTMLAttributes['class'] } & AlertDialogActionProps
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogAction
|
||||
v-bind="delegatedProps"
|
||||
:class="cn(buttonVariants(), props.class)"
|
||||
>
|
||||
<slot></slot>
|
||||
</AlertDialogAction>
|
||||
</template>
|
@@ -1,30 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared';
|
||||
|
||||
import { AlertDialogCancel, type AlertDialogCancelProps } from 'radix-vue';
|
||||
|
||||
import { buttonVariants } from '../button';
|
||||
|
||||
const props = defineProps<
|
||||
{ class?: HTMLAttributes['class'] } & AlertDialogCancelProps
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogCancel
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', props.class)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</AlertDialogCancel>
|
||||
</template>
|
@@ -1,46 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared';
|
||||
|
||||
import {
|
||||
AlertDialogContent,
|
||||
type AlertDialogContentEmits,
|
||||
type AlertDialogContentProps,
|
||||
AlertDialogOverlay,
|
||||
AlertDialogPortal,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue';
|
||||
|
||||
const props = defineProps<
|
||||
{ class?: HTMLAttributes['class'] } & AlertDialogContentProps
|
||||
>();
|
||||
const emits = defineEmits<AlertDialogContentEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogPortal>
|
||||
<AlertDialogOverlay
|
||||
class="bg-overlay data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-[1000] backdrop-blur-sm"
|
||||
/>
|
||||
<AlertDialogContent
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] border-border fixed left-1/2 top-1/2 z-[1000] grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</AlertDialogContent>
|
||||
</AlertDialogPortal>
|
||||
</template>
|
@@ -1,29 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared';
|
||||
|
||||
import {
|
||||
AlertDialogDescription,
|
||||
type AlertDialogDescriptionProps,
|
||||
} from 'radix-vue';
|
||||
|
||||
const props = defineProps<
|
||||
{ class?: HTMLAttributes['class'] } & AlertDialogDescriptionProps
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogDescription
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('text-muted-foreground text-sm', props.class)"
|
||||
>
|
||||
<slot></slot>
|
||||
</AlertDialogDescription>
|
||||
</template>
|
@@ -1,22 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="
|
||||
cn(
|
||||
'flex flex-col-reverse sm:flex-row sm:justify-end sm:gap-x-2',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
@@ -1,17 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class'];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="cn('flex flex-col gap-y-2 text-center sm:text-left', props.class)"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
@@ -1,26 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared';
|
||||
|
||||
import { AlertDialogTitle, type AlertDialogTitleProps } from 'radix-vue';
|
||||
|
||||
const props = defineProps<
|
||||
{ class?: HTMLAttributes['class'] } & AlertDialogTitleProps
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogTitle
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('text-lg font-semibold', props.class)"
|
||||
>
|
||||
<slot></slot>
|
||||
</AlertDialogTitle>
|
||||
</template>
|
@@ -1,11 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { AlertDialogTrigger, type AlertDialogTriggerProps } from 'radix-vue';
|
||||
|
||||
const props = defineProps<AlertDialogTriggerProps>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AlertDialogTrigger v-bind="props">
|
||||
<slot></slot>
|
||||
</AlertDialogTrigger>
|
||||
</template>
|
@@ -1,9 +0,0 @@
|
||||
export { default as AlertDialog } from './AlertDialog.vue';
|
||||
export { default as AlertDialogAction } from './AlertDialogAction.vue';
|
||||
export { default as AlertDialogCancel } from './AlertDialogCancel.vue';
|
||||
export { default as AlertDialogContent } from './AlertDialogContent.vue';
|
||||
export { default as AlertDialogDescription } from './AlertDialogDescription.vue';
|
||||
export { default as AlertDialogFooter } from './AlertDialogFooter.vue';
|
||||
export { default as AlertDialogHeader } from './AlertDialogHeader.vue';
|
||||
export { default as AlertDialogTitle } from './AlertDialogTitle.vue';
|
||||
export { default as AlertDialogTrigger } from './AlertDialogTrigger.vue';
|
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { cn } from '@vben-core/shared';
|
||||
|
||||
@@ -17,7 +17,8 @@ import {
|
||||
const props = withDefaults(
|
||||
defineProps<
|
||||
{
|
||||
class?: HTMLAttributes['class'];
|
||||
class?: any;
|
||||
closeClass?: any;
|
||||
showClose?: boolean;
|
||||
} & DialogContentProps
|
||||
>(),
|
||||
@@ -32,6 +33,12 @@ const delegatedProps = computed(() => {
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
|
||||
const contentRef = ref<InstanceType<typeof DialogContent> | null>(null);
|
||||
|
||||
defineExpose({
|
||||
getContentRef: () => contentRef.value,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -41,10 +48,11 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
@click="() => emits('close')"
|
||||
/>
|
||||
<DialogContent
|
||||
ref="contentRef"
|
||||
v-bind="forwarded"
|
||||
:class="
|
||||
cn(
|
||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] border-border fixed left-1/2 top-1/2 z-[1000] grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 border p-6 shadow-lg outline-none duration-300 sm:rounded-lg',
|
||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-top-[48%] fixed z-[1000] w-full p-6 shadow-lg outline-none sm:rounded-xl',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
@@ -53,7 +61,12 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
|
||||
<DialogClose
|
||||
v-if="showClose"
|
||||
class="data-[state=open]:bg-accent data-[state=open]:text-muted-foreground hover:bg-accent hover:text-accent-foreground text-foreground/80 flex-center absolute right-3 top-3 h-6 w-6 rounded-full px-1 text-lg opacity-70 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none"
|
||||
:class="
|
||||
cn(
|
||||
'data-[state=open]:bg-accent data-[state=open]:text-muted-foreground hover:bg-accent hover:text-accent-foreground text-foreground/80 flex-center absolute right-3 top-3 h-6 w-6 rounded-full px-1 text-lg opacity-70 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none',
|
||||
props.closeClass,
|
||||
)
|
||||
"
|
||||
@click="() => emits('close')"
|
||||
>
|
||||
<Cross2Icon class="h-4 w-4" />
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
|
||||
export const sheetVariants = cva(
|
||||
'fixed z-50 gap-4 bg-background shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 border-border',
|
||||
'fixed z-[1000] bg-background shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500 border-border',
|
||||
{
|
||||
defaultVariants: {
|
||||
side: 'right',
|
||||
@@ -10,9 +10,9 @@ export const sheetVariants = cva(
|
||||
side: {
|
||||
bottom:
|
||||
'inset-x-0 bottom-0 border-t border-border data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
|
||||
left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
|
||||
left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left ',
|
||||
right:
|
||||
'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
|
||||
'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right',
|
||||
top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
|
||||
},
|
||||
},
|
||||
|
Reference in New Issue
Block a user