This repository was archived by the owner on Jul 20, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
feat: drawer #137
Closed
Closed
feat: drawer #137
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import type { Meta, StoryObj } from "@storybook/react"; | ||
import { useState } from "react"; | ||
import { Drawer } from "./Drawer"; | ||
|
||
const meta: Meta<typeof Drawer> = { | ||
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<typeof meta>; | ||
|
||
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 ( | ||
<div className="relative h-96 overflow-hidden border rounded-lg bg-white dark:bg-gray-900"> | ||
<button | ||
onClick={() => setIsOpen(true)} | ||
className="m-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" | ||
> | ||
Open Theme Drawer (Right) | ||
</button> | ||
|
||
<Drawer | ||
isOpen={isOpen} | ||
onClose={() => setIsOpen(false)} | ||
title="Theme" | ||
direction="right" | ||
> | ||
<div className="flex flex-col"> | ||
{themes.map((theme) => ( | ||
<button | ||
key={theme.id} | ||
onClick={() => { | ||
setSelectedTheme(theme.id); | ||
console.log(`Theme selected: ${theme.id}`); | ||
}} | ||
className={` | ||
flex items-center justify-between p-4 hover:bg-gray-100 dark:hover:bg-gray-800 | ||
${selectedTheme === theme.id ? "bg-gray-50 dark:bg-gray-800" : ""} | ||
`} | ||
> | ||
<div className="flex flex-col items-start"> | ||
<span className="font-medium">{theme.label}</span> | ||
<span className="text-sm text-gray-500 dark:text-gray-400"> | ||
{theme.description} | ||
</span> | ||
</div> | ||
{selectedTheme === theme.id && ( | ||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
<path d="M16.6667 5L7.5 14.1667L3.33333 10" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/> | ||
</svg> | ||
)} | ||
</button> | ||
))} | ||
</div> | ||
</Drawer> | ||
</div> | ||
); | ||
}, | ||
}; | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<DrawerProps> = ({ | ||
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 ( | ||
<svg | ||
width="16" | ||
height="16" | ||
viewBox="0 0 16 16" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
className="text-accent-primary" | ||
> | ||
<path | ||
d="M6 4L10 8L6 12" | ||
stroke="currentColor" | ||
strokeWidth="2" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
/> | ||
</svg> | ||
); | ||
case "top": | ||
return ( | ||
<svg | ||
width="16" | ||
height="16" | ||
viewBox="0 0 16 16" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
className="text-accent-primary" | ||
> | ||
<path | ||
d="M4 10L8 6L12 10" | ||
stroke="currentColor" | ||
strokeWidth="2" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
/> | ||
</svg> | ||
); | ||
case "bottom": | ||
return ( | ||
<svg | ||
width="16" | ||
height="16" | ||
viewBox="0 0 16 16" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
className="text-accent-primary" | ||
> | ||
<path | ||
d="M4 6L8 10L12 6" | ||
stroke="currentColor" | ||
strokeWidth="2" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
/> | ||
</svg> | ||
); | ||
default: // right | ||
return ( | ||
<svg | ||
width="16" | ||
height="16" | ||
viewBox="0 0 16 16" | ||
fill="none" | ||
xmlns="http://www.w3.org/2000/svg" | ||
className="text-accent-primary" | ||
> | ||
<path | ||
d="M10 12L6 8L10 4" | ||
stroke="currentColor" | ||
strokeWidth="2" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
/> | ||
</svg> | ||
); | ||
} | ||
}; | ||
|
||
return ( | ||
<div | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The drawer container lacks ARIA roles and attributes. Consider adding Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||
className={twJoin( | ||
"absolute transform transition-transform duration-300 ease-in-out rounded-3xl md:rounded-lg", | ||
"bg-[#FFFFFF] dark:bg-[#252525]", | ||
getPositionClasses(), | ||
getTransformClasses(), | ||
className, | ||
)} | ||
> | ||
{/* Header */} | ||
<div className="flex items-center gap-3 p-4 border-b border-[#38708533] dark:border-[#404040]"> | ||
{showBackButton && ( | ||
<button | ||
onClick={onClose} | ||
className="flex items-center justify-center w-8 h-8 rounded hover:bg-accent-secondary/10 transition-colors" | ||
aria-label="Go back" | ||
> | ||
{getBackIcon()} | ||
</button> | ||
)} | ||
<h3 className="text-sm font-medium text-accent-primary">{title}</h3> | ||
</div> | ||
|
||
<div className="flex-1 overflow-y-auto">{children}</div> | ||
</div> | ||
); | ||
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { Drawer } from "./Drawer"; |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] SVG markup is duplicated for each direction. You could extract a base arrow icon component and apply CSS rotation or transform props instead of repeating nearly identical SVG blocks.
Copilot uses AI. Check for mistakes.