PlTooltip
포인터가 무언가에 머무를 때 나타나는 짧은 라벨입니다. 컴포넌트 전체가 감싸개일 뿐이라 레이아웃에 요소를 더하지 않고, 자식은 원래의 그것으로 남습니다.
import { PlTooltip } from 'plass-ui';
<PlTooltip content="Copy to clipboard">
<PlButton aria-label="Copy">
<CopyIcon />
</PlButton>
</PlTooltip>;import 'package:plass_ui/plass_ui.dart';
PlTooltip(
content: const Text('Copy to clipboard'),
child: PlButton(
semanticLabel: 'Copy',
onPressed: copy,
child: const PlIcon(icon: CopyGlyph()),
),
);tooltip은 판을 트리 밖으로 들어 올리므로 위쪽에 Overlay가 필요합니다. navigator가 있는 WidgetsApp과 MaterialApp이 둘 다 제공합니다. 감싸개 자체는 레이아웃에 상자를 더하지 않습니다.
Props
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| content * | ReactNode | — | tooltip이 하는 말. 짧은 구절이어야 합니다 — tooltip은 컨테이너가 아닙니다. 터치 화면에서는 포인터가 닿을 수 없고, 주의가 옮겨가는 순간 사라지며, 그 안의 무엇도 누를 수 없습니다 |
| children * | ReactElement | — | tooltip이 매달리는 요소. 정확히 하나여야 하고, ref와 props를 받을 수 있어야 합니다 — 모든 Plass 컴포넌트가 그렇습니다 |
| side공통 | 'top' | 'right' | 'bottom' | 'left' | 'top' | 트리거의 어느 변에 나타날지. 자리가 없으면 반대편으로 뒤집힐 수 있고, 그것이 옳은 동작입니다 |
| align공통 | 'start' | 'center' | 'end' | 'center' | 그 변을 따라 어디에 놓일지 |
| sideOffset | number | 6 | 트리거와의 거리(px) |
| delay | number | 600 | 포인터가 얼마나 머물러야 열리는지(ms) |
| closeDelay | number | 0 | 포인터가 떠난 뒤 닫히기까지 기다리는 시간 |
| arrow | boolean | true | 트리거를 가리키는 작은 쐐기를 그릴지 |
| size공통 | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'sm' | 판의 타입 스케일과 여백 |
| color공통 | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'secondary' | 의미론적 색 역할. tooltip은 다른 무언가에 **대한** 메모이지 그 무언가 자체가 아니므로 중립 계열이 정직한 기본값입니다 — 삭제 버튼 위의 빨간 tooltip은 tooltip이 알지 못하는 것을 말하고 있는 셈입니다 |
| density공통 | 'default' | 'compact' | 'default' | 판의 가로 여백만 바꿉니다 |
| open | boolean | — | tooltip이 열려 있는지. controlled로 쓰려면 onOpenChange와 함께 |
| defaultOpen | boolean | — | uncontrolled일 때 처음부터 열려 있을지 |
| onOpenChange | (open: boolean) => void | — | 열림 상태가 바뀔 때 |
| disabled공통 | boolean | false | 트리거는 그대로 두고 tooltip만 열리지 않게 합니다. 라벨이 잘렸을 때만 존재하는 tooltip을 위한 것입니다 |
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| content * | Widget | — | tooltip이 하는 말. 짧은 구절이어야 합니다 — tooltip은 컨테이너가 아닙니다. 터치 화면에서는 머무를 포인터가 없고, 주의가 옮겨가는 순간 사라지며, 그 안의 무엇도 누를 수 없습니다 |
| child * | Widget | — | tooltip이 매달리는 것. 감싸개는 레이아웃에 상자를 더하지 않습니다 |
| side공통 | PlassSide | PlassSide.top | 트리거의 어느 변에 나타날지. 자리가 없으면 반대편으로 뒤집힙니다 — 뒤집을 뿐 옆으로 미끄러지지는 않습니다 |
| align공통 | PlassAlign | PlassAlign.center | 그 변을 따라 어디에 놓일지 |
| offset | double | 6 | 트리거에서 떨어진 거리, 논리 픽셀 |
| delay | Duration | Duration(milliseconds: 600) | 포인터가 얼마나 머물러야 열리는지(ms) |
| closeDelay | Duration | Duration.zero | 포인터가 떠난 뒤 닫히기까지 기다리는 시간 |
| arrow | bool | true | 트리거를 가리키는 작은 쐐기를 그릴지 |
| open | bool? | — | 바깥에서 tooltip을 움직입니다. null이면 포인터와 길게 누르기, focus에 맡깁니다 — 패키지에서 컴포넌트가 자기 상태를 쥐는 유일한 자리입니다 |
| onOpenChanged | ValueChanged<bool>? | — | 열리거나 닫힐 때마다, 무엇이 청했든 불립니다 |
| disabled공통 | bool | false | 트리거는 그대로 두고 tooltip만 열리지 않게 합니다. 라벨이 잘렸을 때만 존재하는 tooltip을 위한 것입니다 |
| semanticLabel | String? | — | 스크린 리더가 트리거의 tooltip으로 읽는 말. content가 Text면 그 글자가 기본값입니다 |
| size공통 | PlassSize | PlassSize.sm | 판의 타입 스케일과 여백 |
| density공통 | PlassDensity | PlassDensity.standard | 판의 가로 여백만 바꿉니다 |
네이티브 <div> 속성은 판에 그대로 전달됩니다. color, content, children은 셋 다 여기서는 Plass의 prop이라 전달 대상에서 제외됩니다.
open은 bool?이고, 기본값인 null은 tooltip이 포인터와 길게 누르기, focus로 자기를 움직인다는 뜻입니다. 패키지에서 컴포넌트가 자기 상태를 쥐는 유일한 자리입니다. 나머지가 전부 controlled인 이유는 값에 대해 호출하는 쪽이 의견을 갖기 때문인데, 포인터가 버튼 위에 머물러 있는지에 대해서는 아무도 의견이 없습니다. onOpenChanged는 어느 쪽이든 보고합니다.
color는 없습니다. tooltip은 무언가에 대한 메모이지 그 무언가 자체가 아니므로 판은 언제나 중립적인 시트입니다. 삭제 버튼 위의 빨간 tooltip은 tooltip이 알지 못하는 것을 말하고 있는 것입니다.
variant도 elevation도 없습니다. 판은 PlSelect의 popup과 같은 떠 있는 시트입니다(가장 불투명한 유리, 그 둘레의 흰 헤어라인, 사다리 꼭대기의 그림자). 대부분의 라이브러리가 tooltip을 그리는 채워진 키가 아닙니다. tooltip은 무언가에 대한 메모이지 누르는 물건이 아니고, 한 화면에 떠 있는 시트가 두 종류인 것은 하나가 너무 많은 것입니다.
라이브러리 전체에서 공유 축(`size` `color` `density` `side` `align``size` `density` `side` `align`)이 뜻하는 바는 prop 규칙에 있습니다.
Examples
side와 align
side는 자리가 없으면 반대편 변으로 뒤집힙니다. 그것이 옳은 동작입니다. 화면 밖으로 반쯤 나간 tooltip은 아무 말도 하지 않습니다.
뒤집을 뿐 미끄러지지는 않습니다. 요청한 쪽에 자리가 없으면 판은 반대편으로 가고, 자기가 놓인 변을 따라 옆으로 밀리지는 않습니다. 미끄러지려면 판의 위치를 매 프레임 뷰포트에 대해 다시 계산해야 하는데, 스크롤하는 앵커에 판을 붙여 두는 layer link가 존재하는 이유가 바로 그것을 피하기 위해서입니다. 그리고 트리거가 가장자리에 가까워질수록 옆으로 기어가는 판은 쐐기가 아무것도 가리키지 않는 판입니다.
PlTooltipProvider
여러 tooltip이 하나의 delay를 나눠 씁니다. 그중 하나가 한 번 열리고 나면 이웃들은 즉시 열리고, 잠시 쉬면 기다림이 다시 돌아옵니다.
툴바를 감쌀 만합니다. 이것이 없으면 아이콘 버튼이 늘어선 줄을 따라 움직일 때마다 매번 delay를 끝까지 기다려야 하고, 그것이 tooltip이 포인터와 싸우는 것처럼 느껴지게 만드는 이유입니다.
React 패키지에는 아직 PlTooltipProvider가 없습니다.
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| child * | Widget | — | 그룹 안에 있는 것 |
| timeout | Duration | Duration(milliseconds: 300) | 하나가 닫힌 뒤 이웃들이 얼마 동안 즉시 열리는지 |
delay, closeDelay, disabled
disabled는 트리거는 그대로 두고 tooltip만 열리지 않게 합니다. 라벨이 잘렸을 때만 존재하는 tooltip을 위한 것입니다.
두 delay는 숫자가 아니라 Duration입니다. 길게 눌러 연 tooltip은 자기 시계를 따릅니다. 손가락이 떨어진 뒤에도 1.5초 동안 남아 있는데, 포인터가 떠나는 것은 읽기를 그만둔 독자이고 손가락이 떨어지는 것은 이제 막 읽기 시작한 독자이기 때문입니다.
size
Accessibility
- 판에는
role="tooltip"이, 트리거에는 그것을 가리키는aria-describedby가 붙습니다. 열려 있는 동안에만입니다. 문서에 없는 요소를 가리키는 참조는 아무것도 가리키지 않는 참조이기 때문입니다. Base UI는 popup이 여러 가지일 수 있어 둘 다 호출하는 쪽에 맡기지만, 여기서는 언제나 tooltip이므로 컴포넌트가 직접 연결합니다. - Base UI의 Trigger는 자기 상자를 그리는 대신 자식에 합쳐집니다. 그래서 tooltip은 레이아웃에 요소도, 자기 tab stop도 더하지 않습니다.
- focus에서 열리되 클릭에서 온 focus에서는 열리지 않고, Escape에서 닫힙니다. 셋 다 프리미티브의 것입니다.
- tooltip은 라벨이 아닙니다. 설명할 뿐 이름을 붙이지 않습니다. 아이콘만 있는 버튼에는 자기
aria-label이 따로 필요합니다. 접근 가능한 이름이 없는 트리거는 음성 제어로 도달할 수 없고, tooltip은 그 이름을 대신 줄 만큼 페이지에 늘 있지 않습니다. - tooltip 안의 무엇도 누를 수 없고, 터치 화면에는 머무를 포인터가 없습니다. 둘 중 하나가 필요한 내용은 자리를 지키는 곳에 있어야 합니다.
- 판에 적힌 문구는 트리거가 자기 tooltip으로 나릅니다. 스크린 리더는 이 경로로 문구를 얻습니다. 판 자체는 semantics에서 제외되는데, 떠 있는 노드가 같은 문구를 되풀이하면 스크린 리더가 두 번 읽기 때문입니다.
content가Text이면 그 문자열이 알아서 쓰이고, 그 외에는semanticLabel이 필요합니다. - 감싸개는 레이아웃에 상자도, 자기 focus stop도 더하지 않습니다. 자식은 원래의 그것으로 남습니다.
- hover와 길게 누르기, focus에서 열리고, 그중 무엇이 끝나든 판은 사라집니다.
- tooltip은 라벨이 아닙니다. 설명할 뿐 이름을 붙이지 않습니다. 아이콘만 있는 버튼에는 자기
semanticLabel이 따로 필요합니다. 자기 이름이 없는 트리거는 무엇도 읽어 줄 수 없는 트리거입니다. - tooltip 안의 무엇도 누를 수 없고, 터치 화면에는 머무를 포인터가 없습니다. 둘 중 하나가 필요한 내용은 자리를 지키는 곳에 있어야 합니다.
React 빌드와 다른 점
| React | Flutter | 이유 |
|---|---|---|
open / defaultOpen / onOpenChange | open: bool? / onOpenChanged | null이 tooltip이 스스로를 움직이는 상태, 즉 uncontrolled였던 것입니다. 값을 주면 넘겨받습니다. |
밀리초인 delay, closeDelay | Duration | 시간 길이에 대한 Dart 자신의 타입입니다. |
role="tooltip"과 aria-describedby | 트리거 자신의 tooltip | Flutter는 상태를 노드 자체에 적습니다. 가리킬 id가 없고, 판은 제외되어 문구가 한 번만 읽힙니다. |
판이 읽어 주는 content | 그려지는 content와 읽히는 semanticLabel | 위젯은 읽어 줄 수 없습니다. Text는 문자열을 알아서 내주고, 그 외에는 뜻하는 바를 적습니다. |
color | — | 내용이 읽던 슬롯에만 닿았고, 여기서 내용은 자기 색을 가지고 옵니다. |
| 두 축 모두의 충돌 처리 | 미끄러짐 없는 뒤집기 | 미끄러지려면 매 프레임 뷰포트에 대해 위치를 다시 계산해야 하고, 트리거에서 벗어난 쐐기는 아무것도 가리키지 않습니다. |
| focus에서 열되 클릭에서 온 focus에서는 안 열림 | focus에서 열림 | tooltip이 감싸는 노드에서 "이 focus는 포인터에서 왔다"에 해당하는 것이 Flutter에는 없습니다. |
className, style, 네이티브 속성 | — | 전달할 클래스 목록도 style 속성도 없습니다. |
delay 그룹은 여기에도 같은 이름으로 있습니다. 툴바를 PlTooltipProvider로 감싸세요.