From 7c5b6966091e599656b832c2638f1a7b992f1c2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9C=EC=A4=80?= <104981505+xeoxxn@users.noreply.github.com> Date: Thu, 10 Sep 2026 15:01:08 +0900 Subject: [PATCH 1/2] =?UTF-8?q?refactor:=20=ED=99=94=EB=A9=B4=20=ED=97=A4?= =?UTF-8?q?=EB=8D=94=EB=A5=BC=20ScreenHeader=20=ED=95=98=EB=82=98=EB=A1=9C?= =?UTF-8?q?=20=ED=86=B5=ED=95=A9(variant/title/leading/trailing=20props)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Figma의 Top Navigation 패턴(타이틀 정렬, leading/trailing 조합, 게시판류 토글형 2단 타이틀)을 props로 고르게 만들었다. display variant에서는 leading을 타입으로 막았다 — WDS 쪽 스타일이 display일 때 leading/trailing 포지셔닝을 안 줘서 레이아웃이 깨진다. --- docs/conventions/component-convention.md | 1 + src/components/ui/ScreenHeader.tsx | 112 +++++++++++++++------ src/features/bililge/BililgeListScreen.tsx | 23 ++++- src/features/home/HomeScreen.tsx | 18 +++- 4 files changed, 120 insertions(+), 34 deletions(-) diff --git a/docs/conventions/component-convention.md b/docs/conventions/component-convention.md index 401208a..6d048bf 100644 --- a/docs/conventions/component-convention.md +++ b/docs/conventions/component-convention.md @@ -40,6 +40,7 @@ interface RentalItemCardProps { - 예: Figma `Chip/Chip` → `import { Chip } from '@wanteddev/wds'` - 아이콘은 `@wanteddev/wds-icon`에서 가져온다. - **WDS 컴포넌트 내부를 임의로 오버라이드하지 않는다.** 간격·배치 같은 레이아웃 조정은 감싸는 wrapper에서 한다. +- **화면 헤더는 화면에서 `TopNavigation`을 직접 새로 조립하지 않고 `src/components/ui/ScreenHeader.tsx`를 거친다.** Figma의 Top Navigation 패턴(타이틀 정렬, leading/trailing 조합, 게시판류의 토글형 타이틀 등)을 `ScreenHeader`의 props(`variant`/`title`/`leading`/`trailing`)로 고르게 돼 있다 — 화면마다 손으로 다시 조립하면 컨벤션이 흩어진다. 새 헤더 패턴이 필요하면 `ScreenHeader`부터 확장한다(`docs/plans/unified-screen-header.md` 참고). ## 4. Stream 고유 UI (신규 컴포넌트) diff --git a/src/components/ui/ScreenHeader.tsx b/src/components/ui/ScreenHeader.tsx index e989900..a67881f 100644 --- a/src/components/ui/ScreenHeader.tsx +++ b/src/components/ui/ScreenHeader.tsx @@ -1,40 +1,90 @@ -import { TopNavigationButton, Typography } from "@wanteddev/wds"; -import { IconBell, IconSearch } from "@wanteddev/wds-icon"; +import { TopNavigation, Typography } from "@wanteddev/wds"; +import type { ReactNode } from "react"; -interface ScreenHeaderProps { - title: string; +interface ScreenHeaderToggleTitle { + options: string[]; + activeIndex: number; + onChange?: (index: number) => void; } -// Figma: Top Navigation(nodeId 1765:71193)의 타이틀 + 검색/알림 아이콘 부분 — 행사·빌릴게 등 -// 여러 화면에서 완전히 동일하게 반복되는 진짜 공통 패턴이라 재사용 컴포넌트로 뺐다. -// "Tool" 슬롯(세그먼트 토글 등)은 화면마다 값·동작이 달라서(대여/반납 vs 행사/신청내역) -// 여기 포함하지 않고 각 화면이 자기 본문에서 직접 그린다. -// -// 이 Top Navigation은 WDS Top Navigation/Resource/Contents가 아니라 Stream 로컬 -// 컴포넌트다 — 세로 패딩 12px + Title 3/Bold(32px)로 총 56px인데, WDS display variant는 -// 세로 패딩이 16px 고정이라 64px이 된다. 그래서 레이아웃만 직접 구현하고, 아이콘 버튼 -// (TopNavigationButton)은 그대로 재사용한다. -function ScreenHeader({ title }: ScreenHeaderProps) { +type ScreenHeaderTitle = string | ScreenHeaderToggleTitle; + +function isToggleTitle( + title: ScreenHeaderTitle, +): title is ScreenHeaderToggleTitle { + return typeof title !== "string"; +} + +// Figma: 게시판류 화면의 "공지 | 열린피드백" 같은 2단 탭 타이틀(nodeId 1256:81792 "Board Title"). +// 활성 옵션은 Label/Strong(검정), 비활성은 Label/Disable(흐림) — 둘 다 같은 Title 3/Bold(24px). +function ScreenHeaderToggleTitle({ + options, + activeIndex, + onChange, +}: ScreenHeaderToggleTitle) { return ( -
- - {title} - -
- - - - - - -
+
+ {options.map((option, index) => { + const active = index === activeIndex; + return ( + + ); + })}
); } +type ScreenHeaderProps = + | { + variant?: "display"; + title?: ScreenHeaderTitle; + trailing?: ReactNode; + } + | { + variant: "normal"; + title?: ScreenHeaderTitle; + leading?: ReactNode; + trailing?: ReactNode; + }; + +// Figma: Top Navigation/Resource/Contents — 화면마다 따로 조립하던 헤더를 여기 하나로 모았다. +// WDS `TopNavigation`을 감싸는 얇은 조합 레이어일 뿐, 내부 스타일은 오버라이드하지 않는다 +// (component-convention.md "WDS 컴포넌트 내부를 임의로 오버라이드하지 않는다"). +// +// variant="display"(기본값)에서는 leading을 받지 않는다 — WDS 쪽 스타일 자체가 display일 때 +// leading/trailing 포지셔닝(topNavigationLeftIconStyle/RightIconStyle)을 안 줘서 레이아웃이 +// 깨진다. leading이 필요한 화면(뒤로가기·닫기 버튼 등)은 variant="normal"을 쓴다. +// +// title이 문자열이면 그대로 렌더링하고, { options, activeIndex } 형태(활성 상태가 있는 경우)면 +// 게시판류의 토글형 2단 타이틀로 렌더링한다. +// +// search variant(타이틀 자리가 검색 필드로 바뀌는 패턴)는 이번 범위에서 뺐다 — +// docs/plans/unified-screen-header.md 참고. 화면이 실제로 생기면 그때 추가한다. +function ScreenHeader(props: ScreenHeaderProps) { + const { title, trailing } = props; + const leading = props.variant === "normal" ? props.leading : undefined; + + return ( + + {title !== undefined && + (isToggleTitle(title) ? : title)} + + ); +} + export default ScreenHeader; diff --git a/src/features/bililge/BililgeListScreen.tsx b/src/features/bililge/BililgeListScreen.tsx index 17c716a..a6725e6 100644 --- a/src/features/bililge/BililgeListScreen.tsx +++ b/src/features/bililge/BililgeListScreen.tsx @@ -1,4 +1,9 @@ -import { SegmentedControl, SegmentedControlItem } from "@wanteddev/wds"; +import { + SegmentedControl, + SegmentedControlItem, + TopNavigationButton, +} from "@wanteddev/wds"; +import { IconBell, IconSearch } from "@wanteddev/wds-icon"; import { startTransition, useState } from "react"; import ScreenHeader from "@/components/ui/ScreenHeader"; @@ -18,7 +23,21 @@ function BililgeListScreen() { const [rentalItem, setRentalItem] = useState(null); const [rentalSheetOpen, setRentalSheetOpen] = useState(false); - useScreenHeader(); + useScreenHeader( + + + + + + + + + } + />, + ); return (
diff --git a/src/features/home/HomeScreen.tsx b/src/features/home/HomeScreen.tsx index 350b856..3f44520 100644 --- a/src/features/home/HomeScreen.tsx +++ b/src/features/home/HomeScreen.tsx @@ -1,3 +1,5 @@ +import { TopNavigationButton } from "@wanteddev/wds"; +import { IconBell, IconSearch } from "@wanteddev/wds-icon"; import { Link } from "react-router-dom"; import ScreenHeader from "@/components/ui/ScreenHeader"; @@ -5,7 +7,21 @@ import { useScreenHeader } from "@/components/ui/useScreenHeader"; // 홈 화면 콘텐츠는 아직 없어서, 라우팅이 실제로 동작하는지 확인할 placeholder만 둔다. function HomeScreen() { - useScreenHeader(); + useScreenHeader( + + + + + + + + + } + />, + ); return (
From bfa23b1fb23c2db24949eb0a551ae6d5d1954e46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9C=EC=A4=80?= <104981505+xeoxxn@users.noreply.github.com> Date: Mon, 14 Sep 2026 19:49:19 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=EB=B9=8C=EB=A6=B4=EA=B2=8C/?= =?UTF-8?q?=ED=99=88=20=ED=97=A4=EB=8D=94=EB=A5=BC=20Figma=20=EB=A1=9C?= =?UTF-8?q?=EC=BB=AC=20Top=20Navigation=20=EC=82=AC=EC=96=91(56px)?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Figma의 Top Navigation이 WDS Top Navigation/Resource/Contents에서 Stream 로컬 컴포넌트(nodeId 1765:71193)로 바뀌었다. 세로 패딩 12px + Title 3/Bold(32px)로 총 56px인데, WDS display variant는 세로 패딩이 16px로 고정돼 있어 64px이 된다. variant="display"(기본값) 경로만 직접 구현으로 바꾸고, leading이 필요한 variant="normal"은 그대로 WDS TopNavigation을 재사용한다. --- src/components/ui/ScreenHeader.tsx | 61 ++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/src/components/ui/ScreenHeader.tsx b/src/components/ui/ScreenHeader.tsx index a67881f..4c18e9e 100644 --- a/src/components/ui/ScreenHeader.tsx +++ b/src/components/ui/ScreenHeader.tsx @@ -57,13 +57,14 @@ type ScreenHeaderProps = trailing?: ReactNode; }; -// Figma: Top Navigation/Resource/Contents — 화면마다 따로 조립하던 헤더를 여기 하나로 모았다. -// WDS `TopNavigation`을 감싸는 얇은 조합 레이어일 뿐, 내부 스타일은 오버라이드하지 않는다 -// (component-convention.md "WDS 컴포넌트 내부를 임의로 오버라이드하지 않는다"). +// variant="display"(기본값, 빌릴게/홈)는 더 이상 WDS `Top Navigation/Resource/Contents`가 아니다 — +// Figma가 별도 Stream 로컬 컴포넌트(nodeId 1765:71193 "Top Navigation")로 바뀌었다: 세로 패딩 +// 12px(기존 WDS display variant는 16px 고정이라 오버라이드 불가) + Title 3/Bold(32px)가 정확히 +// 들어가서 총 56px. leading은 이 패턴에서 쓴 적이 없어 그대로 받지 않는다. // -// variant="display"(기본값)에서는 leading을 받지 않는다 — WDS 쪽 스타일 자체가 display일 때 -// leading/trailing 포지셔닝(topNavigationLeftIconStyle/RightIconStyle)을 안 줘서 레이아웃이 -// 깨진다. leading이 필요한 화면(뒤로가기·닫기 버튼 등)은 variant="normal"을 쓴다. +// variant="normal"(모달형 닫기 버튼 등, leading 필요)은 아직 WDS `TopNavigation`을 그대로 쓴다 — +// Figma 쪽 해당 패턴은 안 바뀌었다(component-convention.md "WDS 컴포넌트 내부를 임의로 +// 오버라이드하지 않는다" 원칙 유지). // // title이 문자열이면 그대로 렌더링하고, { options, activeIndex } 형태(활성 상태가 있는 경우)면 // 게시판류의 토글형 2단 타이틀로 렌더링한다. @@ -72,18 +73,46 @@ type ScreenHeaderProps = // docs/plans/unified-screen-header.md 참고. 화면이 실제로 생기면 그때 추가한다. function ScreenHeader(props: ScreenHeaderProps) { const { title, trailing } = props; - const leading = props.variant === "normal" ? props.leading : undefined; + + if (props.variant === "normal") { + return ( + + {title !== undefined && + (isToggleTitle(title) ? ( + + ) : ( + title + ))} + + ); + } return ( - - {title !== undefined && - (isToggleTitle(title) ? : title)} - +
+
+ {title !== undefined && + (isToggleTitle(title) ? ( + + ) : ( + + {title} + + ))} +
+ {trailing !== undefined && ( +
{trailing}
+ )} +
); }