알림 센터
나에게 온 것(멘션 · 댓글 · 초대 · 승인 요청 · 끝난 작업 · 지난 토스트)을 모아 읽고 처리하는 판입니다.
상단 바의 종에서 열고, 넓은 화면은 팝오버(비모달), 640 미만은 아래 시트(모달)로 뜹니다. 안 읽음은 점 · 굵기 · 진한 글자로 말하고 개수 칩은 빨강을 쓰지 않습니다. 판을 연다고 다 읽음이 되지 않습니다 — 열린 동안 화면에 보인 안 읽은 항목만 판을 닫을 때 읽음으로 알립니다. 열린 동안 온 알림은 목록에 끼워 넣지 않고 ‘새 알림 n개’ 버튼으로 받습니다. 날짜 묶음은 ‘오늘 · 이전’ 둘뿐입니다.
- import
- import { NotificationBell, NotificationCenter, type NotificationItem } from "@uyoolabs/ui/feedback";
- 쓰는 경우
- 상단 바의 알림 판에 씁니다. 토스트로 알린 결과(내보내기 완료 · 업로드 실패)를 다시 볼 곳이기도 합니다(kind: toast).
- 쓰지 않는 경우
- 워크스페이스 전체 기록은 ActivityFeed, 방금 한 일의 결과 한 문장은 Toast, 알림 설정은 설정 화면입니다. 알림 안에 수락 · 거절 같은 결정 버튼을 두지 않고(대상 화면에서), 항목 문장 안에 링크를 두지 않습니다(항목 전체가 링크). 개수 칩에 빨강을 쓰지 않습니다.
- 알림
- 열린 동안 온 알림 수가 0 에서 n 이 될 때 ‘새 알림 n개’, ‘모두 읽음’ 뒤 ‘모든 알림을 읽음으로 표시했습니다’ 를 role=status 로 한 번 알립니다. 판이 닫혀 있을 때의 개수 변화는 읽지 않습니다(급한 것은 토스트).
NotificationCenter
읽음 · 모두 읽음 · 끄기 · 묶음 · 방해 금지.
종을 누르면 판이 열립니다. 판을 닫을 때 화면에 보인 안 읽은 항목만 읽음이 됩니다.
위 견본의 ‘출시 체크리스트’ 댓글 세 개가 한 항목으로 묶여 있습니다.
NotificationBell
없음 · 안 읽음 3 · 99+ · 방해 금지.
키보드
| 키 | 동작 |
|---|---|
| EnterSpace | 종에서 판을 엽니다. 포커스는 고른 거르기 칸으로 갑니다 |
| ←→ | 거르기(전체 · 멘션 · 안 읽음)를 고릅니다 |
| TabShift+Tab | 거르기 → 항목 → 그 항목의 동작 둘(읽음으로 표시 · 알림 끄기) → 다음 항목 … → 바닥 |
| Enter | 항목의 대상으로 갑니다. 판은 닫힙니다. 한 · 일 조합 중 Enter 는 무시합니다 |
| Esc | 판을 닫고 포커스를 종으로 돌립니다 |
props
소스의 타입과 JSDoc 에서 생성한 표입니다. 컴포넌트를 고치면 pnpm gen 으로 다시 만듭니다.
NotificationCenter
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| items필수 | NotificationItem[] | — | |
| unreadCount필수 | number | — | 종 칩 · '안 읽음' 거르기의 수 |
| counts | Partial<Record<NotificationFilter, number>> | — | 거르기 칸의 수. unread 가 없으면 unreadCount |
| filter | "all" | "mentions" | "unread" | — | |
| defaultFilter | "all" | "mentions" | "unread" | "all" | 기본 "all" |
| onFilterChange | (filter: NotificationFilter) => void | — | |
| open | boolean | — | |
| defaultOpen | boolean | false | |
| onOpenChange | (open: boolean) => void | — | |
| onOpenItem | (item: NotificationItem, items: NotificationItem[]) => void | — | 항목을 누르면 링크로 가기 전에 불린다(분석 등). 판은 닫힌다. 묶음이면 묶인 항목 전부를 둘째 인자로 |
| onMarkRead | (ids: string[]) => void | — | 읽음 처리. 항목의 [읽음으로 표시]와, 판이 열린 동안 화면에 보인 안 읽은 항목(판을 닫을 때 한 번, 결정 #21)이 부른다. 낙관적으로 바꾸고 실패하면 소비자가 되돌린다 |
| onMarkAllRead | () => void | — | unreadCount 가 0 이면 버튼이 비활성이다 |
| onMute | (item: NotificationItem) => void | — | |
| settingsHref | string | — | |
| allHref | string | — | |
| loading | boolean | false | 머리 · 거르기는 바로, 목록 자리만 뼈대(loadingDelay 뒤 · 최소 400ms) |
| loadingDelay | number | 300 | 뼈대가 나타나기까지(ms) |
| error | ReactNode | boolean | — | 머리 · 거르기는 남기고 목록 자리에 알림 + 다시 시도. true 면 labels.error |
| onRetry | () => void | — | |
| hasMore | boolean | false | |
| onLoadMore | () => void | — | |
| pendingCount | number | 0 | 열려 있는 동안 온 알림 수. 목록 위 '새 알림 n개' 버튼으로만 받는다(끼워 넣지 않는다) |
| onShowPending | () => void | — | |
| dnd | { until?: string | Date; onTurnOff?: () => void } | null | — | 방해 금지. 판 위 안내 띠 + 종은 bell-off 와 조용한 칩 |
| groupWindow | number | 600 | 묶음 기준 시간(초). 0 이면 묶지 않는다. 기본 600(10분) |
| renderGroup | (group: NotificationGroup) => ReactNode | — | 묶음의 문장. 없으면 가장 최근 알림의 문장 + 메타 '외 n건' |
| presentation | "auto" | "popover" | "sheet" | "auto" | "auto"(기본): 640 미만이면 아래 시트(모달), 이상이면 팝오버(비모달) |
| trigger | ReactElement | — | 기본 <NotificationBell count={unreadCount} dnd={!!dnd} />. 요소 하나여야 한다(asChild) |
| lang | "ko" | "en" | "ja" | "ko" | |
| labels | Partial<NotificationCenterLabels> | — | |
| timeZone | string | "Asia/Seoul" | 기본 "Asia/Seoul" |
| now | Date | — | 상대 시각 · 오늘의 기준(테스트 · 스냅샷) |
NotificationBell
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| count | number | 0 | 안 읽은 알림 수. 0 이면 칩이 없다 |
| max | number | 99 | 이보다 크면 칩에 99+ |
| dnd | boolean | false | 방해 금지: bell-off 아이콘 + 조용한 칩(primary 면을 쓰지 않는다) |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<NotificationCenterLabels> | — |
이 밖에 <button> props(children 제외)를 그대로 받습니다.