Skip to content

Commit 64e2f9c

Browse files
authored
feat: vaul-vue integration (#374)
1 parent 0265b48 commit 64e2f9c

29 files changed

+900
-2
lines changed

apps/www/.vitepress/theme/config/docs.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,12 @@ export const docsConfig: DocsConfig = {
214214
href: '/docs/components/dialog',
215215
items: [],
216216
},
217+
{
218+
title: 'Drawer',
219+
href: '/docs/components/drawer',
220+
items: [],
221+
label: 'New',
222+
},
217223
{
218224
title: 'Dropdown Menu',
219225
href: '/docs/components/dropdown-menu',

apps/www/.vitepress/theme/layout/MainLayout.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ watch(() => $route.path, (n) => {
8484
</script>
8585

8686
<template>
87-
<div class="flex min-h-screen flex-col bg-background">
87+
<div vaul-drawer-wrapper class="flex min-h-screen flex-col bg-background">
8888
<header class="sticky z-40 top-0 bg-background/80 backdrop-blur-lg border-b border-border">
8989
<div
9090
class="container flex justify-between h-14 max-w-screen-2xl items-center"

apps/www/__registry__/index.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,20 @@ export const Index = {
402402
component: () => import("../src/lib/registry/default/example/DialogScrollOverlayDemo.vue").then((m) => m.default),
403403
files: ["../src/lib/registry/default/example/DialogScrollOverlayDemo.vue"],
404404
},
405+
"DrawerDemo": {
406+
name: "DrawerDemo",
407+
type: "components:example",
408+
registryDependencies: ["button","drawer"],
409+
component: () => import("../src/lib/registry/default/example/DrawerDemo.vue").then((m) => m.default),
410+
files: ["../src/lib/registry/default/example/DrawerDemo.vue"],
411+
},
412+
"DrawerDialog": {
413+
name: "DrawerDialog",
414+
type: "components:example",
415+
registryDependencies: ["button","dialog","drawer","label","input"],
416+
component: () => import("../src/lib/registry/default/example/DrawerDialog.vue").then((m) => m.default),
417+
files: ["../src/lib/registry/default/example/DrawerDialog.vue"],
418+
},
405419
"DropdownMenuDemo": {
406420
name: "DropdownMenuDemo",
407421
type: "components:example",
@@ -1355,6 +1369,20 @@ export const Index = {
13551369
component: () => import("../src/lib/registry/new-york/example/DialogScrollOverlayDemo.vue").then((m) => m.default),
13561370
files: ["../src/lib/registry/new-york/example/DialogScrollOverlayDemo.vue"],
13571371
},
1372+
"DrawerDemo": {
1373+
name: "DrawerDemo",
1374+
type: "components:example",
1375+
registryDependencies: ["button","drawer"],
1376+
component: () => import("../src/lib/registry/new-york/example/DrawerDemo.vue").then((m) => m.default),
1377+
files: ["../src/lib/registry/new-york/example/DrawerDemo.vue"],
1378+
},
1379+
"DrawerDialog": {
1380+
name: "DrawerDialog",
1381+
type: "components:example",
1382+
registryDependencies: ["button","dialog","drawer","label","input"],
1383+
component: () => import("../src/lib/registry/new-york/example/DrawerDialog.vue").then((m) => m.default),
1384+
files: ["../src/lib/registry/new-york/example/DrawerDialog.vue"],
1385+
},
13581386
"DropdownMenuDemo": {
13591387
name: "DropdownMenuDemo",
13601388
type: "components:example",

apps/www/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"radix-vue": "^1.4.9",
3535
"tailwindcss-animate": "^1.0.7",
3636
"v-calendar": "^3.1.2",
37+
"vaul-vue": "^0.1.0",
3738
"vee-validate": "4.12.5",
3839
"vue": "^3.4.21",
3940
"vue-sonner": "^1.1.1",
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
title: Drawer
3+
description: A drawer component for vue.
4+
source: apps/www/src/lib/registry/default/ui/drawer
5+
primitive: https://github.com/radix-vue/vaul-vue
6+
---
7+
8+
<ComponentPreview name="DrawerDemo" />
9+
10+
## About
11+
12+
Drawer is built on top of [Vaul Vue](https://github.com/radix-vue/vaul-vue).
13+
14+
## Installation
15+
16+
```bash
17+
npx shadcn-vue@latest add drawer
18+
```
19+
20+
## Usage
21+
22+
```vue showLineNumbers
23+
<script setup lang="ts">
24+
import {
25+
Drawer,
26+
DrawerClose,
27+
DrawerContent,
28+
DrawerDescription,
29+
DrawerFooter,
30+
DrawerHeader,
31+
DrawerTitle,
32+
DrawerTrigger,
33+
} from '@/components/ui/drawer'
34+
</script>
35+
36+
<template>
37+
<Drawer>
38+
<DrawerTrigger>Open</DrawerTrigger>
39+
<DrawerContent>
40+
<DrawerHeader>
41+
<DrawerTitle>Are you absolutely sure?</DrawerTitle>
42+
<DrawerDescription>This action cannot be undone.</DrawerDescription>
43+
</DrawerHeader>
44+
<DrawerFooter>
45+
<Button>Submit</Button>
46+
<DrawerClose>
47+
<Button variant="outline">
48+
Cancel
49+
</Button>
50+
</DrawerClose>
51+
</DrawerFooter>
52+
</DrawerContent>
53+
</Drawer>
54+
</template>
55+
```
56+
57+
## Examples
58+
59+
### Responsive Dialog
60+
61+
You can combine the `Dialog` and `Drawer` components to create a responsive dialog. This renders a `Dialog` component on desktop and a `Drawer` on mobile.
62+
63+
<ComponentPreview name="DrawerDialog" />
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<script setup lang="ts">
2+
import { ref } from 'vue'
3+
import { Minus, Plus } from 'lucide-vue-next'
4+
import { VisStackedBar, VisXYContainer } from '@unovis/vue'
5+
import { Button } from '@/lib/registry/default/ui/button'
6+
import {
7+
Drawer,
8+
DrawerClose,
9+
DrawerContent,
10+
DrawerDescription,
11+
DrawerFooter,
12+
DrawerHeader,
13+
DrawerTitle,
14+
DrawerTrigger,
15+
} from '@/lib/registry/default/ui/drawer'
16+
17+
const goal = ref(350)
18+
19+
type Data = typeof data[number]
20+
const data = [
21+
{ goal: 400 },
22+
{ goal: 300 },
23+
{ goal: 200 },
24+
{ goal: 300 },
25+
{ goal: 200 },
26+
{ goal: 278 },
27+
{ goal: 189 },
28+
{ goal: 239 },
29+
{ goal: 300 },
30+
{ goal: 200 },
31+
{ goal: 278 },
32+
{ goal: 189 },
33+
{ goal: 349 },
34+
]
35+
</script>
36+
37+
<template>
38+
<Drawer>
39+
<DrawerTrigger as-child>
40+
<Button variant="outline">
41+
Open Drawer
42+
</Button>
43+
</DrawerTrigger>
44+
<DrawerContent>
45+
<div class="mx-auto w-full max-w-sm">
46+
<DrawerHeader>
47+
<DrawerTitle>Move Goal</DrawerTitle>
48+
<DrawerDescription>Set your daily activity goal.</DrawerDescription>
49+
</DrawerHeader>
50+
<div class="p-4 pb-0">
51+
<div class="flex items-center justify-center space-x-2">
52+
<Button
53+
variant="outline"
54+
size="icon"
55+
class="h-8 w-8 shrink-0 rounded-full"
56+
:disabled="goal <= 200"
57+
@click="goal -= 10"
58+
>
59+
<Minus class="h-4 w-4" />
60+
<span class="sr-only">Decrease</span>
61+
</Button>
62+
<div class="flex-1 text-center">
63+
<div class="text-7xl font-bold tracking-tighter">
64+
{{ goal }}
65+
</div>
66+
<div class="text-[0.70rem] uppercase text-muted-foreground">
67+
Calories/day
68+
</div>
69+
</div>
70+
<Button
71+
variant="outline"
72+
size="icon"
73+
class="h-8 w-8 shrink-0 rounded-full"
74+
:disabled="goal >= 400"
75+
@click="goal += 10"
76+
>
77+
<Plus class="h-4 w-4" />
78+
<span class="sr-only">Increase</span>
79+
</Button>
80+
</div>
81+
<div class="my-3 px-3 h-[120px]">
82+
<VisXYContainer
83+
:data="data"
84+
class="h-[120px]"
85+
:style="{
86+
'opacity': 0.9,
87+
'--theme-primary': `hsl(var(--foreground))`,
88+
}"
89+
>
90+
<VisStackedBar
91+
:x="(d: Data, i :number) => i"
92+
:y="(d: Data) => d.goal"
93+
color="var(--theme-primary)"
94+
:bar-padding="0.1"
95+
:rounded-corners="0"
96+
/>
97+
</VisXYContainer>
98+
</div>
99+
</div>
100+
<DrawerFooter>
101+
<Button>Submit</Button>
102+
<DrawerClose as-child>
103+
<Button variant="outline">
104+
Cancel
105+
</Button>
106+
</DrawerClose>
107+
</DrawerFooter>
108+
</div>
109+
</DrawerContent>
110+
</Drawer>
111+
</template>
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<script lang="ts" setup>
2+
import { ref } from 'vue'
3+
import { createReusableTemplate, useMediaQuery } from '@vueuse/core'
4+
import { Button } from '@/lib/registry/default/ui/button'
5+
import {
6+
Dialog,
7+
DialogContent,
8+
DialogDescription,
9+
DialogHeader,
10+
DialogTitle,
11+
DialogTrigger,
12+
} from '@/lib/registry/default/ui/dialog'
13+
import {
14+
Drawer,
15+
DrawerClose,
16+
DrawerContent,
17+
DrawerDescription,
18+
DrawerFooter,
19+
DrawerHeader,
20+
DrawerTitle,
21+
DrawerTrigger,
22+
} from '@/lib/registry/default/ui/drawer'
23+
import { Label } from '@/lib/registry/default/ui/label'
24+
import { Input } from '@/lib/registry/default/ui/input'
25+
26+
// Reuse `form` section
27+
const [UseTemplate, GridForm] = createReusableTemplate()
28+
const isDesktop = useMediaQuery('(min-width: 768px)')
29+
30+
const isOpen = ref(false)
31+
</script>
32+
33+
<template>
34+
<UseTemplate>
35+
<form class="grid items-start gap-4 px-4">
36+
<div class="grid gap-2">
37+
<Label html-for="email">Email</Label>
38+
<Input id="email" type="email" default-value="shadcn@example.com" />
39+
</div>
40+
<div class="grid gap-2">
41+
<Label html-for="username">Username</Label>
42+
<Input id="username" default-value="@shadcn" />
43+
</div>
44+
<Button type="submit">
45+
Save changes
46+
</Button>
47+
</form>
48+
</UseTemplate>
49+
50+
<Dialog v-if="isDesktop" v-model:open="isOpen">
51+
<DialogTrigger as-child>
52+
<Button variant="outline">
53+
Edit Profile
54+
</Button>
55+
</DialogTrigger>
56+
<DialogContent class="sm:max-w-[425px]">
57+
<DialogHeader>
58+
<DialogTitle>Edit profile</DialogTitle>
59+
<DialogDescription>
60+
Make changes to your profile here. Click save when you're done.
61+
</DialogDescription>
62+
</DialogHeader>
63+
<GridForm />
64+
</DialogContent>
65+
</Dialog>
66+
67+
<Drawer v-else v-model:open="isOpen">
68+
<DrawerTrigger as-child>
69+
<Button variant="outline">
70+
Edit Profile
71+
</Button>
72+
</DrawerTrigger>
73+
<DrawerContent>
74+
<DrawerHeader class="text-left">
75+
<DrawerTitle>Edit profile</DrawerTitle>
76+
<DrawerDescription>
77+
Make changes to your profile here. Click save when you're done.
78+
</DrawerDescription>
79+
</DrawerHeader>
80+
<GridForm />
81+
<DrawerFooter class="pt-2">
82+
<DrawerClose as-child>
83+
<Button variant="outline">
84+
Cancel
85+
</Button>
86+
</DrawerClose>
87+
</DrawerFooter>
88+
</DrawerContent>
89+
</Drawer>
90+
</template>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<script lang="ts" setup>
2+
import type { DrawerRootEmits, DrawerRootProps } from 'vaul-vue'
3+
import { DrawerRoot } from 'vaul-vue'
4+
import { useForwardPropsEmits } from 'radix-vue'
5+
6+
const props = withDefaults(defineProps<DrawerRootProps>(), {
7+
shouldScaleBackground: true,
8+
})
9+
10+
const emits = defineEmits<DrawerRootEmits>()
11+
12+
const forwarded = useForwardPropsEmits(props, emits)
13+
</script>
14+
15+
<template>
16+
<DrawerRoot v-bind="forwarded">
17+
<slot />
18+
</DrawerRoot>
19+
</template>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<script lang="ts" setup>
2+
import { DrawerContent, DrawerPortal } from 'vaul-vue'
3+
import type { DialogContentEmits, DialogContentProps } from 'radix-vue'
4+
import { useForwardPropsEmits } from 'radix-vue'
5+
import type { HtmlHTMLAttributes } from 'vue'
6+
import DrawerOverlay from './DrawerOverlay.vue'
7+
import { cn } from '@/lib/utils'
8+
9+
const props = defineProps<DialogContentProps & { class?: HtmlHTMLAttributes['class'] }>()
10+
const emits = defineEmits<DialogContentEmits>()
11+
12+
const forwarded = useForwardPropsEmits(props, emits)
13+
</script>
14+
15+
<template>
16+
<DrawerPortal>
17+
<DrawerOverlay />
18+
<DrawerContent
19+
v-bind="forwarded" :class="cn(
20+
'fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background',
21+
props.class,
22+
)"
23+
>
24+
<div class="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
25+
<slot />
26+
</DrawerContent>
27+
</DrawerPortal>
28+
</template>

0 commit comments

Comments
 (0)