diff --git a/docs/conventions/wds-component-usage.md b/docs/conventions/wds-component-usage.md index fd28c06..8cfb4b6 100644 --- a/docs/conventions/wds-component-usage.md +++ b/docs/conventions/wds-component-usage.md @@ -63,6 +63,11 @@ WDS 컴포넌트만 합산하면 파일 안에서 **약 350회 이상**의 인 | `Icon/Normal/Home` | Bottom Nav "홈" 탭(Normal 상태) | `980:35475` | | `Icon/Normal/Ticket` | Bottom Nav "행사" 탭(Normal 상태) | `980:35529` | | `Icon/Normal/List` | Bottom Nav "게시판" 탭(Normal 상태) | `980:35703` | +| `Tab/Tab` | 게시판-공지 화면(`1256:81776`) 상단 전체/일반 공지/제휴 공지 탭 | `440:7593` — [문서](https://montage.wanted.co.kr/docs/components/navigations/tab/design). 코드 export는 `Tab`(컨텍스트)+`TabList`+`TabListItem` 3개 조합(`TabItem` 같은 단일 export 아님) — `node_modules/@wanteddev/wds/dist/components/tab/`에서 확인 | + +### 반례 — 게시판-공지 고정 핀 아이콘은 `Icon/Normal/Pin`(WDS)이 아니었다 + +`search_design_system`으로 "Icon/Normal/Pin" 이름이 WDS 라이브러리에 있는 걸 확인하고 한 번은 위 표에 WDS로 기록했었다. 그런데 `/figma-check`로 재검증하며 해당 인스턴스(`1256:76326`)의 실제 SVG 에셋을 직접 열어보니, `wds-icon`의 `IconPin`/`IconPinFill`(둘 다 똑바로 선 압정 모양, `currentColor` 상속)과 달리 **기울어진 압정 모양 + `#0066FF` 고정 fill**이 박힌 별개의 도형이었다 — `get_design_context` 응답의 "Component descriptions"에도 이 아이콘 항목이 아예 없었는데(있었다면 처음부터 알아챘을 것) 그때는 놓치고 이름 매칭만 믿었다. **이름이 WDS 컴포넌트와 같아도, `search_design_system` 이름 매칭만으로 확정하지 말고 이 문서의 "완전 확정 방법"(실제 SVG/컴포넌트 설명 대조)까지 거쳐야 한다.** 코드는 실제 Figma SVG를 그대로 받아 `src/assets/icons/pin.svg` + `src/features/notice/components/NoticeCard.tsx` 참고. 코드에서는 `@wanteddev/wds-icon`의 `IconSearch`/`IconBell`/`IconHome`/`IconTicket`/`IconList`로 대응된다(각각 default export를 `index.d.ts`에서 named export로 재노출). `Segmented Control`은 `@wanteddev/wds`의 `SegmentedControl`/`SegmentedControlItem`으로 대응된다. diff --git a/src/app/App.tsx b/src/app/App.tsx index 8dd6739..c666681 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,6 +1,7 @@ import { Route, Routes } from "react-router-dom"; import HomeScreen from "@/features/home/HomeScreen"; +import NoticeListScreen from "@/features/notice/NoticeListScreen"; import RentalListScreen from "@/features/rental/RentalListScreen"; // 데스크톱에서 보기 좋게 아이폰 화면 크기로 가운데 정렬만 해준다. @@ -9,6 +10,7 @@ function App() {
} path="/" /> + } path="/notice" /> } path="/rental" />
diff --git a/src/assets/icons/pin.svg b/src/assets/icons/pin.svg new file mode 100644 index 0000000..1a0fa0b --- /dev/null +++ b/src/assets/icons/pin.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/features/home/HomeScreen.tsx b/src/features/home/HomeScreen.tsx index 7a89447..466dd13 100644 --- a/src/features/home/HomeScreen.tsx +++ b/src/features/home/HomeScreen.tsx @@ -13,6 +13,8 @@ function HomeScreen() { setBottomNavValue(value); if (value === "rental") { navigate("/rental"); + } else if (value === "board") { + navigate("/notice"); } }; diff --git a/src/features/notice/NoticeListScreen.tsx b/src/features/notice/NoticeListScreen.tsx new file mode 100644 index 0000000..5f73ab1 --- /dev/null +++ b/src/features/notice/NoticeListScreen.tsx @@ -0,0 +1,108 @@ +import { + Tab, + TabList, + TabListItem, + TopNavigation, + TopNavigationButton, +} from "@wanteddev/wds"; +import { IconBell, IconSearch } from "@wanteddev/wds-icon"; +import { Fragment, useState } from "react"; +import { useNavigate } from "react-router-dom"; + +import type { BottomNavValue } from "@/components/ui/BottomNav"; +import ScreenLayout from "@/components/ui/ScreenLayout"; +import NoticeCard from "@/features/notice/components/NoticeCard"; +import { + NOTICES, + type NoticeCategory, +} from "@/features/notice/constants/notices"; + +type NoticeTab = "all" | "general" | "partnership"; + +const TAB_CATEGORY: Record = { + all: "all", + general: "일반", + partnership: "제휴", +}; + +// Figma: 게시판 - 공지 (nodeId 1256:81776), 탭 선택 시 (nodeId 1256:81812) +function NoticeListScreen() { + const [tab, setTab] = useState("all"); + const [bottomNavValue, setBottomNavValue] = useState("board"); + const navigate = useNavigate(); + + const handleBottomNavValueChange = (value: BottomNavValue) => { + setBottomNavValue(value); + if (value === "home") { + navigate("/"); + } else if (value === "rental") { + navigate("/rental"); + } + }; + + const notices = NOTICES.filter( + (notice) => tab === "all" || notice.category === TAB_CATEGORY[tab], + ); + + return ( + setTab(value as NoticeTab)} + value={tab} + > + + 전체 + 일반 공지 + 제휴 공지 + + + } + trailingContent={ + <> + + + + + + + + } + variant="display" + > + {/* 열린피드백 게시판 전환은 이번 범위 밖이라 비활성 텍스트로만 표시 */} + + 공지 + 열린피드백 + + + } + onBottomNavValueChange={handleBottomNavValueChange} + > +
+ {notices.map((notice, index) => ( + + + {index < notices.length - 1 && ( +
+
+
+ )} + + ))} +
+ + ); +} + +export default NoticeListScreen; diff --git a/src/features/notice/components/NoticeCard.tsx b/src/features/notice/components/NoticeCard.tsx new file mode 100644 index 0000000..70e7aeb --- /dev/null +++ b/src/features/notice/components/NoticeCard.tsx @@ -0,0 +1,50 @@ +import pinIcon from "@/assets/icons/pin.svg"; + +interface NoticeCardProps { + title: string; + date: string; + category: string; + isPinned?: boolean; + hasThumbnail?: boolean; +} + +// Figma: Notice-card (nodeId 1256:81801) — Stream 로컬 컴포넌트, WDS 아님 +// (docs/conventions/wds-component-usage.md "제외됨" 표 참고) +// 고정 핀 아이콘은 Icon/Normal/Pin(WDS)로 오판했던 것을 재검증해서 바로잡음 — 실제 Figma +// 에셋은 기울어진 모양 + #0066FF 고정색이라 wds-icon의 IconPin/IconPinFill(직선 모양)과 +// 다른 도형이라 대응 export가 없다. src/assets/icons/pin.svg로 원본 SVG를 그대로 받아 씀. +function NoticeCard({ + title, + date, + category, + isPinned = false, + hasThumbnail = false, +}: NoticeCardProps) { + return ( +
+
+ {hasThumbnail ? ( +
+ {isPinned && } +

+ {title} +

+
+ ) : ( +

{title}

+ )} +

