Skip to content

Commit

Permalink
design: 모바일 Modal 관련 컴포넌트 구현 및 스토리북 작성
Browse files Browse the repository at this point in the history
  • Loading branch information
areumH committed Dec 7, 2024
1 parent ba8f5c9 commit f79425b
Show file tree
Hide file tree
Showing 25 changed files with 643 additions and 4 deletions.
5 changes: 5 additions & 0 deletions src/assets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ export { default as IcTrash } from './svg/trash.svg';
export { default as MobileBookmarkDF } from './svg/mobile-bookmark-df.svg';
export { default as MobileBookmarkPR } from './svg/mobile-bookmark-pr.svg';
export { default as MobileShare } from './svg/mobile-share.svg';
export { default as MobileModalClose } from './svg/mobile-modal-close.svg';
export { default as MobileFolder } from './svg/mobile-folder.svg';
export { default as MobileFolderCheck } from './svg/mobile-folder-check.svg';
export { default as MobileCheckIcon } from './svg/mobile-check-icon.svg';
export { default as MobileTempIcon } from './svg/mobile-temp-icon.svg';

// TODO: 이전 SVG
export { default as Email } from './svg/email.svg';
Expand Down
4 changes: 4 additions & 0 deletions src/assets/svg/mobile-check-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions src/assets/svg/mobile-folder-check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions src/assets/svg/mobile-folder.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/svg/mobile-modal-close.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/assets/svg/mobile-temp-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ export const checkBoxRadioStyling = css({

export const checkBoxTextStyling = css(typo.Main.Medium, {
marginLeft: '14px',
color: color.GY[4],
color: '#555555',
cursor: 'pointer',
});
46 changes: 46 additions & 0 deletions src/components/mobile/atoms/Modal/Modal.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { css } from '@emotion/react';
import color from '@/styles/color';
import type { ModalProps } from './Modal';

export const modalStyling = css({
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
position: 'relative',
width: '335px',
backgroundColor: color.WT,
borderRadius: '12px',
boxShadow: '1px 2px 30px rgba(0, 0, 0, 0.15)',
});

export const getModalSize = (action: Required<ModalProps>['action']) => {
const style = {
share: css({
paddingTop: '24px',
paddingLeft: '20px',
paddingRight: '20px',
paddingBottom: '26px',
}),
tag: css({
paddingTop: '24px',
paddingLeft: '20px',
paddingRight: '20px',
paddingBottom: '30px',
}),
tempGame: css({
paddingTop: '24px',
paddingLeft: '20px',
paddingRight: '20px',
paddingBottom: '24px',
}),
};

return style[action as keyof typeof style];
};

export const modalCloseStyling = css({
position: 'absolute',
top: '24px',
right: '20px',
cursor: 'pointer',
});
40 changes: 40 additions & 0 deletions src/components/mobile/atoms/Modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useRef, ReactNode } from 'react';
import { MobileModalClose } from '@/assets';
import useOutsideClick from '@/hooks/common/useOutsideClick';
import * as S from './Modal.style';

export interface ModalProps {
action?: 'share' | 'tag' | 'tempGame';
isOpen?: boolean;
onClose?: () => void;
hasCloseButton?: boolean;
children?: ReactNode;
}

const Modal = ({
isOpen,
onClose,
action = 'share',
hasCloseButton = true,
children,
}: ModalProps) => {
const modalRef = useRef<HTMLDivElement>(null);

useOutsideClick(modalRef, () => onClose?.());

return (
<div>
{isOpen && (
<div ref={modalRef} css={[S.modalStyling, S.getModalSize(action)]}>
{hasCloseButton && (
<MobileModalClose css={S.modalCloseStyling} onClick={onClose} />
)}
{children}
</div>
)}
</div>
);
};

export default Modal;
35 changes: 35 additions & 0 deletions src/components/mobile/atoms/TempGameButton/TempGameButton.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import color from '@/styles/color';
import typo from '@/styles/typo';
import { css } from '@emotion/react';

export const tempGameButtonStyling = css({
display: 'flex',
flexDirection: 'column',
justifyContent: 'flex-end',
padding: '10px',
width: '142px',
height: '136px',
backgroundColor: color.WT,
borderRadius: '10px',
border: `1px solid ${color.GY[3]}`,
boxShadow: '1px 2px 15px rgba(0, 0, 0, 0.05)',
cursor: 'pointer',
});

export const itemWrapper = css({
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
height: '95px',
});

export const buttonStyling = css(typo.Mobile.Text.SemiBold_14, {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '122px',
height: '34px',
borderRadius: '8px',
backgroundColor: color.WT_VIOLET,
color: color.MAIN,
});
24 changes: 24 additions & 0 deletions src/components/mobile/atoms/TempGameButton/TempGameButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { forwardRef } from 'react';
import type { ComponentPropsWithRef, ForwardedRef } from 'react';
import { MobileFolder, MobileFolderCheck } from '@/assets';
import * as S from './TempGameButton.style';

export interface ButtonProps extends ComponentPropsWithRef<'button'> {
action?: 'save' | 'get';
}

const TempGameButton = (
{ action = 'save', ...attributes }: ButtonProps,
ref: ForwardedRef<HTMLButtonElement>,
) => (
<button type="button" ref={ref} css={S.tempGameButtonStyling} {...attributes}>
<div css={S.itemWrapper}>
<div>{action === 'save' ? <MobileFolderCheck /> : <MobileFolder />}</div>
<div css={S.buttonStyling}>
{action === 'save' ? '임시저장하기' : '임시저장 불러오기'}
</div>
</div>
</button>
);

export default forwardRef(TempGameButton);
81 changes: 81 additions & 0 deletions src/components/mobile/molecules/GameTagModal/GameTagModal.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { css } from '@emotion/react';
import color from '@/styles/color';
import typo from '@/styles/typo';

export const contentWrapper = css({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '20px',
});

export const textBox = css({
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
gap: '3px',
});

export const textStyling = css(typo.Main.SemiBold, {
color: color.BK,
});

export const tagTextStyling = css(typo.Mobile.Text.SemiBold_14, {
color: color.GY[1],
});

export const markStyling = css(typo.Mobile.Text.SemiBold_14, {
color: color.MAIN,
});

export const tagWrapper = css({
width: '100%',
display: 'flex',
flexDirection: 'column',
gap: '6px',
});

export const buttonWrapper = css({
display: 'flex',
gap: '8px',
});

export const inputStyling = css(typo.Mobile.Text.Medium_12, {
fontSize: '14px',
width: '295px',
height: '42px',
padding: '10px 20px',
border: `1px solid ${color.GY[4]}`,
borderRadius: '6px',
});

export const customButtonStyle = (isDisabled: boolean) =>
css({
cursor: isDisabled ? 'not-allowed' : 'pointer',
});

export const customDisabledBtnStyle = css({
color: color.WT,
backgroundColor: color.GY[2],
outline: '1px solid transparent',
pointerEvents: 'none',
':hover': {
backgroundColor: color.GY[2],
},
});

export const getButtonStyling = (isSelected: boolean) => {
return css({
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
width: '64px',
height: '34px',
borderRadius: '6px',
border: isSelected ? 'null' : `1px solid ${color.GY[4]}`,
backgroundColor: isSelected ? color.MAIN : color.GY[5],
color: isSelected ? color.WT : color.GY[1],
cursor: 'pointer',
});
};
85 changes: 85 additions & 0 deletions src/components/mobile/molecules/GameTagModal/GameTagModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React, { useState } from 'react';
import { MobileCheckIcon } from '@/assets';
import Modal from '@/components/mobile/atoms/Modal/Modal';
import Button from '@/components/mobile/atoms/Button/Button';
import Divider from '@/components/atoms/Divider/Divider';
import * as S from './GameTagModal.style';

interface GameTagModalProps {
isOpen?: boolean;
onClose?: () => void;
onTagSubmit: (mainTag: string, subTag: string) => void;
}

const GameTagModal = ({ isOpen, onClose, onTagSubmit }: GameTagModalProps) => {
const [mainTag, setMainTag] = useState<string | null>(null);
const [subTag, setSubTag] = useState<string>('');

const handleTagSubmit = () => {
if (mainTag) {
onTagSubmit(mainTag, subTag);
onClose?.();
}
};

return (
<Modal isOpen={isOpen} onClose={onClose} action="tag">
<div css={S.contentWrapper}>
<MobileCheckIcon />
<div css={S.textStyling}>밸런스게임 태그를 선택해주세요!</div>
<Divider orientation="width" length={50} />
<div css={S.tagWrapper}>
<div css={S.textBox}>
<span css={S.tagTextStyling}>메인태그</span>
<span css={S.markStyling}>*</span>
</div>
<div css={S.buttonWrapper}>
<button
type="button"
css={S.getButtonStyling(mainTag === '커플')}
onClick={() => setMainTag('커플')}
>
커플
</button>
<button
type="button"
css={S.getButtonStyling(mainTag === '취향')}
onClick={() => setMainTag('취향')}
>
취향
</button>
<button
type="button"
css={S.getButtonStyling(mainTag === '기타')}
onClick={() => setMainTag('기타')}
>
기타
</button>
</div>
</div>
<div css={S.tagWrapper}>
<div css={S.textBox}>
<span css={S.tagTextStyling}>서브태그</span>
</div>
<input
css={S.inputStyling}
placeholder="ex) 아이돌, 연애..."
value={subTag}
onChange={(e) => setSubTag(e.target.value)}
/>
</div>
<Button
size="large"
variant="roundPrimary"
onClick={handleTagSubmit}
disabled={!mainTag}
css={S.customButtonStyle(!mainTag)}
>
등록하기
</Button>
</div>
</Modal>
);
};

export default GameTagModal;
19 changes: 19 additions & 0 deletions src/components/mobile/molecules/ShareModal/ShareModal.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { css } from '@emotion/react';
import color from '@/styles/color';
import typo from '@/styles/typo';

export const shareModalStyling = css({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
});

export const textStyling = css(typo.Comment.SemiBold, {
marginTop: '24px',
marginBottom: '23px',
color: color.BK,
});

export const shareTextStyling = css(typo.Comment.SemiBold, {
color: color.MAIN,
});
Loading

0 comments on commit f79425b

Please sign in to comment.