<script lang="ts">
import MinusIcon from "@lucide/svelte/icons/minus";
import PlusIcon from "@lucide/svelte/icons/plus";
import * as Drawer from "$lib/components/ui/drawer/index.js";
import { Button, buttonVariants } from "$lib/components/ui/button/index.js";
import { BarChart, type ChartContextValue } from "layerchart";
import { scaleBand } from "d3-scale";
import { cubicInOut } from "svelte/easing";
const data = [
{
goal: 400
},
{
goal: 300
},
{
goal: 200
},
{
goal: 300
},
{
goal: 200
},
{
goal: 278
},
{
goal: 189
},
{
goal: 239
},
{
goal: 300
},
{
goal: 200
},
{
goal: 278
},
{
goal: 189
},
{
goal: 349
}
];
let goal = $state(350);
function handleClick(adjustment: number) {
goal = Math.max(200, Math.min(400, goal + adjustment));
}
let context = $state<ChartContextValue>();
</script>
<Drawer.Root>
<Drawer.Trigger class={buttonVariants({ variant: "outline" })}
>드로어 열기</Drawer.Trigger
>
<Drawer.Content>
<div class="mx-auto w-full max-w-sm">
<Drawer.Header>
<Drawer.Title>목표 이동</Drawer.Title>
<Drawer.Description>일일 활동 목표를 설정하세요.</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="size-8 shrink-0 rounded-full"
onclick={() => handleClick(-10)}
disabled={goal <= 200}
>
<MinusIcon />
<span class="sr-only">감소</span>
</Button>
<div class="flex-1 text-center">
<div class="text-7xl font-bold tracking-tighter">
{goal}
</div>
<div class="text-muted-foreground text-[0.70rem] uppercase">
칼로리/일
</div>
</div>
<Button
variant="outline"
size="icon"
class="size-8 shrink-0 rounded-full"
onclick={() => handleClick(10)}
disabled={goal >= 400}
>
<PlusIcon />
<span class="sr-only">증가</span>
</Button>
</div>
<div class="mt-3 h-[120px]">
<div class="h-full w-full">
<BarChart
bind:context
data={data.map((d, i) => ({ goal: d.goal, index: i }))}
y="goal"
x="index"
xScale={scaleBand().padding(0.25)}
axis={false}
tooltip={false}
props={{
bars: {
stroke: "none",
rounded: "all",
radius: 4,
// use the height of the chart to animate the bars
initialY: context?.height,
initialHeight: 0,
motion: {
x: { type: "tween", duration: 500, easing: cubicInOut },
width: { type: "tween", duration: 500, easing: cubicInOut },
height: {
type: "tween",
duration: 500,
easing: cubicInOut
},
y: { type: "tween", duration: 500, easing: cubicInOut }
},
fill: "var(--color-foreground)",
fillOpacity: 0.9
},
highlight: { area: { fill: "none" } }
}}
/>
</div>
</div>
</div>
<Drawer.Footer>
<Button>제출</Button>
<Drawer.Close class={buttonVariants({ variant: "outline" })}
>취소</Drawer.Close
>
</Drawer.Footer>
</div>
</Drawer.Content>
</Drawer.Root> 소개
Drawer는 Emil Kowalski의 Vaul을 Svelte로 포팅한 Vaul Svelte 기반으로 만들어졌습니다.
설치
vaul-svelte를 설치합니다:
다음 코드를 프로젝트에 복사하여 붙여넣습니다.
사용법
<script lang="ts">
import * as Drawer from "$lib/components/ui/drawer/index.js";
</script> <Drawer.Root>
<Drawer.Trigger>열기</Drawer.Trigger>
<Drawer.Content>
<Drawer.Header>
<Drawer.Title>정말 확실하신가요?</Drawer.Title>
<Drawer.Description>이 작업은 취소할 수 없습니다.</Drawer.Description>
</Drawer.Header>
<Drawer.Footer>
<Button>제출</Button>
<Drawer.Close>취소</Drawer.Close>
</Drawer.Footer>
</Drawer.Content>
</Drawer.Root> 예제
반응형 대화상자
Dialog와 Drawer 컴포넌트를 결합하여 반응형 대화상자를 만들 수 있습니다. 데스크톱에서는 Dialog로, 모바일에서는 Drawer로 렌더링됩니다.
<script lang="ts">
import { MediaQuery } from "svelte/reactivity";
import * as Dialog from "$lib/components/ui/dialog/index.js";
import * as Drawer from "$lib/components/ui/drawer/index.js";
import { Input } from "$lib/components/ui/input/index.js";
import { Label } from "$lib/components/ui/label/index.js";
import { Button, buttonVariants } from "$lib/components/ui/button/index.js";
let open = $state(false);
const isDesktop = new MediaQuery("(min-width: 768px)");
const id = $props.id();
</script>
{#if isDesktop.current}
<Dialog.Root bind:open>
<Dialog.Trigger class={buttonVariants({ variant: "outline" })}
>프로필 편집</Dialog.Trigger
>
<Dialog.Content class="sm:max-w-[425px]">
<Dialog.Header>
<Dialog.Title>프로필 편집</Dialog.Title>
<Dialog.Description>
여기에서 프로필을 변경하세요. 완료되면 저장을 클릭하세요.
</Dialog.Description>
</Dialog.Header>
<form class="grid items-start gap-4">
<div class="grid gap-2">
<Label for="email-{id}">이메일</Label>
<Input type="email" id="email-{id}" value="shadcn@example.com" />
</div>
<div class="grid gap-2">
<Label for="username-{id}">사용자명</Label>
<Input id="username-{id}" value="@shadcn" />
</div>
<Button type="submit">변경사항 저장</Button>
</form>
</Dialog.Content>
</Dialog.Root>
{:else}
<Drawer.Root bind:open>
<Drawer.Trigger class={buttonVariants({ variant: "outline" })}
>프로필 편집</Drawer.Trigger
>
<Drawer.Content>
<Drawer.Header class="text-start">
<Drawer.Title>프로필 편집</Drawer.Title>
<Drawer.Description>
여기에서 프로필을 변경하세요. 완료되면 저장을 클릭하세요.
</Drawer.Description>
</Drawer.Header>
<form class="grid items-start gap-4 px-4">
<div class="grid gap-2">
<Label for="email-{id}">이메일</Label>
<Input type="email" id="email-{id}" value="shadcn@example.com" />
</div>
<div class="grid gap-2">
<Label for="username-{id}">사용자명</Label>
<Input id="username-{id}" value="@shadcn" />
</div>
<Button type="submit">변경사항 저장</Button>
</form>
<Drawer.Footer class="pt-2">
<Drawer.Close class={buttonVariants({ variant: "outline" })}
>취소</Drawer.Close
>
</Drawer.Footer>
</Drawer.Content>
</Drawer.Root>
{/if}