diff --git a/src/assets/index.ts b/src/assets/index.ts index 6c1642c2..dc568b9e 100644 --- a/src/assets/index.ts +++ b/src/assets/index.ts @@ -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'; diff --git a/src/assets/svg/mobile-check-icon.svg b/src/assets/svg/mobile-check-icon.svg new file mode 100644 index 00000000..8f1c6e73 --- /dev/null +++ b/src/assets/svg/mobile-check-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svg/mobile-folder-check.svg b/src/assets/svg/mobile-folder-check.svg new file mode 100644 index 00000000..6e33df9e --- /dev/null +++ b/src/assets/svg/mobile-folder-check.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/svg/mobile-folder.svg b/src/assets/svg/mobile-folder.svg new file mode 100644 index 00000000..c815ebf1 --- /dev/null +++ b/src/assets/svg/mobile-folder.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/svg/mobile-modal-close.svg b/src/assets/svg/mobile-modal-close.svg new file mode 100644 index 00000000..5a75ae98 --- /dev/null +++ b/src/assets/svg/mobile-modal-close.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/svg/mobile-temp-icon.svg b/src/assets/svg/mobile-temp-icon.svg new file mode 100644 index 00000000..88c27224 --- /dev/null +++ b/src/assets/svg/mobile-temp-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/atoms/CheckBoxButton/CheckBoxButton.style.ts b/src/components/atoms/CheckBoxButton/CheckBoxButton.style.ts index 9662a9d5..fbf5b81e 100644 --- a/src/components/atoms/CheckBoxButton/CheckBoxButton.style.ts +++ b/src/components/atoms/CheckBoxButton/CheckBoxButton.style.ts @@ -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', }); diff --git a/src/components/mobile/atoms/Modal/Modal.style.ts b/src/components/mobile/atoms/Modal/Modal.style.ts new file mode 100644 index 00000000..b0109966 --- /dev/null +++ b/src/components/mobile/atoms/Modal/Modal.style.ts @@ -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['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', +}); diff --git a/src/components/mobile/atoms/Modal/Modal.tsx b/src/components/mobile/atoms/Modal/Modal.tsx new file mode 100644 index 00000000..f02b0b87 --- /dev/null +++ b/src/components/mobile/atoms/Modal/Modal.tsx @@ -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(null); + + useOutsideClick(modalRef, () => onClose?.()); + + return ( +
+ {isOpen && ( +
+ {hasCloseButton && ( + + )} + {children} +
+ )} +
+ ); +}; + +export default Modal; diff --git a/src/components/mobile/atoms/TempGameButton/TempGameButton.style.ts b/src/components/mobile/atoms/TempGameButton/TempGameButton.style.ts new file mode 100644 index 00000000..c7df6486 --- /dev/null +++ b/src/components/mobile/atoms/TempGameButton/TempGameButton.style.ts @@ -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, +}); diff --git a/src/components/mobile/atoms/TempGameButton/TempGameButton.tsx b/src/components/mobile/atoms/TempGameButton/TempGameButton.tsx new file mode 100644 index 00000000..58484fca --- /dev/null +++ b/src/components/mobile/atoms/TempGameButton/TempGameButton.tsx @@ -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, +) => ( + +); + +export default forwardRef(TempGameButton); diff --git a/src/components/mobile/molecules/GameTagModal/GameTagModal.style.ts b/src/components/mobile/molecules/GameTagModal/GameTagModal.style.ts new file mode 100644 index 00000000..b0e3845d --- /dev/null +++ b/src/components/mobile/molecules/GameTagModal/GameTagModal.style.ts @@ -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', + }); +}; diff --git a/src/components/mobile/molecules/GameTagModal/GameTagModal.tsx b/src/components/mobile/molecules/GameTagModal/GameTagModal.tsx new file mode 100644 index 00000000..ce4f04cf --- /dev/null +++ b/src/components/mobile/molecules/GameTagModal/GameTagModal.tsx @@ -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(null); + const [subTag, setSubTag] = useState(''); + + const handleTagSubmit = () => { + if (mainTag) { + onTagSubmit(mainTag, subTag); + onClose?.(); + } + }; + + return ( + +
+ +
밸런스게임 태그를 선택해주세요!
+ +
+
+ 메인태그 + * +
+
+ + + +
+
+
+
+ 서브태그 +
+ setSubTag(e.target.value)} + /> +
+ +
+
+ ); +}; + +export default GameTagModal; diff --git a/src/components/mobile/molecules/ShareModal/ShareModal.style.ts b/src/components/mobile/molecules/ShareModal/ShareModal.style.ts new file mode 100644 index 00000000..b4f08219 --- /dev/null +++ b/src/components/mobile/molecules/ShareModal/ShareModal.style.ts @@ -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, +}); diff --git a/src/components/mobile/molecules/ShareModal/ShareModal.tsx b/src/components/mobile/molecules/ShareModal/ShareModal.tsx new file mode 100644 index 00000000..9968e7be --- /dev/null +++ b/src/components/mobile/molecules/ShareModal/ShareModal.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import Button from '@/components/mobile/atoms/Button/Button'; +import Modal from '@/components/mobile/atoms/Modal/Modal'; +import * as S from './ShareModal.style'; + +export interface ShareModalProps { + isOpen?: boolean; + onConfirm?: () => void; + onClose?: () => void; +} + +const ShareModal = ({ isOpen, onConfirm, onClose }: ShareModalProps) => { + return ( + +
+
+ 카카오톡으로 공유하시겠습니까? +
+ +
+
+ ); +}; + +export default ShareModal; diff --git a/src/components/mobile/molecules/TempGameModal/TempGameModal.style.ts b/src/components/mobile/molecules/TempGameModal/TempGameModal.style.ts new file mode 100644 index 00000000..7e18b461 --- /dev/null +++ b/src/components/mobile/molecules/TempGameModal/TempGameModal.style.ts @@ -0,0 +1,21 @@ +import { css } from '@emotion/react'; +import color from '@/styles/color'; +import typo from '@/styles/typo'; + +export const tempGameModalStyling = css({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: '10px', +}); + +export const textStyling = css(typo.Main.SemiBold, { + color: color.BK, +}); + +export const buttonWrapper = css({ + display: 'flex', + flexDirection: 'row', + gap: '11px', + marginTop: '5px', +}); diff --git a/src/components/mobile/molecules/TempGameModal/TempGameModal.tsx b/src/components/mobile/molecules/TempGameModal/TempGameModal.tsx new file mode 100644 index 00000000..c4be6991 --- /dev/null +++ b/src/components/mobile/molecules/TempGameModal/TempGameModal.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { MobileTempIcon } from '@/assets'; +import Modal from '@/components/mobile/atoms/Modal/Modal'; +import TempGameButton from '@/components/mobile/atoms/TempGameButton/TempGameButton'; +import * as S from './TempGameModal.style'; + +export interface TempGameModalProps { + isOpen?: boolean; + onSaveGame?: () => void; + onGetGame?: () => void; + onClose?: () => void; +} + +const TempGameModal = ({ + isOpen, + onSaveGame, + onGetGame, + onClose, +}: TempGameModalProps) => { + return ( + +
+ +
임시 저장
+
+ + +
+
+
+ ); +}; + +export default TempGameModal; diff --git a/src/components/molecules/ReportModal/ReportModal.style.ts b/src/components/molecules/ReportModal/ReportModal.style.ts index 375920ad..a431cffe 100644 --- a/src/components/molecules/ReportModal/ReportModal.style.ts +++ b/src/components/molecules/ReportModal/ReportModal.style.ts @@ -43,6 +43,6 @@ export const reportTextAreaStyling = css(typo.Comment.Regular, { border: `1px solid ${color.GY[2]}`, backgroundColor: color.WT, boxShadow: '1px 2px 15px rgba(0, 0, 0, 0.05)', - color: color.GY[4], + color: '#555555', resize: 'none', }); diff --git a/src/components/molecules/TopBanner/TopBanner.style.ts b/src/components/molecules/TopBanner/TopBanner.style.ts index 6a667102..3ab1dc2c 100644 --- a/src/components/molecules/TopBanner/TopBanner.style.ts +++ b/src/components/molecules/TopBanner/TopBanner.style.ts @@ -31,7 +31,7 @@ export const dotStyling = css({ width: '10px', height: '10px', borderRadius: '50%', - backgroundColor: color.GY[4], + backgroundColor: '#555555', transition: '0.5s ease-in-out', cursor: 'pointer', }); diff --git a/src/stories/mobile/atoms/Modal.stories.tsx b/src/stories/mobile/atoms/Modal.stories.tsx new file mode 100644 index 00000000..bb564d99 --- /dev/null +++ b/src/stories/mobile/atoms/Modal.stories.tsx @@ -0,0 +1,69 @@ +import React, { useState } from 'react'; +import Modal from '@/components/mobile/atoms/Modal/Modal'; +import type { Meta, StoryObj } from '@storybook/react'; +import { storyContainer, storyInnerContainer } from '@/stories/story.styles'; + +const meta = { + title: 'mobile/atoms/Modal', + component: Modal, + parameters: { + layout: 'centered', + }, + argTypes: { + action: { + options: ['share', 'tag', 'tempGame'], + control: { type: 'radio' }, + }, + isOpen: { control: { type: 'boolean' } }, + hasCloseButton: { control: { type: 'boolean' } }, + children: { control: { type: 'text' } }, + }, + args: { + isOpen: true, + children: 'Modal', + hasCloseButton: true, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + isOpen: true, + action: 'share', + hasCloseButton: true, + }, +}; + +export const All: Story = { + render: (args) => { + const [modalOpen, setModalOpen] = useState(true); + const handleCloseModal = () => setModalOpen(!modalOpen); + + return ( +
    +
  • +

    Close Modal

    + + Close Modal + +
  • +
  • +

    Share

    + + Share Modal + +

    Tag

    + + Tag Modal + +

    TempGame

    + + TempGame Modal + +
  • +
+ ); + }, +}; diff --git a/src/stories/mobile/atoms/TempGameButton.stories.tsx b/src/stories/mobile/atoms/TempGameButton.stories.tsx new file mode 100644 index 00000000..528d0d1f --- /dev/null +++ b/src/stories/mobile/atoms/TempGameButton.stories.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import TempGameButton from '@/components/mobile/atoms/TempGameButton/TempGameButton'; +import { storyContainer, storyInnerContainer } from '@/stories/story.styles'; + +const meta = { + title: 'mobile/atoms/TempGameButton', + component: TempGameButton, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + action: { + options: ['save', 'get'], + control: { type: 'radio' }, + }, + }, + args: { + action: 'save', + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const All: Story = { + render: (args) => ( +
    +
  • +

    임시저장하기

    + +

    임시저장 불러오기

    + +
  • +
+ ), +}; diff --git a/src/stories/mobile/molecules/GameTagModal.stories.tsx b/src/stories/mobile/molecules/GameTagModal.stories.tsx new file mode 100644 index 00000000..1b3d2c2c --- /dev/null +++ b/src/stories/mobile/molecules/GameTagModal.stories.tsx @@ -0,0 +1,26 @@ +/* eslint-disable no-console */ +import GameTagModal from '@/components/mobile/molecules/GameTagModal/GameTagModal'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'mobile/molecules/GameTagModal', + component: GameTagModal, + parameters: { + layout: 'centered', + }, + argTypes: { + onTagSubmit: { action: 'tagSubmit' }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + isOpen: true, + onTagSubmit: (mainTag: string, subTag: string) => { + console.log(`메인 태그 : ${mainTag}, 서브 태그 : ${subTag}`); + }, + }, +}; diff --git a/src/stories/mobile/molecules/ShareModal.stories.tsx b/src/stories/mobile/molecules/ShareModal.stories.tsx new file mode 100644 index 00000000..8d7ef274 --- /dev/null +++ b/src/stories/mobile/molecules/ShareModal.stories.tsx @@ -0,0 +1,26 @@ +import ShareModal from '@/components/mobile/molecules/ShareModal/ShareModal'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'mobile/molecules/ShareModal', + component: ShareModal, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + isOpen: { control: { type: 'boolean' } }, + }, + args: { + isOpen: true, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + isOpen: true, + }, +}; diff --git a/src/stories/mobile/molecules/TempGameModal.stories.tsx b/src/stories/mobile/molecules/TempGameModal.stories.tsx new file mode 100644 index 00000000..9a50e329 --- /dev/null +++ b/src/stories/mobile/molecules/TempGameModal.stories.tsx @@ -0,0 +1,26 @@ +import TempGameModal from '@/components/mobile/molecules/TempGameModal/TempGameModal'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'mobile/molecules/TempGameModal', + component: TempGameModal, + parameters: { + layout: 'centered', + }, + tags: ['autodocs'], + argTypes: { + isOpen: { control: { type: 'boolean' } }, + }, + args: { + isOpen: true, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + isOpen: true, + }, +}; diff --git a/src/styles/color.ts b/src/styles/color.ts index 11a77b93..42f3bc75 100644 --- a/src/styles/color.ts +++ b/src/styles/color.ts @@ -5,7 +5,8 @@ const color = { 1: '#8C8C8C', 2: '#D9D9D9', 3: '#F1F1F1', - 4: '#555555', + 4: '#E6E9EF', + 5: '#F6F7F9', }, WT: '#FFFFFF', WT_VIOLET: '#F2F3FF',