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

[FE] feat: Alert Modal과 Error Alert Modal 컴포넌트 구현 및 Button 컴포넌트 확장 #165

Merged
merged 15 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
bf001e4
refactor: 공통 버튼 컴포넌트가 children과 style을 받도록 수정
chysis Jul 31, 2024
ff6c62f
chore: error alert modal에서 사용할 primary 색상의 경고 삼각형 추가
chysis Jul 31, 2024
271fc9e
feat: ErrorAlertModal 컴포넌트 구현
chysis Jul 31, 2024
2501775
feat: AlertModal 컴포넌트 구현
chysis Jul 31, 2024
c30fb62
chore: 변경된 Button 컴포넌트의 구조에 맞게 코드 수정
chysis Jul 31, 2024
281d650
fix: AlertModal이 Esc 또는 background의 클릭으로 닫히지 않도록 수정
chysis Jul 31, 2024
d19c304
chore: AlertModal과 ErrorAlertModal export
chysis Jul 31, 2024
658cd36
Merge branch 'develop' of https://github.com/woowacourse-teams/2024-r…
chysis Aug 1, 2024
949bad9
refactor: Button 컴포넌트가 type을 별도로 받도록 수정 및 기존의 buttonType을 styleType으로 변경
chysis Aug 1, 2024
c20d25d
refactor: background 클릭 또는 Esc 키를 통해 모달을 닫는 것을 분리
chysis Aug 1, 2024
31c9f16
refactor: AlertModal의 모달 닫는 정책을 props로 선택 가능하도록 수정
chysis Aug 1, 2024
2374c37
refactor: ErrorAlertModal을 AlertModal을 사용해 구현
chysis Aug 1, 2024
c8d3867
refactor: 닫기 버튼 이외의 방법으로 모달을 닫을 수 있는지 여부와 handler를 props로 전달받도록 변경
chysis Aug 1, 2024
aa03b2a
Merge branch 'develop' of https://github.com/woowacourse-teams/2024-r…
chysis Aug 1, 2024
1f8cbc7
refactor: 비어있는 스타일 컴포넌트 제거
chysis Aug 1, 2024
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
3 changes: 3 additions & 0 deletions frontend/src/assets/alertTrianglePrimary.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 5 additions & 9 deletions frontend/src/components/common/Button/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import { ButtonType } from '@/types';
import { ButtonStyleType, EssentialPropsWithChildren } from '@/types';

import * as S from './styles';

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
buttonType: ButtonType;
text: string;
image?: string;
imageDescription?: string;
styleType: ButtonStyleType;
}

const Button = ({ buttonType, text, image, imageDescription, onClick }: ButtonProps) => {
const Button = ({ styleType: buttonType, type, style, children, onClick }: EssentialPropsWithChildren<ButtonProps>) => {
return (
<S.Button buttonType={buttonType} onClick={onClick}>
{image && <S.Image src={image} alt={imageDescription || ''} />}
{text}
<S.Button buttonType={buttonType} type={type} onClick={onClick} $style={style}>
{children}
</S.Button>
);
};
Comment on lines +9 to 15
Copy link
Contributor

Choose a reason for hiding this comment

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

확장성면에서 children 사용한 거 너무 좋은 것 같아요!!

Expand Down
14 changes: 9 additions & 5 deletions frontend/src/components/common/Button/styles.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { css, Theme } from '@emotion/react';
import styled from '@emotion/styled';

import { ButtonType } from '@/types';
import { ButtonStyleType } from '@/types';

const primaryStyle = (theme: Theme) => css`
color: ${theme.colors.white};
Expand Down Expand Up @@ -33,7 +33,7 @@ const disabledStyle = (theme: Theme) => css`
}
`;

const getButtonStyle = (buttonType: ButtonType, theme: Theme) => {
const getButtonStyle = (buttonType: ButtonStyleType, theme: Theme) => {
switch (buttonType) {
case 'primary':
return primaryStyle(theme);
Expand All @@ -46,18 +46,22 @@ const getButtonStyle = (buttonType: ButtonType, theme: Theme) => {
}
};

export const Button = styled.button<{ buttonType: ButtonType }>`
interface ButtonProps {
buttonType: ButtonStyleType;
$style?: React.CSSProperties;
}

export const Button = styled.button<ButtonProps>`
display: flex;
align-items: center;
justify-content: center;

width: 10rem;
height: 4rem;
padding: 1rem 2rem;

border: 0.1rem solid ${({ theme }) => theme.colors.primary};
border-radius: 0.8rem;

${({ $style }) => $style && { ...$style }};
${({ buttonType, theme }) => getButtonStyle(buttonType, theme)};
`;

