Zaidan

Command Palette

Search for a command to run...

GitHub43

Drawer

A drawer component for SolidJS.

About

Drawer is built on top of Corvu Drawer by Jasmin.

Installation

CLI

Manual

Install the following dependencies:

Terminal window
bun add corvu

Copy and paste the following code into your project.

import {
Close,
type CloseProps,
Content,
type ContentProps,
Description,
type DescriptionProps,
type DynamicProps,
Label,
type LabelProps,
Overlay,
type OverlayProps,
Portal,
Root,
type RootProps,
Trigger,
type TriggerProps,
useContext,
} from "@corvu/drawer";
import type { ComponentProps, ValidComponent } from "solid-js";
import { splitProps } from "solid-js";
import { cn } from "~/lib/utils";
type DrawerRootProps<T extends ValidComponent = "div"> = DynamicProps<T, RootProps>;
const DrawerRoot = <T extends ValidComponent = "div">(props: DrawerRootProps<T>) => {
return <Root data-slot="drawer" {...(props as RootProps)} />;
};
type DrawerTriggerProps<T extends ValidComponent = "button"> = DynamicProps<T, TriggerProps>;
const DrawerTrigger = <T extends ValidComponent = "button">(props: DrawerTriggerProps<T>) => {
return <Trigger data-slot="drawer-trigger" {...(props as TriggerProps)} />;
};
type DrawerCloseProps<T extends ValidComponent = "button"> = DynamicProps<T, CloseProps>;
const DrawerClose = <T extends ValidComponent = "button">(props: DrawerCloseProps<T>) => {
return <Close data-slot="drawer-close" {...(props as CloseProps)} />;
};
type DrawerOverlayProps<T extends ValidComponent = "div"> = DynamicProps<T, OverlayProps> &
Pick<ComponentProps<T>, "class">;
const DrawerOverlay = <T extends ValidComponent = "div">(props: DrawerOverlayProps<T>) => {
const [local, others] = splitProps(props as DrawerOverlayProps, ["class"]);
const context = useContext();
return (
<Overlay
data-slot="drawer-overlay"
class={cn("fixed inset-0 z-50 z-drawer-overlay", local.class)}
{...others}
style={{
"background-color": `rgb(0 0 0 / ${0.1 * context.openPercentage()})`,
"backdrop-filter": `blur(${4 * context.openPercentage()}px)`,
}}
/>
);
};
type DrawerContentProps<T extends ValidComponent = "div"> = DynamicProps<T, ContentProps> &
Pick<ComponentProps<T>, "class" | "children">;
const DrawerContent = <T extends ValidComponent = "div">(props: DrawerContentProps<T>) => {
const [local, others] = splitProps(props as DrawerContentProps, ["class", "children"]);
return (
<Portal data-slot="drawer-portal">
<DrawerOverlay />
<Content
data-slot="drawer-content"
class={cn("group/drawer-content fixed z-50 z-drawer-content", local.class)}
{...others}
>
<div class="z-drawer-handle mx-auto hidden shrink-0 bg-muted group-data-[side=bottom]/drawer-content:block" />
{local.children}
</Content>
</Portal>
);
};
type DrawerHeaderProps = ComponentProps<"div">;
const DrawerHeader = (props: DrawerHeaderProps) => {
const [local, others] = splitProps(props, ["class"]);
return (
<div
data-slot="drawer-header"
class={cn("z-drawer-header flex flex-col", local.class)}
{...others}
/>
);
};
type DrawerFooterProps = ComponentProps<"div">;
const DrawerFooter = (props: DrawerFooterProps) => {
const [local, others] = splitProps(props, ["class"]);
return (
<div
data-slot="drawer-footer"
class={cn("z-drawer-footer mt-auto flex flex-col", local.class)}
{...others}
/>
);
};
type DrawerLabelProps<T extends ValidComponent = "h2"> = DynamicProps<T, LabelProps> &
Pick<ComponentProps<T>, "class">;
const DrawerLabel = <T extends ValidComponent = "h2">(props: DrawerLabelProps<T>) => {
const [local, others] = splitProps(props as DrawerLabelProps, ["class"]);
return <Label data-slot="drawer-title" class={cn("z-drawer-title", local.class)} {...others} />;
};
type DrawerDescriptionProps<T extends ValidComponent = "p"> = DynamicProps<T, DescriptionProps> &
Pick<ComponentProps<T>, "class">;
const DrawerDescription = <T extends ValidComponent = "p">(props: DrawerDescriptionProps<T>) => {
const [local, others] = splitProps(props as DrawerDescriptionProps, ["class"]);
return (
<Description
data-slot="drawer-description"
class={cn("z-drawer-description", local.class)}
{...others}
/>
);
};
export {
DrawerRoot as Drawer,
DrawerOverlay,
DrawerTrigger,
DrawerClose,
DrawerContent,
DrawerHeader,
DrawerFooter,
DrawerLabel as DrawerTitle,
DrawerDescription,
};

Examples

Here are the source code of all the examples from the preview page:

Scrollable Content

import { Index } from "solid-js";
import { Button } from "~/components/ui/button";
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "~/components/ui/drawer";
function DrawerScrollableContent() {
return (
<Example title="Scrollable Content">
<Drawer side="right">
<DrawerTrigger as={Button} variant="outline">
Scrollable Content
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>Move Goal</DrawerTitle>
<DrawerDescription>Set your daily activity goal.</DrawerDescription>
</DrawerHeader>
<div class="no-scrollbar overflow-y-auto px-4">
<Index each={Array.from({ length: 10 })}>
{() => (
<p class="mb-4 style-lyra:mb-2 leading-normal style-lyra:leading-relaxed">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute
irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui
officia deserunt mollit anim id est laborum.
</p>
)}
</Index>
</div>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose as={Button} variant="outline">
Cancel
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
</Example>
);
}

Sides

import { For, Index } from "solid-js";
import { Button } from "~/components/ui/button";
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
} from "~/components/ui/drawer";
function DrawerWithSides() {
return (
<Example title="Sides">
<div class="flex flex-wrap gap-2">
<For each={DRAWER_SIDES}>
{(side) => (
<Drawer side={side}>
<DrawerTrigger as={Button} variant="outline" class="capitalize">
{side}
</DrawerTrigger>
<DrawerContent class="data-[side=bottom]:max-h-[50vh] data-[side=top]:max-h-[50vh]">
<DrawerHeader>
<DrawerTitle>Move Goal</DrawerTitle>
<DrawerDescription>Set your daily activity goal.</DrawerDescription>
</DrawerHeader>
<div class="no-scrollbar overflow-y-auto px-4">
<Index each={Array.from({ length: 10 })}>
{() => (
<p class="mb-4 style-lyra:mb-2 leading-normal style-lyra:leading-relaxed">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat
non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
)}
</Index>
</div>
<DrawerFooter>
<Button>Submit</Button>
<DrawerClose as={Button} variant="outline">
Cancel
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
)}
</For>
</div>
</Example>
);
}

Props

The Drawer component uses Corvu's Drawer primitive under the hood. Here are the main props:

PropTypeDefaultDescription
side"top" | "right" | "bottom" | "left""bottom"The side the drawer opens from
openboolean-The controlled open state
defaultOpenbooleanfalseThe default open state
onOpenChange(open: boolean) => void-Handler called when open state changes
snapPointsSize[][0, 1]Positions to snap to
activeSnapPointSize-The controlled active snap point
allowSkippingSnapPointsbooleantrueWhether to allow skipping snap points
closeOnOutsidePointerbooleantrueWhether to close on outside pointer
closeOnEscapeKeyDownbooleantrueWhether to close on escape key