PlList
행이 쌓인 묶음입니다. 목록이 시트이고 행은 그 위에 놓인 것이므로, size와 density는 묶음의 속성이고 행은 그것을 물려받습니다.
import { PlList, PlListItem } from 'plass-ui';
<PlList>
<PlListItem description="Three unread" onClick={open}>
Inbox
</PlListItem>
<PlListItem description="One saved">Drafts</PlListItem>
</PlList>;import 'package:plass_ui/plass_ui.dart';
PlList(
children: <Widget>[
PlListItem(description: const Text('Three unread'), onPressed: open, child: const Text('Inbox')),
const PlListItem(description: Text('One saved'), child: Text('Drafts')),
],
);Props
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| variant공통 | 'solid' | 'glass' | 'ghost' | 'glass' | 시트의 재질. 컨테이너가 그렇듯 시트에는 색이 들어가지 않습니다. card 안이라면 ghost — card가 이미 시트인데 그 안의 두 번째 테두리는 사각형이 하나 더 늘어난 것뿐입니다 |
| size공통 | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'md' | 행의 타입 스케일과 여백. 행마다가 아니라 묶음 전체의 속성입니다 |
| color공통 | 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info' | 'primary' | 의미론적 색 역할. 임의 색상값은 받지 않습니다 |
| density공통 | 'default' | 'compact' | 'default' | 여백만 바꿉니다. 높이와 글자 크기는 그대로 |
| elevation공통 | 0 | 1 | 2 | 3 | 0 | 그림자 깊이. 0은 그림자 없음 |
| dividers | boolean | false | 행을 여백 대신 헤어라인으로 나눕니다. 들리는 것보다 많이 바뀝니다 — 선이 시트의 양 끝까지 닿아야 하므로 시트는 안쪽 여백을, 행은 둥근 모서리를 내놓습니다 |
| render | useRender.RenderProp | — | ul이 아닌 다른 요소로 렌더링합니다 — 순서가 핵심인 목록이라면 render={<ol />} |
| children | ReactNode | — | PlListItem들 |
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| children * | List<Widget> | — | 행들 |
| variant공통 | PlassVariant | PlassVariant.glass | 시트의 재질. 컨테이너가 그렇듯 시트에는 색이 들어가지 않습니다. card 안이라면 ghost — card가 이미 시트인데 그 안의 두 번째 테두리는 사각형이 하나 더 늘어난 것뿐입니다 |
| size공통 | PlassSize | PlassSize.md | 행의 타입 스케일과 여백. 행마다가 아니라 묶음 전체의 속성입니다 |
| color공통 | PlassColor | PlassColor.primary | 의미론적 색 역할. 임의 색상값은 받지 않습니다 |
| density공통 | PlassDensity | PlassDensity.standard | 여백만 바꿉니다. 높이와 글자 크기는 그대로 |
| elevation공통 | int | 0 | 그림자 깊이. 0은 그림자 없음 |
| dividers | bool | false | 행을 여백 대신 헤어라인으로 나눕니다. 들리는 것보다 많이 바뀝니다 — 선이 시트의 양 끝까지 닿아야 하므로 시트는 안쪽 여백을, 행은 둥근 모서리를 내놓습니다 |
네이티브 <ul> 속성은 그대로 전달됩니다. color는 여기서 Plass의 prop이라 전달 대상에서 제외됩니다.
PlListItem
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| onClick | (event: MouseEvent) => void | — | 넘기는 것이 행을 진짜 button으로 만듭니다. li가 아니라 그 button에 놓입니다 |
| href | string | — | 행을 링크로 그립니다 |
| startIcon | ReactNode | — | 라벨 앞의 것 — 아이콘, avatar, 상태 점 |
| endIcon | ReactNode | — | 라벨 뒤, 누를 수 있는 영역 안쪽의 것 |
| description | ReactNode | — | 라벨 아래 한 줄. 타입 스케일 한 칸 아래에 흐린 색 |
| action | ReactNode | — | 행 끝에 고정되는 컨트롤 — switch, 메뉴 버튼. 일부러 누를 수 있는 영역 **바깥**입니다. 이동도 하고 토글도 담는 행에는 누를 것이 둘이고, button 안의 button은 브라우저가 파싱하며 다시 쓰는 마크업입니다 |
| selected | boolean | false | 선택된 행 — 열린 페이지, 현재 필터. 링크에는 aria-current="page", button에는 "true"가 붙습니다 |
| disabled공통 | boolean | false | 사용할 수 없음. 빛이 꺼지고 누를 수 없게 됩니다 |
| children | ReactNode | — | 라벨 |
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| child | Widget? | — | 라벨 |
| onPressed | VoidCallback? | — | 넘기는 것이 행을 진짜 button으로 만듭니다. li가 아니라 그 button에 놓입니다 |
| startIcon | Widget? | — | 라벨 앞의 것 — 아이콘, avatar, 상태 점 |
| endIcon | Widget? | — | 라벨 뒤, 누를 수 있는 영역 안쪽의 것 |
| description | Widget? | — | 라벨 아래 한 줄. 타입 스케일 한 칸 아래에 흐린 색 |
| action | Widget? | — | 행 끝에 고정되는 컨트롤 — switch, 메뉴 버튼. 일부러 누를 수 있는 영역 **바깥**입니다. 이동도 하고 토글도 담는 행에는 누를 것이 둘이고, button 안의 button은 브라우저가 파싱하며 다시 쓰는 마크업입니다 |
| selected | bool | false | 선택된 행 — 열린 페이지, 현재 필터. 링크에는 aria-current="page", button에는 "true"가 붙습니다 |
| disabled공통 | bool | false | 사용할 수 없음. 빛이 꺼지고 누를 수 없게 됩니다 |
네이티브 <li> 속성은 안쪽의 button이나 link가 아니라 <li>에 그대로 전달됩니다. size, density, dividers는 감싸는 PlList에서 상속됩니다. 그중 하나를 두고 이웃과 의견이 다른 행은 구멍 난 목록입니다.
size, density, color, dividers는 감싸는 PlList에서 InheritedWidget을 통해 상속됩니다. 그중 하나를 두고 이웃과 의견이 다른 행은 구멍 난 목록입니다. PlList 밖의 PlListItem이 기본값을 고르는 대신 단언으로 막는 이유이기도 합니다. 행은 무언가의 행입니다.
라이브러리 전체에서 공유 축(variant size color density elevation)이 뜻하는 바는 prop 규칙에 있습니다.
Examples
행 하나
껍데기는 언제나 <li>입니다. 바뀌는 것은 그 안에 든 것입니다. 그냥 내용이 놓이거나, onClick이나 href가 주어지면 그 내용을 감싸는 진짜 <button> 또는 <a>가 놓입니다.
action은 일부러 그 누를 수 있는 영역 바깥에 놓입니다. 이동도 하고 토글도 담는 행에는 누를 것이 둘이고, <button> 안의 <button>은 브라우저가 파싱하며 다시 쓰는 마크업입니다.
onPressed가 있는 행은 버튼으로 알려지는 focus stop이고, 없는 행은 role도 focus stop도 더하지 않습니다.
action은 일부러 그 누를 수 있는 영역 바깥에 놓입니다. 이동도 하고 토글도 담는 행에는 누를 것이 둘이고, 중첩된 제스처 인식기는 탭 하나를 두 번 받습니다.
dividers
dividers를 켜면 선이 시트의 양 끝까지 닿아야 하므로, 목록은 안쪽 여백을 내놓고 행은 둥근 모서리를 내놓습니다. 행이 떠 있는 타일이면서 동시에 그어진 줄일 수는 없습니다.
variant
PlCard가 그렇듯 시트에는 색이 들어가지 않습니다. 목록은 남의 내용을 담고, 그 내용은 자기 색을 가지고 도착합니다.
card 안이라면 ghost입니다. card가 이미 시트인데, 그 안의 두 번째 테두리 사각형은 사각형이 하나 더 늘어난 것뿐입니다.
size
Accessibility
- 아래에 Base UI 프리미티브가 없는 것은 의도입니다. 목록은 복합 위젯이 아닙니다. roving focus도, 선택 모델도, 자기만의 키보드 규약도 없습니다. menu나 listbox 프리미티브를 끌어오면 그냥 링크 목록에 메뉴의 의미를 붙이게 됩니다.
role="list"를 명시적으로 씁니다. Tailwind의 리셋이 모든<ul>에서 불릿을 없애고, Safari는 그와 함께 목록 의미까지 없애기 때문입니다.- 선택된 링크는
aria-current="page"를, 선택된 button은aria-current="true"를 답니다. 앞의 것은 "지금 보고 있는 페이지", 뒤의 것은 "이것들 중 고른 하나"입니다.aria-pressed는 세 번째 것, 즉 토글이고, 선택된 행은 토글이 아닙니다. onClick도href도 없는 행은 role도 tab stop도 더하지 않습니다. click 핸들러만 달린 죽은<div>는 키보드에 보이지 않습니다.action에 든 컨트롤에는 자기 이름을 주세요. 행과는 별개의 tab stop이고, 거기 있는 이유가 그것입니다.
- 목록은 복합 위젯이 아닙니다(roving focus도, 선택 모델도, 자기만의 키보드 규약도 없습니다). 그래서 행을 묶는 것 말고는 role을 더하지 않고, 각 행이 스스로 이름을 갖습니다.
- 선택된 행은 선택되었다고 보고합니다. 토글이 아니고, 토글인 척하지도 않습니다.
onPressed가 없는 행은 role도 focus stop도 더하지 않습니다.action에 든 위젯에는 자기 이름을 주세요. 행과는 별개의 focus stop이고, 거기 있는 이유가 그것입니다.- 목록에 선이 그어져 있으면 행의 focus ring은 안쪽으로 돌아섭니다. 잘리는 시트 가장자리에서 잘려 나가지 않게 하기 위해서입니다.
React 빌드와 다른 점
| React | Flutter | 이유 |
|---|---|---|
onClick / href | onPressed | Flutter에는 링크 요소가 없습니다. 이동하는 행은 onPressed에서 라우터를 부릅니다. |
<ul> / <li>와 role="list" | 묶인 semantics 노드 | 리셋할 불릿도, 리셋이 앗아 갈 목록 의미도 없습니다. |
aria-current="page"와 "true" | selected | Flutter의 semantics 트리에는 선택 플래그 하나가 있을 뿐, 페이지와 선택지의 구분이 없습니다. |
| React 컨텍스트 | InheritedWidget | 같은 생각을 Flutter의 말로, 같은 이유로 한 것입니다. 자식을 복제하는 방식은 호출자가 행을 한 번 감싸는 순간 닿지 않게 됩니다. |
render | — | Flutter에는 요소를 바꿔 끼우는 수단이 없습니다. |
행의 children | child | Flutter의 이름입니다. |