-
Notifications
You must be signed in to change notification settings - Fork 1
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: Dialog 컴포넌트 구현 #19
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import DialogProvider from '../DialogProvider'; | ||
import ThemeProvider from '../ThemeProvider'; | ||
|
||
interface BooltiUIProviderProps { | ||
children: React.ReactNode; | ||
} | ||
|
||
const BooltiUIProvider = ({ children }: BooltiUIProviderProps) => { | ||
return ( | ||
<ThemeProvider> | ||
<DialogProvider>{children}</DialogProvider> | ||
</ThemeProvider> | ||
); | ||
}; | ||
|
||
export default BooltiUIProvider; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
const CloseIcon = () => ( | ||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
<g clipPath="url(#clip0_248_3217)"> | ||
<path | ||
d="M18 6L6 18" | ||
stroke="#6D747C" | ||
strokeWidth="2" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
/> | ||
<path | ||
d="M6 6L18 18" | ||
stroke="#6D747C" | ||
strokeWidth="2" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
/> | ||
</g> | ||
<defs> | ||
<clipPath id="clip0_248_3217"> | ||
<rect width="24" height="24" fill="white" /> | ||
</clipPath> | ||
</defs> | ||
</svg> | ||
); | ||
|
||
export default CloseIcon; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import styled from '@emotion/styled'; | ||
|
||
const DIALOG_WIDTH = '450px'; | ||
|
||
const DimmedArea = styled.div` | ||
position: fixed; | ||
inset: 0; | ||
background-color: ${({ theme }) => theme.palette.dim.dialog}; | ||
display: flex; | ||
justify-content: center; | ||
align-items: center; | ||
`; | ||
|
||
const Dialog = styled.div` | ||
width: ${DIALOG_WIDTH}; | ||
border-radius: 8px; | ||
background-color: ${({ theme }) => theme.palette.grey.w}; | ||
`; | ||
|
||
const DialogHeader = styled.div` | ||
padding: 16px 32px; | ||
border-bottom: 1px solid ${({ theme }) => theme.palette.grey.g30}; | ||
position: relative; | ||
`; | ||
|
||
const DialogTitle = styled.h2` | ||
${({ theme }) => theme.typo.sh2}; | ||
color: ${({ theme }) => theme.palette.grey.g70}; | ||
`; | ||
|
||
const DialogCloseButton = styled.button` | ||
position: absolute; | ||
top: 17px; | ||
right: 32px; | ||
width: 24px; | ||
height: 24px; | ||
display: inline-flex; | ||
justify-content: center; | ||
align-items: center; | ||
cursor: pointer; | ||
`; | ||
|
||
const DialogContent = styled.div` | ||
padding: 32px; | ||
`; | ||
|
||
export default { | ||
DimmedArea, | ||
Dialog, | ||
DialogHeader, | ||
DialogTitle, | ||
DialogCloseButton, | ||
DialogContent, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import Portal from '../Portal'; | ||
import CloseIcon from './CloseIcon'; | ||
import Styled from './Dialog.styles'; | ||
|
||
interface DialogProps { | ||
open: boolean; | ||
children: React.ReactNode; | ||
title?: string; | ||
onClose?: () => void; | ||
} | ||
|
||
const Dialog = ({ open, children, title, onClose }: DialogProps) => { | ||
if (!open) return null; | ||
|
||
return ( | ||
<Portal> | ||
<Styled.DimmedArea> | ||
<Styled.Dialog> | ||
<Styled.DialogHeader> | ||
<Styled.DialogTitle>{title}</Styled.DialogTitle> | ||
<Styled.DialogCloseButton aria-label="닫기" onClick={onClose}> | ||
<CloseIcon /> | ||
</Styled.DialogCloseButton> | ||
</Styled.DialogHeader> | ||
<Styled.DialogContent>{children}</Styled.DialogContent> | ||
</Styled.Dialog> | ||
</Styled.DimmedArea> | ||
</Portal> | ||
); | ||
}; | ||
|
||
export default Dialog; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { useState } from 'react'; | ||
import dialogContext, { IDialog } from '../../contexts/dialogContext'; | ||
import Dialog from '../Dialog'; | ||
|
||
interface DialogProviderProps { | ||
children: React.ReactNode; | ||
} | ||
|
||
const DialogProvider = ({ children }: DialogProviderProps) => { | ||
const [dialogList, setDialogList] = useState<IDialog[]>([]); | ||
|
||
return ( | ||
<dialogContext.Provider value={{ dialogList, setDialogList }}> | ||
{children} | ||
{dialogList.map((dialog) => ( | ||
<Dialog | ||
key={dialog.id} | ||
open={!!dialogList.find(({ id }) => dialog.id === id)} | ||
title={dialog.title} | ||
onClose={() => { | ||
dialog.onClose?.(); | ||
setDialogList((prev) => prev.filter(({ id }) => dialog.id !== id)); | ||
}} | ||
> | ||
{dialog.content} | ||
</Dialog> | ||
))} | ||
</dialogContext.Provider> | ||
); | ||
}; | ||
|
||
export default DialogProvider; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import React from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
|
||
interface PortalProps { | ||
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. id같은 것을 추가로 받아서 이미 만들어졌으면 사용하고 없으면 새로 생성하는 방식으로 써도 좋겠다! 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. 포탈에 id를 달아야 하는 이유가 있을까? id로 뭔가 관리를 해야하는 경우가 생길까? 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. 포탈 쓰는 이유가 계층 내부에수 선택자 충돌에 대비한거니까, 모달, 다이얼로그 같은 컴포넌트는 각각의 포탈을 갖는게 맞다고 생각했어! (사실 크게 중요치는 않을 듯) 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. 내가 Portal에 대해서 잘 모르고 하는 이야기일 수도 있지만, Portal을 생성하는데 많은 비용이 들지는 않을 거라고 생각해서 요건 이슈가 생기면 달아줘도 좋을 것 같다고 생각해! |
||
children: React.ReactNode; | ||
} | ||
|
||
const Portal = ({ children }: PortalProps) => ReactDOM.createPortal(children, document.body); | ||
|
||
export default Portal; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
import ThemeProvider from './ThemeProvider'; | ||
import BooltiUIProvider from './BooltiUIProvider'; | ||
import Button from './Button'; | ||
import TextButton from './TextButton'; | ||
import Badge from './Badge'; | ||
import Dialog from './Dialog'; | ||
|
||
export { ThemeProvider, Button, TextButton, Badge }; | ||
export { BooltiUIProvider, Button, TextButton, Badge, Dialog }; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { createContext } from 'react'; | ||
|
||
export interface IDialog { | ||
id: string; | ||
content: React.ReactNode; | ||
title?: string; | ||
onClose?: () => void; | ||
} | ||
|
||
interface DialogContext { | ||
dialogList: IDialog[]; | ||
setDialogList: React.Dispatch<React.SetStateAction<IDialog[]>>; | ||
} | ||
|
||
const dialogContext = createContext<DialogContext | null>(null); | ||
|
||
export default dialogContext; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
import useToast from './useToast'; | ||
import useDialog from './useDialog'; | ||
|
||
export { useToast }; | ||
export { useToast, useDialog }; |
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.
아이콘은 나중에 모아서 svgr 넣어서 파일로 만들거나.. 아이콘 폴더 따로 만들어야겠다!