-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: 모집안내 -> 디프만 카카오톡 연결 * refactor: 버튼 분리 * feat: 지원하기 버튼 없애기 * fix: 주석 제거 --------- Co-authored-by: byun sumi <[email protected]>
- Loading branch information
Showing
11 changed files
with
315 additions
and
91 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains 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
This file contains 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,100 @@ | ||
import React, { useState } from 'react'; | ||
import Image from 'next/image'; | ||
import { css, Theme } from '@emotion/react'; | ||
|
||
import { Button } from '~/components/Button'; | ||
import CloseIcon from '~/components/Icons/CloseIcon'; | ||
import Modal from '~/components/Modal/Modal'; | ||
import { mediaQuery } from '~/styles/media'; | ||
|
||
function GuideModalButton() { | ||
const [isAlertOpen, setIsAlertOpen] = useState(false); | ||
|
||
const onAlertClose = () => setIsAlertOpen(false); | ||
|
||
const onAlertButtonClick = () => { | ||
setIsAlertOpen(true); | ||
}; | ||
return ( | ||
<> | ||
<Button size="lg" overrideCss={buttonCss} onClick={onAlertButtonClick}> | ||
15기 알림신청 | ||
</Button> | ||
|
||
<Modal isShowing={isAlertOpen} onClickOutside={onAlertClose} mode="wait"> | ||
<article css={modalCss}> | ||
<h2>15기 알림 신청</h2> | ||
<p>디프만 카카오톡 채널 친구 추가 시 기수 모집 알림을 보내드립니다</p> | ||
<Image src={'/images/kakao-qr.png'} alt="kakao qr link" width={90} height={90} /> | ||
<div css={buttonWrapperCss}> | ||
<a href="https://pf.kakao.com/_xoxmcxed"> | ||
<Button size="md">카카오톡 채널 바로가기</Button> | ||
</a> | ||
</div> | ||
<div className="close-icon" onClick={onAlertClose}> | ||
<CloseIcon /> | ||
</div> | ||
</article> | ||
</Modal> | ||
</> | ||
); | ||
} | ||
|
||
export default GuideModalButton; | ||
|
||
const buttonCss = css` | ||
max-width: 443px; | ||
margin: 0 auto; | ||
${mediaQuery('mobile')} { | ||
max-width: 266px; | ||
} | ||
`; | ||
|
||
const buttonWrapperCss = css` | ||
margin-top: 39px; | ||
margin-bottom: 24px; | ||
padding: 0 24px; | ||
width: 100%; | ||
button { | ||
height: 46px; | ||
width: 100%; | ||
${mediaQuery('mobile')} { | ||
height: 46px; | ||
} | ||
} | ||
`; | ||
|
||
const modalCss = (theme: Theme) => css` | ||
background-color: ${theme.colors.black400}; | ||
display: flex; | ||
flex-direction: column; | ||
justify-content: center; | ||
align-items: center; | ||
text-align: center; | ||
width: 320px; | ||
position: relative; | ||
h2 { | ||
${theme.typos.pretendard.body1}; | ||
color: ${theme.colors.white}; | ||
margin-bottom: 8px; | ||
margin-top: 47px; | ||
} | ||
p { | ||
${theme.typos.pretendard.body2}; | ||
color: ${theme.colors.gray100}; | ||
margin-bottom: 39px; | ||
max-width: 221px; | ||
} | ||
.close-icon { | ||
position: absolute; | ||
top: 16px; | ||
right: 16px; | ||
cursor: pointer; | ||
} | ||
`; |
This file contains 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,24 @@ | ||
import { Props, Svg } from '~/components/Icons/Svg'; | ||
|
||
function CloseIcon({ color = '#606475', ...props }: Props) { | ||
return ( | ||
<Svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
width="24" | ||
height="24" | ||
viewBox="0 0 24 24" | ||
fill="none" | ||
{...props} | ||
> | ||
<path | ||
d="M18 18L12 12M12 12L6 6M12 12L18 6M12 12L6 18" | ||
stroke={color} | ||
strokeWidth="2" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
/> | ||
</Svg> | ||
); | ||
} | ||
|
||
export default CloseIcon; |
This file contains 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,29 @@ | ||
import { type ComponentProps } from 'react'; | ||
import { AnimatePresence } from 'framer-motion'; | ||
|
||
import Portal from '~/components/Modal/Portal'; | ||
|
||
interface Props extends ComponentProps<typeof Portal> { | ||
/** | ||
* children의 렌더링 여부 | ||
*/ | ||
isShowing: boolean; | ||
/** | ||
* framer-motion AnimatePresence의 mode | ||
* @default 'wait' | ||
*/ | ||
mode?: ComponentProps<typeof AnimatePresence>['mode']; | ||
} | ||
|
||
/** | ||
* @description Portal을 AnimatePresence와 함께 사용합니다 | ||
*/ | ||
const AnimatePortal = ({ children, isShowing, mode = 'wait' }: Props) => { | ||
return ( | ||
<Portal> | ||
<AnimatePresence mode={mode}>{isShowing && children}</AnimatePresence> | ||
</Portal> | ||
); | ||
}; | ||
|
||
export default AnimatePortal; |
This file contains 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,121 @@ | ||
import { type ComponentProps, type MouseEvent, type PropsWithChildren } from 'react'; | ||
import { css } from '@emotion/react'; | ||
import { m, Variants } from 'framer-motion'; | ||
|
||
import AnimatePortal from '~/components/Modal/AnimatePortal'; | ||
import useScrollLock from '~/components/Modal/useScrollLock'; | ||
|
||
interface Props { | ||
/** | ||
* 외부영역 클릭시 호출될 함수 | ||
*/ | ||
onClickOutside?: VoidFunction; | ||
} | ||
|
||
/** | ||
* | ||
* @param isShowing 열림/닫힘 상태 | ||
* @param mode AnimatePresence mode | ||
* @param onClickOutside 외부영역 클릭시 호출될 함수 | ||
*/ | ||
const Modal = ({ | ||
isShowing, | ||
mode, | ||
onClickOutside, | ||
children, | ||
}: PropsWithChildren<Props> & ComponentProps<typeof AnimatePortal>) => { | ||
useScrollLock({ lock: isShowing }); | ||
|
||
return ( | ||
<AnimatePortal isShowing={isShowing} mode={mode}> | ||
<div css={dialogPositionCss}> | ||
<ModalBlur onClickOutside={onClickOutside} /> | ||
<m.div | ||
variants={defaultFadeInVariants} | ||
initial="initial" | ||
animate="animate" | ||
exit="exit" | ||
css={containerCss} | ||
> | ||
{children} | ||
</m.div> | ||
</div> | ||
</AnimatePortal> | ||
); | ||
}; | ||
const defaultEasing = [0.6, -0.05, 0.01, 0.99]; | ||
|
||
const defaultFadeInVariants: Variants = { | ||
initial: { | ||
opacity: 0, | ||
transition: { duration: 0.3, ease: defaultEasing }, | ||
willChange: 'opacity', | ||
}, | ||
animate: { | ||
opacity: 1, | ||
transition: { duration: 0.3, ease: defaultEasing }, | ||
willChange: 'opacity', | ||
}, | ||
exit: { | ||
opacity: 0, | ||
transition: { duration: 0.3, ease: defaultEasing }, | ||
willChange: 'opacity', | ||
}, | ||
}; | ||
|
||
const dialogPositionCss = css` | ||
position: absolute; | ||
top: 0; | ||
left: 0; | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
width: 100%; | ||
height: 100vh; | ||
`; | ||
|
||
const ModalBlur = ({ onClickOutside }: Pick<Props, 'onClickOutside'>) => { | ||
const onClickOutsideDefault = (e: MouseEvent) => { | ||
if (e.target !== e.currentTarget) return; | ||
if (onClickOutside) onClickOutside(); | ||
}; | ||
|
||
return ( | ||
<m.div | ||
onClick={onClickOutsideDefault} | ||
css={blurCss} | ||
variants={defaultFadeInVariants} | ||
initial="initial" | ||
animate="animate" | ||
exit="exit" | ||
style={{ | ||
cursor: onClickOutside ? 'pointer' : 'default', | ||
}} | ||
/> | ||
); | ||
}; | ||
|
||
const blurCss = css` | ||
position: fixed; | ||
z-index: 9999; | ||
top: 0; | ||
left: 0; | ||
width: 100%; | ||
height: 100%; | ||
background: var(--DIM-70, rgba(19, 28, 40, 0.7)); | ||
`; | ||
|
||
const containerCss = css` | ||
position: fixed; | ||
z-index: 10000; | ||
display: flex; | ||
flex-direction: column; | ||
border-radius: 16px; | ||
`; | ||
|
||
export default Modal; |
This file contains 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,21 @@ | ||
import { type PropsWithChildren, useEffect, useState } from 'react'; | ||
import { createPortal } from 'react-dom'; | ||
|
||
/** | ||
* @description react.createPortal을 이용해 document.body에 children을 렌더링합니다 | ||
*/ | ||
const Portal = ({ children }: PropsWithChildren) => { | ||
const [container, setContainer] = useState<Element | null>(null); | ||
|
||
useEffect(() => { | ||
if (document) { | ||
setContainer(document.body); | ||
} | ||
}, []); | ||
|
||
if (!container) return null; | ||
|
||
return createPortal(children, container); | ||
}; | ||
|
||
export default Portal; |
This file contains 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,14 @@ | ||
import { useEffect } from 'react'; | ||
|
||
const useScrollLock = ({ lock }: { lock: boolean }): void => { | ||
useEffect((): (() => void) => { | ||
const originalStyle: string = window.getComputedStyle(document.body).overflow; | ||
if (lock) { | ||
document.body.style.overflow = 'hidden'; | ||
} | ||
|
||
return () => (document.body.style.overflow = originalStyle); | ||
}, [lock]); | ||
}; | ||
|
||
export default useScrollLock; |
Oops, something went wrong.