Expand Down
45 changes: 45 additions & 0 deletions frontend/src/components/common/modals/AlertModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ButtonStyleType, EssentialPropsWithChildren } from '@/types';

import Button from '../../Button';
import ModalBackground from '../ModalBackground';
import ModalPortal from '../ModalPortal';

import * as S from './styles';

interface CloseButton {
type: ButtonStyleType;
handleClick: () => void;
content: React.ReactNode;
}

interface AlertModalProps {
closeButton: CloseButton;
isClosableOnBackground: boolean;
handleClose: (() => void) | null;
}

const AlertModal = ({
closeButton,
isClosableOnBackground,
handleClose,
children,
}: EssentialPropsWithChildren<AlertModalProps>) => {
return (
<ModalPortal>
<ModalBackground closeModal={isClosableOnBackground ? handleClose : null}>
<S.AlertModalContainer>
<S.Contents>{children}</S.Contents>
<Button
styleType={closeButton.type}
onClick={closeButton.handleClick}
style={{ width: '100%', minWidth: '30rem', height: '4rem' }}
>
{closeButton.content}
</Button>
</S.AlertModalContainer>
</ModalBackground>
</ModalPortal>
);
};

export default AlertModal;
24 changes: 24 additions & 0 deletions frontend/src/components/common/modals/AlertModal/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import styled from '@emotion/styled';

export const AlertModalContainer = styled.div`
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);

display: flex;
flex-direction: column;
gap: 3.2rem;
align-items: center;
justify-content: space-between;

max-height: 80vh;
padding: 3.2rem;

background-color: ${({ theme }) => theme.colors.white};
border-radius: ${({ theme }) => theme.borderRadius.basic};
`;

export const Contents = styled.div`
display: flex;
`;
6 changes: 3 additions & 3 deletions frontend/src/components/common/modals/ConfirmModal/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';

import { ButtonType } from '@/types';
import { ButtonStyleType } from '@/types';

import Button from '../../Button';
import ModalBackground from '../ModalBackground';
Expand All @@ -9,7 +9,7 @@ import ModalPortal from '../ModalPortal';
import * as S from './styles';

