본문으로 건너뛰기

첨부 · 검색 · 알림

메시지 안의 파일 · 이미지 · 링크, 메시지 검색, 접속 · 사람 카드, 알림 설정입니다.

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

환불정책-v2.pdf2.3MB · PDF
시안.png17.4MB 중 11.1MB · 64%

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)을 정한다
hrefstring—내려받기 주소. 없으면 onDownload
state"done" | "uploading" | "error" | "gone""done"기본 done
progressnumber—0–100, uploading 일 때
uploadednumber—올린 바이트('18.2MB 중 11.6MB')
errorstring—앱이 준 실패 이유('100MB 를 넘습니다'). 없으면 labels.failed 만
onDownload() => void—
onRetry() => void—error 일 때 ↻
onCancel() => void—uploading 일 때 ×
onRemove() => void—작성창 안: 첨부 빼기 ×
lang"ko" | "en" | "ja""ko"
labelsPartial<AttachmentLabels>—
classNamestring—

ImageAttachment

prop타입기본값설명
srcstring—없으면 자리만(올리는 중)
width필수number—원본 크기. 먼저 받아 자리를 잡는다(다 올라온 뒤 목록이 흔들리지 않는다). 한 장 최대 360 × 360
height필수number—
name필수string—
altstring—보낸 사람이 단 설명. 없으면 name
state"done" | "uploading" | "error" | "gone" | "broken""done"broken = 불러오지 못함(원본 열기)
progressnumber—
hrefstring—원본 주소(못 불러옴 · 라이트박스 '원본 열기')
onOpen() => void—라이트박스
onRetry() => void—
lang"ko" | "en" | "ja""ko"
labelsPartial<AttachmentLabels>—
classNamestring—
morenumber—격자 안의 '+n'

ImageGrid

prop타입기본값설명
images필수Omit<ImageAttachmentProps, "lang" | "labels" | "onOpen" | "more">[]—
maxnumber4
onOpen(index: number) => void—
lang"ko" | "en" | "ja"—
labelsPartial<AttachmentLabels>—
classNamestring—

Lightbox

prop타입기본값설명
images필수LightboxImage[]—
indexnumber—
defaultIndexnumber0
onIndexChange(index: number) => void—
openboolean—
defaultOpenboolean—
onOpenChange(open: boolean) => void—
onDownload(index: number) => void—없으면 원본 주소로 내려받는다
timeZonestring—
lang"ko" | "en" | "ja""ko"
labelsPartial<AttachmentLabels>—

LinkPreview

prop타입기본값설명
url필수string—원래 링크. 제목 링크의 href
site필수string—서버가 준 사이트 이름 · 도메인
siteIconReactNode—기본 globe. 파비콘은 서버가 프록시한 그림만
title필수string—글자로만 그린다(HTML 해석 없음)
descriptionstring—두 줄에서 말줄임
image{ src: string; width: number; height: number; alt?: string }—서버 프록시 주소만 받는다(보는 사람의 IP 를 외부 사이트에 넘기지 않는다)
layout"compact" | "large""compact"compact: 오른쪽 썸네일 72 · large: 그림이 아래(동영상 · 문서 표지)
loadingbooleanfalse뼈대(서버가 아직 가져오는 중)
onRemove() => void—보낸 사람에게만 넘긴다. 있으면 hover · 포커스에 ×
externalbooleantrue같은 워크스페이스 링크는 false: 새 탭 대신 LinkProvider(앱 라우터)로 연다
lang"ko" | "en" | "ja"—
labelsPartial<LinkPreviewLabels>—
classNamestring—

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[]—
loadingbooleanfalse
errorbooleanfalse
onRetry() => void—
onOpen(item: SearchResultItem) => void—
onDateRange() => void—날짜 필터의 '날짜 범위…'(W2 DatePicker 를 앱이 연다)
recentstring[][]빈 검색칸 도움 목록의 최근 검색
timeZonestring—
nowstring—
lang"ko" | "en" | "ja""ko"
labelsPartial<SearchResultsLabels>—
classNamestring—

SearchResult

