-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/#550 Confirm Modal 구현 #553
Changes from 1 commit
d3aee9e
3a8246e
31ecdd0
580124d
3273fa7
c25781d
d526aa9
e382050
a526142
1179f35
dbc1c32
2af5e0b
6441a12
3f60df5
a225ac0
aab67ba
67963b6
b1ad8a7
ac5d64c
f27bd28
a6eb162
a2fb196
b94ea94
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,95 +1,111 @@ | ||||||||||||
import { createContext, useState } from 'react'; | ||||||||||||
import { createPortal } from 'react-dom'; | ||||||||||||
import { useEffect } from 'react'; | ||||||||||||
import { Flex } from 'shook-layout'; | ||||||||||||
import styled from 'styled-components'; | ||||||||||||
import styled, { css } from 'styled-components'; | ||||||||||||
import Spacing from '../Spacing'; | ||||||||||||
import type { ReactNode } from 'react'; | ||||||||||||
|
||||||||||||
const ConfirmContext = createContext<null | { | ||||||||||||
getConfirmation: (modalState: ModalState) => Promise<boolean>; | ||||||||||||
}>(null); | ||||||||||||
|
||||||||||||
const Backdrop = styled.div``; | ||||||||||||
const ContainerFlex = styled(Flex)``; | ||||||||||||
const ButtonFlex = styled(Flex)``; | ||||||||||||
const Title = styled.header``; | ||||||||||||
const Content = styled.div``; | ||||||||||||
const CancelButton = styled.button``; | ||||||||||||
const ConfirmButton = styled.button``; | ||||||||||||
|
||||||||||||
interface ModalState { | ||||||||||||
interface ConfirmModalProps { | ||||||||||||
title: string; | ||||||||||||
content: ReactNode; | ||||||||||||
cancelName?: string; | ||||||||||||
confirmName?: string; | ||||||||||||
cancelName: string; | ||||||||||||
confirmName: string; | ||||||||||||
onCancel: () => void; | ||||||||||||
onConfirm: () => void; | ||||||||||||
onKeyDown: (event: KeyboardEvent) => void; | ||||||||||||
} | ||||||||||||
|
||||||||||||
type Resolver<T> = (value: T) => void; | ||||||||||||
const ConfirmModal = ({ | ||||||||||||
title, | ||||||||||||
content, | ||||||||||||
cancelName, | ||||||||||||
confirmName, | ||||||||||||
onCancel, | ||||||||||||
onConfirm, | ||||||||||||
onKeyDown, | ||||||||||||
}: ConfirmModalProps) => { | ||||||||||||
useEffect(() => { | ||||||||||||
document.addEventListener('keydown', onKeyDown); | ||||||||||||
document.body.style.overflow = 'hidden'; | ||||||||||||
|
||||||||||||
const createPromise = <T,>() => { | ||||||||||||
let resolver: Resolver<T> | null = null; | ||||||||||||
const promise = new Promise<T>((resolve) => { | ||||||||||||
resolver = resolve; | ||||||||||||
}); | ||||||||||||
return () => { | ||||||||||||
document.removeEventListener('keydown', onKeyDown); | ||||||||||||
document.body.style.overflow = 'auto'; | ||||||||||||
}; | ||||||||||||
}, []); | ||||||||||||
|
||||||||||||
return { promise, resolver }; | ||||||||||||
return ( | ||||||||||||
<> | ||||||||||||
<Backdrop role="dialog" aria-modal="true" /> | ||||||||||||
<Container> | ||||||||||||
<Title>{title}</Title> | ||||||||||||
<Spacing direction="vertical" size={10} /> | ||||||||||||
<Content>{content}</Content> | ||||||||||||
<Spacing direction="vertical" size={10} /> | ||||||||||||
<ButtonFlex $gap={16}> | ||||||||||||
<CancelButton onClick={onCancel}>{cancelName}</CancelButton> | ||||||||||||
<ConfirmButton onClick={onConfirm}>{confirmName}</ConfirmButton> | ||||||||||||
</ButtonFlex> | ||||||||||||
</Container> | ||||||||||||
</> | ||||||||||||
); | ||||||||||||
}; | ||||||||||||
|
||||||||||||
const ConfirmProvider = (children: ReactNode) => { | ||||||||||||
const [isOpen, setIsOpen] = useState(false); | ||||||||||||
const [resolve, setResolve] = useState<((value: boolean) => void) | null>(null); | ||||||||||||
const [modalState, setModalState] = useState<ModalState>({ | ||||||||||||
title: '', | ||||||||||||
content: '', | ||||||||||||
cancelName: '닫기', | ||||||||||||
confirmName: '확인', | ||||||||||||
}); | ||||||||||||
const { title, content, cancelName, confirmName } = modalState; | ||||||||||||
export default ConfirmModal; | ||||||||||||
|
||||||||||||
const getConfirmation = ({ | ||||||||||||
cancelName = '닫기', | ||||||||||||
confirmName = '확인', | ||||||||||||
...restState | ||||||||||||
}: ModalState): Promise<boolean> => { | ||||||||||||
const { promise, resolver } = createPromise<boolean>(); | ||||||||||||
setResolve(resolver); | ||||||||||||
setModalState({ | ||||||||||||
cancelName, | ||||||||||||
confirmName, | ||||||||||||
...restState, | ||||||||||||
}); | ||||||||||||
setIsOpen(true); | ||||||||||||
const Backdrop = styled.div` | ||||||||||||
position: fixed; | ||||||||||||
top: 0; | ||||||||||||
right: 0; | ||||||||||||
bottom: 0; | ||||||||||||
left: 0; | ||||||||||||
|
||||||||||||
return promise; | ||||||||||||
}; | ||||||||||||
width: 100%; | ||||||||||||
height: 100%; | ||||||||||||
margin: 0; | ||||||||||||
padding: 0; | ||||||||||||
|
||||||||||||
const onClick = (status: boolean) => { | ||||||||||||
setIsOpen(false); | ||||||||||||
background-color: rgba(0, 0, 0, 0.7); | ||||||||||||
`; | ||||||||||||
|
||||||||||||
if (resolve) { | ||||||||||||
resolve(status); | ||||||||||||
} | ||||||||||||
}; | ||||||||||||
const Container = styled.section` | ||||||||||||
position: fixed; | ||||||||||||
top: 50%; | ||||||||||||
left: 50%; | ||||||||||||
transform: translate(-50%, -50%); | ||||||||||||
|
||||||||||||
return ( | ||||||||||||
<ConfirmContext.Provider value={{ getConfirmation }}> | ||||||||||||
{children} | ||||||||||||
{isOpen && | ||||||||||||
createPortal( | ||||||||||||
<> | ||||||||||||
<Backdrop /> | ||||||||||||
<ContainerFlex> | ||||||||||||
<Title>{title}</Title> | ||||||||||||
<Content>{content}</Content> | ||||||||||||
<ButtonFlex> | ||||||||||||
<CancelButton onClick={() => onClick(false)}>{cancelName}</CancelButton> | ||||||||||||
<ConfirmButton onClick={() => onClick(true)}>{confirmName}</ConfirmButton> | ||||||||||||
</ButtonFlex> | ||||||||||||
</ContainerFlex> | ||||||||||||
</>, | ||||||||||||
document.body | ||||||||||||
)} | ||||||||||||
</ConfirmContext.Provider> | ||||||||||||
); | ||||||||||||
}; | ||||||||||||
min-width: 300px; | ||||||||||||
margin: 0 auto; | ||||||||||||
padding: 24px; | ||||||||||||
|
||||||||||||
color: #ffffff; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💬 theme .color.white 레츠고우~ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이것도 함께 이야기 나누면 좋을 것 같아용. 항상 color 값 줄때마다 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 공감합니다. 테마를 동적으로 바꾸는 일도 없어서 더욱더 그렇네용 저도 현 구조에서 theme의 장점이 있을지 한번 더 생각해볼게요! |
||||||||||||
|
||||||||||||
background-color: #17171c; | ||||||||||||
border: none; | ||||||||||||
border-radius: 16px; | ||||||||||||
`; | ||||||||||||
const ButtonFlex = styled(Flex)` | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 개행 부분이랑 style lint 수정완료 했어요 |
||||||||||||
width: 100%; | ||||||||||||
`; | ||||||||||||
const Title = styled.header` | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
text-align: left; | ||||||||||||
font-size: 18px; | ||||||||||||
`; | ||||||||||||
const Content = styled.div``; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
|
||||||||||||
const buttonStyle = css` | ||||||||||||
flex: 1; | ||||||||||||
height: 36px; | ||||||||||||
color: ${({ theme: { color } }) => color.white}; | ||||||||||||
width: 100%; | ||||||||||||
|
||||||||||||
border-radius: 10px; | ||||||||||||
`; | ||||||||||||
|
||||||||||||
export default ConfirmProvider; | ||||||||||||
const CancelButton = styled.button` | ||||||||||||
background-color: ${({ theme: { color } }) => color.secondary}; | ||||||||||||
${buttonStyle} | ||||||||||||
`; | ||||||||||||
const ConfirmButton = styled.button` | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
background-color: ${({ theme: { color } }) => color.primary}; | ||||||||||||
${buttonStyle} | ||||||||||||
`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
inset
으로 1줄로 줄여볼까용~?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
좋아요!