회의
회의실 · 음성 채널의 부품입니다. 미디어 SDK 를 모르고, 앱이 넘긴 MediaStream 과 상태(음소거 · 말하는 중 · 연결 품질 · 녹화 중)를 그리기만 합니다.
- import
- import { … } from "@uyoolabs/ui/meeting";
- 쓰는 경우
- 회의실(상단 바 · 무대 · 측면 패널 · 컨트롤 막대), 입장 전 화면, 음성 채널의 참여자 줄과 도크, 자막과 실시간 기록에 씁니다.
- 쓰지 않는 경우
- 음소거 · 카메라 꺼짐 · 권한 거부를 빨강으로 그리지 않습니다(오류가 아닙니다). 말하는 중을 초록으로 그리지 않습니다(primary 띠 + 소리 막대). 영상 위 글자를 반투명 판에 얹지 않습니다. 녹화 표시를 빨간 점이나 아이콘만으로 두지 않습니다(결정 #36).
- 빨강
- 컨트롤 막대의 나가기 하나, 녹화 중의 정지 모양, 연결 실패 · 오프라인 띠에만 씁니다(결정 #38). 다른 곳의 나가기는 secondary 입니다.
서브패스 ./meeting 은 radix-ui 만 선택 peer 로 가져옵니다. 회의 CSS 는 @uyoolabs/ui/css 에 들어 있습니다.
MeetingRoom · MeetingPanel
회의실 틀입니다. 상단 바 · 무대 · 측면 패널 · 컨트롤 막대 네 띠로 나누고 F6 으로 옮깁니다. 이름은 브레드크럼이 말하고 h1 을 두지 않습니다.
조립 견본은 P-Meeting 보드와 픽스처 meeting-room(다크 고정 · 화면공유 · 자막 · 녹화 · 기록 패널)에 있습니다. 다크 고정은 틀 안의 토큰만 바꿉니다 — body 에 붙는 메뉴 · 툴팁 · 대화상자는 앱 테마를 따릅니다.
| 키 | 동작 |
|---|---|
| F6 | 상단 바 → 무대 → 컨트롤 → 패널. Shift+F6 은 거꾸로 |
VideoTile
사람(또는 공유 화면) 하나의 창입니다. 판지에 낸 창이라 두께가 없고, 이름 판은 불투명합니다. 말하는 중은 안쪽 primary 띠 + 소리 막대, 음소거는 빗금 마이크로 말합니다.
접근성 이름은 이름 + 음소거 + 손 듦만 담습니다(‘김하늘, 음소거’). 말하는 중은 바뀔 때마다 이름을 다시 읽는 소음이라 넣지 않습니다(결정 #42). 손 듦 · 고정은 role=status 로 한 번 읽습니다. stream 이 바뀌면 같은 video 요소의 srcObject 만 바꿉니다.
MeetingStage
참가자와 공유 화면을 받아 격자 · 발표자 · 공유 배치를 고르고 타일을 늘어놓습니다. 격자는 자리를 바꾸지 않고 테두리만 옮깁니다.
| 키 | 동작 |
|---|---|
| Tab | 무대의 활성 타일 하나로 들어온다(roving tabindex) |
| ←·→·↑·↓ | 타일 사이 이동(격자는 상하좌우, 발표자 · 공유는 앞뒤) |
| Home·End | 첫 · 마지막 타일 |
| Page Up·Page Down | 쪽 넘김(많을 때). 바뀐 쪽은 ‘참가자 9–16 / 20’ 으로 읽는다 |
| Enter·Shift+F10 | 타일 메뉴(고정 · 전체 화면 · 호스트 동작) |
| ⌘⇧P·Ctrl+Shift+P | 지금 타일 고정 · 해제 |
| Esc | 메뉴를 닫고 타일로 돌아온다 |
CallControls
회의실 바닥의 큰 키캡 막대입니다. 켠 토글은 눌린 채로 가라앉고 아이콘에 빗금이 생깁니다. 토글은 이름이 고정이고 aria-pressed 가 상태를 말합니다(결정 #37).
| 키 | 동작 |
|---|---|
| ⌘D·Ctrl+D | 음소거 · 해제(입력칸 안에서도) |
| ⌘E·Ctrl+E | 카메라 끄기 · 켜기 |
| Space | 누르는 동안 말하기(음소거일 때, 입력칸 밖 · IME 조합 중 제외). 떼거나 창이 흐려지면 다시 음소거 |
| ⌘⇧H·Ctrl+Shift+H | 손 들기 · 내리기 |
| ⌘⇧C·Ctrl+Shift+C | 자막 켜기 · 끄기 |
| ←·→·Home·End | 막대 안 이동(APG Toolbar). 꺾쇠 메뉴는 ↑ 로도 연다 |
⌘D · ⌘E 는 브라우저 북마크 · 검색과 겹칩니다. 막대가 그려져 있는 동안에만 붙고, 처리한 키만 막습니다. 호스트만 할 수 있는 키는 숨기지 않고 비활성 + 이유 툴팁으로 둡니다.
PreJoin
회의에 들어가기 직전 내 모습 · 소리를 확인하고 장치와 이름을 정합니다. 권한이 없거나 장치가 없어도 ‘소리 없이 참여’로 들어갈 길을 남깁니다. 녹화 중인 회의는 여기서 먼저 알립니다.
카메라를 켜는 중입니다
내 화면은 거울처럼 보입니다. 다른 사람에게는 뒤집지 않은 화면이 갑니다.
주간 점검
| 키 | 동작 |
|---|---|
| Tab | 마이크 키 → 카메라 키 → 설정 → 이름 → 장치 셋 → 참여 → 소리 없이 참여 |
| ⌘D·Ctrl+D | 음소거 켜기 · 끄기(회의실과 같다) |
| ⌘E·Ctrl+E | 카메라 끄기 · 켜기 |
| Enter | 이름 칸에서 참여(IME 조합 중 Enter 는 무시) |
| Esc | 대기실에서 기다리기 취소 |
RecordingIndicator · 녹화 대화상자
녹화 · 기록이 켜진 동안 모든 참가자의 모든 화면에 늘 보이는 반전 칸입니다. 빨간 점 대신 녹화 원 + 글자로 말합니다. 시작은 호스트의 동의 확인을 거치고, 시작되는 순간 모든 참가자가 계속 참여할지 고릅니다.
| 키 | 동작 |
|---|---|
| Enter·Space | 녹화 표시: 정보 팝오버 열기 · 닫기 |
| Esc | 팝오버 · 시작 · 중지 대화상자를 닫는다. 참가자 고지는 닫히지 않는다(둘 중 하나를 고른다) |
| Tab | 대화상자 안에서 돈다. 처음 포커스: 시작은 동의 체크, 고지는 ‘계속 참여’, 중지는 ‘계속 녹화’ |
대화상자의 기본 문구는 예시입니다. 법무 검토를 거친 고지 문구 · 보관 기간 설명을 labels 로 덮어 씁니다(결정 #40). 컴포넌트는 누가 · 언제(UTC → 보는 사람 시간대) · 무엇이 · 어디에 · 얼마나의 자리를 강제합니다.
ConnectionBanner
연결 문제가 이어지는 동안 무대 맨 위에 남는 띠입니다(토스트가 아닙니다). 저절로 고쳐지는 문제는 주의, 사용자가 움직여야 하는 문제만 오류, 다시 연결됨은 성공(4초)입니다.
| 키 | 동작 |
|---|---|
| Tab | 띠의 동작 → 닫기(무대 앞). 띠는 포커스를 빼앗지 않는다 |
| Esc | 띠 안에 포커스가 있으면 닫을 수 있는 띠(품질 · 다시 연결됨 · 마이크)를 닫는다 |
VoiceChannelMembers · VoiceDock
음성 채널은 누르면 바로 들어가는 방입니다. 채널 아래에 참여자 줄이 보이고, 들어가면 사이드바 바닥에 연결 도크가 남습니다.
- 김하늘
- 박도윤, 음소거
- 최유나, 화면 공유 중
- 이서연(나)
음성 연결됨
디자인 리뷰 · 한빛랩
Captions · Transcript
자막은 무대 아래 가운데의 불투명 판(두 줄, 보는 사람 하나의 설정)이고, 기록은 측면 패널의 흐르는 기록입니다. 기록을 저장하는 동안은 모두에게 ‘기록 중’을 보입니다.
기록
기록 중- 김하늘
결제 실패 알림은 이번 배포에 넣기로 했습니다.
- 박도윤
그럼 환불 정책 문구는 다음 주 화요일까지 정리해서 법무팀에 보내 드릴게요.
| 키 | 동작 |
|---|---|
| Enter·Shift+Enter | 찾기 칸에서 다음 · 이전 결과(IME 조합 중 Enter 는 무시) |
| Esc | 찾기 글을 지우고, 다시 누르면 패널을 닫는다 |
| End | 기록 안에서 최신으로 |
자막은 role=log · aria-live=off 입니다(스크린 리더는 기록 패널에서 읽습니다). 기록은 role=log · polite 이고 확정된 문장만 읽습니다(결정 #42). 확정 전 글은 보조 색이고 aria-hidden 입니다.
props
소스의 타입과 JSDoc 에서 생성한 표입니다. 컴포넌트를 고치면 pnpm gen 으로 다시 만듭니다.
MeetingRoom
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| tone | "auto" | "dark" | "auto" | 회의실 테마. auto 는 앱 테마를 따르고, dark 는 회의실만 다크로 고정한다(결정 #9). 고정은 이 틀 안의 토큰만 바꾼다 — body 에 붙는 메뉴 · 툴팁 · 대화상자는 앱 테마를 따른다 |
| top | ReactNode | — | 상단 바: 브레드크럼(회의 이름) · 녹화 표시 · 시계 · 참가자 묶음. 제품 화면이라 h1 을 두지 않는다 |
| banner | ReactNode | — | 무대 맨 위 연결 띠(ConnectionBanner). Tab 순서상 무대 앞 |
| panel | ReactNode | — | 측면 패널(MeetingPanel). 없으면 무대가 넓어진다 |
| controls | ReactNode | — | 바닥 컨트롤 막대(CallControls) |
| children필수 | ReactNode | — | 무대(MeetingStage) |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<MeetingRoomLabels> | — | |
| className | string | — |
MeetingPanel
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| title필수 | ReactNode | — | 머리 제목(h2) |
| actions | ReactNode | — | 제목 옆(‘기록 중’ 표시 · 탭) |
| onClose | () => void | — | 있으면 닫기 단추 |
| tools | ReactNode | — | 머리 아래 도구 줄(찾기 칸) |
| footer | ReactNode | — | 바닥(내려받기 · AI 요약) |
| children필수 | ReactNode | — | 본문(스크롤 자리) |
| bodyRef | Ref<HTMLDivElement> | — | 본문 스크롤 요소. 흐르는 기록의 따라가기에 쓴다 |
| onBodyScroll | () => void | — | 본문 스크롤(바닥 따라가기가 사람이 위로 올린 것을 안다) |
| id | string | — | 패널 키의 aria-controls 가 가리킬 id |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<MeetingRoomLabels> | — | |
| className | string | — |
VideoTile
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| participant필수 | MeetingParticipant | — | |
| stream | MediaStream | null | — | 앱이 받은 MediaStream. 바뀌면 video.srcObject 만 바꾼다(요소를 다시 만들지 않는다 — 깜빡임) |
| media | ReactNode | — | 앱이 직접 그린 미디어(자기 <video> · 포스터 그림). 주면 stream 대신 이것을 창 안에 채운다. 실기기가 없는 문서 · 픽스처 · SDK 가 요소를 직접 주는 경우에 쓴다 |
| kind | "camera" | "screen" | — | screen 이면 자르지 않고 담고(contain) 이름이 ‘○○의 화면’이다 |
| camera | "on" | "off" | "connecting" | "paused" | — | 기본: stream · media 가 있으면 on, 없으면 off |
| speaking | boolean | — | |
| muted | boolean | — | |
| quality | "good" | "fair" | "poor" | "lost" | — | good 은 그리지 않는다(나빠질 때만 보인다) |
| handRaised | number | null | — | 손 든 순번(1부터). 없으면 안 든 것 |
| reaction | { emoji: string; label: string } | null | — | 지금 떠 있는 반응. 4초 뒤 앱이 지운다 |
| self | boolean | — | 나. 이름 뒤에 ‘(나)’ |
| mirror | boolean | — | 거울. 기본 self && kind === "camera" |
| pinned | boolean | — | 제어 · 비제어 둘 다 |
| defaultPinned | boolean | — | |
| onPinnedChange | (pinned: boolean) => void | — | |
| menu | ReactNode | — | 타일 메뉴에 더할 항목(호스트 동작은 앱이 넘긴다). 없으면 고정 · 전체 화면만 |
| showMenu | boolean | — | false 면 메뉴 단추를 그리지 않는다(입장 전 미리보기) |
| onVisibleChange | (visible: boolean) => void | — | 보이는지. IntersectionObserver 로 재서 알린다(보이지 않는 타일은 영상을 받지 않게 — 대역폭) |
| onPlaybackBlocked | () => void | — | 자동 재생이 막혔을 때(Safari 의 play() 거부). 타일은 ‘누르면 영상이 보입니다’ 단추를 그린다 |
| size | "lg" | "md" | "sm" | "xs" | — | lg 무대 · md 격자 · sm 필름스트립(168) · xs 아주 작음(120, 이름 판만) |
| notice | ReactNode | — | 카메라가 꺼진 동안 가운데 아바타 대신 그릴 안내(입장 전 권한 · 장치 상태). role=status 로 바뀔 때 읽는다 |
| aria-label | string | — | 접근성 이름을 바꿀 때(입장 전 ‘내 미리보기’) |
| children | ReactNode | — | 위에 겹칠 것(입장 전 미리보기의 장치 키) |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<VideoTileLabels> | — | |
| className | string | — | |
| style | CSSProperties | — | |
| ref | Ref<HTMLDivElement> | — |
MeetingStage
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| tiles필수 | StageTile[] | — | 참가자 타일. 들어온 순서대로 넘긴다(격자는 이 자리를 지킨다) |
| screenShare | StageTile | null | — | 공유 화면 타일(kind="screen"). 있으면 auto 가 share 가 된다 |
| layout | "auto" | "grid" | "speaker" | "share" | — | 제어 · 비제어. 기본 "auto"(공유 있으면 share, 고정이 있으면 speaker, 없으면 grid) |
| defaultLayout | "auto" | "grid" | "speaker" | "share" | "auto" | |
| onLayoutChange | (layout: StageLayout) => void | — | |
| pinnedId | string | null | — | 제어 · 비제어. 고정한 사람(나에게만 적용) |
| defaultPinnedId | string | null | null | |
| onPinnedChange | (id: string | null) => void | — | |
| activeSpeakerId | string | null | — | 지금 말하는 사람. 발표자 배치의 큰 자리 · 필름스트립 순서에 쓴다 |
| speakingLog | SpeakingEntry[] | [] | 최근 말한 기록. 필름스트립 순서(300ms 이상 말한 사람을 보이는 자리로) |
| maxPerPage | number | 9 | 한 쪽 최대 칸. 기본 9. 넘치면 마지막 칸이 ‘+N명’이다 |
| page | number | — | 제어 · 비제어. 0 부터 |
| defaultPage | number | 0 | |
| onPageChange | (page: number) => void | — | |
| onVisibleChange | (ids: string[]) => void | — | 화면에 그려진(영상을 받아야 하는) 참가자 id. 바뀔 때만 부른다 |
| onShowAll | () => void | — | ‘+N명’ 칸을 누르면(참가자 패널 열기 — 포커스는 패널의 찾기 칸으로) |
| hideSelf | boolean | false | 내 화면 숨기기(혼자일 때는 숨기지 않는다) |
| children | ReactNode | — | 자막 판처럼 무대 위에 겹칠 것 |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<MeetingStageLabels> | — | |
| className | string | — |
CallControls
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| micMuted필수 | boolean | — | |
| onMicMutedChange필수 | (muted: boolean) => void | — | |
| cameraOff필수 | boolean | — | |
| onCameraOffChange필수 | (off: boolean) => void | — | |
| devices | Device[] | — | 있으면 마이크 · 카메라 키에 장치 꺾쇠 메뉴를 붙인다 |
| selectedDeviceIds | DeviceSelection | {} | |
| onDeviceChange | (kind: DeviceKind, id: string) => void | — | |
| sharing | boolean | false | 내가 화면을 공유하는 중 |
| onSharingChange | (sharing: boolean) => void | — | 없으면 공유 키를 그리지 않는다 |
| canShare | boolean | { reason: string } | true | false 면 비활성, { reason } 이면 비활성 + 이유 툴팁 |
| recording | "idle" | "starting" | "on" | "paused" | "idle" | 녹화 상태. idle 이 아니면 녹화 키가 ‘녹화 중지’(정지 모양 빨강)가 된다 |
| onRecordRequest | (action: "start" | "stop") => void | — | 녹화 키를 누르면. 시작이면 RecordingStartDialog, 중지면 RecordingStopDialog 를 앱이 연다. 없으면 녹화 키가 없다 |
| canRecord | boolean | { reason: string } | true | |
| handRaised | boolean | false | |
| onHandRaisedChange | (raised: boolean) => void | — | |
| reactions | Array<{ emoji: string; label: string }> | — | 반응 목록. 기본 여섯(👍 👏 😂 ❤️ 🎉 😮) |
| onReact | (emoji: string) => void | — | 없으면 반응 키를 그리지 않는다 |
| captionsOn | boolean | false | |
| onCaptionsChange | (on: boolean) => void | — | 없으면 자막 키를 그리지 않는다 |
| panel | PanelId | null | — | 제어 · 비제어. 열린 측면 패널 |
| defaultPanel | PanelId | null | null | |
| onPanelChange | (panel: PanelId | null) => void | — | |
| panelId | string | — | 패널 키(aria-controls)가 가리킬 측면 패널 id |
| panels | PanelId[] | ["people", "chat", "ai"] | 그릴 패널 키. 기본 셋 다 |
| counts | { people?: number; unreadChat?: number } | {} | |
| onLeave필수 | () => void | — | 나가기. 확인 없이 바로(한 번에 나가야 한다) |
| isHost | boolean | false | |
| onEndForAll | () => void | — | 호스트의 나가기 꺾쇠 → ‘모두에게 회의 끝내기’. 앱이 AlertDialog 로 한 번 묻는다 |
| onTransferHostAndLeave | () => void | — | |
| layout | "auto" | "grid" | "speaker" | "share" | — | 더 보기 메뉴의 배치 라디오 |
| onLayoutChange | (layout: StageLayout) => void | — | |
| moreItems | ReactNode | — | 더 보기 메뉴에 더할 항목(DropdownMenuItem) |
| speakingWhileMuted | boolean | false | 앱이 ‘음소거 중 말함’을 2초 이상 감지하면 true. 한 회의에 3번까지 토스트로 알린다(ToastRegion 필요) |
| pushToTalk | boolean | true | 누르는 동안 말하기(Space). 기본 true |
| density | "auto" | "comfortable" | "compact" | "auto" | 기본 "auto"(640 미만 compact) |
| shortcutTarget | HTMLElement | Document | null | — | 전역 단축키를 붙일 대상. 기본 document. 막대가 그려져 있는 동안만 붙는다 |
| title | ReactNode | — | 왼쪽 회의 이름 · 경과 시간 |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<CallControlsLabels> | — | |
| className | string | — |
CallKey
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| icon필수 | IconName | ReactElement | — | 아이콘 이름(./icons) 또는 아이콘 요소. 켠 토글은 빗금 아이콘으로 바꿔 넘긴다 |
| label필수 | string | — | 화면 이름 = 접근성 이름. 상태가 바뀌어도 고정이다(결정 #37) |
| ariaLabel | string | — | 접근성 이름이 화면 이름과 다를 때(개수를 더한 ‘참가자 8명’). 화면 이름으로 시작해야 음성 입력으로 부를 수 있다 (WCAG 2.5.3) |
| pressed | boolean | — | 토글이면 준다 → aria-pressed. 눌린 채로 가라앉는다 |
| expanded | boolean | — | 패널 · 메뉴를 여는 키 → aria-expanded |
| controls | string | — | 여는 패널의 id(aria-controls) |
| variant | "default" | "danger" | "default" | danger 는 되돌릴 수 없는 나가기 하나에만(결정 #38) |
| state | "recording" | — | recording: 녹화 중의 녹화 키. 정지 모양만 빨강 |
| count | number | — | 키 오른쪽 위 개수 칩. 숫자는 장식이라 label 에 개수를 넣는다 |
| countTone | "neutral" | "strong" | "neutral" | strong 은 새 것(primary 면) |
| shortcut | string | — | "Mod+D" · "Mod+Shift+H". aria-keyshortcuts 와 툴팁 키캡이 된다(Mod = Mac ⌘ · 그 밖 Ctrl) |
| tooltip | string | — | 툴팁 글. 토글은 누르면 일어날 다음 동작(‘음소거 해제’). 없으면 label |
| disabled | boolean | { reason: string } | — | true 면 비활성, { reason } 이면 비활성 + 이유 툴팁(호스트만 할 수 있는 키는 숨기지 않는다) |
| busy | boolean | — | 처리 중(녹화를 시작하는 중) |
| onClick | () => void | — | |
| caret | { label: string; menu: ReactNode } | — | 붙은 꺾쇠 키(장치 고르기 · 나가기 방법). 막대가 바닥이라 메뉴는 위로 열고 ↑ 로도 연다 |
| ref | Ref<HTMLButtonElement> | — | |
| className | string | — |
PreJoin
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| meeting필수 | MeetingInfo | — | |
| stream | MediaStream | null | — | 미리보기 스트림. 없으면 카메라 꺼짐 · 권한 상태 화면 |
| media | ReactNode | — | 실기기 없이 그릴 미리보기(문서 · 픽스처의 포스터) |
| permission필수 | "prompt" | "granted" | "denied" | "unavailable" | "in-use" | — | |
| onRetryPermission | () => void | — | |
| devices필수 | Device[] | — | |
| selectedDeviceIds필수 | DeviceSelection | — | 장치 선택은 제어만(앱이 기억 · 저장한다) |
| onDeviceChange필수 | (kind: DeviceKind, id: string) => void | — | |
| onTestSpeaker | () => void | — | |
| micMuted | boolean | — | 제어 · 비제어 둘 다. 기본 false(켜진 채 시작) |
| defaultMicMuted | boolean | false | |
| onMicMutedChange | (muted: boolean) => void | — | |
| cameraOff | boolean | — | |
| defaultCameraOff | boolean | false | |
| onCameraOffChange | (off: boolean) => void | — | |
| micLevel | number | 0 | 0–1. 앱이 AnalyserNode 로 재서 넘긴다(rAF 당 1회 이하) |
| displayName | string | — | |
| defaultDisplayName | string | "" | |
| onDisplayNameChange | (name: string) => void | — | |
| nameRequired | boolean | false | 손님이면 이름 칸이 필수 |
| identity | number | 1 | 미리보기 아바타 색(1–12) |
| onJoin필수 | (choice: PreJoinChoice) => void | — | |
| joining | boolean | false | |
| waiting | "lobby" | "declined" | "full" | null | null | 대기실 · 거절 · 인원 초과 |
| onCancelWaiting | () => void | — | |
| onRequestAgain | () => void | — | |
| autoFocus | boolean | true | 처음 포커스를 참여 버튼(이름이 필수이고 비었으면 이름 칸)에 둔다. 기본 true |
| timeZone | string | — | 시각을 그릴 시간대. 기본 서울 |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<PreJoinLabels> | — | |
| className | string | — |
MicLevel
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| level필수 | number | — | 0–1. 앱이 AnalyserNode 로 재서 넘긴다(rAF 당 1회 이하) |
| muted | boolean | false | |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<Pick<PreJoinLabels, "levelOk" | "levelSilent" | "levelLoud" | "mutedNote">> | — | |
| className | string | — |
RecordingIndicator
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| state필수 | "starting" | "on" | "paused" | "transcribing" | — | starting 솟은 면 + 스피너 · on 반전 면 · paused 솟은 면 + 경계 · transcribing 반전 면(‘기록 중’) |
| notice필수 | RecordingNotice | — | |
| since | string | — | 경과 시간의 기준(UTC). 없으면 notice.startedAt |
| elapsedSeconds | number | — | 앱이 센 경과 초. 주면 컴포넌트가 시계를 돌리지 않는다(일시 정지 시간을 빼는 앱 · 고정 스냅샷) |
| compact | boolean | false | 아주 좁을 때: 글자 ‘녹화’ + 원만, 시간은 옆 칸. 글자는 남긴다(아이콘만으로는 고지가 아니다) |
| showTime | boolean | true | 경과 시간을 그린다. 기본 true(음성 도크처럼 자리가 좁은 곳은 false) |
| canControl | boolean | false | 호스트: 정보 팝오버에 일시 정지 · 중지 |
| onPause | () => void | — | |
| onResume | () => void | — | |
| onStopRequest | () => void | — | RecordingStopDialog 를 연다 |
| announce | boolean | true | 시작 · 일시 정지 · 다시 시작을 role=status 로 한 번 읽는다. 참가자 고지 대화상자가 같은 일을 하는 화면에서는 false(중복으로 읽지 않는다). 기본 true |
| timeZone | string | — | 시작 시각을 그릴 시간대. 기본 서울 |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<RecordingLabels> | — | 기본 문구는 예시다. 고지 문구는 제품 법무가 정한 것으로 덮는다(결정 #40) |
| className | string | — |
RecordingStartDialog
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| open필수 | boolean | — | |
| onOpenChange필수 | (open: boolean) => void | — | |
| notice필수 | Omit<RecordingNotice, "startedBy" | "startedAt"> | — | 고지 내용. retentionDays 는 필수다 — 없으면 타입 오류로 대화상자를 그릴 수 없다 |
| onConfirm필수 | () => void | — | 동의 체크 전에는 부를 수 없다 |
| confirming | boolean | false | |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<RecordingLabels> | — | 기본 문구는 예시다(결정 #40) |
RecordingNoticeDialog
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| open필수 | boolean | — | |
| notice필수 | RecordingNotice | — | |
| onStay필수 | () => void | — | |
| onLeave필수 | () => void | — | 나가기는 안전한 선택이라 secondary(빨강 아님) |
| timeZone | string | — | |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<RecordingLabels> | — | 기본 문구는 예시다(결정 #40) |
RecordingStopDialog
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| open필수 | boolean | — | |
| onOpenChange필수 | (open: boolean) => void | — | |
| durationSeconds필수 | number | — | 지금까지 녹화한 길이(초). 분으로 반올림해 말한다 |
| destination필수 | RecordingNotice["destination"] | — | |
| onStop필수 | () => void | — | 되돌릴 수 없는 동작이라 danger |
| stopping | boolean | false | |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<RecordingLabels> | — |
ConnectionBanner
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| issues필수 | ConnectionIssue[] | — | 동시에 여럿이면 급한 하나만 그린다(실패 > 오프라인 > 재연결 > 품질 > 장치 > 다시 연결됨) |
| onRetry | () => void | — | 재연결 중 ‘지금 다시 시도’ |
| onRejoin | () => void | — | 실패 ‘다시 들어가기’ |
| onTurnOffVideo | () => void | — | 품질 ‘내 영상 끄기’ |
| onOpenDevices | () => void | — | 장치 ‘장치 설정’ |
| onDismiss | (kind: ConnectionIssue["kind"]) => void | — | 닫을 수 있는 것(degraded · restored · no-mic)을 닫았을 때. restored 는 시간이 지나 거둘 때도 부른다 |
| restoredMs | number | 4000 | restored 를 거두는 시간(ms). 기본 4000 |
| density | "stage" | "dock" | "stage" | stage 는 무대 맨 위 띠, dock 은 음성 도크 안 한 줄(동작 하나) |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<ConnectionBannerLabels> | — | |
| className | string | — |
VoiceChannelMembers
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| channelId필수 | string | — | |
| channelName필수 | string | — | 목록 이름(‘디자인 리뷰 참여자’)에 쓴다 |
| members필수 | VoiceMember[] | — | |
| selfId | string | — | |
| memberMenu | (member: VoiceMember) => ReactNode | — | 사람 줄 메뉴 항목(프로필 · 음량 · 호스트 동작). 주면 줄에 ⋯ 단추가 생긴다 |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<VoiceLabels> | — | |
| className | string | — |
VoiceDock
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| status필수 | "connecting" | "connected" | "degraded" | — | |
| channel필수 | { name: string; workspace?: string; href?: string } | — | |
| recording | RecordingNotice | null | — | 있으면 ‘녹화 중’ 칸(모든 참가자에게 늘 보인다) |
| micMuted필수 | boolean | — | |
| onMicMutedChange필수 | (muted: boolean) => void | — | |
| deafened필수 | boolean | — | 소리 끄기 = 듣기도 끔. 음소거를 포함한다 |
| onDeafenedChange필수 | (deafened: boolean) => void | — | |
| sharing | boolean | false | |
| onSharingChange | (sharing: boolean) => void | — | 없으면 공유 키를 그리지 않는다 |
| onLeave필수 | () => void | — | |
| banner | ReactNode | — | 연결 띠(ConnectionBanner density="dock")를 끼울 자리 |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<VoiceLabels> | — | |
| className | string | — |
Captions
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| lines필수 | CaptionLine[] | — | 인식 결과. 마지막 두 사람 몫만 그린다 |
| state | "starting" | "on" | "error" | "on" | |
| onRetry | () => void | — | |
| size | "sm" | "md" | "lg" | "md" | 14 · 16 · 20. 기본 md. 보는 사람 하나의 설정이다 |
| spokenLang | "auto" | "ko" | "en" | "ja" | "auto" | 말하는 언어(인식). 보이는 글의 lang 속성이 된다 — 일본어는 글자 사이에서 줄을 바꾼다 |
| floating | boolean | false | 무대 아래 가운데에 띄운다(MeetingStage 의 children 으로 둘 때) |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<CaptionLabels> | — | |
| className | string | — |
Transcript
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| items필수 | TranscriptItem[] | — | |
| targetId | string | null | — | 인용 · 링크로 건너온 항목. 그 자리로 굴리고 옅은 면으로 짚는다 |
| query | string | — | 제어 · 비제어. 찾기 글 |
| defaultQuery | string | "" | |
| onQueryChange | (query: string) => void | — | |
| recording | boolean | false | 기록을 저장하는 중이면 머리에 ‘기록 중’(모두에게 보이는 표시와 같은 규칙) |
| onDownload | () => void | — | |
| onSummarize | () => void | — | AI 요약 카드를 만든다(./ai) |
| onClose | () => void | — | 패널 닫기. 찾기 칸이 비었을 때 Esc 도 같다 |
| id | string | — | 패널 키의 aria-controls 가 가리킬 id |
| timeZone | string | — | 시각을 그릴 시간대. 기본 서울 |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<TranscriptLabels> | — | |
| className | string | — |