Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/conventions/terminology.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
| 슬랑제 | `seulrangjes` |
| 아카이빙 | `archives` |
| 검색 | `search` |
| 알림 | `notifications` |
| 챗봇 | `chat` |
| 파일 | `files` |
| 행사(event) | `events` |
Expand Down
28 changes: 28 additions & 0 deletions docs/conventions/wds-component-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -621,3 +621,31 @@ Figma는 헤더 배경이 없어서 배너 그림자(`Shadow/Spread/Small`, 60px
### 빈 상태는 WDS `FallbackView`가 아니라 홈 로컬 `HomeEmptyState`

일러스트 44px + 문구 한 줄 + 작은 버튼이라 행사 목록 Empty State 때와 같은 이유(`FallbackView`의 일러스트 폭·패딩 고정)로 맞지 않는다. 그림은 빌릴게 반납 빈 상태(`icons/bililge-empty/return-items.svg`)·행사 목록 빈 상태(`icons/events/empty-events.svg`)와 같은 도형이라 크기만 줄여 그대로 쓴다.

## 알림(`3595:107100` 목록 / `3595:107112` empty / `3628:110837` 설정) 구현 중 확정된 매핑

코드는 `src/features/notifications/` 참고.

| Figma | 코드 | 확인 내용 |
|---|---|---|
| `Switch/Switch` (`679:16080`, Small) | `Switch size="small"` | 39×24, 켜짐 배경 `Primary/Normal`(#0066FF), 꺼짐 `Fill/Strong` — 실측으로 Figma와 일치한다. 값은 `checked`/`onCheckedChange`로 제어한다 |
| `Icon/Normal/Setting` (`492:5735`) | `IconSetting` | 알림 목록 헤더 트레일링 설정 버튼. `ScreenHeader variant="normal"`의 `trailing` + `TopNavigationButton variant="icon"` |
| `Icon/Normal/Chevron Left` | `IconChevronLeft` | 뒤로가기 — 다른 `variant="normal"` 화면과 같다 |
| `Divider(new)` 8px (`487:19439`) | `<div className="h-2 … bg-background-alternative">` | 검색 전체 탭의 섹션 구분 띠와 같은 로컬 처리 |
| `Icon/Normal/Megaphone`·`Icon/Normal/Ticket` | 쓰지 않음 | 알림 항목의 카테고리 아이콘은 WDS 아이콘이 아니라 24px 배지 전체(`알림 - 공지` 등 6종)가 Stream 로컬 에셋이다. 배경(`#F7F7F8`, radius 6)과 아이콘 색이 종류마다 고정이라 `src/assets/icons/notifications/*.svg`로 받았다 |

### `Notification Item`은 Stream 로컬이다

항목 하나(`3595:107106`)는 카테고리 배지·라벨·상대 시간 줄 + 메시지 줄로 이뤄진 Stream 고유 목록 행이다. WDS `List`/`Card` 계열과 구조가 달라 `NotificationsItem`으로 새로 만들었고 텍스트는 `Typography`로 채웠다(라벨 `caption1`/`regular`, 시간 `caption2`/`regular`, 메시지 `label1-reading`/`medium`).

- 안 읽은 항목만 `Atomic/Blue/99` 배경이라 `index.css`에 `--color-notification-unread` 토큰을 추가했다. **아이콘 색은 읽음 여부와 상관없다** — 처음엔 첫 항목만 진한 아이콘으로 보여 읽음 표현이라고 오해하기 쉬운데, `빌릴게 반납`·`공지`·`행사`는 원래 진한 파랑, `빌릴게 대여`·`열린피드백`은 원래 연한 파랑이다.
- 항목을 눌렀을 때 이동은 디자인에 없어서 읽음 처리까지만 넣었다.
- 에셋은 `download_assets`의 whole-node svg 내보내기에 컴포넌트 세트 배경(회색 사각형, 가로 5천 px짜리 프레임 도형)이 같이 딸려 와서, 그중 `알림 - …` 24px 배지 그룹만 꺼내 `viewBox="0 0 24 24"` svg로 다시 감쌌다. 공지·행사·열린피드백은 `download_assets`의 `svgAssets`가 회전한 Ratio 레이어 조각으로 쪼개져 나와서 쓸 수 없었다.

### Empty State는 폭을 고정하지 않는다

행사·검색 empty와 같은 뼈대지만 Figma 컨테이너 폭 203px은 가장 긴 문구("새로운 소식이 있으면 알려 드릴게요.")에 맞춘 값이다. `w-[203px]`로 고정하면 폰트 폭이 0.2px만 넓어도(Pretendard를 못 불러온 환경에서 실제로 203.2px) 마지막 글자 "요."가 다음 줄로 넘어간다 — 문구 폭에 맡긴다. 수직 위치는 프레임 전체 높이 기준 가운데(헤더 56px만큼 `pb-14`로 올림)이고, 812px 뷰포트에서 중심이 406px로 Figma와 같다.

### 설정 화면의 "전체 알림"은 상태를 따로 두지 않는다

Figma 초기 상태가 전체 꺼짐 + 빌릴게만 켜짐이라 "전체"는 독립된 값이 아니라 카테고리가 모두 켜졌는지로 파생한다. 누르면 네 카테고리를 같은 값으로 맞추고, 하나라도 끄면 전체가 꺼진다. 값은 화면 state뿐이라 새로고침하면 초기값으로 돌아간다(서버 API 연동 때 교체).
20 changes: 20 additions & 0 deletions src/app/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import LockersLockerSelectScreen from "@/features/lockers/LockersLockerSelectScr
import LockersSectionSelectScreen from "@/features/lockers/LockersSectionSelectScreen";
import NoticesDetailScreen from "@/features/notices/NoticesDetailScreen";
import NoticesListScreen from "@/features/notices/NoticesListScreen";
import NotificationsScreen from "@/features/notifications/NotificationsScreen";
import NotificationsSettingsScreen from "@/features/notifications/NotificationsSettingsScreen";
import SearchScreen from "@/features/search/SearchScreen";

// 앱의 모든 라우트는 이 객체 배열 한곳에서 정의한다 — 새 화면은 여기에 라우트를 추가한다.
Expand Down Expand Up @@ -135,6 +137,24 @@ const routes = [
} satisfies ScreenRouteHandle,
path: "/search",
},
{
element: <NotificationsScreen />,
// Figma 알림 화면에는 Bottom Nav가 없고(뒤로가기로 진입한 화면에 복귀) 배경이 흰 면이다
handle: {
background: "normal",
hasBottomNav: false,
} satisfies ScreenRouteHandle,
path: "/notifications",
},
{
element: <NotificationsSettingsScreen />,
// 알림 화면의 설정 아이콘으로 들어가는 화면 — 알림 화면과 같은 이유로 Bottom Nav 없이 흰 면이다
handle: {
background: "normal",
hasBottomNav: false,
} satisfies ScreenRouteHandle,
path: "/notifications/settings",
},
{
element: <ChatEntryScreen />,
// Figma 챗봇 진입 화면에는 Bottom Nav가 없다(뒤로가기로 홈에 복귀). 흰 배경 위에
Expand Down
6 changes: 6 additions & 0 deletions src/assets/icons/notifications/bililge-rental.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/assets/icons/notifications/bililge-return.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions src/assets/icons/notifications/empty-notifications.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions src/assets/icons/notifications/event.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/assets/icons/notifications/feedback.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions src/assets/icons/notifications/locker.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions src/assets/icons/notifications/notice.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/entities/notifications/notificationsApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { NOTIFICATIONS } from "@/entities/notifications/notificationsMock";
import type { NotificationItem } from "@/entities/notifications/types";
import { mockupApi } from "@/lib/mockupApi";

// 실 API가 붙으면 함수 안쪽만 요청 코드로 바꾼다 — 화면은 이 함수로만 데이터를 받는다.
export function fetchNotifications(): Promise<NotificationItem[]> {
return mockupApi("notifications", () => NOTIFICATIONS);
}
50 changes: 50 additions & 0 deletions src/entities/notifications/notificationsMock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { NotificationItem } from "@/entities/notifications/types";

// Figma: 알림 (nodeId 3595:107100) 목업 데이터 — 첫 항목만 안 읽은 알림이다.
export const NOTIFICATIONS: NotificationItem[] = [
{
id: "1",
isRead: false,
message: "우산 반납이 완료됐어요.",
timeLabel: "1시간 전",
type: "bililgeReturn",
},
{
id: "2",
isRead: true,
message:
"관리자가 대여 신청을 승인했어요. 학생회실에 방문해 우산을 대여해 주세요.",
timeLabel: "1시간 전",
type: "bililgeRental",
},
{
id: "3",
isRead: true,
message: "새로운 공지사항이 올라왔어요. 자세한 내용을 확인해 주세요.",
timeLabel: "1시간 전",
type: "notices",
},
{
id: "4",
isRead: true,
message:
"열린피드백에 새로운 답변이 올라왔어요. 어떤 답변인지 확인해 보세요.",
timeLabel: "1시간 전",
type: "feedbacks",
},
{
id: "5",
isRead: true,
message:
"‘2026-02 기말고사 간식행사’가 내일 열려요. 행사 일정과 장소를 확인해 주세요.",
timeLabel: "1시간 전",
type: "events",
},
{
id: "6",
isRead: true,
message: "사물함을 신청했어요. 배정된 사물함 번호를 확인해 주세요.",
timeLabel: "1시간 전",
type: "lockers",
},
];
17 changes: 17 additions & 0 deletions src/entities/notifications/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// 알림 종류 — 아이콘·라벨이 종류마다 다르다. 빌릴게는 대여/반납 아이콘이 따로라 둘로 나눴다.
export type NotificationType =
| "bililgeRental"
| "bililgeReturn"
| "notices"
| "feedbacks"
| "events"
| "lockers";

export interface NotificationItem {
id: string;
type: NotificationType;
message: string;
/** 이미 포맷된 표시용 문자열 (예: "1시간 전") — 실 API가 시각을 주면 화면 쪽에서 상대 시간으로 바꾼다 */
timeLabel: string;
isRead: boolean;
}
Loading