Skip to content
This repository was archived by the owner on Jul 20, 2025. It is now read-only.

Commit a44e7ec

Browse files
feat: add setting menu
1 parent 357e6f6 commit a44e7ec

File tree

6 files changed

+459
-59
lines changed

6 files changed

+459
-59
lines changed

src/components/Menu/MenuDrawer.tsx

Lines changed: 89 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from "react";
22
import { twJoin } from "tailwind-merge";
3+
import { Portal } from "../Portal";
34

45
interface MenuDrawerProps {
56
isOpen: boolean;
@@ -36,73 +37,102 @@ export const MenuDrawer: React.FC<MenuDrawerProps> = ({
3637
right: "text-right",
3738
};
3839

39-
return (
40-
<>
41-
{fullHeight && isOpen && onBackdropClick && (
42-
<div className="fixed inset-0 z-40 bg-black/50 transition-opacity duration-300" onClick={onBackdropClick} />
43-
)}
44-
40+
const renderHeader = () => {
41+
if (!(showBackButton || title)) return null;
42+
return (
4543
<div
46-
className={twJoin(
47-
"transform transition-transform duration-300 ease-in-out",
48-
"bg-[#FFFFFF] dark:bg-[#252525]",
49-
fullHeight
50-
? twJoin("fixed inset-y-0 right-0 z-50", fullWidth ? "w-full" : "w-full max-w-sm")
51-
: "absolute inset-0 rounded-lg",
52-
isOpen ? "translate-x-0" : "translate-x-full",
53-
className,
54-
)}
44+
className={twJoin("flex items-center p-4", showDivider && "border-b border-[#38708533] dark:border-[#404040]")}
5545
>
56-
{/* Header */}
57-
{(showBackButton || title) && (
58-
<div
59-
className={twJoin(
60-
"flex items-center p-4",
61-
showDivider && "border-b border-[#38708533] dark:border-[#404040]",
62-
)}
46+
{showBackButton && (
47+
<button
48+
onClick={onClose}
49+
className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded transition-colors hover:bg-accent-secondary/10"
50+
aria-label="Go back"
6351
>
64-
{showBackButton && (
65-
<button
66-
onClick={onClose}
67-
className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded transition-colors hover:bg-accent-secondary/10"
68-
aria-label="Go back"
52+
{backIcon || (
53+
<svg
54+
width="16"
55+
height="16"
56+
viewBox="0 0 16 16"
57+
fill="none"
58+
xmlns="http://www.w3.org/2000/svg"
59+
className="text-accent-primary"
6960
>
70-
{backIcon || (
71-
<svg
72-
width="16"
73-
height="16"
74-
viewBox="0 0 16 16"
75-
fill="none"
76-
xmlns="http://www.w3.org/2000/svg"
77-
className="text-accent-primary"
78-
>
79-
<path
80-
d="M10 12L6 8L10 4"
81-
stroke="currentColor"
82-
strokeWidth="2"
83-
strokeLinecap="round"
84-
strokeLinejoin="round"
85-
/>
86-
</svg>
87-
)}
88-
</button>
61+
<path
62+
d="M10 12L6 8L10 4"
63+
stroke="currentColor"
64+
strokeWidth="2"
65+
strokeLinecap="round"
66+
strokeLinejoin="round"
67+
/>
68+
</svg>
8969
)}
90-
{title && (
91-
<h3
92-
className={twJoin(
93-
"flex-1 text-sm font-medium text-accent-primary",
94-
showBackButton ? "ml-3" : "",
95-
titleAlignment[titleAlign],
96-
)}
97-
>
98-
{title}
99-
</h3>
70+
</button>
71+
)}
72+
{title && (
73+
<h3
74+
className={twJoin(
75+
"flex-1 text-sm font-medium text-accent-primary",
76+
showBackButton ? "ml-3" : "",
77+
titleAlignment[titleAlign],
10078
)}
101-
</div>
79+
>
80+
{title}
81+
</h3>
10282
)}
103-
104-
<div className="flex-1 overflow-y-auto">{children}</div>
10583
</div>
84+
);
85+
};
86+
87+
const renderContent = () => (
88+
<>
89+
{renderHeader()}
90+
<div className="flex-1 overflow-y-auto">{children}</div>
10691
</>
10792
);
93+
94+
// For fullHeight drawers (mobile), use Portal to ensure proper theme sync
95+
if (fullHeight) {
96+
return (
97+
<Portal mounted={true} rootClassName="menu-drawer-portal">
98+
{onBackdropClick && (
99+
<div
100+
className={twJoin(
101+
"fixed inset-0 z-40 bg-black/50 transition-opacity duration-300",
102+
isOpen ? "opacity-100" : "pointer-events-none opacity-0",
103+
)}
104+
onClick={onBackdropClick}
105+
/>
106+
)}
107+
108+
<div
109+
className={twJoin(
110+
"transform transition-transform duration-300 ease-in-out",
111+
"bg-[#FFFFFF] dark:bg-[#252525]",
112+
"fixed inset-y-0 right-0 z-50",
113+
fullWidth ? "w-full" : "w-full max-w-sm",
114+
isOpen ? "translate-x-0" : "translate-x-full",
115+
className,
116+
)}
117+
>
118+
{renderContent()}
119+
</div>
120+
</Portal>
121+
);
122+
}
123+
124+
// For non-fullHeight drawers (desktop), render normally
125+
return (
126+
<div
127+
className={twJoin(
128+
"transform transition-transform duration-300 ease-in-out",
129+
"bg-[#FFFFFF] dark:bg-[#252525]",
130+
"absolute inset-0 rounded-lg",
131+
isOpen ? "translate-x-0" : "translate-x-full",
132+
className,
133+
)}
134+
>
135+
{renderContent()}
136+
</div>
137+
);
108138
};

src/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export * from "./widgets/new-design/AmountSubsection";
3434
export * from "./widgets/new-design/FinalityProviderSubsection";
3535
export * from "./widgets/new-design/FeesSection";
3636
export * from "./widgets/new-design/PreviewModal";
37+
export * from "./widgets/new-design/SettingMenu";
3738

3839
export * from "./components/ListLegacy";
3940

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { Meta, StoryFn } from "@storybook/react";
2+
import { useState } from "react";
3+
import { SettingMenu } from "./SettingMenu";
4+
import { Button } from "@/components/Button";
5+
6+
export default {
7+
title: "Widgets/New Design/SettingMenu",
8+
component: SettingMenu,
9+
argTypes: {
10+
onOpenChange: { action: "open state changed" },
11+
},
12+
tags: ["autodocs"],
13+
} as Meta<typeof SettingMenu>;
14+
15+
const ThemeIcon = () => (
16+
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
17+
<rect width="40" height="40" rx="20" fill="#387085" fillOpacity="0.12" />
18+
<path
19+
d="M20 30C25.52 30 30 25.52 30 20C30 14.48 25.52 10 20 10C14.48 10 10 14.48 10 20C10 25.52 14.48 30 20 30ZM21 12.07C24.94 12.56 28 15.92 28 20C28 24.08 24.95 27.44 21 27.93V12.07Z"
20+
fill="#387085"
21+
/>
22+
</svg>
23+
);
24+
25+
const ReportABugIcon = () => (
26+
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
27+
<rect width="40" height="40" rx="20" fill="#387085" fillOpacity="0.12" />
28+
<path
29+
d="M23.73 11H16.27L11 16.27V23.73L16.27 29H23.73L29 23.73V16.27L23.73 11ZM20 25.3C19.28 25.3 18.7 24.72 18.7 24C18.7 23.28 19.28 22.7 20 22.7C20.72 22.7 21.3 23.28 21.3 24C21.3 24.72 20.72 25.3 20 25.3ZM21 21H19V15H21V21Z"
30+
fill="#387085"
31+
/>
32+
</svg>
33+
);
34+
35+
export const Default: StoryFn = () => {
36+
const [selectedTheme, setSelectedTheme] = useState<"light" | "dark" | "auto">("auto");
37+
38+
const handleThemeChange = (theme: "light" | "dark" | "auto") => {
39+
setSelectedTheme(theme);
40+
console.log(`${theme} theme selected`);
41+
};
42+
43+
const getThemeDescription = () => {
44+
switch (selectedTheme) {
45+
case "light":
46+
return "Light";
47+
case "dark":
48+
return "Dark";
49+
case "auto":
50+
return "Auto";
51+
default:
52+
return "Auto";
53+
}
54+
};
55+
56+
return (
57+
<div className="p-4">
58+
<SettingMenu>
59+
<SettingMenu.Title>Settings</SettingMenu.Title>
60+
61+
<SettingMenu.Group background="secondary">
62+
<SettingMenu.SubMenu icon={<ThemeIcon />}>
63+
Theme
64+
<SettingMenu.Description>{getThemeDescription()}</SettingMenu.Description>
65+
<SettingMenu.Item selected={selectedTheme === "light"} onClick={() => handleThemeChange("light")}>
66+
Light
67+
</SettingMenu.Item>
68+
<SettingMenu.Item selected={selectedTheme === "dark"} onClick={() => handleThemeChange("dark")}>
69+
Dark
70+
</SettingMenu.Item>
71+
<SettingMenu.Item selected={selectedTheme === "auto"} onClick={() => handleThemeChange("auto")}>
72+
Auto
73+
</SettingMenu.Item>
74+
</SettingMenu.SubMenu>
75+
76+
<SettingMenu.Item icon={<ReportABugIcon />} onClick={() => console.log("Report bug clicked")}>
77+
Report a Bug
78+
</SettingMenu.Item>
79+
</SettingMenu.Group>
80+
81+
<SettingMenu.Spacer />
82+
83+
<SettingMenu.Group background="secondary">
84+
<SettingMenu.Item onClick={() => window.open("https://example.com/terms", "_blank")}>
85+
Terms of Use
86+
</SettingMenu.Item>
87+
88+
<SettingMenu.Item onClick={() => window.open("https://example.com/privacy", "_blank")}>
89+
Privacy Policy
90+
</SettingMenu.Item>
91+
</SettingMenu.Group>
92+
93+
<SettingMenu.Spacer />
94+
95+
<SettingMenu.CustomContent className="my-4 flex justify-center">
96+
<Button className="!bg-[#D5FCE8] !text-black" variant="contained">
97+
Switch to BABY Staking
98+
</Button>
99+
</SettingMenu.CustomContent>
100+
</SettingMenu>
101+
</div>
102+
);
103+
};

0 commit comments

Comments
 (0)