Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,19 @@
"build-storybook": "storybook build"
},
"dependencies": {
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@tanstack/react-query": "^5.80.7",
"@tanstack/react-query-devtools": "^5.80.7",
"date-fns": "^4.1.0",
"next": "^15.3.0",
"next-auth": "5.0.0-beta.28",
"overlay-kit": "^1.8.6",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"vaul": "^1.1.2",
"zod": "^3.25.67"
},
"devDependencies": {
Expand Down
108 changes: 108 additions & 0 deletions apps/web/src/shared/components/BottomSheet/BottomSheet.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import type { Meta, StoryObj } from "@storybook/nextjs-vite";
import { OverlayProvider, overlay } from "overlay-kit";
import { BottomSheet } from "./BottomSheet";

/**
* **vaul docs**
* https://vaul.emilkowal.ski/api
*/
const meta: Meta<typeof BottomSheet> = {
title: "V2/Components/BottomSheet",
component: BottomSheet,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
locked: {
control: "boolean",
description: "์™ธ๋ถ€ ์š”์†Œ์™€์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.",
},
radius: {
control: "select",
options: ["none", "small", "medium", "large"],
},
theme: {
control: "select",
options: ["light", "dark"],
},
},
decorators: [
Story => (
<OverlayProvider>
<Story />
</OverlayProvider>
),
],
} satisfies Meta<typeof BottomSheet>;

export default meta;
type Story = StoryObj<typeof meta>;

const BottomSheetNoScroll = () => {
return (
<div style={{ height: 240, padding: 24, display: "flex", alignItems: "center", justifyContent: "center" }}>
BottomSheet
</div>
);
};

const BottomSheetScroll = () => {
return (
<div style={{ height: 2000, padding: 24, display: "flex", alignItems: "center", justifyContent: "center" }}>
BottomSheet
</div>
);
};

