From d401022dbc390432bd8003548cd64a8e8a54abaa Mon Sep 17 00:00:00 2001 From: jeremy-babylonlabs Date: Thu, 17 Jul 2025 16:47:32 +0800 Subject: [PATCH] feat: add drawer direction support --- src/components/Drawer/Drawer.stories.tsx | 87 ++++++++++++ src/components/Drawer/Drawer.tsx | 167 +++++++++++++++++++++++ src/components/Drawer/index.ts | 1 + 3 files changed, 255 insertions(+) create mode 100644 src/components/Drawer/Drawer.stories.tsx create mode 100644 src/components/Drawer/Drawer.tsx create mode 100644 src/components/Drawer/index.ts diff --git a/src/components/Drawer/Drawer.stories.tsx b/src/components/Drawer/Drawer.stories.tsx new file mode 100644 index 0000000..4e59023 --- /dev/null +++ b/src/components/Drawer/Drawer.stories.tsx @@ -0,0 +1,87 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { useState } from "react"; +import { Drawer } from "./Drawer"; + +const meta: Meta = { + title: "Components/Drawer", + component: Drawer, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: "A sliding drawer component that can appear from any side (left, right, top, bottom), commonly used for sub-menus and additional content.", + }, + }, + }, + argTypes: { + direction: { + control: { type: "select" }, + options: ["left", "right", "top", "bottom"], + description: "The direction from which the drawer slides in", + }, + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: () => { + const [isOpen, setIsOpen] = useState(false); + const [selectedTheme, setSelectedTheme] = useState("light"); + + const themes = [ + { id: "light", label: "Light", description: "Light theme for daytime use" }, + { id: "dark", label: "Dark", description: "Dark theme for reduced eye strain" }, + { id: "auto", label: "Auto", description: "Automatically switch based on system settings" }, + ]; + + return ( +
+ + + setIsOpen(false)} + title="Theme" + direction="right" + > +
+ {themes.map((theme) => ( + + ))} +
+
+
+ ); + }, +}; + diff --git a/src/components/Drawer/Drawer.tsx b/src/components/Drawer/Drawer.tsx new file mode 100644 index 0000000..4c13f94 --- /dev/null +++ b/src/components/Drawer/Drawer.tsx @@ -0,0 +1,167 @@ +import React from "react"; +import { twJoin } from "tailwind-merge"; + +type DrawerDirection = "left" | "right" | "top" | "bottom"; + +interface DrawerProps { + isOpen: boolean; + onClose: () => void; + title: string; + children: React.ReactNode; + className?: string; + showBackButton?: boolean; + backIcon?: React.ReactNode; + direction?: DrawerDirection; +} + +export const Drawer: React.FC = ({ + isOpen, + onClose, + title, + children, + className = "", + showBackButton = true, + backIcon, + direction = "right", +}) => { + const getTransformClasses = () => { + switch (direction) { + case "left": + return isOpen ? "translate-x-0" : "-translate-x-full"; + case "right": + return isOpen ? "translate-x-0" : "translate-x-full"; + case "top": + return isOpen ? "translate-y-0" : "-translate-y-full"; + case "bottom": + return isOpen ? "translate-y-0" : "translate-y-full"; + default: + return isOpen ? "translate-x-0" : "translate-x-full"; + } + }; + + const getPositionClasses = () => { + switch (direction) { + case "left": + return "left-0 top-0 bottom-0 w-80"; + case "right": + return "right-0 top-0 bottom-0 w-80"; + case "top": + return "top-0 left-0 right-0 h-80"; + case "bottom": + return "bottom-0 left-0 right-0 h-80"; + default: + return "right-0 top-0 bottom-0 w-80"; + } + }; + + const getBackIcon = () => { + if (backIcon) return backIcon; + + switch (direction) { + case "left": + return ( + + + + ); + case "top": + return ( + + + + ); + case "bottom": + return ( + + + + ); + default: // right + return ( + + + + ); + } + }; + + return ( +
+ {/* Header */} +
+ {showBackButton && ( + + )} +

{title}

+
+ +
{children}
+
+ ); +}; diff --git a/src/components/Drawer/index.ts b/src/components/Drawer/index.ts new file mode 100644 index 0000000..fb5a3af --- /dev/null +++ b/src/components/Drawer/index.ts @@ -0,0 +1 @@ +export { Drawer } from "./Drawer";