From fd31850cffedfcf9822caa5b350225f0c49205c2 Mon Sep 17 00:00:00 2001 From: jymaeng1234 <102141309+jymaeng1234@users.noreply.github.com> Date: Thu, 31 Oct 2024 09:28:25 +0900 Subject: [PATCH] =?UTF-8?q?fix=20:=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EB=8C=80=ED=91=9C?= =?UTF-8?q?=EC=84=9C=EB=B8=8C=EB=AA=A8=EB=93=9C=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#77)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/EmotionButton/ui/EmotionButton.tsx | 24 ++-- .../ui/EmotionButtonList.stories.tsx | 114 ++++++++++++++++++ .../ui/EmotionButtonList.styled.ts | 0 .../ui/EmotionButtonList.tsx | 83 +++++++++++++ .../ui/EmotionButtonList.stories.tsx | 26 ---- .../EmotionList/ui/EmotionButtonList.tsx | 39 ------ 6 files changed, 209 insertions(+), 77 deletions(-) create mode 100644 src/shared/EmotionButtonList/ui/EmotionButtonList.stories.tsx rename src/shared/{EmotionList => EmotionButtonList}/ui/EmotionButtonList.styled.ts (100%) create mode 100644 src/shared/EmotionButtonList/ui/EmotionButtonList.tsx delete mode 100644 src/shared/EmotionList/ui/EmotionButtonList.stories.tsx delete mode 100644 src/shared/EmotionList/ui/EmotionButtonList.tsx diff --git a/src/shared/EmotionButton/ui/EmotionButton.tsx b/src/shared/EmotionButton/ui/EmotionButton.tsx index fdc6ed4..718444d 100644 --- a/src/shared/EmotionButton/ui/EmotionButton.tsx +++ b/src/shared/EmotionButton/ui/EmotionButton.tsx @@ -1,5 +1,4 @@ import React, { useEffect, useState } from 'react'; - import EmotionIcon from '../../EmotionIcon/ui/EmotionIcon'; import { Emotions, getEmotionInfo } from '../../model/EmotionEnum'; import { StyledEmotionButton } from './EmotionButton.styled'; @@ -8,24 +7,25 @@ interface EmotionButtonProps { emotion: Emotions; initialClicked: boolean; onClick: () => void; + disabled: boolean; } -const EmotionButton = ({ +const EmotionButton: React.FC = ({ emotion, onClick, - initialClicked = false -}: EmotionButtonProps) => { + initialClicked, + disabled +}) => { const [isClicked, setIsClicked] = useState(initialClicked); - useEffect(() => { - setIsClicked(initialClicked); - }, [initialClicked]); - const handleClick = () => { - setIsClicked((prev) => !prev); - onClick(); - }; + useEffect(() => setIsClicked(initialClicked), [initialClicked]); + return ( - +

{getEmotionInfo(emotion).description}

