Skip to content

Commit c77dc98

Browse files
committed
feat: add initial dialog component
1 parent b8232a7 commit c77dc98

11 files changed

Lines changed: 252 additions & 0 deletions
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<script setup lang="ts">
2+
import type { DialogRootEmits, DialogRootProps } from 'reka-ui';
3+
import { DialogRoot, useForwardPropsEmits } from 'reka-ui';
4+
5+
const props = defineProps<DialogRootProps>();
6+
const emits = defineEmits<DialogRootEmits>();
7+
8+
const forwarded = useForwardPropsEmits(props, emits);
9+
</script>
10+
11+
<template>
12+
<DialogRoot data-slot="dialog" v-bind="forwarded">
13+
<slot />
14+
</DialogRoot>
15+
</template>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script setup lang="ts">
2+
import type { DialogCloseProps } from 'reka-ui';
3+
import { DialogClose } from 'reka-ui';
4+
5+
const props = defineProps<DialogCloseProps>();
6+
</script>
7+
8+
<template>
9+
<DialogClose data-slot="dialog-close" v-bind="props">
10+
<slot />
11+
</DialogClose>
12+
</template>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<script setup lang="ts">
2+
import type { DialogContentEmits, DialogContentProps } from 'reka-ui';
3+
import type { HTMLAttributes } from 'vue';
4+
import { reactiveOmit } from '@vueuse/core';
5+
import { DialogClose, DialogContent, DialogPortal, useForwardPropsEmits } from 'reka-ui';
6+
import { cn } from '@/utils';
7+
import DialogOverlay from './DialogOverlay.vue';
8+
9+
const props = defineProps<DialogContentProps & { class?: HTMLAttributes['class'] }>();
10+
const emits = defineEmits<DialogContentEmits>();
11+
12+
const delegatedProps = reactiveOmit(props, 'class');
13+
14+
const forwarded = useForwardPropsEmits(delegatedProps, emits);
15+
</script>
16+
17+
<template>
18+
<DialogPortal>
19+
<DialogOverlay />
20+
<DialogContent
21+
data-slot="dialog-content"
22+
v-bind="forwarded"
23+
:class="
24+
cn(
25+
'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 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
26+
props.class,
27+
)
28+
"
29+
>
30+
<slot />
31+
32+
<DialogClose
33+
class="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute start-4 top-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
34+
>
35+
<Icon name="lucide:x" class="size-4" />
36+
<span class="sr-only">{{ $t('ui.close') }}</span>
37+
</DialogClose>
38+
</DialogContent>
39+
</DialogPortal>
40+
</template>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script setup lang="ts">
2+
import type { DialogDescriptionProps } from 'reka-ui';
3+
import type { HTMLAttributes } from 'vue';
4+
import { reactiveOmit } from '@vueuse/core';
5+
import { DialogDescription, useForwardProps } from 'reka-ui';
6+
import { cn } from '@/utils';
7+
8+
const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes['class'] }>();
9+
10+
const delegatedProps = reactiveOmit(props, 'class');
11+
12+
const forwardedProps = useForwardProps(delegatedProps);
13+
</script>
14+
15+
<template>
16+
<DialogDescription
17+
data-slot="dialog-description"
18+
v-bind="forwardedProps"
19+
:class="cn('text-muted-foreground text-sm', props.class)"
20+
>
21+
<slot />
22+
</DialogDescription>
23+
</template>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from 'vue';
3+
import { cn } from '@/utils';
4+
5+
const props = defineProps<{ class?: HTMLAttributes['class'] }>();
6+
</script>
7+
8+
<template>
9+
<div
10+
data-slot="dialog-footer"
11+
:class="cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', props.class)"
12+
>
13+
<slot />
14+
</div>
15+
</template>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from 'vue';
3+
import { cn } from '@/utils';
4+
5+
const props = defineProps<{
6+
class?: HTMLAttributes['class'];
7+
}>();
8+
</script>
9+
10+
<template>
11+
<div
12+
data-slot="dialog-header"
13+
:class="cn('flex flex-col gap-2 text-center sm:text-left', props.class)"
14+
>
15+
<slot />
16+
</div>
17+
</template>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<script setup lang="ts">
2+
import type { DialogOverlayProps } from 'reka-ui';
3+
import type { HTMLAttributes } from 'vue';
4+
import { reactiveOmit } from '@vueuse/core';
5+
import { DialogOverlay } from 'reka-ui';
6+
import { cn } from '@/utils';
7+
8+
const props = defineProps<DialogOverlayProps & { class?: HTMLAttributes['class'] }>();
9+
10+
const delegatedProps = reactiveOmit(props, 'class');
11+
</script>
12+
13+
<template>
14+
<DialogOverlay
15+
data-slot="dialog-overlay"
16+
v-bind="delegatedProps"
17+
:class="
18+
cn(
19+
'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-50 bg-black/80',
20+
props.class,
21+
)
22+
"
23+
>
24+
<slot />
25+
</DialogOverlay>
26+
</template>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<script setup lang="ts">
2+
import type { DialogContentEmits, DialogContentProps } from 'reka-ui';
3+
import type { HTMLAttributes } from 'vue';
4+
import { reactiveOmit } from '@vueuse/core';
5+
import {
6+
DialogClose,
7+
DialogContent,
8+
DialogOverlay,
9+
DialogPortal,
10+
useForwardPropsEmits,
11+
} from 'reka-ui';
12+
import { cn } from '@/utils';
13+
14+
const props = defineProps<DialogContentProps & { class?: HTMLAttributes['class'] }>();
15+
const emits = defineEmits<DialogContentEmits>();
16+
17+
const delegatedProps = reactiveOmit(props, 'class');
18+
19+
const forwarded = useForwardPropsEmits(delegatedProps, emits);
20+
</script>
21+
22+
<template>
23+
<DialogPortal>
24+
<DialogOverlay
25+
class="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-50 grid place-items-center overflow-y-auto bg-black/80"
26+
>
27+
<DialogContent
28+
:class="
29+
cn(
30+
'border-border bg-background relative z-50 my-8 grid w-full max-w-lg gap-4 border p-6 shadow-lg duration-200 sm:rounded-lg md:w-full',
31+
props.class,
32+
)
33+
"
34+
v-bind="forwarded"
35+
@pointer-down-outside="
36+
(event) => {
37+
const originalEvent = event.detail.originalEvent;
38+
const target = originalEvent.target as HTMLElement;
39+
if (
40+
originalEvent.offsetX > target.clientWidth ||
41+
originalEvent.offsetY > target.clientHeight
42+
) {
43+
event.preventDefault();
44+
}
45+
}
46+
"
47+
>
48+
<slot />
49+
50+
<DialogClose
51+
class="hover:bg-secondary absolute start-4 top-4 rounded-md p-0.5 transition-colors"
52+
>
53+
<Icon name="lucide:x" class="size-4" />
54+
<span class="sr-only">{{ $t('ui.close') }}</span>
55+
</DialogClose>
56+
</DialogContent>
57+
</DialogOverlay>
58+
</DialogPortal>
59+
</template>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<script setup lang="ts">
2+
import type { DialogTitleProps } from 'reka-ui';
3+
import type { HTMLAttributes } from 'vue';
4+
import { reactiveOmit } from '@vueuse/core';
5+
import { DialogTitle, useForwardProps } from 'reka-ui';
6+
import { cn } from '@/utils';
7+
8+
const props = defineProps<DialogTitleProps & { class?: HTMLAttributes['class'] }>();
9+
10+
const delegatedProps = reactiveOmit(props, 'class');
11+
12+
const forwardedProps = useForwardProps(delegatedProps);
13+
</script>
14+
15+
<template>
16+
<DialogTitle
17+
data-slot="dialog-title"
18+
v-bind="forwardedProps"
19+
:class="cn('text-lg leading-none font-semibold', props.class)"
20+
>
21+
<slot />
22+
</DialogTitle>
23+
</template>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<script setup lang="ts">
2+
import type { DialogTriggerProps } from 'reka-ui';
3+
import { DialogTrigger } from 'reka-ui';
4+
5+
const props = defineProps<DialogTriggerProps>();
6+
</script>
7+
8+
<template>
9+
<DialogTrigger data-slot="dialog-trigger" v-bind="props">
10+
<slot />
11+
</DialogTrigger>
12+
</template>

0 commit comments

Comments
 (0)