interface ConfirmModalButton {
type: ButtonType;
type: ButtonStyleType;
text: string;
handleClick: (e: React.MouseEvent) => void;
}
Expand All @@ -34,7 +34,7 @@ const ConfirmModal: React.FC<React.PropsWithChildren<ConfirmModalProps>> = ({
<S.Contents>{children}</S.Contents>
<S.ButtonContainer>
{buttonList.map(({ type, text, handleClick }) => (
<Button key={text} buttonType={type} text={text} onClick={handleClick} />
<Button key={text} styleType={type} text={text} onClick={handleClick} />
))}
</S.ButtonContainer>
</S.ConfirmModalInner>
Expand Down
31 changes: 31 additions & 0 deletions frontend/src/components/common/modals/ErrorAlertModal/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import AlertTrianglePrimaryIcon from '@/assets/alertTrianglePrimary.svg';
import { ButtonStyleType } from '@/types';

import AlertModal from '../AlertModal';

import * as S from './styles';

interface CloseButton {
type: ButtonStyleType;
handleClick: () => void;
content: React.ReactNode;
}

interface ErrorAlertModalProps {
closeButton: CloseButton;
errorText: string;
handleClose: () => void;
}

const ErrorAlertModal = ({ closeButton, errorText, handleClose }: ErrorAlertModalProps) => {
return (
<AlertModal closeButton={closeButton} isClosableOnBackground={true} handleClose={handleClose}>
<S.Contents>
<S.AlertTriangle src={AlertTrianglePrimaryIcon} alt="경고 마크" />
<p>{errorText}</p>
</S.Contents>
</AlertModal>
);
};

export default ErrorAlertModal;
12 changes: 12 additions & 0 deletions frontend/src/components/common/modals/ErrorAlertModal/styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import styled from '@emotion/styled';

export const Contents = styled.div`
display: flex;
flex-direction: column;
align-items: center;
`;

export const AlertTriangle = styled.img`
width: 6rem;
height: 6rem;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import { useModalClose } from '@/hooks';
import * as S from './styles';

interface ModalBackgroundProps {
closeModal: () => void;
closeModal: (() => void) | null;
}

const ModalBackground: React.FC<PropsWithChildren<ModalBackgroundProps>> = ({ children, closeModal }) => {
const modalBackgroundRef = useRef<HTMLDivElement>(null);
useModalClose(closeModal, modalBackgroundRef);
useModalClose({ closeModal, modalBackgroundRef });

return <S.ModalBackground ref={modalBackgroundRef}>{children}</S.ModalBackground>;
};
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/components/common/modals/index.tsx
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export { default as SideModal } from './SideModal';
export { default as ConfirmModal } from './ConfirmModal';
export { default as AlertModal } from './AlertModal';
export { default as ErrorAlertModal } from './ErrorAlertModal';
23 changes: 17 additions & 6 deletions frontend/src/components/error/ErrorSection/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import AlertTriangle from '@/assets/alertTriangle.svg';
import HomeIcon from '@/assets/home.svg';
import ReloadIcon from '@/assets/reload.svg';
import { Button } from '@/components';
import { ButtonType } from '@/types/styles';
import { ButtonStyleType } from '@/types/styles';

import * as S from './styles';

Expand All @@ -12,21 +12,31 @@ interface ErrorSectionProps {
handleGoHome: () => void;
}

interface ErrorSectionButton {
buttonType: ButtonStyleType;
key: string;
text: string;
/* eslint-disable */
imageSrc: any;
imageDescription: string;
onClick: () => void;
}

const ErrorSection = ({ errorMessage, handleReload, handleGoHome }: ErrorSectionProps) => {
const buttonList = [
const buttonList: ErrorSectionButton[] = [
{
buttonType: 'primary' as ButtonType,
buttonType: 'primary',
key: 'refreshButton',
text: '새로고침하기',
image: ReloadIcon,
imageSrc: ReloadIcon,
imageDescription: '새로고침 이미지',
onClick: handleReload,
},
{
buttonType: 'secondary' as ButtonType,
key: 'homeButton',
text: '홈으로 이동하기',
image: HomeIcon,
imageSrc: HomeIcon,
imageDescription: '홈 이미지',
onClick: handleGoHome,
},
Expand All @@ -41,10 +51,11 @@ const ErrorSection = ({ errorMessage, handleReload, handleGoHome }: ErrorSection
<S.Container>
{buttonList.map((button) => (
<Button
type="button"
key={button.key}
buttonType={button.buttonType}
text={button.text}
image={button.image}
image={button.imageSrc}
imageDescription={button.imageDescription}
onClick={button.onClick}
/>
Expand Down
10 changes: 9 additions & 1 deletion frontend/src/hooks/useModalClose.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { useEffect, RefObject } from 'react';

const useModalClose = (closeModal: () => void, modalBackgroundRef: RefObject<HTMLElement>) => {
interface UseModalCloseProps {
closeModal: (() => void) | null;
modalBackgroundRef: RefObject<HTMLElement>;
}

const useModalClose = ({ closeModal, modalBackgroundRef }: UseModalCloseProps) => {
const isNodeElement = (element: EventTarget | null): element is Node => {
return element instanceof Node;
};
Expand All @@ -23,6 +28,7 @@ const useModalClose = (closeModal: () => void, modalBackgroundRef: RefObject<HTM

const handleBackgroundClick = (event: MouseEvent) => {
if (isNodeElement(event.target) && isModalBackground(event.target)) {
if (!closeModal) return;
closeModal();
}
};
Expand All @@ -32,6 +38,8 @@ const useModalClose = (closeModal: () => void, modalBackgroundRef: RefObject<HTM
event.preventDefault();

blurFocusing();

if (!closeModal) return;
closeModal();
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const SearchSection = ({ onChange, options, placeholder }: SearchSectionProps) =
<S.Container>
<S.SearchBox>
<SearchInput $width="48rem" $height="100%" placeholder={placeholder} />
<Button buttonType="secondary" text="검색" />
<Button styleType="secondary" text="검색" />
</S.SearchBox>
<DropDown onChange={onChange} options={options} />
</S.Container>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/ReviewWriting/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ const ReviewWritingPage = () => {
</S.KeywordContainer>
<S.ButtonContainer>
{/* <Button buttonType="secondary" text="저장" /> */}
<Button buttonType={isValidForm ? 'primary' : 'disabled'} text="제출" />
<Button styleType={isValidForm ? 'primary' : 'disabled'}>제출</Button>
</S.ButtonContainer>
</S.ReviewFormMain>
</S.ReviewWritingPage>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/types/styles.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export type ButtonType = 'primary' | 'secondary' | 'disabled';
export type ButtonStyleType = 'primary' | 'secondary' | 'disabled';