diff --git a/src/shared/EmotionButtonList/ui/EmotionButtonList.stories.tsx b/src/shared/EmotionButtonList/ui/EmotionButtonList.stories.tsx new file mode 100644 index 0000000..9045f3d --- /dev/null +++ b/src/shared/EmotionButtonList/ui/EmotionButtonList.stories.tsx @@ -0,0 +1,114 @@ +import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import EmotionButtonList from './EmotionButtonList'; +import { Emotions } from '../../../shared/model/EmotionEnum'; + +const meta: Meta = { + component: EmotionButtonList, + title: 'Shared/EmotionButtonList', + tags: ['autodocs'], + argTypes: { + isPrimary: { + control: { type: 'boolean' }, + description: '대표 감정 : true, 서브 감정 : false ', + }, + maxSelections: { + control: { type: 'number' }, + description: '사용자가 선택할 수 있는 최대 감정 버튼 개수', + }, + initialSelectedEmotions: { + control: { + type: 'array', + options: Object.values(Emotions), + }, + description: '초기 선택된 감정 목록입니다.\n\n' + + '배열의 크기가 maxSelections 수량보다 클 경우, 앞에서부터 maxSelections 수량만큼 설정됩니다.', + }, + onSelectionChange: { + description: '선택된 감정 목록이 변경될 때 호출되는 콜백 함수', + action: 'onSelectionChange', + }, + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + onSelectionChange: (selectedEmotions) => { + console.log('selected:', selectedEmotions); + }, + maxSelections: 3, + isPrimary: true, + initialSelectedEmotions: [], + }, + parameters: { + docs: { + description: { + story: '대표 감정을 선택하기 위한 모드입니다.', + }, + }, + }, +}; + +export const PrimaryMode_Selected: Story = { + args: { + onSelectionChange: (selectedEmotions) => { + console.log('selected:', selectedEmotions); + }, + maxSelections: 3, + isPrimary: true, + initialSelectedEmotions: [Emotions.Happy, Emotions.Sad], + }, + parameters: { + docs: { + description: { + story: '대표 감정을 선택하기 위한 모드입니다.\n\n' + + '선택된 감정의 초기값을 설정할 수 있습니다.\n\n' + + '단, 배열의 길이가 maxSelections보다 클 경우 맨 앞의 항목만 선택됩니다. (ex. 기뻐요, 슬퍼요 선택 => 기뻐요)', + }, + }, + }, +}; + +export const SubEmotionMode_Default: Story = { + args: { + onSelectionChange: (selectedEmotions) => { + console.log('selected:', selectedEmotions); + }, + maxSelections: 3, + isPrimary: false, + initialSelectedEmotions: [], + }, + parameters: { + docs: { + description: { + story: '서브 감정을 선택하기 위한 모드입니다. 초기 선택된 감정을 설정할 수 있습니다.\n\n' + + '단, 배열의 길이가 maxSelections보다 클 경우 앞에서부터 maxSelections번째 항목까지 감정이 설정됩니다.\n\n' + + '(ex) maxSelections = 3으로 설정', + }, + }, + }, +}; + +export const SubEmotionMode_Selected: Story = { + args: { + onSelectionChange: (selectedEmotions) => { + console.log('selected:', selectedEmotions); + }, + maxSelections: 3, + isPrimary: false, + initialSelectedEmotions: [Emotions.Happy, Emotions.Sad, Emotions.Awkward, Emotions.Blank], + }, + parameters: { + docs: { + description: { + story: '서브 감정을 선택하기 위한 모드입니다.\n\n' + + 'initialSelectedEmotions에 감정 키워드 배열을 넘기면 초기 설정이 가능합니다.\n\n' + + '배열의 길이가 maxSelections를 초과할 경우, 앞에서부터 maxSelections 개수만큼 설정됩니다. (ex. 기뻐요, 슬퍼요, 곤란해요 초기값 설정)', + }, + }, + }, +}; diff --git a/src/shared/EmotionList/ui/EmotionButtonList.styled.ts b/src/shared/EmotionButtonList/ui/EmotionButtonList.styled.ts similarity index 100% rename from src/shared/EmotionList/ui/EmotionButtonList.styled.ts rename to src/shared/EmotionButtonList/ui/EmotionButtonList.styled.ts diff --git a/src/shared/EmotionButtonList/ui/EmotionButtonList.tsx b/src/shared/EmotionButtonList/ui/EmotionButtonList.tsx new file mode 100644 index 0000000..9df032d --- /dev/null +++ b/src/shared/EmotionButtonList/ui/EmotionButtonList.tsx @@ -0,0 +1,83 @@ +import React, { useState, useEffect } from 'react'; +import { StyledEmotionButtonList } from './EmotionButtonList.styled'; +import EmotionButton from '../../EmotionButton/ui/EmotionButton'; +import { Emotions } from '../../model/EmotionEnum'; + +interface EmotionListProps { + isPrimary: boolean; + maxSelections: number; + initialSelectedEmotions: Emotions[]; + onSelectionChange: (selectedEmotions: Emotions[]) => void; +} + +/** + * 게시글에 대한 반응을 선택하는 버튼 리스트 컴포넌트입니다.
+ * 대표 감정 모드와 서브 감정 모드를 지원하며, 초기 선택된 감정을 설정하고 최대 선택 가능 수를 제한할 수 있습니다. + */ + +const EmotionList: React.FC = ({ + isPrimary = true, + maxSelections, + initialSelectedEmotions = [], + onSelectionChange +}) => { + const [selectedEmotions, setSelectedEmotions] = useState([]); + + useEffect(() => { + if (isPrimary) { + if (initialSelectedEmotions.length > 0) { + setSelectedEmotions([initialSelectedEmotions[0]]); + onSelectionChange([initialSelectedEmotions[0]]); + } else { + setSelectedEmotions([]); + onSelectionChange([]); + } + } else { + const validInitialEmotions = initialSelectedEmotions.slice( + 0, + maxSelections + ); + setSelectedEmotions(validInitialEmotions); + onSelectionChange(validInitialEmotions); + } + }, [isPrimary, initialSelectedEmotions, onSelectionChange, maxSelections]); + + const handleEmotionClick = (emotion: Emotions) => { + setSelectedEmotions((prev) => { + let newSelection: Emotions[]; + + if (isPrimary) { + newSelection = [emotion]; + } else { + // eslint-disable-next-line no-lonely-if + if (prev.includes(emotion)) { + newSelection = prev.filter((e) => e !== emotion); + } else if (prev.length < maxSelections) { + newSelection = [...prev, emotion]; + } else { + alert(`최대 ${maxSelections}개 감정만 선택할 수 있습니다.`); + newSelection = prev; + } + } + + onSelectionChange(newSelection); + return newSelection; + }); + }; + + return ( + + {Object.values(Emotions).map((emotion) => ( + handleEmotionClick(emotion)} + disabled={false} + /> + ))} + + ); +}; + +export default EmotionList; diff --git a/src/shared/EmotionList/ui/EmotionButtonList.stories.tsx b/src/shared/EmotionList/ui/EmotionButtonList.stories.tsx deleted file mode 100644 index b264f99..0000000 --- a/src/shared/EmotionList/ui/EmotionButtonList.stories.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import type { Meta, StoryObj } from '@storybook/react'; - -import EmotionButtonList from './EmotionButtonList'; - -const meta: Meta = { - component: EmotionButtonList, - title: 'Shared/ui/EmotionButtonList', - tags: ['autodocs'], - argTypes: { - onSelectionChange: { - description: '선택된 감정 리스트가 변경될 때 호출되는 함수입니다.\n 이를 활용해 선택된 버튼 목록을 확인할 수있습니다.\n (StoryBook에서는 콘솔로 출력 확인 가능)', - }, - }, -}; -export default meta; - -type Story = StoryObj; - -export const Default: Story = { - args: { - onSelectionChange: (selectedEmotions) => { - console.log('selected:', selectedEmotions); - }, - }, - }; diff --git a/src/shared/EmotionList/ui/EmotionButtonList.tsx b/src/shared/EmotionList/ui/EmotionButtonList.tsx deleted file mode 100644 index 7d4d0f1..0000000 --- a/src/shared/EmotionList/ui/EmotionButtonList.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { useState } from 'react'; -import { StyledEmotionButtonList } from './EmotionButtonList.styled'; -import EmotionButton from '../../EmotionButton/ui/EmotionButton'; -import { Emotions } from '../../model/EmotionEnum'; - -interface EmotionListProps { - onSelectionChange: (selectedEmotions: Emotions[]) => void; // prop 추가 -} - -/** 게시글에 대한 반응을 나타내기 위한 버튼입니다. */ -const EmotionList = ({ onSelectionChange }: EmotionListProps) => { - const [selectedEmotions, setSelectedEmotions] = useState([]); - - const handleEmotionClick = (emotion: Emotions) => { - setSelectedEmotions((prev) => { - const newSelection = prev.includes(emotion) - ? prev.filter((e) => e !== emotion) - : [...prev, emotion]; - - onSelectionChange(newSelection); - return newSelection; - }); - }; - - return ( - - {Object.values(Emotions).map((emotion) => ( - handleEmotionClick(emotion)} - /> - ))} - - ); -}; - -export default EmotionList;