Skip to content

Commit

Permalink
Merge branch 'Feature/#119_모달_공통_컴포넌트_구현' of https://github.com/boost…
Browse files Browse the repository at this point in the history
…campwm-2024/web33-Nocta into Feature/#127_탭_브라우징_피드백_반영
  • Loading branch information
pipisebastian committed Nov 15, 2024
2 parents 473da03 + 6781944 commit aca483a
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 2 deletions.
5 changes: 3 additions & 2 deletions client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<title>Nocta</title>
</head>

<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<div id="modal">
<script type="module" src="/src/main.tsx"></script>
</body>

</html>
34 changes: 34 additions & 0 deletions client/src/components/button/textButton.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { cva, cx } from "@styled-system/css";
import { glassContainer } from "@styled-system/recipes";

export const textButtonContainer = ({ variant }: { variant: "primary" | "secondary" }) => {
return cx(glassContainer({ border: "lg" }), textButton({ variant }));
};

const textButton = cva({
base: {
borderRadius: "md",
width: "150px",
height: "40px",
cursor: "pointer",
},
variants: {
variant: {
primary: {
background: "white/35",
_hover: {
background: "white/40",
},
},
secondary: {
background: "transparent",
_hover: {
background: "white/10",
},
},
},
},
defaultVariants: {
variant: "primary",
},
});
15 changes: 15 additions & 0 deletions client/src/components/button/textButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { textButtonContainer } from "./textButton.style";

interface TextButtonProps {
variant?: "primary" | "secondary";
children: React.ReactNode;
onClick?: () => void;
}

export const TextButton = ({ variant = "primary", children, onClick }: TextButtonProps) => {
return (
<button className={textButtonContainer({ variant })} onClick={onClick}>
{children}
</button>
);
};
29 changes: 29 additions & 0 deletions client/src/components/modal/modal.animation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export const overlayAnimation = {
initial: {
opacity: 0,
},
animate: {
opacity: 1,
},
exit: {
opacity: 0,
},
};

export const modalContainerAnimation = {
initial: {
scale: 0.7,
opacity: 0,
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
},
animate: {
scale: 1,
opacity: 1,
},
exit: {
scale: 0.7,
opacity: 0,
},
};
55 changes: 55 additions & 0 deletions client/src/components/modal/modal.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { css, cx } from "@styled-system/css";
import { glassContainer } from "@styled-system/recipes";

export const container = css({
display: "flex",
zIndex: 10000,
position: "fixed",
inset: 0,
justifyContent: "center",
alignItems: "center",
width: "100vw",
height: "100vh",
});

export const overlayBox = css({
position: "absolute",
inset: 0,
width: "100%",
height: "100%",
background: "gray.500/30",
backdropFilter: "blur(5px)",
});

export const modalContainer = cx(
glassContainer({ border: "lg" }),
css({
display: "flex",
zIndex: 10001,
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
flexDirection: "column",
width: "400px",
height: "200px",
padding: "md",
boxShadow: "md",
}),
);
export const modalContent = css({
display: "flex",
justifyContent: "center",
alignItems: "center",
width: "100%",
height: "100%",
textAlign: "center",
});

export const buttonContainer = css({
display: "flex",
gap: "md",
justifyContent: "center",
alignItems: "center",
width: "100%",
});
64 changes: 64 additions & 0 deletions client/src/components/modal/modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { motion, AnimatePresence } from "framer-motion";
import { createPortal } from "react-dom";
import { TextButton } from "../button/textButton";
import { modalContainerAnimation, overlayAnimation } from "./modal.animation";
import {
buttonContainer,
container,
modalContainer,
modalContent,
overlayBox,
} from "./modal.style";

interface ModalProps {
isOpen: boolean;
children: React.ReactNode;
primaryButtonLabel: string;
primaryButtonOnClick: () => void;
secondaryButtonLabel?: string;
secondaryButtonOnClick?: () => void;
}

export const Modal = ({
isOpen,
children,
primaryButtonLabel,
primaryButtonOnClick,
secondaryButtonLabel,
secondaryButtonOnClick,
}: ModalProps) => {
const portal = document.getElementById("modal") as HTMLElement;

return createPortal(
<AnimatePresence>
{isOpen && (
<div className={container}>
<motion.div
initial={overlayAnimation.initial}
animate={overlayAnimation.animate}
exit={overlayAnimation.exit}
className={overlayBox}
/>

<motion.div
initial={modalContainerAnimation.initial}
animate={modalContainerAnimation.animate}
exit={modalContainerAnimation.exit}
className={modalContainer}
>
<div className={modalContent}>{children}</div>
<div className={buttonContainer}>
{secondaryButtonLabel && (
<TextButton onClick={secondaryButtonOnClick} variant="secondary">
{secondaryButtonLabel}
</TextButton>
)}
<TextButton onClick={primaryButtonOnClick}>{primaryButtonLabel}</TextButton>
</div>
</motion.div>
</div>
)}
</AnimatePresence>,
portal,
);
};
14 changes: 14 additions & 0 deletions client/src/components/modal/useModal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { useState } from "react";

export const useModal = () => {
const [isOpen, setIsOpen] = useState(false);

const openModal = () => setIsOpen(true);
const closeModal = () => setIsOpen(false);

return {
isOpen,
openModal,
closeModal,
};
};

0 comments on commit aca483a

Please sign in to comment.