첨부 · 검색 · 알림
메시지 안의 파일 · 이미지 · 링크, 메시지 검색, 접속 · 사람 카드, 알림 설정입니다.
- import
- import { FileAttachment, ImageAttachment, ImageGrid, Lightbox, LinkPreview, SearchResults, TypingIndicator, ProfileCard, ProfileCardTrigger, StatusMenu, CustomStatus, NotificationPrefs, DndStatus, ChannelNotifyMenu } from "@uyoolabs/ui/chat";
- 쓰는 경우
- 메시지에 붙는 파일 · 이미지 · 링크 미리보기, 워크스페이스 메시지 검색, 사람 카드 · 내 상태, 알림 설정 화면 · 채널 종 메뉴.
- 쓰지 않는 경우
- 워크스페이스 안의 문서 · 작업 링크(권한을 아는 참조 카드, W5), 화면 이동 · 명령(명령 팔레트), 이메일 · 모바일 푸시 설정(계정 설정)에는 쓰지 않습니다. 파일 종류마다 색을 칠하지 않습니다.
- 시각
- 끝 시각(DndStatus · 상태 메시지 until)은 UTC 로 받아 보일 때 바꿉니다. 방해 금지 일정만은 반복 규칙이라 벽시계 시각 + 사용자 시간대로 받고 보입니다(결정 #32, AGENTS.md 의 시각 규칙 예외).
FileAttachment · ImageGrid · Lightbox · LinkPreview
SearchResults
찾은 말은 서버가 준 구간 그대로 ./ui 의 Highlight 와 같은 마커로 칠합니다.
보낸 사람:이도현
ProfileCard · StatusMenu · TypingIndicator
이도현 · 한서윤 님이 입력하고 있습니다
NotificationPrefs · ChannelNotifyMenu
알림
알림 받을 메시지
채널별 설정이 있으면 그것이 이깁니다.
팔로우한 스레드의 답글
내가 쓰거나 답한 스레드에 새 답글이 오면 알립니다.
키워드
이 낱말이 들어간 메시지도 멘션처럼 알립니다.
배포장애방해 금지 일정
이 시간에는 알림을 멈추고 접속 점이 방해 금지로 바뀝니다.
시간대: Asia/Seoul데스크톱 알림
이 브라우저에만 적용됩니다.
알림 소리
키보드
| 키 | 동작 |
|---|---|
| Tab | 파일 칸은 동작 단추 하나만, 이미지는 버튼 하나. 링크 미리보기는 제목 → × |
| ←·→ | 라이트박스 안 이전 · 다음 이미지 |
| Esc | 라이트박스 · 사람 카드 · 메뉴를 닫고 연 단추로 |
| Enter | 검색칸: 곧바로 찾기(조합 중 무시) · 키워드 칸: 칩 더하기(조합 중 무시) |
| ↓·↑ | 검색칸에서 결과로, 결과 사이. 결과에서 Esc 는 검색칸으로 |
| Backspace | 빈 키워드 칸에서 두 번: 마지막 칩 지우기(첫 번은 고른다) |
| →·← | 내 상태 메뉴의 방해 금지 시간 하위 메뉴 열기 · 닫기 |
props
소스의 타입과 JSDoc 에서 생성한 표입니다. 컴포넌트를 고치면 pnpm gen 으로 다시 만듭니다.
FileAttachment
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| name필수 | string | — | |
| size필수 | number | — | 바이트. lang 으로 형식화(2.4MB · 2.4 MB) |
| type필수 | string | — | MIME. 종류 아이콘 · 종류 이름(PDF · Word · CSV)을 정한다 |
| href | string | — | 내려받기 주소. 없으면 onDownload |
| state | "done" | "uploading" | "error" | "gone" | "done" | 기본 done |
| progress | number | — | 0–100, uploading 일 때 |
| uploaded | number | — | 올린 바이트('18.2MB 중 11.6MB') |
| error | string | — | 앱이 준 실패 이유('100MB 를 넘습니다'). 없으면 labels.failed 만 |
| onDownload | () => void | — | |
| onRetry | () => void | — | error 일 때 ↻ |
| onCancel | () => void | — | uploading 일 때 × |
| onRemove | () => void | — | 작성창 안: 첨부 빼기 × |
| lang | "ko" | "en" | "ja" | "ko" | |
| labels | Partial<AttachmentLabels> | — | |
| className | string | — |
ImageAttachment
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| src | string | — | 없으면 자리만(올리는 중) |
| width필수 | number | — | 원본 크기. 먼저 받아 자리를 잡는다(다 올라온 뒤 목록이 흔들리지 않는다). 한 장 최대 360 × 360 |
| height필수 | number | — | |
| name필수 | string | — | |
| alt | string | — | 보낸 사람이 단 설명. 없으면 name |
| state | "done" | "uploading" | "error" | "gone" | "broken" | "done" | broken = 불러오지 못함(원본 열기) |
| progress | number | — | |
| href | string | — | 원본 주소(못 불러옴 · 라이트박스 '원본 열기') |
| onOpen | () => void | — | 라이트박스 |
| onRetry | () => void | — | |
| lang | "ko" | "en" | "ja" | "ko" | |
| labels | Partial<AttachmentLabels> | — | |
| className | string | — | |
| more | number | — | 격자 안의 '+n' |
ImageGrid
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| images필수 | Omit<ImageAttachmentProps, "lang" | "labels" | "onOpen" | "more">[] | — | |
| max | number | 4 | |
| onOpen | (index: number) => void | — | |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<AttachmentLabels> | — | |
| className | string | — |
Lightbox
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| images필수 | LightboxImage[] | — | |
| index | number | — | |
| defaultIndex | number | 0 | |
| onIndexChange | (index: number) => void | — | |
| open | boolean | — | |
| defaultOpen | boolean | — | |
| onOpenChange | (open: boolean) => void | — | |
| onDownload | (index: number) => void | — | 없으면 원본 주소로 내려받는다 |
| timeZone | string | — | |
| lang | "ko" | "en" | "ja" | "ko" | |
| labels | Partial<AttachmentLabels> | — |
LinkPreview
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| url필수 | string | — | 원래 링크. 제목 링크의 href |
| site필수 | string | — | 서버가 준 사이트 이름 · 도메인 |
| siteIcon | ReactNode | — | 기본 globe. 파비콘은 서버가 프록시한 그림만 |
| title필수 | string | — | 글자로만 그린다(HTML 해석 없음) |
| description | string | — | 두 줄에서 말줄임 |
| image | { src: string; width: number; height: number; alt?: string } | — | 서버 프록시 주소만 받는다(보는 사람의 IP 를 외부 사이트에 넘기지 않는다) |
| layout | "compact" | "large" | "compact" | compact: 오른쪽 썸네일 72 · large: 그림이 아래(동영상 · 문서 표지) |
| loading | boolean | false | 뼈대(서버가 아직 가져오는 중) |
| onRemove | () => void | — | 보낸 사람에게만 넘긴다. 있으면 hover · 포커스에 × |
| external | boolean | true | 같은 워크스페이스 링크는 false: 새 탭 대신 LinkProvider(앱 라우터)로 연다 |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<LinkPreviewLabels> | — | |
| className | string | — |
SearchResults
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| query필수 | string | — | |
| onQueryChange | (q: string) => void | — | 입력 250ms 멈춤 뒤 · Enter 에 부른다 |
| filters필수 | SearchFilters | — | |
| onFiltersChange필수 | (f: SearchFilters) => void | — | |
| kind필수 | "messages" | "files" | "channels" | "people" | — | |
| onKindChange필수 | (k: SearchKind) => void | — | |
| counts필수 | Record<SearchKind, number> | — | 종류 단추의 개수. 0 이어도 숨기지 않는다 |
| sort | "relevance" | "newest" | "relevance" | |
| onSortChange | (s: "relevance" | "newest") => void | — | |
| items필수 | SearchResultItem[] | — | |
| loading | boolean | false | |
| error | boolean | false | |
| onRetry | () => void | — | |
| onOpen | (item: SearchResultItem) => void | — | |
| onDateRange | () => void | — | 날짜 필터의 '날짜 범위…'(W2 DatePicker 를 앱이 연다) |
| recent | string[] | [] | 빈 검색칸 도움 목록의 최근 검색 |
| timeZone | string | — | |
| now | string | — | |
| lang | "ko" | "en" | "ja" | "ko" | |
| labels | Partial<SearchResultsLabels> | — | |
| className | string | — |
SearchResult
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| item필수 | SearchResultItem | — | |
| onOpen | (item: SearchResultItem) => void | — | |
| timeZone | string | — | |
| now | string | — | 오늘 · 어제 기준(테스트 · 서버 렌더에서 고정) |
| lang | "ko" | "en" | "ja" | "ko" | |
| labels | Partial<SearchResultsLabels> | — |
TypingIndicator
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| users필수 | string[] | — | 표시 이름. 빈 배열이면 자리(20)만 잡고 비운다 |
| max | number | 2 | 이름을 적는 최대 수. 넘으면 labels.many |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<TypingIndicatorLabels> | — | |
| className | string | — |
ProfileCard
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| person필수 | ProfileCardPerson | — | |
| status | "online" | "away" | "dnd" | "offline" | — | |
| customStatus | CustomStatusValue | — | |
| timeZone | string | — | 그 사람의 IANA 시간대. 현지 시각을 이것으로 바꾼다 |
| viewerTimeZone | string | — | 차이 계산 · 끝 시각 표시. 기본은 실행 환경의 시간대 |
| now | string | — | UTC ISO. 테스트 · 서버 렌더에서 고정 |
| afterHours | boolean | — | 그 사람에게 지금이 업무 시간 밖이다(달 아이콘). 근무 시간은 제품 프로필이 안다 |
| self | boolean | — | 나: 동작이 '상태 설정' · '프로필 수정' |
| loading | boolean | — | 뼈대 |
| onMessage | () => void | — | |
| onProfile | () => void | — | |
| onSetStatus | () => void | — | |
| onEditProfile | () => void | — | |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<ProfileCardLabels> | — | |
| className | string | — | |
| standalone | boolean | true | false: 팝오버 안의 몸통만(역할 · 판지는 팝오버가 갖는다) |
ProfileCardTrigger
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| person필수 | ProfileCardPerson | — | |
| status | "online" | "away" | "dnd" | "offline" | — | |
| customStatus | CustomStatusValue | — | |
| timeZone | string | — | 그 사람의 IANA 시간대. 현지 시각을 이것으로 바꾼다 |
| viewerTimeZone | string | — | 차이 계산 · 끝 시각 표시. 기본은 실행 환경의 시간대 |
| now | string | — | UTC ISO. 테스트 · 서버 렌더에서 고정 |
| afterHours | boolean | — | 그 사람에게 지금이 업무 시간 밖이다(달 아이콘). 근무 시간은 제품 프로필이 안다 |
| self | boolean | — | 나: 동작이 '상태 설정' · '프로필 수정' |
| loading | boolean | — | 뼈대 |
| onMessage | () => void | — | |
| onProfile | () => void | — | |
| onSetStatus | () => void | — | |
| onEditProfile | () => void | — | |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<ProfileCardLabels> | — | |
| className | string | — | |
| children필수 | ReactElement | — | 이름 · 아바타 단추(asChild) |
| open | boolean | — | |
| defaultOpen | boolean | — | |
| onOpenChange | (open: boolean) => void | — |
CustomStatus
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| emoji | string | — | |
| text필수 | string | — | |
| until | string | — | UTC ISO |
| timeZone | string | — | 끝 시각을 보일 시간대(보는 사람) |
| lang | "ko" | "en" | "ja" | "ko" | |
| labels | Partial<Pick<ProfileCardLabels, "until">> | — | |
| className | string | — |
StatusMenu
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| value필수 | "auto" | "away" | "dnd" | — | |
| onChange필수 | (value: "auto" | "away") => void | — | |
| dndUntil | string | — | UTC ISO. 있으면 방해 금지 항목에 끝 시각 |
| onDnd필수 | (duration: DndDuration) => void | — | custom 이면 앱이 시각 고르기를 연다 |
| onSetCustomStatus | () => void | — | |
| trigger필수 | ReactElement | — | SidebarUser 단추(asChild) |
| timeZone | string | — | 끝 시각을 보일 시간대(보는 사람) |
| open | boolean | — | |
| defaultOpen | boolean | — | |
| onOpenChange | (open: boolean) => void | — | |
| modal | boolean | — | 기본 true(메뉴 밖을 가린다). 열린 채로 보여 주는 문서 · 픽스처에서만 false |
| lang | "ko" | "en" | "ja" | "ko" | |
| labels | Partial<StatusMenuLabels> | — |
NotificationPrefs
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| value필수 | NotificationPrefsValue | — | 제어만(서버 값) |
| onChange필수 | (next: NotificationPrefsValue, changed: keyof NotificationPrefsValue) => void | — | |
| permission필수 | "granted" | "denied" | "default" | — | 브라우저 알림 권한 |
| onRequestPermission | () => void | — | default 일 때 데스크톱 스위치를 켜면 부른다 |
| saveState | "idle" | "saving" | "saved" | "error" | "idle" | |
| onRetrySave | () => void | — | |
| lang | "ko" | "en" | "ja" | "ko" | |
| labels | Partial<NotificationPrefsLabels> | — | |
| className | string | — |
DndStatus
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| until필수 | string | — | UTC ISO. 보는 사람의 시간대로 바꿔 보인다 |
| timeZone | string | — | |
| onResume필수 | () => void | — | |
| lang | "ko" | "en" | "ja" | "ko" | |
| labels | Partial<NotificationPrefsLabels> | — | |
| className | string | — |
ChannelNotifyMenu
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| channelName필수 | string | — | |
| level필수 | "default" | "all" | "mentions" | "off" | — | |
| onLevelChange필수 | (level: ChannelNotifyLevel) => void | — | |
| defaultLevel필수 | "all" | "mentions" | "none" | — | '기본값 따르기' 옆에 지금 기본값을 보인다 |
| threadReplies필수 | boolean | — | |
| onThreadRepliesChange필수 | (value: boolean) => void | — | |
| onOpenSettings | () => void | — | |
| trigger필수 | ReactElement | — | 상단 바 종 단추(asChild) |
| lang | "ko" | "en" | "ja" | "ko" | |
| labels | Partial<NotificationPrefsLabels> | — |