prop타입기본값설명
item필수SearchResultItem—
onOpen(item: SearchResultItem) => void—
timeZonestring—
nowstring—오늘 · 어제 기준(테스트 · 서버 렌더에서 고정)
lang"ko" | "en" | "ja""ko"
labelsPartial<SearchResultsLabels>—

TypingIndicator

prop타입기본값설명
users필수string[]—표시 이름. 빈 배열이면 자리(20)만 잡고 비운다
maxnumber2이름을 적는 최대 수. 넘으면 labels.many
lang"ko" | "en" | "ja"—
labelsPartial<TypingIndicatorLabels>—
classNamestring—

ProfileCard

prop타입기본값설명
person필수ProfileCardPerson—
status"online" | "away" | "dnd" | "offline"—
customStatusCustomStatusValue—
timeZonestring—그 사람의 IANA 시간대. 현지 시각을 이것으로 바꾼다
viewerTimeZonestring—차이 계산 · 끝 시각 표시. 기본은 실행 환경의 시간대
nowstring—UTC ISO. 테스트 · 서버 렌더에서 고정
afterHoursboolean—그 사람에게 지금이 업무 시간 밖이다(달 아이콘). 근무 시간은 제품 프로필이 안다
selfboolean—나: 동작이 '상태 설정' · '프로필 수정'
loadingboolean—뼈대
onMessage() => void—
onProfile() => void—
onSetStatus() => void—
onEditProfile() => void—
lang"ko" | "en" | "ja"—
labelsPartial<ProfileCardLabels>—
classNamestring—
standalonebooleantruefalse: 팝오버 안의 몸통만(역할 · 판지는 팝오버가 갖는다)

ProfileCardTrigger

prop타입기본값설명
person필수ProfileCardPerson—
status"online" | "away" | "dnd" | "offline"—
customStatusCustomStatusValue—
timeZonestring—그 사람의 IANA 시간대. 현지 시각을 이것으로 바꾼다
viewerTimeZonestring—차이 계산 · 끝 시각 표시. 기본은 실행 환경의 시간대
nowstring—UTC ISO. 테스트 · 서버 렌더에서 고정
afterHoursboolean—그 사람에게 지금이 업무 시간 밖이다(달 아이콘). 근무 시간은 제품 프로필이 안다
selfboolean—나: 동작이 '상태 설정' · '프로필 수정'
loadingboolean—뼈대
onMessage() => void—
onProfile() => void—
onSetStatus() => void—
onEditProfile() => void—
lang"ko" | "en" | "ja"—
labelsPartial<ProfileCardLabels>—
classNamestring—
children필수ReactElement—이름 · 아바타 단추(asChild)
openboolean—
defaultOpenboolean—
onOpenChange(open: boolean) => void—

CustomStatus

prop타입기본값설명
emojistring—
text필수string—
untilstring—UTC ISO
timeZonestring—끝 시각을 보일 시간대(보는 사람)
lang"ko" | "en" | "ja""ko"
labelsPartial<Pick<ProfileCardLabels, "until">>—
classNamestring—

StatusMenu

prop타입기본값설명
value필수"auto" | "away" | "dnd"—
onChange필수(value: "auto" | "away") => void—
dndUntilstring—UTC ISO. 있으면 방해 금지 항목에 끝 시각
onDnd필수(duration: DndDuration) => void—custom 이면 앱이 시각 고르기를 연다
onSetCustomStatus() => void—
trigger필수ReactElement—SidebarUser 단추(asChild)
timeZonestring—끝 시각을 보일 시간대(보는 사람)
openboolean—
defaultOpenboolean—
onOpenChange(open: boolean) => void—
modalboolean—기본 true(메뉴 밖을 가린다). 열린 채로 보여 주는 문서 · 픽스처에서만 false
lang"ko" | "en" | "ja""ko"
labelsPartial<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"
labelsPartial<NotificationPrefsLabels>—
classNamestring—

DndStatus

prop타입기본값설명
until필수string—UTC ISO. 보는 사람의 시간대로 바꿔 보인다
timeZonestring—
onResume필수() => void—
lang"ko" | "en" | "ja""ko"
labelsPartial<NotificationPrefsLabels>—
classNamestring—

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"
labelsPartial<NotificationPrefsLabels>—