export const BottomSheets: Story = {
args: {
showHandle: true,
radius: "medium",
},
render: args => {
const openNoScrollBottomSheet = () => {
overlay.open(({ isOpen, close }) => (
<BottomSheet
{...args}
open={isOpen}
onClose={() => {
close();
}}
content={<BottomSheetNoScroll />}
/>
));
Comment on lines +65 to +74

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue | ๐ŸŸ  Major

overlay ์ •๋ฆฌ ์ฝœ๋ฐฑ ๋ˆ„๋ฝ์œผ๋กœ ์ธํ•œ ์ž ์žฌ์  ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜

overlay.open ์ฝœ๋ฐฑ์—์„œ unmount ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. learnings์— ๋”ฐ๋ฅด๋ฉด, overlay-kit์˜ ์ฝœ๋ฐฑ์€ { isOpen, close, unmount }๋ฅผ ์ œ๊ณตํ•˜๋ฉฐ, unmount๋Š” ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ์™„์ „ํžˆ ์ข…๋ฃŒ/์–ธ๋งˆ์šดํŠธ๋  ๋•Œ ํ˜ธ์ถœ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ๋ˆ„๋ฝํ•˜๋ฉด ์˜ค๋ฒ„๋ ˆ์ด ์ธ์Šคํ„ด์Šค๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ์—์„œ ์ œ๋Œ€๋กœ ์ •๋ฆฌ๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Based on learnings.

๋‹ค์Œ diff๋ฅผ ์ ์šฉํ•˜์—ฌ unmount ์ฝœ๋ฐฑ์„ ์ถ”๊ฐ€ํ•˜์„ธ์š”:

     const openNoScrollBottomSheet = () => {
-      overlay.open(({ isOpen, close }) => (
+      overlay.open(({ isOpen, close, unmount }) => (
         <BottomSheet
           {...args}
           open={isOpen}
           onClose={() => {
             close();
+            unmount();
           }}
           content={<BottomSheetNoScroll />}
         />
       ));
     };

     const openScrollBottomSheet = () => {
-      overlay.open(({ isOpen, close }) => (
+      overlay.open(({ isOpen, close, unmount }) => (
         <BottomSheet
           {...args}
           open={isOpen}
           onClose={() => {
             close();
+            unmount();
           }}
           content={<BottomSheetScroll />}
         />
       ));
     };

์ฐธ๊ณ : BottomSheet ์ปดํฌ๋„ŒํŠธ์˜ onClose prop์ด Drawer.Root์˜ onClose์™€ ์–ด๋–ป๊ฒŒ ์—ฐ๊ฒฐ๋˜๋Š”์ง€ ํ™•์ธํ•˜๊ณ , Drawer๊ฐ€ ์™„์ „ํžˆ ์–ธ๋งˆ์šดํŠธ๋œ ํ›„ unmount๊ฐ€ ํ˜ธ์ถœ๋˜๋„๋ก ํƒ€์ด๋ฐ์„ ์กฐ์ •ํ•ด์•ผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Also applies to: 78-87

๐Ÿค– Prompt for AI Agents
In apps/web/src/shared/components/BottomSheet/BottomSheet.stories.tsx around
lines 65-74 (and likewise 78-87), the overlay.open callback does not accept or
call the provided unmount parameter which can cause overlay instances to not be
fully cleaned up; update the overlay.open callbacks to accept ({ isOpen, close,
unmount }) and ensure unmount() is invoked after the BottomSheet/Drawer has
fully closed (e.g., call close(), then trigger unmount in the BottomSheet
onClose completion or via a post-close callback/animation end so Drawer has time
to unmount before calling unmount()).

};

const openScrollBottomSheet = () => {
overlay.open(({ isOpen, close }) => (
<BottomSheet
{...args}
open={isOpen}
onClose={() => {
close();
}}
content={<BottomSheetScroll />}
/>
));
};

return (
<div
style={{
display: "flex",
flexDirection: "column",
gap: 16,
alignItems: "center",
}}
>
<button type="button" onClick={openNoScrollBottomSheet}>
Open(No Scroll)
</button>
<button type="button" onClick={openScrollBottomSheet}>
Open(Scroll)
</button>
</div>
);
},
};
71 changes: 71 additions & 0 deletions apps/web/src/shared/components/BottomSheet/BottomSheet.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { css, cva } from "../../../../styled-system/css";

/* Color tokens */
const SEED_PALETTE_COLOR_BASE_100 = "#ffffff";
const SEED_PALETTE_COLOR_BASE_800 = "#494f54";

/* Radius tokens */
const SEED_SPACING_100 = "0px";
const SEED_SPACING_300 = "8px";
const SEED_SPACING_600 = "20px";
const SEED_SPACING_900 = "32px";

export const overlay = css({
position: "fixed",
inset: 0,
backgroundColor: "rgba(0, 0, 0, 0.4)",
});

export const drawerContent = cva({
base: {
position: "fixed",
bottom: 0,
left: 0,
right: 0,
height: "fit-content",
backgroundColor: "#fff",
outline: "none",
maxHeight: "calc(100dvh - 32px)",
borderBottomLeftRadius: "0 !important",
borderBottomRightRadius: "0 !important",
overflowY: "hidden",
},
variants: {
radius: {
none: {
borderTop: SEED_SPACING_100,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue | ๐Ÿ”ด Critical

CSS ์†์„ฑ ์˜ค๋ฅ˜

borderTop: SEED_SPACING_100์€ ์œ ํšจํ•˜์ง€ ์•Š์€ CSS์ž…๋‹ˆ๋‹ค. borderTop์€ width style color ํ˜•์‹์ด ํ•„์š”ํ•˜๋ฉฐ, spacing ๊ฐ’๋งŒ์œผ๋กœ๋Š” ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. borderRadius: SEED_SPACING_100์„ ์˜๋„ํ•œ ๊ฒƒ์œผ๋กœ ๋ณด์ž…๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •ํ•˜์„ธ์š”:

       none: {
-        borderTop: SEED_SPACING_100,
+        borderRadius: SEED_SPACING_100,
       },
๐Ÿ“ Committable suggestion

โ€ผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
borderTop: SEED_SPACING_100,
none: {
borderRadius: SEED_SPACING_100,
},
๐Ÿค– Prompt for AI Agents
In apps/web/src/shared/components/BottomSheet/BottomSheet.style.ts around line
36, the property borderTop: SEED_SPACING_100 is invalid CSS (borderTop requires
width/style/color) โ€” replace it with borderRadius: SEED_SPACING_100 (or if you
intended a top border, use borderTopWidth, borderTopStyle and borderTopColor) so
the style uses a valid CSS property and value.

},
small: {
borderRadius: SEED_SPACING_300,
},
medium: {
borderRadius: SEED_SPACING_600,
},
large: {
borderRadius: SEED_SPACING_900,
},
},
theme: {
light: {
backgroundColor: SEED_PALETTE_COLOR_BASE_100,
},
dark: {
backgroundColor: SEED_PALETTE_COLOR_BASE_800,
},
},
},
});

export const drawerContentInner = css({
maxHeight: "calc(100dvh - 68px)",
overflowY: "auto",
WebkitOverflowScrolling: "touch",
});

export const drawerHandle = css({
height: "36px",
width: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
});
44 changes: 44 additions & 0 deletions apps/web/src/shared/components/BottomSheet/BottomSheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"use client";

import { SwitchCase } from "@/shared/utils/SwitchCase";
import { Drawer } from "vaul";
import * as styles from "./BottomSheet.style";
import type { BottomSheetProps } from "./BottomSheet.type";

export const BottomSheet = ({
open,
onClose,
locked = true,
showHandle = true,
handleOnly = false,
radius,
theme = "light",
zIndex = 1,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue | ๐ŸŸก Minor

zIndex ๊ธฐ๋ณธ๊ฐ’์ด ๋‚ฎ์„ ์ˆ˜ ์žˆ์Œ

์˜ค๋ฒ„๋ ˆ์ด ์ปดํฌ๋„ŒํŠธ์˜ zIndex ๊ธฐ๋ณธ๊ฐ’์ด 1๋กœ ์„ค์ •๋˜์–ด ์žˆ๋Š”๋ฐ, ์ผ๋ฐ˜์ ์œผ๋กœ ๋ชจ๋‹ฌ/๋ฐ”ํ…€์‹œํŠธ๋Š” 1000 ์ด์ƒ์˜ ๋†’์€ z-index๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋ฅธ UI ์š”์†Œ์— ๊ฐ€๋ ค์งˆ ์œ„ํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ˆ˜์ •์„ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค:

-  zIndex = 1,
+  zIndex = 1000,
๐Ÿ“ Committable suggestion

โ€ผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
zIndex = 1,
zIndex = 1000,
๐Ÿค– Prompt for AI Agents
In apps/web/src/shared/components/BottomSheet/BottomSheet.tsx around line 16,
the zIndex default is set to 1 which is too low for overlays; update the
component to use a much higher default (e.g., zIndex = 1000) so the bottom
sheet/modal appears above other UI, and ensure any TypeScript prop signature or
defaultProps are adjusted accordingly (and update any Storybook/docs or tests
that rely on the old default).

className,
content,
}: BottomSheetProps) => {
return (
<Drawer.Root open={open} onClose={onClose} modal={locked} handleOnly={handleOnly}>
<Drawer.Portal>
<Drawer.Overlay className={styles.overlay} data-frieeren-component="BottomSheetOverlay" style={{ zIndex }} />
<Drawer.Content
className={`${styles.drawerContent({ radius, theme })} ${className || ""}`}
data-frieeren-component="BottomSheet"
style={{ zIndex }}
>
<SwitchCase
value={String(showHandle)}
caseBy={{
true: (
<div className={styles.drawerHandle}>
<Drawer.Handle style={{ width: 36 }} />
</div>
),
}}
/>
<div className={styles.drawerContentInner}>{content}</div>
</Drawer.Content>
</Drawer.Portal>
</Drawer.Root>
);
};
38 changes: 38 additions & 0 deletions apps/web/src/shared/components/BottomSheet/BottomSheet.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
export const BottomSheetRadius = {
NONE: "none",
SMALL: "small",
MEDIUM: "medium",
LARGE: "large",
} as const;

export const BottomSheetTheme = {
light: "light",
dark: "dark",
} as const;

type ValueOf<T> = T[keyof T];

export type BottomSheetRadius = ValueOf<typeof BottomSheetRadius>;
export type BottomSheetTheme = ValueOf<typeof BottomSheetTheme>;
export interface BottomSheetProps {
/** ๋ฐ”ํ…€์‹œํŠธ์˜ ์—ด๋ฆผ/๋‹ซํž˜ ์ƒํƒœ๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. */
open?: boolean;
/** ๋ฐ”ํ…€์‹œํŠธ๊ฐ€ ๋‹ซํž ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ฝœ๋ฐฑ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. */
onClose?: () => void;
/** ๋ฐ”ํ…€์‹œํŠธ์˜ ๋ชจ๋‹ฌ๋ฆฌํ‹ฐ๋ฅผ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค. true์ผ ๊ฒฝ์šฐ ์™ธ๋ถ€ ์š”์†Œ์™€์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ณ  ์Šคํฌ๋ฆฐ ๋ฆฌ๋”์—๋Š” ๋ฐ”ํ…€์‹œํŠธ ์ฝ˜ํ…์ธ ๋งŒ ๋ณด์ž…๋‹ˆ๋‹ค. */
locked?: boolean;
/** ๋ฐ”ํ…€์‹œํŠธ ์ƒ๋‹จ์˜ ๋“œ๋ž˜๊ทธ ํ•ธ๋“ค์„ ํ‘œ์‹œํ• ์ง€ ์—ฌ๋ถ€๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. */
showHandle?: boolean;
/** true์ผ ๊ฒฝ์šฐ ํ•ธ๋“ค์„ ํ†ตํ•ด์„œ๋งŒ ๋“œ๋ž˜๊ทธ๊ฐ€ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. */
handleOnly?: boolean;
/** ๋ฐ”ํ…€์‹œํŠธ์˜ z-index ๊ฐ’์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. */
zIndex?: number;
/** ๋ฐ”ํ…€์‹œํŠธ์˜ ๋ชจ์„œ๋ฆฌ ๋‘ฅ๊ธ€๊ธฐ ์ •๋„๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. */
radius?: BottomSheetRadius;
/** ๋ฐ”ํ…€์‹œํŠธ์˜ ํ…Œ๋งˆ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. */
theme?: BottomSheetTheme;
/** ์ถ”๊ฐ€ CSS ํด๋ž˜์Šค๋ช…์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค. */
className?: string;
/** ๋ฐ”ํ…€์‹œํŠธ ๋‚ด๋ถ€์— ํ‘œ์‹œํ•  ์ฝ˜ํ…์ธ ์ž…๋‹ˆ๋‹ค. */
content?: React.ReactNode;
}
2 changes: 2 additions & 0 deletions apps/web/src/shared/components/BottomSheet/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { BottomSheet } from "./BottomSheet";
export type { BottomSheetProps } from "./BottomSheet.type";
Loading