add drawer component

This commit is contained in:
2026-02-24 08:52:14 +01:00
parent 117d44718e
commit dd44f053f9
5 changed files with 226 additions and 12 deletions

View File

@@ -0,0 +1,148 @@
import { DrawerPreview as BaseDrawer } from "@base-ui/react/drawer";
import { tv } from "tailwind-variants";
const drawerStyles = tv({
slots: {
viewport: "fixed inset-y-0 right-0 pointer-events-none",
popup: [
"pointer-events-auto",
"fixed top-0 right-0 h-dvh w-[400px]",
"bg-card text-card-foreground border-l border-border shadow-lg",
"flex flex-col",
"transition-transform duration-100 ease-out",
],
title: "text-lg font-semibold",
description: "text-sm text-muted-foreground",
close: [
"focus-visible:ring-ring focus-visible:outline-none",
"focus-visible:ring-2 focus-visible:ring-offset-2",
],
content: "flex-1 overflow-y-auto",
},
});
const { viewport, popup, title, description, close, content } = drawerStyles();
type DrawerRootProps = React.ComponentProps<typeof BaseDrawer.Root>;
function DrawerRoot(props: DrawerRootProps) {
return <BaseDrawer.Root modal={false} swipeDirection="right" {...props} />;
}
type DrawerTriggerProps = React.ComponentProps<typeof BaseDrawer.Trigger>;
function DrawerTrigger({ className, ...props }: DrawerTriggerProps) {
return <BaseDrawer.Trigger className={className} {...props} />;
}
type DrawerPortalProps = React.ComponentProps<typeof BaseDrawer.Portal>;
function DrawerPortal(props: DrawerPortalProps) {
return <BaseDrawer.Portal {...props} />;
}
type DrawerViewportProps = React.ComponentProps<typeof BaseDrawer.Viewport>;
function DrawerViewport({ className, ...props }: DrawerViewportProps) {
return (
<BaseDrawer.Viewport
className={(state) =>
viewport({
class: typeof className === "function" ? className(state) : className,
})
}
{...props}
/>
);
}
type DrawerPopupProps = React.ComponentProps<typeof BaseDrawer.Popup>;
function DrawerPopup({ className, ...props }: DrawerPopupProps) {
return (
<BaseDrawer.Popup
data-drawer-popup=""
className={(state) =>
popup({
class: typeof className === "function" ? className(state) : className,
})
}
{...props}
/>
);
}
type DrawerTitleProps = React.ComponentProps<typeof BaseDrawer.Title>;
function DrawerTitle({ className, ...props }: DrawerTitleProps) {
return (
<BaseDrawer.Title
className={(state) =>
title({
class: typeof className === "function" ? className(state) : className,
})
}
{...props}
/>
);
}
type DrawerDescriptionProps = React.ComponentProps<
typeof BaseDrawer.Description
>;
function DrawerDescription({ className, ...props }: DrawerDescriptionProps) {
return (
<BaseDrawer.Description
className={(state) =>
description({
class: typeof className === "function" ? className(state) : className,
})
}
{...props}
/>
);
}
type DrawerCloseProps = React.ComponentProps<typeof BaseDrawer.Close>;
function DrawerClose({ className, ...props }: DrawerCloseProps) {
return (
<BaseDrawer.Close
className={(state) =>
close({
class: typeof className === "function" ? className(state) : className,
})
}
{...props}
/>
);
}
type DrawerContentProps = React.ComponentProps<typeof BaseDrawer.Content>;
function DrawerContent({ className, ...props }: DrawerContentProps) {
return (
<BaseDrawer.Content
className={(state) =>
content({
class: typeof className === "function" ? className(state) : className,
})
}
{...props}
/>
);
}
export {
DrawerRoot,
DrawerTrigger,
DrawerPortal,
DrawerViewport,
DrawerPopup,
DrawerTitle,
DrawerDescription,
DrawerClose,
DrawerContent,
drawerStyles,
};

View File

@@ -129,3 +129,8 @@
@apply font-sans;
}
}
[data-drawer-popup][data-starting-style],
[data-drawer-popup][data-ending-style] {
translate: 100% 0;
}

View File

@@ -1,8 +1,19 @@
import { createFileRoute } from "@tanstack/react-router";
import { Heart, Search } from "lucide-react";
import { Heart, Search, X } from "lucide-react";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import {
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerPopup,
DrawerPortal,
DrawerRoot,
DrawerTitle,
DrawerTrigger,
DrawerViewport,
} from "@/components/ui/drawer";
import {
FieldControl,
FieldDescription,
@@ -166,6 +177,57 @@ function DemoPage() {
</Button>
</div>
</Section>
{/* Drawer */}
<Section title="Drawer">
<DrawerRoot>
<DrawerTrigger>
<Button variant="outline">Open Drawer</Button>
</DrawerTrigger>
<DrawerPortal>
<DrawerViewport>
<DrawerPopup>
<div className="flex items-center justify-between border-b border-border p-6">
<div className="space-y-1">
<DrawerTitle>Edit Item</DrawerTitle>
<DrawerDescription>
Update the details for this timeline item.
</DrawerDescription>
</div>
<DrawerClose>
<Button variant="outline" size="icon" aria-label="Close">
<X className="h-4 w-4" />
</Button>
</DrawerClose>
</div>
<DrawerContent>
<form
className="grid gap-4 p-6"
onSubmit={(e) => e.preventDefault()}
>
<FieldRoot>
<FieldLabel>Title</FieldLabel>
<FieldControl placeholder="Item title" />
</FieldRoot>
<FieldRoot>
<FieldLabel>Date</FieldLabel>
<FieldControl type="date" />
</FieldRoot>
<FieldRoot>
<FieldLabel>Description</FieldLabel>
<FieldControl placeholder="Describe this event" />
<FieldDescription>Optional</FieldDescription>
</FieldRoot>
<Button type="submit" className="w-fit">
Save
</Button>
</form>
</DrawerContent>
</DrawerPopup>
</DrawerViewport>
</DrawerPortal>
</DrawerRoot>
</Section>
</div>
);
}