Skip to content

Commit

Permalink
(#16) Modal & Drawer Component
Browse files Browse the repository at this point in the history
(#16) Modal & Drawer Component
  • Loading branch information
baegofda committed Nov 26, 2023
1 parent f62420d commit 5614efd
Show file tree
Hide file tree
Showing 21 changed files with 707 additions and 165 deletions.
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
"@stylistic/eslint-plugin": "1.4.0",
"@stylistic/eslint-plugin-ts": "1.4.0",
"@types/node": "20.8.10",
"@types/react": "18.2.15",
"@types/react-dom": "18.2.7",
"@types/react": "18.2.0",
"@types/react-dom": "18.2.0",
"@typescript-eslint/eslint-plugin": "6.11.0",
"@typescript-eslint/parser": "6.11.0",
"@vitejs/plugin-react": "4.0.3",
Expand All @@ -57,6 +57,7 @@
"vite-tsconfig-paths": "4.2.1"
},
"resolutions": {
"jackspeak": "2.1.1"
"jackspeak": "2.1.1",
"@types/react": "18.2.0"
}
}
69 changes: 69 additions & 0 deletions src/core/components/Drawer/Drawer.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Meta } from "@storybook/react";
import { useState } from "react";

import Drawer from "./index";
import { DrawerProps } from "./types";

const meta = {
title: "core/Drawer",
component: Drawer,
parameters: {
layout: "fullscreen",
},
argTypes: {
target: {
control: "text",
defaultValue: "modal",
description: "Modal Render Position Element id",
},
title: {
control: "text",
description: "Drawer Title",
},
titleSub: {
control: "text",
description: "Drawer Title Sub",
},
isOpen: {
control: "boolean",
defaultValue: false,
description: "Open Modal",
},
onClose: {
action: "clicked",
description: "Drawer Close Function",
},
},
} satisfies Meta<typeof Drawer>;

export default meta;

export const Default = (props: DrawerProps) => {
const [ isOpen, setIsOpen ] = useState(false);

const onToggle = () => setIsOpen(v => !v);

return (
<>
<header className = "h-14 bg-gray-01">Header</header>
<div className = "flex h-[calc(100vh-4rem)]">
<nav className = "w-[15rem] border-r border-gray-01">SideBar</nav>
<main className = "relative flex-1">
<div id = {props.target ?? "bar"} />
<Drawer
title = {props.title ?? "알림"}
titleSub = {props.titleSub}
target = {props.target ?? "bar"}
isOpen = {props.isOpen || isOpen}
onClose = {onToggle}
>
Drawer
</Drawer>
<button onClick = {onToggle}>
Drawer Open!!
</button>
</main>
</div>
</>
);
};
55 changes: 55 additions & 0 deletions src/core/components/Drawer/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import clsx from "clsx";
import { PropsWithChildren, forwardRef } from "react";

import ModalBase from "../Modal/ModalBase";
import Section from "../Section";
import Typography from "../Typography";
import { DrawerProps } from "./types";

