From 07b2e9ab9d8d50a91ece16314290c7ce735af702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A1=B0=EC=98=88=EC=A7=84?= Date: Sun, 8 Sep 2024 20:33:54 +0900 Subject: [PATCH] refactor: extract header component & use color constants --- .../form/confirm/FormConfirmPage.module.css | 10 +++++ src/pages/form/confirm/FormConfirmPage.tsx | 8 +--- .../ideal_partner/IdealPartnerPage.module.css | 15 ++----- .../form/ideal_partner/IdealPartnerPage.tsx | 26 ++++++----- .../form/my_profile/MyProfilePage.module.css | 12 +----- src/pages/form/my_profile/MyProfilePage.tsx | 22 ++++++---- src/pages/profile/ProfilePage.tsx | 37 +++++++++------- .../my_profile/QuestionForm/QuestionForm.tsx | 3 +- .../_store/myProfileFormProcessStore.ts | 2 + src/shared/styles/constants.ts | 16 +++++++ src/shared/ui/Accordion/Accordion.tsx | 3 +- src/shared/ui/BottomSheet/BottomSheet.tsx | 26 ++--------- .../InputTriggerChip/InputTriggerChip.tsx | 3 +- src/shared/ui/Profile/ProfileCellHeader.tsx | 3 +- src/shared/ui/Radio/Radio.tsx | 7 ++- src/shared/ui/Toast/toastOption.ts | 3 +- src/shared/ui/layout/Header/Header.module.css | 17 ++++++++ src/shared/ui/layout/Header/Header.tsx | 43 +++++++++++++++++++ .../GenerateFormLinkBottomSheet.tsx | 3 +- .../LocationSelectTable.tsx | 5 ++- 20 files changed, 170 insertions(+), 94 deletions(-) create mode 100644 src/shared/styles/constants.ts create mode 100644 src/shared/ui/layout/Header/Header.module.css create mode 100644 src/shared/ui/layout/Header/Header.tsx diff --git a/src/pages/form/confirm/FormConfirmPage.module.css b/src/pages/form/confirm/FormConfirmPage.module.css index 0e54177..69e755c 100644 --- a/src/pages/form/confirm/FormConfirmPage.module.css +++ b/src/pages/form/confirm/FormConfirmPage.module.css @@ -5,6 +5,16 @@ height: 100%; } +.Header { + flex-shrink: 0; + width: 100%; + display: grid; + grid-template-columns: 1fr 3fr 1fr; + align-items: center; + text-align: center; + height: 44px; +} + .TitleSection { margin-bottom: 20px; diff --git a/src/pages/form/confirm/FormConfirmPage.tsx b/src/pages/form/confirm/FormConfirmPage.tsx index f7866b2..e6b338e 100644 --- a/src/pages/form/confirm/FormConfirmPage.tsx +++ b/src/pages/form/confirm/FormConfirmPage.tsx @@ -1,9 +1,9 @@ -import { ArrowLeft } from 'src/shared/ui/icons'; import { Button } from 'src/shared/ui/Button/Button'; import { useMyProfileStore } from 'src/entities/profile/model/myProfileStore'; import { useIdealPartnerStore } from 'src/entities/ideal_partner/model/idealPartnerStore'; import styles from './FormConfirmPage.module.css'; import { ProfileTab } from 'src/widgets/ProfileTab/ProfileTab'; +import { Header } from 'src/shared/ui/layout/Header/Header'; export const FormConfirmPage = ({ onClickNextStep }: { onClickNextStep: () => void }) => { const profile = useMyProfileStore((state) => state); @@ -11,11 +11,7 @@ export const FormConfirmPage = ({ onClickNextStep }: { onClickNextStep: () => vo return ( <>
-
- -
+

입력한 정보를 마지막으로 확인해주세요.

각 답변을 선택하면 수정이 가능합니다. diff --git a/src/pages/form/ideal_partner/IdealPartnerPage.module.css b/src/pages/form/ideal_partner/IdealPartnerPage.module.css index 63a920a..b188fac 100644 --- a/src/pages/form/ideal_partner/IdealPartnerPage.module.css +++ b/src/pages/form/ideal_partner/IdealPartnerPage.module.css @@ -5,24 +5,15 @@ overflow: hidden; } -.Header { + +.TitleSection { margin-bottom: 30px; } -.HeaderBar { - margin-bottom: 15px; - height: 44px; - display: flex; - justify-content: space-between; - align-items: center; - +.FormCount { font-weight: 500; font-size: 16px; color: var(--color-neutral-40); - - & span { - margin-left: auto; - } } .Description { diff --git a/src/pages/form/ideal_partner/IdealPartnerPage.tsx b/src/pages/form/ideal_partner/IdealPartnerPage.tsx index 20b3281..5cf8885 100644 --- a/src/pages/form/ideal_partner/IdealPartnerPage.tsx +++ b/src/pages/form/ideal_partner/IdealPartnerPage.tsx @@ -1,11 +1,12 @@ -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { useProfileFirstName } from 'src/entities/profile/lib/useProfileFirstName'; import styles from './IdealPartnerPage.module.css'; -import { ArrowLeft } from 'src/shared/ui/icons'; import { Button } from 'src/shared/ui/Button/Button'; import { IdealPartnerStepMeta } from 'src/pages/form/ideal_partner/IdealPartnerStepMeta'; import { useIdealPartnerStore } from 'src/entities/ideal_partner/model/idealPartnerStore'; import { useIdealPartnerFormProcessStore } from 'src/processes/ideal_partner/_store/idealPartnerFormProcessStore'; +import { Header } from 'src/shared/ui/layout/Header/Header'; +import { Spacing } from 'src/shared/ui/Spacing/Spacing'; const Steps = Object.values(IdealPartnerStepMeta); const StepKeys = Object.keys(IdealPartnerStepMeta); @@ -30,20 +31,25 @@ export const IdealPartnerPage = ({ onClickNextStep }: { onClickNextStep: () => v } }; + const handleClickPrev = useCallback(() => { + setCurrentStep((prev) => Math.max(0, prev - 1)); + }, []); + return (
-
-
- {currentStepIdx > 0 && ( - setCurrentStep((prev) => Math.max(0, prev - 1))} /> - )} - +
0 ? handleClickPrev : undefined} + suffixSlot={ + {currentStepIdx + 1}/{Steps.length} -
+ } + /> + +

{currentStep.title({ name })}

{currentStep.description && {currentStep.description()}} -
+
{currentStep.form({})}
{urls.map((url) => ( diff --git a/src/processes/my_profile/QuestionForm/QuestionForm.tsx b/src/processes/my_profile/QuestionForm/QuestionForm.tsx index 8b66283..a2c5624 100644 --- a/src/processes/my_profile/QuestionForm/QuestionForm.tsx +++ b/src/processes/my_profile/QuestionForm/QuestionForm.tsx @@ -9,6 +9,7 @@ import { DateStyleForm } from 'src/processes/my_profile/QuestionForm/DateStyleFo import { FoodForm } from 'src/processes/my_profile/QuestionForm/FoodForm/FoodForm'; import { MovieForm } from 'src/processes/my_profile/QuestionForm/MovieForm/MovieForm'; import { PetForm } from 'src/processes/my_profile/QuestionForm/PetForm/PetForm'; +import { Theme } from 'src/shared/styles/constants'; type QuestionType = 'PET' | 'DATE_STYLE' | 'FOOD' | 'DRIVING' | 'BOOK' | 'MOVIE'; @@ -76,7 +77,7 @@ const QuestionButton = ({ text, filled, onClick }: { text: string; filled: boole widthType={'fill'} size={'M'} textAlign={'left'} - suffixSlot={filled ? : } + suffixSlot={filled ? : } onClick={onClick} > {text} diff --git a/src/processes/my_profile/_store/myProfileFormProcessStore.ts b/src/processes/my_profile/_store/myProfileFormProcessStore.ts index 6c7ba8b..e1d71cc 100644 --- a/src/processes/my_profile/_store/myProfileFormProcessStore.ts +++ b/src/processes/my_profile/_store/myProfileFormProcessStore.ts @@ -9,9 +9,11 @@ type MyProfileFormProcessStore = { type Action = { addTouchedStep: (step: MyProfileStepKey) => void; + hasTouchedStep: (step: MyProfileStepKey) => boolean; }; export const useMyProfileFormProcessStore = create((set, get) => ({ touchedSteps: new Set(), addTouchedStep: (step) => set({ touchedSteps: new Set([...get().touchedSteps, step]) }), + hasTouchedStep: (step) => get().touchedSteps.has(step), })); diff --git a/src/shared/styles/constants.ts b/src/shared/styles/constants.ts new file mode 100644 index 0000000..52c15fb --- /dev/null +++ b/src/shared/styles/constants.ts @@ -0,0 +1,16 @@ +export const Theme = { + color: { + primary: '#d752ff', + positive: '#3bb5a3', + negative: '#f35b5b', + neutral90: '#121113', + neutral80: '#333036', + neutral70: '#534e55', + neutral60: '#726d74', + neutral50: '#918b92', + neutral40: '#afaab1', + neutral30: '#cdcace', + neutral20: '#ebeaeb', + neutral10: '#f5f5f5', + }, +} as const; diff --git a/src/shared/ui/Accordion/Accordion.tsx b/src/shared/ui/Accordion/Accordion.tsx index cbfd262..6be990f 100644 --- a/src/shared/ui/Accordion/Accordion.tsx +++ b/src/shared/ui/Accordion/Accordion.tsx @@ -1,6 +1,7 @@ import { PropsWithChildren } from 'react'; import styles from './Accordion.module.css'; import { ArrowRight } from 'src/shared/ui/icons'; +import { Theme } from 'src/shared/styles/constants'; type AccordionProps = PropsWithChildren<{ summary: string; @@ -12,7 +13,7 @@ export const Accordion = ({ summary, initialOpen = false, children }: AccordionP {summary} - + {children} diff --git a/src/shared/ui/BottomSheet/BottomSheet.tsx b/src/shared/ui/BottomSheet/BottomSheet.tsx index 9ec4fba..3fc51f7 100644 --- a/src/shared/ui/BottomSheet/BottomSheet.tsx +++ b/src/shared/ui/BottomSheet/BottomSheet.tsx @@ -1,8 +1,7 @@ import { Sheet } from 'react-modal-sheet'; import { PropsWithChildren, ReactNode } from 'react'; -import { Button } from 'src/shared/ui/Button/Button'; -import { ArrowLeft, Close } from 'src/shared/ui/icons'; import styles from './BottomSheet.module.css'; +import { Header } from 'src/shared/ui/layout/Header/Header'; type BottomSheetProps = PropsWithChildren[0]>; @@ -20,27 +19,10 @@ type BottomSheetHeaderProps = PropsWithChildren<{ onPrev?: () => void; }>; -const BottomSheetHeader = ({ onPrev, onClose, children }: BottomSheetHeaderProps) => { +const BottomSheetHeader = (props: BottomSheetHeaderProps) => { return ( - - {onPrev && ( - - )} - {children} - {onClose && ( - - )} + +
); }; diff --git a/src/shared/ui/Chip/InputTriggerChip/InputTriggerChip.tsx b/src/shared/ui/Chip/InputTriggerChip/InputTriggerChip.tsx index 71cd302..f4b1dfa 100644 --- a/src/shared/ui/Chip/InputTriggerChip/InputTriggerChip.tsx +++ b/src/shared/ui/Chip/InputTriggerChip/InputTriggerChip.tsx @@ -1,6 +1,7 @@ import { Chip } from 'src/shared/ui/Chip/Chip'; import { Plus } from 'src/shared/ui/icons'; import styles from './InputTriggerChip.module.css'; +import { Theme } from 'src/shared/styles/constants'; type InputTriggerChipProps = { onClick: () => void; @@ -10,7 +11,7 @@ export const InputTriggerChip = ({ onClick }: InputTriggerChipProps) => { return (
- + 직접 추가하기
diff --git a/src/shared/ui/Profile/ProfileCellHeader.tsx b/src/shared/ui/Profile/ProfileCellHeader.tsx index 1c539fb..4f2676c 100644 --- a/src/shared/ui/Profile/ProfileCellHeader.tsx +++ b/src/shared/ui/Profile/ProfileCellHeader.tsx @@ -1,11 +1,12 @@ import styles from './Profile.module.css'; import { Edit } from 'src/shared/ui/icons'; +import { Theme } from 'src/shared/styles/constants'; export const ProfileCellHeader = ({ title }: { title: string }) => { return (
{title} - +
); }; diff --git a/src/shared/ui/Radio/Radio.tsx b/src/shared/ui/Radio/Radio.tsx index 26f645a..bb0f5c2 100644 --- a/src/shared/ui/Radio/Radio.tsx +++ b/src/shared/ui/Radio/Radio.tsx @@ -1,6 +1,7 @@ import styles from './Radio.module.css'; import { DetailedHTMLProps, InputHTMLAttributes } from 'react'; import { CheckedRadio, UncheckedRadio } from 'src/shared/ui/icons'; +import { Theme } from 'src/shared/styles/constants'; export type RadioProps = Exclude, HTMLInputElement>, 'type'> & { label: string; @@ -10,7 +11,11 @@ export const Radio = ({ className, label, ...props }: RadioProps) => { return ( ); diff --git a/src/shared/ui/Toast/toastOption.ts b/src/shared/ui/Toast/toastOption.ts index 4d493e4..93e264e 100644 --- a/src/shared/ui/Toast/toastOption.ts +++ b/src/shared/ui/Toast/toastOption.ts @@ -1,8 +1,9 @@ import { ToastOptions } from 'react-hot-toast'; +import { Theme } from 'src/shared/styles/constants'; export const ToastOption: ToastOptions = { style: { - background: '#918b92', + background: Theme.color.neutral50, borderRadius: '32px', color: '#fff', fontSize: '16px', diff --git a/src/shared/ui/layout/Header/Header.module.css b/src/shared/ui/layout/Header/Header.module.css new file mode 100644 index 0000000..e144927 --- /dev/null +++ b/src/shared/ui/layout/Header/Header.module.css @@ -0,0 +1,17 @@ +.Header { + padding: 10px 0; + display: flex; + justify-content: space-between; + align-items: center; +} + +.CloseButton { + margin-left: auto; +} + +.Suffix { + margin-left: auto; +} +.Prefix { + margin-right: auto; +} \ No newline at end of file diff --git a/src/shared/ui/layout/Header/Header.tsx b/src/shared/ui/layout/Header/Header.tsx new file mode 100644 index 0000000..fe2ab74 --- /dev/null +++ b/src/shared/ui/layout/Header/Header.tsx @@ -0,0 +1,43 @@ +import { Button } from 'src/shared/ui/Button/Button'; +import { ArrowLeft, Close } from 'src/shared/ui/icons'; +import styles from './Header.module.css'; +import { PropsWithChildren, ReactNode } from 'react'; +import { Theme } from 'src/shared/styles/constants'; + +export const Header = ({ + suffixSlot, + prefixSlot, + onPrev, + onClose, + children, +}: PropsWithChildren<{ + suffixSlot?: ReactNode; + prefixSlot?: ReactNode; + onClose?: () => void; + onPrev?: () => void; +}>) => { + return ( +
+ {onPrev && ( + + )} + {prefixSlot && {prefixSlot}} + {children} + {suffixSlot && {suffixSlot}} + {onClose && ( + + )} +
+ ); +}; diff --git a/src/widgets/GenerateFormLink/GenerateFormLinkBottomSheet.tsx b/src/widgets/GenerateFormLink/GenerateFormLinkBottomSheet.tsx index f623099..4746b75 100644 --- a/src/widgets/GenerateFormLink/GenerateFormLinkBottomSheet.tsx +++ b/src/widgets/GenerateFormLink/GenerateFormLinkBottomSheet.tsx @@ -5,6 +5,7 @@ import toast from 'react-hot-toast'; import styles from 'src/widgets/GenerateFormLink/GenerateFormLink.module.css'; import { Link, Refresh } from 'src/shared/ui/icons'; import { Button } from 'src/shared/ui/Button/Button'; +import { Theme } from 'src/shared/styles/constants'; export const GenerateFormLinkBottomSheet = ({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) => { return ( @@ -70,7 +71,7 @@ const GenerateFormBottomSheetContent = () => {

새로운 링크 생성

diff --git a/src/widgets/LocationSelectTable/LocationSelectTable.tsx b/src/widgets/LocationSelectTable/LocationSelectTable.tsx index e7107ca..b11c262 100644 --- a/src/widgets/LocationSelectTable/LocationSelectTable.tsx +++ b/src/widgets/LocationSelectTable/LocationSelectTable.tsx @@ -4,6 +4,7 @@ import { useState } from 'react'; import { Location } from 'src/entities/location/types/location'; import { locationListMock } from 'src/entities/location/api/__mock__/location.mock'; import { ScrollView } from 'src/shared/ui/ScrollView/ScrollView'; +import { Theme } from 'src/shared/styles/constants'; type Props = { selectedLocations: Location[]; @@ -42,9 +43,9 @@ export const LocationSelectTable = ({ selectedLocations, selectLocation }: Props > {town.townName} {selectedLocations.some(({ town: selectedTown }) => selectedTown[0].town === town.town) ? ( - + ) : ( - + )} ))}