+ {date} | {category} +

+
+ {/* #d9d9d9는 실제 공지 썸네일 이미지가 아직 없어 Figma가 그대로 쓰는 자리표시 회색이라 시맨틱 토큰 없이 하드코딩 */} + {hasThumbnail && ( +
+ )} +
+ ); +} + +export default NoticeCard; diff --git a/src/features/notice/constants/notices.ts b/src/features/notice/constants/notices.ts new file mode 100644 index 0000000..32b933b --- /dev/null +++ b/src/features/notice/constants/notices.ts @@ -0,0 +1,48 @@ +export type NoticeCategory = "일반" | "제휴"; + +export interface Notice { + id: string; + title: string; + date: string; + category: NoticeCategory; + isPinned?: boolean; + hasThumbnail?: boolean; +} + +// Figma: 게시판 - 공지 (nodeId 1256:81776) 목업 데이터 +export const NOTICES: Notice[] = [ + { + category: "일반", + date: "2026.09.03", + hasThumbnail: true, + id: "1", + isPinned: true, + title: "2026-2학기 사물함 신청 안내", + }, + { + category: "일반", + date: "2026.09.03", + hasThumbnail: true, + id: "2", + title: "2026-2학기 사물함 신청 안내", + }, + { + category: "일반", + date: "2026.09.03", + id: "3", + title: "2026-2학기 사물함 신청 안내", + }, + { + category: "제휴", + date: "2026.09.03", + id: "4", + title: "2026-2학기 사물함 신청 안내", + }, + { + category: "제휴", + date: "2026.09.03", + hasThumbnail: true, + id: "5", + title: "2026-2학기 사물함 신청 안내", + }, +]; diff --git a/src/features/rental/RentalListScreen.tsx b/src/features/rental/RentalListScreen.tsx index 25e9086..93ce61b 100644 --- a/src/features/rental/RentalListScreen.tsx +++ b/src/features/rental/RentalListScreen.tsx @@ -26,6 +26,8 @@ function RentalListScreen() { setBottomNavValue(value); if (value === "home") { navigate("/"); + } else if (value === "board") { + navigate("/notice"); } }; diff --git a/src/index.css b/src/index.css index 509d881..509ff63 100644 --- a/src/index.css +++ b/src/index.css @@ -5,16 +5,19 @@ /* WDS(@wanteddev/wds/global.css)의 semantic/atomic CSS 변수를 Tailwind 색상 토큰으로 별칭 연결. */ /* 값을 새로 정의하지 않고 WDS 걸 그대로 참조하므로 다크 테마(html[data-theme=dark])도 자동 반영된다. */ /* Figma get_variable_defs로 뽑은 실제 값과 대조해서 확정함(빌릴게 화면, nodeId 1243:73331 기준). */ + /* label-disable·line-normal-alternative는 게시판-공지 화면(nodeId 1256:81776) 추가 시 확정. */ --color-label-strong: var(--semantic-label-strong); --color-label-normal: var(--semantic-label-normal); --color-label-alternative: var(--semantic-label-alternative); --color-label-assistive: var(--semantic-label-assistive); + --color-label-disable: var(--semantic-label-disable); --color-background-normal: var(--semantic-background-normal-normal); --color-background-alternative: var(--semantic-background-normal-alternative); --color-line-solid-neutral: var(--semantic-line-solid-neutral); --color-line-normal-neutral: var(--semantic-line-normal-neutral); + --color-line-normal-alternative: var(--semantic-line-normal-alternative); --color-primary: var(--semantic-primary-normal); --color-primary-subtle: var(--atomic-blue-95);