Skip to content
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

Merged
merged 4 commits into from
Jan 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions apps/admin/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { QueryClientProvider } from '@boolti/api';
import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom';
import { ThemeProvider } from '@boolti/ui';
import { BooltiUIProvider } from '@boolti/ui';
import LoginPage from './pages/Login/LoginPage';
import SignUpCompletePage from './pages/SignUpComplete/SignUpCompletePage';
import 'the-new-css-reset/css/reset.css';
Expand Down Expand Up @@ -33,9 +33,9 @@ const App = () => {
// console.log(data?.hello)
return (
<QueryClientProvider>
<ThemeProvider>
<BooltiUIProvider>
<RouterProvider router={router} />
</ThemeProvider>
</BooltiUIProvider>
</QueryClientProvider>
);
};
Expand Down
61 changes: 60 additions & 1 deletion apps/admin/src/pages/HomePage/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useToast } from '@boolti/ui';
import { useToast, useDialog } from '@boolti/ui';
import Header from '../../components/Header/Header';
import Layout from '../../components/Layout/Layout';
import { PATH } from '../../constants/routes';
Expand All @@ -7,6 +7,10 @@ import Styled from './HomePage.styles';
const HomePage = () => {
const toast = useToast();

const dialog1 = useDialog();
const dialog2 = useDialog();
const dialog3 = useDialog();

return (
<Layout
header={
Expand Down Expand Up @@ -53,6 +57,61 @@ const HomePage = () => {
정보 제공 토스트 띄우기
</button>
</div>
<button
onClick={() => {
dialog1.open({
title: '다이얼로그 1 ',
content: (
<button
onClick={() => {
dialog3.open({
title: '다이얼로그 안의 다이얼로그',
content: (
<p>
다이얼로그 내용
<br />
다이얼로그 ID : {dialog3.id}
</p>
),
onClose: () => {
console.log('다이얼로그 안의 다이얼로그 닫힘');
},
});
}}
>
다이얼로그 안의 다이얼로그 열기
<br />
다이얼로그 ID : {dialog1.id}
</button>
),
onClose: () => {
console.log('다이얼로그 1 닫힘');
},
});
}}
>
Open Dialog 1
</button>

<button
onClick={() => {
dialog2.open({
title: '다이얼로그 2',
content: (
<p>
다이얼로그 내용
<br />
다이얼로그 ID : {dialog2.id}
</p>
),
onClose: () => {
console.log('다이얼로그 2 닫힘');
},
});
}}
>
Open Dialog 2
</button>
</Layout>
);
};
Expand Down
6 changes: 3 additions & 3 deletions apps/preview/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Badge, Button, TextButton, ThemeProvider } from '@boolti/ui';
import { Badge, Button, TextButton, BooltiUIProvider } from '@boolti/ui';

const Icon = () => (
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
Expand All @@ -13,7 +13,7 @@ const Icon = () => (

const App = () => {
return (
<ThemeProvider>
<BooltiUIProvider>
<h1>
Preview
<div
Expand Down Expand Up @@ -200,7 +200,7 @@ const App = () => {
<Badge colorTheme="grey">공연 종료</Badge>
</div>
</h1>
</ThemeProvider>
</BooltiUIProvider>
);
};

Expand Down
1 change: 1 addition & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"dependencies": {
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
"nanoid": "^5.0.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1"
Expand Down
16 changes: 16 additions & 0 deletions packages/ui/src/components/BooltiUIProvider/index.tsx
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;
27 changes: 27 additions & 0 deletions packages/ui/src/components/Dialog/CloseIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const CloseIcon = () => (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아이콘은 나중에 모아서 svgr 넣어서 파일로 만들거나.. 아이콘 폴더 따로 만들어야겠다!

<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;
54 changes: 54 additions & 0 deletions packages/ui/src/components/Dialog/Dialog.styles.ts
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,
};
32 changes: 32 additions & 0 deletions packages/ui/src/components/Dialog/index.tsx
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;
32 changes: 32 additions & 0 deletions packages/ui/src/components/DialogProvider/index.tsx
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;
10 changes: 10 additions & 0 deletions packages/ui/src/components/Portal/index.tsx
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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

id같은 것을 추가로 받아서 이미 만들어졌으면 사용하고 없으면 새로 생성하는 방식으로 써도 좋겠다!
모달, 다이얼로그 등등.. 혹시나 포탈 쓰는 것들이 많아지면 필요할 것 같아!

Copy link
Member Author

@Puterism Puterism Jan 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

포탈에 id를 달아야 하는 이유가 있을까? id로 뭔가 관리를 해야하는 경우가 생길까?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

포탈 쓰는 이유가 계층 내부에수 선택자 충돌에 대비한거니까, 모달, 다이얼로그 같은 컴포넌트는 각각의 포탈을 갖는게 맞다고 생각했어! (사실 크게 중요치는 않을 듯)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

내가 Portal에 대해서 잘 모르고 하는 이야기일 수도 있지만, Portal을 생성하는데 많은 비용이 들지는 않을 거라고 생각해서 요건 이슈가 생기면 달아줘도 좋을 것 같다고 생각해!
현재 Dialog 컴포넌트도 각각 Portal을 갖도록 구현하기도 했으니까!

children: React.ReactNode;
}

const Portal = ({ children }: PortalProps) => ReactDOM.createPortal(children, document.body);

export default Portal;
5 changes: 3 additions & 2 deletions packages/ui/src/components/index.ts
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 };
17 changes: 17 additions & 0 deletions packages/ui/src/contexts/dialogContext.tsx
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;
3 changes: 2 additions & 1 deletion packages/ui/src/hooks/index.ts
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 };
Loading
Loading