const Drawer = forwardRef((
{
title,
titleSub,
onClose,
children,
...props
}: PropsWithChildren<DrawerProps>,
ref: React.Ref<HTMLDialogElement>,
) => {
const { className, ...rest } = props;
const CloseIcon =
<svg xmlns = "http://www.w3.org/2000/svg" width = "32" height = "32" viewBox = "0 0 32 32" fill = "none" focusable = "false">
<path d = "M25.7076 24.2926C25.8005 24.3855 25.8742 24.4958 25.9245 24.6172C25.9747 24.7386 26.0006 24.8687 26.0006 25.0001C26.0006 25.1315 25.9747 25.2616 25.9245 25.383C25.8742 25.5044 25.8005 25.6147 25.7076 25.7076C25.6147 25.8005 25.5044 25.8742 25.383 25.9245C25.2616 25.9747 25.1315 26.0006 25.0001 26.0006C24.8687 26.0006 24.7386 25.9747 24.6172 25.9245C24.4958 25.8742 24.3855 25.8005 24.2926 25.7076L16.0001 17.4138L7.70757 25.7076C7.51993 25.8952 7.26543 26.0006 7.00007 26.0006C6.7347 26.0006 6.48021 25.8952 6.29257 25.7076C6.10493 25.5199 5.99951 25.2654 5.99951 25.0001C5.99951 24.7347 6.10493 24.4802 6.29257 24.2926L14.5863 16.0001L6.29257 7.70757C6.10493 7.51993 5.99951 7.26543 5.99951 7.00007C5.99951 6.7347 6.10493 6.48021 6.29257 6.29257C6.48021 6.10493 6.7347 5.99951 7.00007 5.99951C7.26543 5.99951 7.51993 6.10493 7.70757 6.29257L16.0001 14.5863L24.2926 6.29257C24.4802 6.10493 24.7347 5.99951 25.0001 5.99951C25.2654 5.99951 25.5199 6.10493 25.7076 6.29257C25.8952 6.48021 26.0006 6.7347 26.0006 7.00007C26.0006 7.26543 25.8952 7.51993 25.7076 7.70757L17.4138 16.0001L25.7076 24.2926Z" fill = "#343330"/>
</svg>;

return (
<ModalBase
ref = {ref}
variants = {"drawer"}
onClose = {onClose}
{...rest}
>
<Section
element = "div"
className = {clsx("w-[29.1875rem] h-full animate-drawer", className)}
hasRounded = {false}
hasShadow
>
<header className = "flex-v-stack gap-y-6 pt-6 pl-6 pr-4 after:content-[''] after:h-[0.0625rem] after:bg-gray-02">
<div className = "flex items-center justify-between">
<div className = {clsx(titleSub && "flex items-center gap-x-2")}>
<Typography element = "strong" theme = "head-01-bold" className = "text-[#000]" text = {title} />
{titleSub && <Typography theme = "body-02-regular" color = "gray-06" text = {titleSub} />}
</div>
<button onClick = {onClose} aria-label = "창 닫기">
{CloseIcon}
</button>
</div>
</header>
{children}
</Section>
</ModalBase>
);
});

export default Drawer;
10 changes: 10 additions & 0 deletions src/core/components/Drawer/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { HTMLAttributes } from "react";

import { ModalBaseProps } from "../../Modal/ModalBase/types";
import { TypographyProps } from "../../Typography";

export interface DrawerProps extends Pick<ModalBaseProps, "target" | "isOpen">, HTMLAttributes<HTMLElement> {
title: TypographyProps<"strong">["text"];
titleSub?: TypographyProps<"span">["text"];
onClose: ModalBaseProps["onClose"];
}
69 changes: 69 additions & 0 deletions src/core/components/Modal/ModalBase/ModalBase.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Meta } from "@storybook/react";
import clsx from "clsx";
import { useState } from "react";

import Section from "../../Section";
import ModalBase from "./index";
import { ModalBaseProps } from "./types";

const meta = {
title: "core/Modal/ModalBase",
component: ModalBase,
parameters: {
layout: "fullscreen",
},
argTypes: {
target: {
control: "text",
defaultValue: "modal",
description: "Modal Render Position Element id",
},
variants: {
control: "select",
options: [ "modal", "drawer" ],
description: "Modal Variants",
},
isOpen: {
control: "boolean",
defaultValue: false,
description: "Open Modal",
},
dimmed: {
control: "boolean",
defaultValue: true,
description: "use Modal Component with dimmed",
},
onClose: {
action: "clicked",
description: "Modal Close Function",
},
},
} satisfies Meta<typeof ModalBase>;

export default meta;

export const Default = (props: ModalBaseProps) => {
const [ isOpen, setIsOpen ] = useState(false);

return (
<>
<div id = {props.target ?? "modal"} />
<ModalBase
variants = {props.variants ?? "modal"}
isOpen = {props.isOpen || isOpen}
target = {props.target}
dimmed = {props.dimmed}
>
<Section
element = {"div"}
className = {clsx("w-[20rem] h-[20rem]",
props.variants === "modal" ? "animate-popup" : "animate-drawer",
)}
/>
</ModalBase>
<button onClick = {() => setIsOpen(true)}>
Modal Open!!
</button>
</>
);
};
22 changes: 22 additions & 0 deletions src/core/components/Modal/ModalBase/constants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { VariantsType } from "../types";

export const VARIANTS = {
MODAL: "modal",
DRAWER: "drawer",
} as const;

export const MODAL_CONTENT_POSITION: Record<VariantsType, string> = {
[VARIANTS.MODAL]: "flex items-center justify-center",
[VARIANTS.DRAWER]: "flex justify-end",
} as const;

