A draggable dialog that is attached to any side of the viewport.
import { Button } from "@repo/tailwindcss/ui/button"; import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerLabel, DrawerTrigger, } from "@repo/tailwindcss/ui/drawer"; import { createSignal } from "solid-js"; const DrawerDemo = () => { const [goal, setGoal] = createSignal(350); const onClick = (adjustment: number) => { setGoal(Math.max(200, Math.min(400, goal() + adjustment))); }; return ( <Drawer> <DrawerTrigger as={Button} variant="outline"> Open Drawer </DrawerTrigger> <DrawerContent> <div class="mx-auto w-full max-w-sm"> <DrawerHeader> <DrawerLabel>Move Goal</DrawerLabel> <DrawerDescription>Set your daily activity goal.</DrawerDescription> </DrawerHeader> <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" onClick={() => onClick(-10)} disabled={goal() <= 200} > <svg class="h-4 w-4" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M6 12H18" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg> <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" onClick={() => onClick(10)} disabled={goal() >= 400} > <svg class="h-4 w-4" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" > <path d="M6 12H12M18 12H12M12 12V6M12 12V18" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg> <span class="sr-only">Increase</span> </Button> </div> </div> <DrawerFooter> <Button>Submit</Button> <DrawerClose as={Button} variant="outline"> Cancel </DrawerClose> </DrawerFooter> </div> </DrawerContent> </Drawer> ); }; export default DrawerDemo;
npx shadcn-solid@latest add drawer
npm install @corvu/drawer
import { cn } from "@/libs/cn"; import type { ContentProps, DescriptionProps, DynamicProps, LabelProps, } from "@corvu/drawer"; import DrawerPrimitive from "@corvu/drawer"; import type { ComponentProps, ParentProps, ValidComponent } from "solid-js"; import { splitProps } from "solid-js"; export const Drawer = DrawerPrimitive; export const DrawerTrigger = DrawerPrimitive.Trigger; export const DrawerClose = DrawerPrimitive.Close; type drawerContentProps<T extends ValidComponent = "div"> = ParentProps< ContentProps<T> & { class?: string; } >; export const DrawerContent = <T extends ValidComponent = "div">( props: DynamicProps<T, drawerContentProps<T>>, ) => { const [local, rest] = splitProps(props as drawerContentProps, [ "class", "children", ]); const ctx = DrawerPrimitive.useContext(); return ( <DrawerPrimitive.Portal> <DrawerPrimitive.Overlay class="fixed inset-0 z-50 data-[transitioning]:transition-colors data-[transitioning]:duration-200" style={{ "background-color": `hsl(var(--background) / ${0.8 * ctx.openPercentage()})`, }} /> <DrawerPrimitive.Content class={cn( "fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-xl border bg-background after:absolute after:inset-x-0 after:top-full after:h-[50%] after:bg-inherit data-[transitioning]:transition-transform data-[transitioning]:duration-200 md:select-none", local.class, )} {...rest} > <div class="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" /> {local.children} </DrawerPrimitive.Content> </DrawerPrimitive.Portal> ); }; export const DrawerHeader = (props: ComponentProps<"div">) => { const [local, rest] = splitProps(props, ["class"]); return ( <div class={cn("grid gap-1.5 p-4 text-center sm:text-left", local.class)} {...rest} /> ); }; export const DrawerFooter = (props: ComponentProps<"div">) => { const [local, rest] = splitProps(props, ["class"]); return ( <div class={cn("mt-auto flex flex-col gap-2 p-4", local.class)} {...rest} /> ); }; type DrawerLabelProps = LabelProps & { class?: string; }; export const DrawerLabel = <T extends ValidComponent = "h2">( props: DynamicProps<T, DrawerLabelProps>, ) => { const [local, rest] = splitProps(props as DrawerLabelProps, ["class"]); return ( <DrawerPrimitive.Label class={cn( "text-lg font-semibold leading-none tracking-tight", local.class, )} {...rest} /> ); }; type DrawerDescriptionProps = DescriptionProps & { class?: string; }; export const DrawerDescription = <T extends ValidComponent = "p">( props: DynamicProps<T, DrawerDescriptionProps>, ) => { const [local, rest] = splitProps(props as DrawerDescriptionProps, ["class"]); return ( <DrawerPrimitive.Description class={cn("text-sm text-muted-foreground", local.class)} {...rest} /> ); };
import { cn } from "@/libs/cn"; import type { ContentProps, DescriptionProps, DynamicProps, LabelProps, } from "@corvu/drawer"; import DrawerPrimitive from "@corvu/drawer"; import type { ComponentProps, ParentProps, ValidComponent } from "solid-js"; import { splitProps } from "solid-js"; export const Drawer = DrawerPrimitive; export const DrawerTrigger = DrawerPrimitive.Trigger; export const DrawerClose = DrawerPrimitive.Close; type drawerContentProps<T extends ValidComponent = "div"> = ParentProps< ContentProps<T> & { class?: string; } >; export const DrawerContent = <T extends ValidComponent = "div">( props: DynamicProps<T, drawerContentProps<T>>, ) => { const [local, rest] = splitProps(props as drawerContentProps, [ "class", "children", ]); const ctx = DrawerPrimitive.useContext(); return ( <DrawerPrimitive.Portal> <DrawerPrimitive.Overlay class="fixed inset-0 z-50 data-[transitioning]:(transition-colors duration-200)" style={{ "background-color": `hsl(var(--background) / ${0.8 * ctx.openPercentage()})`, }} /> <DrawerPrimitive.Content class={cn( "fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-xl border bg-background after:(absolute inset-x-0 top-full h-[50%] bg-inherit) data-[transitioning]:(transition-transform duration-200) md:select-none", local.class, )} {...rest} > <div class="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" /> {local.children} </DrawerPrimitive.Content> </DrawerPrimitive.Portal> ); }; export const DrawerHeader = (props: ComponentProps<"div">) => { const [local, rest] = splitProps(props, ["class"]); return ( <div class={cn("grid gap-1.5 p-4 text-center sm:text-left", local.class)} {...rest} /> ); }; export const DrawerFooter = (props: ComponentProps<"div">) => { const [local, rest] = splitProps(props, ["class"]); return ( <div class={cn("mt-auto flex flex-col gap-2 p-4", local.class)} {...rest} /> ); }; type DrawerLabelProps = LabelProps & { class?: string; }; export const DrawerLabel = <T extends ValidComponent = "h2">( props: DynamicProps<T, DrawerLabelProps>, ) => { const [local, rest] = splitProps(props as DrawerLabelProps, ["class"]); return ( <DrawerPrimitive.Label class={cn( "text-lg font-semibold leading-none tracking-tight", local.class, )} {...rest} /> ); }; type DrawerDescriptionProps = DescriptionProps & { class?: string; }; export const DrawerDescription = <T extends ValidComponent = "p">( props: DynamicProps<T, DrawerDescriptionProps>, ) => { const [local, rest] = splitProps(props as DrawerDescriptionProps, ["class"]); return ( <DrawerPrimitive.Description class={cn("text-sm text-muted-foreground", local.class)} {...rest} /> ); };
import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerLabel, DrawerTrigger } from "@/components/ui/drawer";
<Drawer> <DrawerTrigger>Open</DrawerTrigger> <DrawerContent> <DialogHeader> <DrawerLabel>Are you sure absolutely sure?</DrawerLabel> <DialogDescription> This action cannot be undone. This will permanently delete your account and remove your data from our servers. </DialogDescription> </DialogHeader> </DrawerContent> </Drawer>