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 1 commit
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
18 changes: 18 additions & 0 deletions apps/admin/src/pages/HomePage/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import Header from '../../components/Header/Header';
import Layout from '../../components/Layout/Layout';
import { PATH } from '../../constants/routes';
import Styled from './HomePage.styles';
import { Dialog, useDialogState } from '@boolti/ui';

const HomePage = () => {
const toast = useToast();
const { open, openDialog, closeDialog } = useDialogState();
alstn2468 marked this conversation as resolved.
Show resolved Hide resolved

return (
<Layout
Expand Down Expand Up @@ -53,6 +55,22 @@ const HomePage = () => {
정보 제공 토스트 띄우기
</button>
</div>
<button
onClick={() => {
openDialog();
}}
>
Open Dialog
</button>
<Dialog
title="Dialog Title"
open={open}
onClose={() => {
closeDialog();
}}
>
<p>Dialog Content</p>
</Dialog>
</Layout>
);
};
Expand Down
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: rgba(86, 86, 86, 0.25);
Copy link
Member

Choose a reason for hiding this comment

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

하드코딩하는 색상은 최대한 피하는게 좋을 듯!? dim.dialog 같이 팔레트에 넣어서 써보는건 어떨까 싶어!
한번 설정하면 잘 바뀌지는 않겠지만, 나중에 찾기 어려울수도 있으니..!

Copy link
Member Author

Choose a reason for hiding this comment

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

6eaedd5 에서 반영 완료

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;
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;
3 changes: 2 additions & 1 deletion packages/ui/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ import ThemeProvider from './ThemeProvider';
import Button from './Button';
import TextButton from './TextButton';
import Badge from './Badge';
import Dialog from './Dialog';

export { ThemeProvider, Button, TextButton, Badge };
export { ThemeProvider, Button, TextButton, Badge, Dialog };
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 useDialogState from './useDialogState';

export { useToast };
export { useToast, useDialogState };
25 changes: 25 additions & 0 deletions packages/ui/src/hooks/useDialogState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useCallback, useState } from 'react';

interface UseDialogParams {
defaultOpen?: boolean;
}

const useDialogState = (params?: UseDialogParams) => {
alstn2468 marked this conversation as resolved.
Show resolved Hide resolved
const [open, setOpen] = useState<boolean>(params?.defaultOpen ?? false);

const openDialog = useCallback(() => {
setOpen(true);
}, []);

const closeDialog = useCallback(() => {
setOpen(false);
}, []);

return {
open,
openDialog,
closeDialog,
};
};

export default useDialogState;
Loading