export const MODAL_DIMMED_COLOR: Record<VariantsType, string> = {
[VARIANTS.MODAL]: "bg-[#1018284d]",
[VARIANTS.DRAWER]: "bg-[#f8faffcc]",
} as const;

export const MODAL_CONTENT_SIZE: Record<VariantsType, string> = {
[VARIANTS.MODAL]: "h-auto",
[VARIANTS.DRAWER]: "h-full",
} as const;

50 changes: 50 additions & 0 deletions src/core/components/Modal/ModalBase/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import clsx from "clsx";
import { PropsWithChildren, forwardRef } from "react";

import useClickOutside from "@/hooks/useClickOutSide";
import ModalPortal from "../ModalPortal";
import { MODAL_CONTENT_POSITION, MODAL_CONTENT_SIZE, MODAL_DIMMED_COLOR } from "./constants";
import { ModalBaseProps } from "./types";

const ModalBase = forwardRef((
{
target,
variants,
isOpen,
dimmed = true,
onClose,
children,
...props
}: PropsWithChildren<ModalBaseProps>,
ref: React.Ref<HTMLDialogElement>,
) => {
const { contentRef } = useClickOutside<HTMLDivElement>(onClose);
const { className, ...rest } = props;

if (!isOpen) return null;

return (
<ModalPortal target = {target}>
<dialog
ref = {ref}
className = {clsx(
"w-full h-full open:animate-fade-in",
MODAL_CONTENT_POSITION[variants],
dimmed && MODAL_DIMMED_COLOR[variants],
className,
)}
open = {isOpen}
{...rest}
>
<div
ref = {contentRef}
className = {MODAL_CONTENT_SIZE[variants]}
>
{children}
</div>
</dialog>
</ModalPortal>
);
});

export default ModalBase;
13 changes: 13 additions & 0 deletions src/core/components/Modal/ModalBase/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { DialogHTMLAttributes } from "react";

import { ModalPortalProps } from "../../ModalPortal";
import { VARIANTS } from "../constants";

export type VariantsType = typeof VARIANTS[keyof typeof VARIANTS];

export interface ModalBaseProps extends DialogHTMLAttributes<HTMLDialogElement>, ModalPortalProps {
variants: VariantsType;
isOpen: boolean;
dimmed?: boolean;
onClose?: () => void;
}
80 changes: 80 additions & 0 deletions src/core/components/Modal/ModalPopUp/ModalPopUp.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Meta } from "@storybook/react";
import { useState } from "react";

import ModalPopUp from "./index";
import { ModalPopUpProps } from "./types";

const meta = {
title: "core/Modal/ModalPopUp",
component: ModalPopUp,
parameters: {
layout: "fullscreen",
},
argTypes: {
target: {
control: "text",
defaultValue: "modal",
description: "Modal Render Position Element id",
},
isOpen: {
control: "boolean",
defaultValue: false,
description: "Open Modal",
},
onClose: {
action: "clicked",
description: "Modal Close Function",
},
},
} satisfies Meta<typeof ModalPopUp>;

export default meta;

export const Default = (props: ModalPopUpProps) => {
const [ isOpen, setIsOpen ] = useState(false);

const onToggle = () => setIsOpen(v => !v);

return (
<>
<div id = {props.target ?? "modal"} />
<ModalPopUp
target = {props.target}
isOpen = {props.isOpen || isOpen}
className = "flex-v-stack gap-y-5 w-[20rem] h-[20rem] p-4"
>
<button className = "ml-auto" onClick = {onToggle}>
닫기
</button>
ModalPopUp
</ModalPopUp>
<button onClick = {onToggle}>
ModalPopUp Open!!
</button>
</>
);
};

export const ModalPopUpUseBlur = (props: ModalPopUpProps) => {
const [ isOpen, setIsOpen ] = useState(false);

const onToggle = () => setIsOpen(v => !v);

return (
<>
<div id = {props.target ?? "foo"} />
<ModalPopUp
target = {props.target ?? "foo"}
isOpen = {props.isOpen || isOpen}
onClose = {onToggle}
className = "w-[20rem] h-[20rem] p-4"
>
ModalPopUp
</ModalPopUp>
<button onClick = {onToggle}>
ModalPopUp Open!!
</button>
</>
);
};

Loading

0 comments on commit 5614efd

Please sign in to comment.