반응 · 이모지
메시지 아래의 작은 키캡 칩(반응)과, 반응 추가 · 작성창이 같이 쓰는 이모지 선택기입니다. 이모지는 기기 글꼴로 그립니다.
- import
import { Reactions, ReactionChip } from "@uyoolabs/ui/chat";import { EmojiPicker, EmojiPickerPopover, SkinTonePicker, EmojiPickerSkeleton, searchEmoji } from "@uyoolabs/ui/emoji";- peer
- radix-ui · emojibase-data(선택 peer, 17). 데이터는 언어마다 수백 KB 라 처음 열 때 lang 하나만 import() 로 받고, 다른 두 언어는 검색을 시작할 때 받습니다. ./chat 도 반응 추가가 ./emoji 를 지연 로드하므로 emojibase-data 를 설치합니다.
- 쓰는 경우
- 메시지 · 댓글에 단 이모지 반응. 숫자는 몇 명이, 눌린 모양은 나도 눌렀는지, 툴팁은 누가 눌렀는지를 말합니다. 선택기는 반응 추가(고르면 닫힘)와 작성창의 이모지 단추(keepOpen)에 씁니다.
- 쓰지 않는 경우
- 평점 · 투표처럼 결과를 세는 기능에 쓰지 않습니다. 반응 칩을 알약(9999 반경)이나 색 배경만으로 그리지 않습니다. 내 반응에 체크를 붙이지 않습니다(눌린 모양 · primary 경계 · 이름이 말합니다). 이모지를 자체 그림으로 바꿔 기기 이모지와 다르게 하지 않습니다. 칩이 많아도 +n 으로 접지 않고 줄을 바꿔 감쌉니다.
반응
칩 순서는 처음 단 순서 그대로입니다(흔들리지 않게). 내가 마지막으로 끈 칩은 0 이 되면 사라지고, 반응이 하나도 없으면 칩 줄 자체가 없습니다.
비활성(보관된 채널)
| 키 | 동작 |
|---|---|
| Tab·Shift+Tab | 칩 사이를 옮깁니다(roving 없음) |
| Space·Enter | 내 반응을 켜고 끕니다. 라이브 영역이 '남겼습니다 · 거뒀습니다' 를 한 번 읽습니다 |
| 포커스 머묾 | 누가 눌렀나 툴팁이 뜨고 aria-describedby 로도 읽힙니다 |
| Shift+F10 | onShowAll(전체 목록). 길게 누름 · 보조 클릭과 같습니다 |
| 반응 추가에서 Enter | 선택기를 엽니다. Esc · 고르기로 닫히면 반응 추가 칩으로 돌아갑니다 |
이모지 선택기
352 × (머리 · 탭 · 격자 252 · 미리보기 48), 9열 · 칸 36. 열면 검색칸에 포커스가 가고, 닫히면 연 단추로 돌아갑니다. 가상 목록 없이 1,900칸 남짓을 두되, 범주를 6줄 덩어리로 나눠 화면 가까이 올 때 그리고 한 번 그린 것은 지우지 않습니다.
팝오버(작성창 · keepOpen)
펼쳐 둔 선택기
고르면 EmojiSelection 이 여기에 나옵니다
| 키 | 동작 |
|---|---|
| 글자(검색칸) | 결과로 격자를 바꾸고 범주 탭을 숨깁니다 |
| ↓ | 검색칸 → 격자 첫 칸 |
| ←·→·↑·↓ | 격자 안에서 칸을 옮깁니다. 줄 끝에서 다음 줄로 이어지고, 첫 줄에서 ↑ 는 검색칸입니다 |
| Home·End | 줄 처음 · 끝. Ctrl 과 함께면 격자 처음 · 끝 |
| PageUp·PageDown | 한 화면(7줄) 위 · 아래 |
| Enter·Space | 고릅니다. 반응은 닫히고, keepOpen 이면 Shift 와 함께일 때 열어 둡니다 |
| Enter(검색칸) | 첫 결과를 고릅니다. IME 조합 중이면 무시합니다 |
| Tab·Shift+Tab | 검색칸 → 피부색 단추 → 범주 탭 → 격자 순입니다 |
| ←·→ | (범주 탭) 범주를 옮기고 그 범주로 스크롤합니다. 포커스는 탭에 남습니다 |
| Esc | 피부색 칸이 열려 있으면 그것만, 아니면 선택기를 닫고 연 단추로 돌아갑니다 |
검색 색인
emojibase-data 의 ko · ja 주석 · 영어 이름 · GitHub 단축어를 한 색인에 넣습니다. lang 과 상관없이 세 언어 검색어가 모두 맞고(하트 · ハート · heart → ❤️), 가타카나와 히라가나를 같게 봅니다. 순서는 최근 사용 → 정확히 맞음 → 앞부분 맞음입니다.
—
내장 문구
lang(ko · en · ja)으로 고르고 labels 로 일부를 바꿉니다(ADR 0011). 이모지 이름은 emojibase 데이터에서 옵니다.
| 키 | ko | en | ja |
|---|---|---|---|
| chip | 좋아요 반응 5 개, 내 반응 | thumbs up, 5 reactions, yours | サムズアップ 5 件、自分のリアクション |
| who | 김하린(나), 이도현 외 3명이 👍 로 반응했습니다 | Harin Kim (you), Dohyun Lee and 3 others reacted with 👍 | 金ハリン(自分)、李ドヒョン ほか 3 人が 👍 でリアクションしました |
| empty | '결제하트'에 일치하는 이모지가 없습니다 | No emoji match 'kitteh' | 「ぬこ」に一致する絵文字がありません |
props
소스의 타입과 JSDoc 에서 생성한 표입니다. 컴포넌트를 고치면 pnpm gen 으로 다시 만듭니다.
Reactions
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| items필수 | Reaction[] | — | 처음 단 순서. 개수가 바뀌어도 순서를 바꾸지 않는다(흔들리지 않게). 개수 0 인 칩은 그리지 않는다 |
| onToggle필수 | (emoji: string, next: boolean) => void | — | 칩을 누를 때. 앱이 낙관적으로 바꾸고, 실패하면 되돌리고 토스트를 띄운다 |
| onAdd | (emoji: string) => void | — | 반응 추가 칩 → EmojiPicker → 고르면 닫힌다. 없으면 반응 추가 칩을 두지 않는다 |
| onShowAll | (emoji: string) => void | — | 칩을 길게 누르거나(500ms) 보조 클릭 · 메뉴 키 — 전체 목록 팝오버를 연다 |
| disabled | boolean | false | 보관된 채널 · 읽기 전용 권한. 평평한 칩, 숫자는 읽히고 포커스도 받는다 |
| self | ReactionUser | — | 나. 툴팁에서 '김하린(나)' 로 맨 앞에 둔다 |
| recent | readonly string[] | — | EmojiPicker 최근 사용 |
| skinTone | 0 | 1 | 2 | 3 | 4 | 5 | — | EmojiPicker 피부색(제어). 앱이 기억한다 |
| onSkinToneChange | (tone: SkinTone) => void | — | |
| lang | "ko" | "en" | "ja" | "ko" | |
| labels | Partial<ReactionsLabels> | — |
이 밖에 <div> props(children · role · onToggle 제외)를 그대로 받습니다.
ReactionChip
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| emoji필수 | string | — | |
| count필수 | number | — | |
| mine | boolean | false | aria-pressed. 체크를 붙이지 않는다(W2~W5 결정 #29) — 눌린 모양 · 경계 · 이름이 말한다 |
이 밖에 <button> props(children · type 제외)를 그대로 받습니다. 기본값을 바꾼 것은 disabled=false 입니다.
EmojiPicker
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| onSelect필수 | (selection: EmojiSelection) => void | — | 고를 때. 반응은 고르면 닫히고, 작성창(keepOpen)은 Shift 와 함께면 열어 둔다 |
| recent | readonly string[] | — | 최근 사용(최대 9, 맨 앞 한 줄). 저장은 앱이 한다. 없으면 👍 ✅ 👀 🎉 🙏 😂 ❤️ 🔥 💯 |
| skinTone | 0 | 1 | 2 | 3 | 4 | 5 | — | 피부색(제어) |
| defaultSkinTone | 0 | 1 | 2 | 3 | 4 | 5 | — | 처음 피부색(비제어) |
| onSkinToneChange | (tone: SkinTone) => void | — | |
| keepOpen | boolean | — | 작성창: true 면 Shift+Enter · Shift+누르기에서 닫지 않는다. 반응은 false |
| lang | "ko" | "en" | "ja" | — | 검색 색인 · 이름 · 범주 이름의 언어. 검색어는 언어와 상관없이 세 언어가 모두 맞는다 |
| labels | Partial<EmojiPickerLabels> | — | |
| defaultQuery | string | — | 처음 검색어. 작성창의 : 목록에서 '더 보기'로 열 때 친 글자를 이어 받는다 |
| onClose | () => void | — | Esc · 고르고 닫힐 때. 선택기를 담은 쪽(대화상자 · 패널)이 닫는다 |
| autoFocus | boolean | true | 그리면 검색칸에 포커스(열면 검색칸). 문서처럼 늘 펼쳐 둔 자리에서는 false |
이 밖에 <div> props(children · defaultValue · onSelect · role 제외)를 그대로 받습니다.
EmojiPickerPopover
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| onSelect필수 | (selection: EmojiSelection) => void | — | 고를 때. 반응은 고르면 닫히고, 작성창(keepOpen)은 Shift 와 함께면 열어 둔다 |
| recent | readonly string[] | — | 최근 사용(최대 9, 맨 앞 한 줄). 저장은 앱이 한다. 없으면 👍 ✅ 👀 🎉 🙏 😂 ❤️ 🔥 💯 |
| skinTone | 0 | 1 | 2 | 3 | 4 | 5 | — | 피부색(제어) |
| defaultSkinTone | 0 | 1 | 2 | 3 | 4 | 5 | — | 처음 피부색(비제어) |
| onSkinToneChange | (tone: SkinTone) => void | — | |
| keepOpen | boolean | — | 작성창: true 면 Shift+Enter · Shift+누르기에서 닫지 않는다. 반응은 false |
| lang | "ko" | "en" | "ja" | — | 검색 색인 · 이름 · 범주 이름의 언어. 검색어는 언어와 상관없이 세 언어가 모두 맞는다 |
| labels | Partial<EmojiPickerLabels> | — | |
| defaultQuery | string | — | 처음 검색어. 작성창의 : 목록에서 '더 보기'로 열 때 친 글자를 이어 받는다 |
| trigger필수 | ReactElement | — | 여는 단추. asChild 로 받는다(반응 추가 칩 · 작성창 이모지 단추) |
| tooltip | string | — | 트리거의 이름표 툴팁(아이콘만 있는 단추). 트리거의 aria-label 과 같은 글자다 |
| open | boolean | — | 열림(제어) |
| defaultOpen | boolean | false | 처음 열림(비제어) |
| onOpenChange | (open: boolean) => void | — | |
| onClose | () => void | — | Esc · 바깥 누름 · 고르고 닫힐 때 |
| side | "top" | "bottom" | "top" | 기본 top(작성창 위로). 자리가 없으면 반대로 뒤집힌다 |
SkinTonePicker
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| value | 0 | 1 | 2 | 3 | 4 | 5 | — | 고른 피부색(제어) |
| defaultValue | 0 | 1 | 2 | 3 | 4 | 5 | 0 | 처음 피부색(비제어) |
| onValueChange | (tone: SkinTone) => void | — | 화살표로 옮기거나 누를 때마다 |
| onPick | (tone: SkinTone) => void | — | 고르기를 마칠 때(누름 · Enter · Space). 화살표 이동은 고름이지만 마침은 아니다. 선택기는 이때 칸을 닫는다 |
| autoFocus | boolean | false | 처음 그릴 때 고른 칸에 포커스를 둔다(선택기 머리에서 열 때) |
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<EmojiPickerLabels> | — |
이 밖에 <div> props(children · onChange · defaultValue · role 제외)를 그대로 받습니다.
EmojiPickerSkeleton
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| lang | "ko" | "en" | "ja" | — | |
| labels | Partial<EmojiPickerLabels> | — |
이 밖에 <div> props(children 제외)를 그대로 받습니다.