A drawer component for Svelte.
<script lang="ts"> import Minus from "svelte-radix/Minus.svelte"; import Plus from "svelte-radix/Plus.svelte"; import { Button } from "$lib/components/ui/button"; import * as Drawer from "$lib/components/ui/drawer"; import { VisXYContainer, VisGroupedBar } from "@unovis/svelte"; const data = [ { id: 1, goal: 400 }, { id: 2, goal: 300 }, { id: 3, goal: 200 }, { id: 4, goal: 300 }, { id: 5, goal: 200 }, { id: 6, goal: 278 }, { id: 7, goal: 189 }, { id: 8, goal: 239 }, { id: 9, goal: 300 }, { id: 10, goal: 200 }, { id: 11, goal: 278 }, { id: 12, goal: 189 }, { id: 13, goal: 349 } ]; const x = (d: { goal: number; id: number }) => d.id; const y = (d: { goal: number; id: number }) => d.goal; let goal = 350; function handleClick(adjustment: number) { goal = Math.max(200, Math.min(400, goal + adjustment)); } </script> <Drawer.Root> <Drawer.Trigger asChild let:builder> <Button builders={[builder]} variant="outline">Open Drawer</Button> </Drawer.Trigger> <Drawer.Content> <div class="mx-auto w-full max-w-sm"> <Drawer.Header> <Drawer.Title>Move Goal</Drawer.Title> <Drawer.Description>Set your daily activity goal.</Drawer.Description> </Drawer.Header> <div class="p-4 pb-0"> <div class="flex items-center justify-center space-x-2"> <Button variant="outline" size="icon" class="h-8 w-8 shrink-0 rounded-full" on:click={() => handleClick(-10)} disabled={goal <= 200} > <Minus class="h-4 w-4" /> <span class="sr-only">Decrease</span> </Button> <div class="flex-1 text-center"> <div class="text-7xl font-bold tracking-tighter"> {goal} </div> <div class="text-[0.70rem] uppercase text-muted-foreground"> Calories/day </div> </div> <Button variant="outline" size="icon" class="h-8 w-8 shrink-0 rounded-full" on:click={() => handleClick(10)} > <Plus class="h-4 w-4" /> <span class="sr-only">Increase</span> </Button> </div> <div class="mt-3 h-[120px]"> <VisXYContainer {data} height={60}> <VisGroupedBar {x} {y} color={"hsl(var(--primary) / 0.2)"} /> </VisXYContainer> </div> </div> <Drawer.Footer> <Button>Submit</Button> <Drawer.Close asChild let:builder> <Button builders={[builder]} variant="outline">Cancel</Button> </Drawer.Close> </Drawer.Footer> </div> </Drawer.Content> </Drawer.Root>
<script lang="ts"> import { Button } from "$lib/components/ui/button"; import * as Drawer from "$lib/components/ui/drawer"; import Minus from "lucide-svelte/icons/minus"; import Plus from "lucide-svelte/icons/plus"; import { VisXYContainer, VisGroupedBar } from "@unovis/svelte"; const data = [ { id: 1, goal: 400 }, { id: 2, goal: 300 }, { id: 3, goal: 200 }, { id: 4, goal: 300 }, { id: 5, goal: 200 }, { id: 6, goal: 278 }, { id: 7, goal: 189 }, { id: 8, goal: 239 }, { id: 9, goal: 300 }, { id: 10, goal: 200 }, { id: 11, goal: 278 }, { id: 12, goal: 189 }, { id: 13, goal: 349 } ]; const x = (d: { goal: number; id: number }) => d.id; const y = (d: { goal: number; id: number }) => d.goal; let goal = 350; function handleClick(adjustment: number) { goal = Math.max(200, Math.min(400, goal + adjustment)); } </script> <Drawer.Root> <Drawer.Trigger asChild let:builder> <Button builders={[builder]} variant="outline">Open Drawer</Button> </Drawer.Trigger> <Drawer.Content> <div class="mx-auto w-full max-w-sm"> <Drawer.Header> <Drawer.Title>Move Goal</Drawer.Title> <Drawer.Description>Set your daily activity goal.</Drawer.Description> </Drawer.Header> <div class="p-4 pb-0"> <div class="flex items-center justify-center space-x-2"> <Button variant="outline" size="icon" class="h-8 w-8 shrink-0 rounded-full" on:click={() => handleClick(-10)} disabled={goal <= 200} > <Minus class="h-4 w-4" /> <span class="sr-only">Decrease</span> </Button> <div class="flex-1 text-center"> <div class="text-7xl font-bold tracking-tighter"> {goal} </div> <div class="text-[0.70rem] uppercase text-muted-foreground"> Calories/day </div> </div> <Button variant="outline" size="icon" class="h-8 w-8 shrink-0 rounded-full" on:click={() => handleClick(10)} disabled={goal >= 400} > <Plus class="h-4 w-4" /> <span class="sr-only">Increase</span> </Button> </div> <div class="mt-3 h-[120px]"> <VisXYContainer {data} height={60}> <VisGroupedBar {x} {y} color={"hsl(var(--primary) / 0.2)"} /> </VisXYContainer> </div> </div> <Drawer.Footer> <Button>Submit</Button> <Drawer.Close asChild let:builder> <Button builders={[builder]} variant="outline">Cancel</Button> </Drawer.Close> </Drawer.Footer> </div> </Drawer.Content> </Drawer.Root>
Drawer is built on top of Vaul Svelte, which is a Svelte port of Vaul by Emil Kowalski.
npx svell@latest add drawer
<script lang="ts"> import * as Drawer from "$lib/components/ui/drawer"; </script> <Drawer.Root> <Drawer.Trigger>Open</Drawer.Trigger> <Drawer.Content> <Drawer.Header> <Drawer.Title>Are you sure absolutely sure?</Drawer.Title> <Drawer.Description>This action cannot be undone.</Drawer.Description> </Drawer.Header> <Drawer.Footer> <Button>Submit</Button> <Drawer.Close>Cancel</Drawer.Close> </Drawer.Footer> </Drawer.Content> </Drawer.Root>
You can combine the Dialog and Drawer components to create a responsive dialog. This renders a Dialog on desktop and a Drawer on mobile.
Dialog
Drawer
<script lang="ts"> import * as Dialog from "$lib/components/ui/dialog"; import * as Drawer from "$lib/components/ui/drawer"; import { Input } from "$lib/components/ui/input"; import { Label } from "$lib/components/ui/label"; import { Button } from "$lib/components/ui/button"; import { mediaQuery } from "svelte-legos"; let open = false; const isDesktop = mediaQuery("(min-width: 768px)"); </script> {#if $isDesktop} <Dialog.Root bind:open> <Dialog.Trigger asChild let:builder> <Button variant="outline" builders={[builder]}>Edit Profile</Button> </Dialog.Trigger> <Dialog.Content class="sm:max-w-[425px]"> <Dialog.Header> <Dialog.Title>Edit profile</Dialog.Title> <Dialog.Description> Make changes to your profile here. Click save when you're done. </Dialog.Description> </Dialog.Header> <form class="grid items-start gap-4"> <div class="grid gap-2"> <Label for="email">Email</Label> <Input type="email" id="email" value="[email protected]" /> </div> <div class="grid gap-2"> <Label for="username">Username</Label> <Input id="username" value="@shadcn" /> </div> <Button type="submit">Save changes</Button> </form> </Dialog.Content> </Dialog.Root> {:else} <Drawer.Root bind:open> <Drawer.Trigger asChild let:builder> <Button variant="outline" builders={[builder]}>Edit Profile</Button> </Drawer.Trigger> <Drawer.Content> <Drawer.Header class="text-left"> <Drawer.Title>Edit profile</Drawer.Title> <Drawer.Description> Make changes to your profile here. Click save when you're done. </Drawer.Description> </Drawer.Header> <form class="grid items-start gap-4 px-4"> <div class="grid gap-2"> <Label for="email">Email</Label> <Input type="email" id="email" value="[email protected]" /> </div> <div class="grid gap-2"> <Label for="username">Username</Label> <Input id="username" value="@shadcn" /> </div> <Button type="submit">Save changes</Button> </form> <Drawer.Footer class="pt-2"> <Drawer.Close asChild let:builder> <Button variant="outline" builders={[builder]}>Cancel</Button> </Drawer.Close> </Drawer.Footer> </Drawer.Content> </Drawer.Root> {/if}
On This Page