Skip to content
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
54 changes: 52 additions & 2 deletions src/components/mobile/molecules/GameTagModal/GameTagModal.style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const contentWrapper = css({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '20px',
gap: '16px',
});

export const textBox = css({
Expand All @@ -24,15 +24,29 @@ export const tagTextStyling = css(typo.Mobile.Text.SemiBold_14, {
color: color.GY[1],
});

export const subTagTextStyling = css(typo.Mobile.Main.Regular_12, {
color: color.GY[2],
});

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

export const errorMessageStyling = css(typo.Mobile.Main.Regular_12, {
color: color.RED,
});

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

export const tagBottomWrapper = css({
width: '100%',
display: 'flex',
flexDirection: 'column',
});

export const buttonWrapper = css({
Expand All @@ -46,6 +60,42 @@ export const buttonStyling = css(typo.Mobile.Text.SemiBold_14, {
borderRadius: '6px',
});

export const inputWrapper = css({
display: 'flex',
flexDirection: 'column',
height: '67px',
gap: '4px',
});

export const subTagChipStyling = css(typo.Mobile.Text.SemiBold_12, {
display: 'flex',
alignItems: 'center',
padding: '5px 9px 5px 12px',
gap: '5px',
borderRadius: '6px',
outline: `1px solid ${color.SECONDARY}`,
backgroundColor: color.WT_VIOLET,
color: color.MAIN,
});

export const subTagButtonStyling = css({
all: 'unset',
display: 'flex',
color: color.MAIN,
fontSize: '12px',
cursor: 'pointer',
});

export const subTagWrapper = (isTagMax: boolean) =>
css({
display: 'flex',
flexWrap: 'wrap',
width: '100%',
gap: '5px',
paddingTop: '3px',
paddingBottom: isTagMax ? '17px' : '5px',
});

export const inputStyling = css(typo.Mobile.Text.Medium_12, {
fontSize: '14px',
width: '295px',
Expand Down
118 changes: 98 additions & 20 deletions src/components/mobile/molecules/GameTagModal/GameTagModal.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import React from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { BalanceGame } from '@/types/game';
import { MobileCheckIcon } from '@/assets';
import { TAG_OPTIONS } from '@/constants/game';
import Modal from '@/components/mobile/atoms/Modal/Modal';
import Button from '@/components/mobile/atoms/Button/Button';
import Divider from '@/components/atoms/Divider/Divider';
import { validateGameTag } from '@/hooks/game/validateBalanceGameForm';
import { createArrayFromCommaString } from '@/utils/array';
import useOutsideClick from '@/hooks/common/useOutsideClick';
import * as S from './GameTagModal.style';

interface GameTagModalProps {
form: BalanceGame;
isOpen?: boolean;
onClose?: () => void;
setMainTagValue: (name: string, tag: string) => void;
setSubTagValue: (e: React.ChangeEvent<HTMLInputElement>) => void;
setSubTagValue: (name: string, tag: string) => void;
submitGame: () => void;
}

Expand All @@ -25,12 +27,62 @@ const GameTagModal = ({
setSubTagValue,
submitGame,
}: GameTagModalProps) => {
const inputRef = useRef<HTMLInputElement>(null);
Copy link
Collaborator

Choose a reason for hiding this comment

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

의 ref를 훅과 적절하게 연결되어 있는거 같네요~


const currentMainTag: string = form.mainTag;
const [subTagArray] = useState(() => createArrayFromCommaString(form.subTag));
const [currentSubTag, setCurrentSubTag] = useState<string[]>(subTagArray);

const [inputValue, setInputValue] = useState<string>('');
const [inputError, setInputError] = useState<boolean>(false);

useEffect(() => {
const subTagList = inputValue
? [...currentSubTag, inputValue]
: currentSubTag;
setSubTagValue('subTag', subTagList.join(','));
}, [currentSubTag, inputValue, setSubTagValue]);

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { value } = e.target;

if (value.length > 10) return;

setInputValue(value);
setInputError(false);
};

const handleSpaceAction = () => {
if (!inputValue.trim()) return;

setCurrentSubTag((prev) => [...prev, inputValue.trim()]);
setInputValue('');
setInputError(false);
};
useOutsideClick(inputRef, handleSpaceAction);

Comment on lines +55 to +63
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

handleSpaceAction에 최대 태그 수 제한 추가 권장

사용자가 이미 3개의 태그를 만든 뒤 입력을 유지한 상태에서 밖을 클릭하면 handleSpaceAction을 통해 추가 태그가 생성될 수도 있습니다. 아래와 같이 간단한 조건을 추가하는 것을 제안드립니다:

 if (!inputValue.trim()) return;
+ if (currentSubTag.length >= 3) return;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleSpaceAction = () => {
if (!inputValue.trim()) return;
setCurrentSubTag((prev) => [...prev, inputValue.trim()]);
setInputValue('');
setInputError(false);
};
useOutsideClick(inputRef, handleSpaceAction);
const handleSpaceAction = () => {
if (!inputValue.trim()) return;
if (currentSubTag.length >= 3) return;
setCurrentSubTag((prev) => [...prev, inputValue.trim()]);
setInputValue('');
setInputError(false);
};
useOutsideClick(inputRef, handleSpaceAction);

const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (!inputValue) {
setInputError(false);
return;
}

if (e.code === 'Space') {
e.preventDefault();
handleSpaceAction();
}

setInputError(inputValue.length >= 10);
};

const handleMainTag = (tag: string) => {
setMainTagValue('mainTag', tag);
};

const handleDeleteSubTag = (idx: number) => {
setCurrentSubTag((prev) => prev.filter((_, i) => i !== idx));
};

const handleTagSubmit = () => {
if (!currentMainTag) return;

Expand Down Expand Up @@ -66,28 +118,54 @@ const GameTagModal = ({
))}
</div>
</div>
<div css={S.tagWrapper}>
<div css={S.tagBottomWrapper}>
<div css={S.textBox}>
<span css={S.tagTextStyling}>서브태그</span>
<span css={S.subTagTextStyling}>(최대 3개)</span>
</div>
<div css={S.subTagWrapper(currentSubTag.length === 3)}>
{currentSubTag.map((tag, idx) => (
<div css={S.subTagChipStyling} key={tag}>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

key 값으로 중복 태그 식별 문제

현재 key={tag}를 사용하면 같은 문자열 태그가 여러 번 입력될 경우 React에서 중복 키 충돌이 일어날 수 있습니다. 안정적인 업데이트를 위해 key={${tag}-${idx}}처럼 인덱스를 함께 포함시키는 것을 권장합니다.

<span>#{tag}</span>
<button
type="button"
css={S.subTagButtonStyling}
onClick={() => handleDeleteSubTag(idx)}
>
</button>
</div>
))}
</div>
<input
name="subTag"
css={S.inputStyling}
placeholder="ex. 너무어려운밸런스게임, 선택장애, 이상형"
value={form.subTag}
onChange={setSubTagValue}
/>
{currentSubTag.length !== 3 && (
<div css={S.inputWrapper}>
<input
type="text"
ref={inputRef}
css={S.inputStyling}
value={inputValue}
placeholder="ex. 연애, 데이트, 데이트취향"
onChange={handleInputChange}
onKeyUp={handleKeyUp}
/>
{inputError && (
<span css={S.errorMessageStyling}>
서브태그 1개 당 최대 10자까지 입력 가능
</span>
)}
</div>
)}
<Button
size="large"
variant="roundPrimary"
onClick={handleTagSubmit}
disabled={!currentMainTag}
active={!!currentMainTag}
css={S.customButtonStyle(!currentMainTag)}
>
등록하기
</Button>
</div>
<Button
size="large"
variant="roundPrimary"
onClick={handleTagSubmit}
disabled={!currentMainTag}
active={!!currentMainTag}
css={S.customButtonStyle(!currentMainTag)}
>
등록하기
</Button>
</div>
</Modal>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const BalanceGameCreateSection = ({
isOpen={tagModalOpen}
onClose={() => setTagModalOpen(false)}
setMainTagValue={setEach}
setSubTagValue={onChange}
setSubTagValue={setEach}
submitGame={handleBalanceGame}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export const descriptionStyling = css(typo.Mobile.Main.Regular_12, {

export const subTagWrapper = css({
display: 'flex',
flexWrap: 'wrap',
width: '100%',
marginBottom: '50px',
gap: '8px',
Expand Down
4 changes: 2 additions & 2 deletions src/stories/mobile/molecules/GameTagModal.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import type { Meta, StoryObj } from '@storybook/react';
const defaultGameOptions = createInitialGameStages(10);
const exampleGame: BalanceGame = {
title: 'title',
mainTag: 'mainTag',
subTag: 'subTag',
mainTag: '커플',
subTag: '커플커플커플커플커플',
games: defaultGameOptions,
};

Expand Down
1 change: 1 addition & 0 deletions src/styles/color.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const color = {
MAIN: '#7782FF',
SECONDARY: '#9DB7FF',
BK: '#181818',
GY: {
1: '#8C8C8C',
Expand Down
2 changes: 1 addition & 1 deletion src/utils/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ export const createRangeArray = (currentPage: number, maxPage: number) => {
};

export const createArrayFromCommaString = (str: string): string[] => {
return str.split(',');
return str ? str.split(',') : [];
};
Loading