오버레이
화면 위에 뜨는 것: 툴팁(80) · 팝오버(60) · 대화상자(가림막 40 · 카드 50) · 드로어(Sheet). 층은 토큰으로만 정합니다.
작고 잠깐 뜨는 팝오버는 지붕 모서리가 없는 뜬 판지, 대화상자 · 드로어는 오른쪽 위가 꺾인 떠 있는 카드입니다. 가림막은 blur 없는 단색입니다. 겹친 대화상자는 가림막이 두 겹이라 아래 대화상자까지 덮습니다. 툴팁 · 팝오버에는 화살표가 없습니다. 모두 radix-ui 가 필요합니다.
- import
- import { Tooltip, TooltipProvider, Popover, Dialog, AlertDialog, Sheet, … } from "@uyoolabs/ui/overlay";
- 고르는 법
- 이름표 · 짧은 이유 → Tooltip. 화면을 떠나지 않는 작은 작업(필터 · 공유 · 링크 편집) → Popover. 멈추고 답해야 하는 폼 · 확인 → Dialog. 되돌릴 수 없는 동작 바로 앞 → AlertDialog. 한 항목을 들여다보기 · 조건이 많은 필터 → Sheet.
- 쓰지 않는 경우
- 툴팁 안에 버튼 · 링크 · 입력을 두지 않습니다. 꼭 읽어야 하는 정보를 툴팁에만 두지 않습니다. 팝오버 안에서 팝오버 · 대화상자를 열지 않습니다. 대화상자 안에서 또 다른 폼 대화상자를 열지 않습니다(확인용 AlertDialog 한 겹만). 시트 위에 시트를 열지 않습니다. '확인 · 예 · 아니요' 버튼을 쓰지 않습니다.
Tooltip
아이콘 버튼의 이름표 · 단축키 · 짧은 이유를 짙은 반전 면에 담습니다. 포커스를 받지 않습니다.
| 키 | 동작 |
|---|---|
| 포인터를 올림 | 400ms 뒤 열린다(delay) |
| 이웃 트리거로 옮김 | 닫힌 지 300ms 안이면 바로(skipDelay) |
| 키보드 포커스 | 바로 열린다 |
| Esc | 닫힌다. 포커스는 트리거에 남는다 |
| 누름 · 떠남 | 바로 닫힌다. 툴팁 위로 옮겨도 열린 채(WCAG 1.4.13) |
| 터치 | 열지 않는다 |
Popover
누르면 열리는 비모달 판지입니다. 포커스를 가두지 않고, Tab 이 밖으로 나가면 닫힙니다.
| 키 | 동작 |
|---|---|
| Enter·Space | 트리거에서 열고 첫 입력(initialFocus)으로 |
| Tab·Shift+Tab | 팝오버 안을 돈다. 끝에서 밖으로 나가면 닫힌다 |
| Esc | 닫고 트리거로 포커스를 돌린다 |
| 바깥 누름 | 닫는다. 포커스는 누른 곳에 |
Dialog
sm 400 · md 560 · lg 720. 본문만 스크롤하고, 넘칠 때만 머리 · 바닥에 헤어라인이 생깁니다. 제출 중에는 dismissible={false} 로 닫힘을 막습니다.
AlertDialog
되돌릴 수 없는 동작의 확인입니다. 제목은 질문, 설명은 결과를 숫자로, 버튼은 하는 일의 동사로 씁니다. 바깥 누름 · 닫기 버튼이 없습니다.
| 키 | 동작 |
|---|---|
| Tab·Shift+Tab | 대화상자 안에서만 돈다 |
| Esc | 닫는다. dismissible=false 면 무시, [data-escape-local] 안이면 양보, AlertDialog 는 취소와 같다 |
| Enter | 폼 제출. 조합 중 Enter(한 · 일 IME)는 막는다 |
| 열 때 · 닫을 때 | 열면 initialFocus → 첫 입력 → 바닥 첫 버튼(AlertDialog 는 [취소]). 닫으면 Trigger → returnFocusRef → 열 때의 요소 |
Sheet
화면 옆에 꺼내 놓는 떠 있는 카드입니다. 오른쪽은 가장자리에서 8 띄우고, 640 미만에서는 아래에서 올라옵니다(side=auto).
| 키 | 동작 |
|---|---|
| Tab·Shift+Tab | 시트 안에서만 돈다 |
| Esc | 닫는다(dismissible=false 면 무시) |
| Alt+← | onBack 이 있으면 [뒤로]와 같다 |
| Enter | 폼 제출. 조합 중 Enter 는 막는다 |
props
소스의 타입과 JSDoc 에서 생성한 표입니다. 컴포넌트를 고치면 pnpm gen 으로 다시 만듭니다.
Tooltip
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| label필수 | ReactNode | — | 툴팁 글자. 글자와 인라인 강조만 — 버튼 · 링크 · 입력을 넣지 않는다 |
| keys | readonly string[] | — | 단축키. 키마다 테두리만 있는 키캡(반전 면용) |
| kind | "name" | "description" | "description" | "name": 트리거의 이름표(아이콘 버튼). 트리거의 aria-label 과 같은 글자라 aria-describedby 를 걸지 않는다. "description": 덧붙이는 설명(비활성 이유 · 짧은 도움말). 열려 있는 동안 트리거의 aria-describedby 가 된다 |
| side | "top" | "right" | "bottom" | "left" | "top" | 화면 끝이면 반대로 뒤집힌다 |
| align | "start" | "center" | "end" | "center" | |
| sideOffset | number | 6 | 트리거와의 간격(px) |
| delay | number | — | 열기 지연(ms). Provider 값을 덮는다 |
| disabled | boolean | false | 툴팁만 끈다(트리거는 그대로) |
| open | boolean | — | |
| defaultOpen | boolean | — | |
| onOpenChange | (open: boolean) => void | — | |
| children필수 | ReactElement | — | 트리거. 포커스를 받는 요소 하나. disabled 버튼은 aria-disabled 로 바꿔 넣어 포커스가 남게 한다 |
TooltipProvider
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| delay | number | 400 | 포인터를 올린 뒤 열리기까지(ms). 지나가는 포인터에 깜박이지 않게 400 |
| skipDelay | number | 300 | 닫힌 지 이 시간 안에 이웃 트리거로 가면 지연 없이 바로 연다(ms) |
| children필수 | ReactNode | — |
PopoverContent
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| size | "sm" | "md" | "lg" | "md" | 240 · 320 · 400 |
| title | ReactNode | — | 보이는 제목. aria-labelledby 가 된다. 없으면 aria-label 을 꼭 준다 |
| sideOffset | number | 4 | 트리거와의 간격(px) |
| align | "start" | "center" | "end" | "start" | 트리거 기준 맞춤. 기본은 시작선 |
| initialFocus | "first" | "content" | RefObject<HTMLElement | null> | "first" | 열 때 포커스. "first" 는 첫 포커스 가능한 요소, "content" 는 판지 자체(읽기 위주) |
이 밖에 Radix Popover.Content props(title 제외)를 그대로 받습니다. 기본값을 바꾼 것은 collisionPadding=8 입니다.
Dialog
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| open | boolean | — | |
| defaultOpen | boolean | false | |
| onOpenChange | (open: boolean) => void | — | |
| dismissKey | string | number | null | — | 열린 동안 이 값이 바뀌면 onOpenChange(false). 열 때의 값이 기준이다. 앱은 주소 키(useLocationKey)를 넘긴다 |
| returnFocusRef | RefObject<HTMLElement | null> | — | 닫은 뒤 포커스를 돌려줄 요소. Trigger 가 없을 때 쓴다. 없으면 열 때의 activeElement |
| dismissible | boolean | true | false 면 Esc · 바깥 누름 · 닫기 버튼으로 닫히지 않는다(제출 중 · 입력이 바뀐 폼) |
| children필수 | ReactNode | — |
DialogContent
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| size | "sm" | "md" | "lg" | "md" | 400 · 560 · 720 |
| initialFocus | RefObject<HTMLElement | null> | — | 열 때 포커스. 없으면 첫 입력, 그것도 없으면 바닥의 첫 버튼 |
| showClose | boolean | true | 머리 오른쪽 닫기 버튼(DialogHeader 가 그린다) |
| lang | "ko" | "en" | "ja" | — | 내장 문구(닫기)의 언어 |
| labels | Partial<DialogLabels> | — | 내장 문구 중 바꿀 것 |
이 밖에 Radix Dialog.Content props(title 제외)를 그대로 받습니다.
DialogTitle
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| hidden | boolean | — | 화면에서 숨기고 스크린 리더에만 읽힌다 |
이 밖에 Radix Dialog.Title props를 그대로 받습니다.
AlertDialog
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| open | boolean | — | |
| defaultOpen | boolean | false | |
| onOpenChange | (open: boolean) => void | — | |
| dismissKey | string | number | null | — | 열린 동안 이 값이 바뀌면 onOpenChange(false). Dialog 와 같다 |
| returnFocusRef | RefObject<HTMLElement | null> | — | 닫은 뒤 포커스를 돌려줄 요소. Trigger 가 없을 때 쓴다 |
| children필수 | ReactNode | — |
AlertDialogContent
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| size | "sm" | "md" | "sm" | 400 · 560 |
| lang | "ko" | "en" | "ja" | — | 내장 문구(취소 · 이름 입력 라벨)의 언어 |
| labels | Partial<AlertDialogLabels> | — | 내장 문구 중 바꿀 것 |
이 밖에 Radix AlertDialog.Content props를 그대로 받습니다.
AlertDialogAction
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| variant | "danger" | "primary" | "danger" | "primary" 는 되돌릴 수 있는데 확인이 필요한 드문 경우만 |
| loading | boolean | false | 스피너 · aria-busy. 그동안 [취소] · Esc 가 막힌다 |
이 밖에 Radix AlertDialog.Action props를 그대로 받습니다.
AlertDialogConfirmInput
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| expected필수 | string | — | 입력해야 하는 글자(프로젝트 이름). 앞뒤 공백을 지우고 NFC 로 맞춘 뒤 정확히 같아야 한다(대소문자 구분) |
| value | string | — | |
| defaultValue | string | "" | |
| onValueChange | (value: string) => void | — | |
| onMatchChange | (matched: boolean) => void | — | |
| label | ReactNode | — | 라벨. 없으면 labels.confirmPrompt(expected) 이고 이름을 굵게 보인다 |
이 밖에 <input> props(value · defaultValue · size 제외)를 그대로 받습니다.
Sheet
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| open | boolean | — | |
| defaultOpen | boolean | false | |
| onOpenChange | (open: boolean) => void | — | |
| dismissKey | string | number | null | — | 열린 동안 이 값이 바뀌면 onOpenChange(false). 상세 보기는 주소 키를 넘긴다(뒤로 가기가 닫는다) |
| returnFocusRef | RefObject<HTMLElement | null> | — | 닫은 뒤 포커스를 돌려줄 요소. 주소로 열려 Trigger 가 없을 때 쓴다. 없으면 열 때의 activeElement |
| dismissible | boolean | true | false 면 Esc · 바깥 누름 · 닫기 버튼으로 닫히지 않는다(저장 중) |
| children필수 | ReactNode | — |
SheetContent
| prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| side | "right" | "bottom" | "auto" | "auto" | "auto": 640 미만 bottom, 이상 right. data-side 에는 실제 쪽이 적힌다 |
| size | "sm" | "md" | "lg" | "md" | 360 · 480 · 640. 화면이 시트 + 320 보다 좁으면 한 단계 작게 |
| initialFocus | "title" | "first" | RefObject<HTMLElement | null> | "first" | 열 때 포커스. "first" 는 첫 입력, 상세 보기는 "title"(읽으러 연 사람이 입력칸부터 만나지 않게) |
| onBack | () => void | — | 있으면 머리 왼쪽에 [뒤로]. Alt+← 도 같다. 시트 안에서 다른 항목으로 옮겼을 때 |
| showClose | boolean | true | 머리 오른쪽 닫기 버튼 |
| lang | "ko" | "en" | "ja" | — | 내장 문구(닫기 · 뒤로)의 언어 |
| labels | Partial<SheetLabels> | — | 내장 문구 중 바꿀 것 |
이 밖에 Radix Dialog.Content props(title 제외)를 그대로 받습니다.