등록페이지에서 간단한 기능을 구현해보았다.
import AssignColorButton from '../components/AssignColorButton';
import AssignColorHeader from '../components/AssignColorHeader';
import ColorSelectContent from '../components/ColorSelectContent';
import MbtiSelectContent from '../components/MbtiSelectContent';
const AssignColorPage = () => {
return (
<div className="flex flex-col h-screen px-[24px] py-[42px] tablet:px-[288px] gap-[50px]">
<AssignColorHeader />
<MbtiSelectContent />
<ColorSelectContent />
<AssignColorButton />
</div>
);
};
export default AssignColorPage;
위와 같이 컴포넌트들을 나누어주었다.
X 버튼을 누르면 이전 페이지(메인 페이지)로 넘어가도록 구현해주었다.
MBTI 들을 선택할 때 EI SN TF JP를 따로 분리하여 중복 선택이 되지 않도록 구현해주었다.
export interface MbtiOptions {
EI: string | null;
SN: string | null;
TF: string | null;
JP: string | null;
} //타입 정의
const mbtiCategories = [
{
category: 'EI',
options: [
{ type: 'E', name: '외향형' },
{ type: 'I', name: '내향형' },
],
},
{
category: 'SN',
options: [
{ type: 'S', name: '감각형' },
{ type: 'N', name: '직관형' },
],
},
{
category: 'TF',
options: [
{ type: 'T', name: '사고형' },
{ type: 'F', name: '감정형' },
],
},
{
category: 'JP',
options: [
{ type: 'J', name: '판단형' },
{ type: 'P', name: '인식형' },
],
},
];
export default mbtiCategories;
//카테고리 생성
//컴포넌트 내부
...
return (
<div className="flex flex-col w-[100%] gap-[20px]">
<p className="text-[#464E5E] text-[24px] font-medium">MBTI</p>
{mbtiCategories.map((category) => (
<div key={category.category} className="grid grid-cols-2 gap-[10px]">
{category.options.map((item) => (
<button
key={item.type}
type="button"
onClick={() => handleSelect(category.category as keyof MbtiOptions, item.type)}
className={`flex rounded-[16px] border-[2px] border-solid h-[76px] py-[14px] px-[40px] justify-center items-center gap-[32px] hover:bg-[#F5F7FB]
${
selectedOptions[category.category as keyof MbtiOptions] === item.type
? 'bg-[#464E5E] border-[#464E5E] ' // 선택된 항목 스타일
: 'bg-white border-[#E7EBF2]' // 비활성화된 항목 스타일
}`}
>
<p
className={` text-[40px] font-medium
${
selectedOptions[category.category as keyof MbtiOptions] === item.type
? 'text-white' // 선택된 항목 스타일
: 'text-[#464E5E] ' // 비활성화된 항목 스타일
}
`}
>
{item.type}
</p>
<p className="text-[#8892A6] text-[24px] font-medium">{item.name}</p>
</button>
))}
</div>
))}
</div>
);
타입과 카테고리를 분리하여
이중 map을 사용하여 중복 선택이 불가하도록 구현해주었다.
기존 요구사항은 아이콘을 누르면 랜덤으로 색상을 정해주는 기능이였다.
하지만 내 생각은 이 와는 다르게 내가 원하는 색상을 지정할 수 있도록 해야 MBTI별로 원하는 색깔에 대한 조사가 될 것이다.
따라서 색깔을 선택할 수 있는 라이브러리 'react-colorful'가 있어 이것을 사용해야겠다고 생각했다.
따라서 아이콘을 누르면 모달창 -> react-colorful HexColorPicker 컴포넌트 -> 색깔 선택 -> 랜더링 흐름을 생각해놓고 구현하였다.
또한 toastify를 사용하여 사용자로 하여금 선택하였는지 판단을 하게끔 해주었다.
import React, { useState, useEffect } from 'react';
import { HexColorPicker } from 'react-colorful';
...
interface SelectColorModalProps {
closeModal: () => void;
}
const SelectColorModal = ({ closeModal }: SelectColorModalProps) => {
...
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex justify-center items-center">
<div className="bg-white p-8 rounded-lg shadow-lg max-w-sm w-full h-auto">
<h2 className="text-[30px] font-bold mb-4">컬러 선택하기</h2>
<p className="text-gray-600 mb-4">원하시는 컬러를 선택해주세요</p>
<div className="flex justify-center items-center mt-12 mb-12" style={{ transform: 'scale(1.2)' }}>
<HexColorPicker color={color} onChange={setColor} />
</div>
<button
type="button"
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
onClick={closeModal}
>
선택
</button>
</div>
</div>
);
};
export default SelectColorModal;
import React, { useState } from 'react';
import { toast } from 'react-toastify';
import SelectColorModal from './modal/SelectColorModal';
...
const ColorSelectContent = () => {
const [isOpenModal, setIsOpenModal] = useState(false);
...
const handleOpenModal = () => {
setIsOpenModal(true);
};
const handleCloseModal = () => {
toast.success('색상이 선택되었습니다.');
setIsOpenModal(false);
};
return (
<div className="flex flex-col gap-[15px]">
<div className="flex gap-[3px] items-center">
<p className="text-[#464E5E] text-[24px] font-medium">컬러</p>
<button type="button" className="p-[8px] inline-flex rounded-[8px] bg-[#F5F7FB]" onClick={handleOpenModal}>
<img src="/icons/repeat.svg" alt="랜덤 아이콘" />
</button>
</div>
<div className="inline-flex p-[25px] justify-between items-center rounded-[16px] border-[2px] border-solid border-[#E7EBF2]">
<p className="text-[#8892A6] text-[24px] font-medium">{selectedColor}</p>
<div
style={{ backgroundColor: selectedColor }}
className="w-[40px] h-[40px] rounded-[8px] border-[3px] border-solid border-[#E8E8E8]"
/>
</div>
{isOpenModal && <SelectColorModal closeModal={handleCloseModal} />}
</div>
);
};
export default ColorSelectContent;
마지막 남은 버튼 컴포넌트는 다음 게시물에서 zustand와 같이 설명할 것이다.