PlToggle
눌린 채로 남는 버튼, 그리고 하나의 상태를 나누는 그 묶음입니다. 꺼진 상태는 중립입니다. 쉬고 있는 토글은 아직 취해지지 않은 액션이 아니라, 지금 거짓인 상태이기 때문입니다.
import { PlToggle, PlToggleGroup } from 'plass-ui';
<PlToggle pressed={bold} onPressedChange={setBold}>
Bold
</PlToggle>;
<PlToggleGroup multiple value={marks} onValueChange={setMarks}>
<PlToggle value="bold">Bold</PlToggle>
<PlToggle value="italic">Italic</PlToggle>
</PlToggleGroup>;import 'package:plass_ui/plass_ui.dart';
PlToggle(
pressed: bold,
onPressedChanged: (bool next) => setState(() => bold = next),
child: const Text('Bold'),
);
PlToggleGroup(
multiple: true,
value: marks,
onValueChanged: (List<String> next) => setState(() => marks = next),
children: const <Widget>[
PlToggle(value: 'bold', child: Text('Bold')),
PlToggle(value: 'italic', child: Text('Italic')),
],
);Props
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| variant공통 | 'solid' | 'glass' | 'ghost' | 'glass' | **꺼져 있을 때** 키가 무엇으로 만들어졌는지. 켜지면 어느 재질이든 색 계열이 나섭니다 |
| 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이 기본이고 PlButton보다 한 단계 아래입니다 — 토글은 액션이 아니라 상태이고, 상태는 페이지 위에 떠서 기다리지 않습니다 |
| pressed | boolean | — | 켜져 있는지. onPressedChange와 함께 controlled로 씁니다 |
| defaultPressed | boolean | false | 켜진 채로 시작할지 |
| onPressedChange | (pressed: boolean) => void | — | 켜지거나 꺼질 때 |
| value | string | — | PlToggleGroup 안에서 이 토글을 식별합니다 |
| startIcon | ReactNode | — | 라벨 앞에 놓이는 내용. em으로 크기가 정해져 라벨을 따릅니다 |
| endIcon | ReactNode | — | 라벨 뒤에 놓이는 내용 |
| fullWidth | boolean | false | 컨테이너 너비까지 늘어납니다 |
| disabled | boolean | false | 눌 수 없게 하고 tab 순서에서 뺍니다 |
| children | ReactNode | — | 라벨. 없으면 받은 아이콘 둘레로 정사각형이 됩니다 — 그래도 aria-label은 필요합니다 |
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| variant공통 | PlassVariant? | PlassVariant.glass | **꺼져 있을 때** 키가 무엇으로 만들어졌는지. 켜지면 어느 재질이든 색 계열이 나섭니다 |
| size공통 | PlassSize? | PlassSize.md | 높이와 타입 스케일 |
| color공통 | PlassColor? | PlassColor.primary | 의미론적 색 역할. 임의 색상값은 받지 않습니다 |
| density공통 | PlassDensity? | PlassDensity.standard | 여백만 바꿉니다. 높이와 글자 크기는 그대로 |
| elevation공통 | int? | 0 | 그림자 깊이. 0이 기본이고 PlButton보다 한 단계 아래입니다 — 토글은 액션이 아니라 상태이고, 상태는 페이지 위에 떠서 기다리지 않습니다 |
| pressed | bool? | — | 켜져 있는지. onPressedChange와 함께 controlled로 씁니다 |
| defaultPressed | bool | false | 켜진 채로 시작할지 |
| onPressedChanged | ValueChanged<bool>? | — | 켜지거나 꺼질 때 |
| value | String? | — | PlToggleGroup 안에서 이 토글을 식별합니다 |
| startIcon | Widget? | — | 라벨 앞에 놓이는 내용. em으로 크기가 정해져 라벨을 따릅니다 |
| endIcon | Widget? | — | 라벨 뒤에 놓이는 내용 |
| fullWidth | bool | false | 컨테이너 너비까지 늘어납니다 |
| disabled | bool? | false | 눌 수 없게 하고 tab 순서에서 뺍니다 |
| semanticLabel | String? | — | 스크린 리더가 부르는 이름. 라벨 없이 아이콘만 있는 토글에는 사실상 필수입니다 |
| focusNode | FocusNode? | — | 바깥에서 focus를 옮겨야 하는 호출자를 위한 focus node |
| autofocus | bool | false | 처음 지어질 때 focus를 가져갈지 |
| child | Widget? | — | 라벨. 없으면 받은 아이콘 둘레로 정사각형이 됩니다 — 그래도 aria-label은 필요합니다 |
네이티브 <button> 속성은 모두 그대로 전달됩니다. color는 여기서 Plass의 prop이라, value는 제출되는 값이 아니라 그룹 안에서 토글을 식별하는 것이라 제외됩니다.
PlToggleGroup
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| variant공통 | 'solid' | 'glass' | 'ghost' | 'glass' | 세트의 모든 토글에 전달됩니다 |
| 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 | 그림자 깊이. 세트의 모든 토글에 전달됩니다 |
| value | readonly string[] | — | 어느 토글이 켜져 있는지, value로. 하나든 여럿이든 배열입니다 — multiple을 켜도 타입이 바뀌지 않는 모양입니다 |
| defaultValue | readonly string[] | — | 어느 것이 켜진 채로 시작할지 |
| onValueChange | (value: string[]) => void | — | 세트의 값이 바뀔 때 |
| multiple | boolean | false | 한 번에 둘 이상 켜질 수 있는지. 꺼져 있으면 하나를 켤 때 이전 것이 꺼집니다 |
| orientation공통 | 'horizontal' | 'vertical' | 'horizontal' | 토글이 늘어서는 방향 |
| disabled | boolean | — | 세트의 모든 토글을 한 번에 끕니다 |
| loopFocus | boolean | true | 화살표 키가 양 끝에서 돌아가는지 |
| fullWidth | boolean | false | 컨테이너까지 늘어나고 너비를 토글들에 고르게 나눕니다 |
| children | ReactNode | — | 세트를 이루는 PlToggle들 |
| Prop | 타입 | 기본값 | 설명 |
|---|---|---|---|
| children * | List<Widget> | — | 세트를 이루는 토글들. 세트에 속하려면 각자 value가 있어야 합니다 |
| value | List<String>? | — | 어느 토글이 켜져 있는지, value로. 하나든 여럿이든 배열입니다 — multiple을 켜도 타입이 바뀌지 않는 모양입니다 |
| defaultValue | List<String> | <String>[] | 어느 것이 켜진 채로 시작할지 |
| onValueChanged | ValueChanged<List<String>>? | — | 세트의 값이 바뀔 때 |
| multiple | bool | false | 한 번에 둘 이상 켜질 수 있는지. 꺼져 있으면 하나를 켤 때 이전 것이 꺼집니다 |
| orientation공통 | PlassOrientation | PlassOrientation.horizontal | 토글이 늘어서는 방향 |
| variant공통 | PlassVariant? | 'glass' | 세트의 모든 토글에 전달됩니다 |
| size공통 | PlassSize? | 'md' | 높이와 타입 스케일 |
| color공통 | PlassColor? | 'primary' | 의미론적 색 역할. 임의 색상값은 받지 않습니다 |
| density공통 | PlassDensity? | 'default' | 여백만 바꿉니다. 높이와 글자 크기는 그대로 |
| elevation공통 | int? | 0 | 그림자 깊이. 세트의 모든 토글에 전달됩니다 |
| disabled | bool? | — | 세트의 모든 토글을 한 번에 끕니다 |
| fullWidth | bool | false | 컨테이너까지 늘어나고 너비를 토글들에 고르게 나눕니다 |
공용 축(variant size color density elevation)이 라이브러리 전체에서 무엇을 뜻하는지는 prop 규약에 있습니다.
Toggle, switch, checkbox 중 고르기
- 토글은 옆에 있는 것의 상태를 바꿉니다. 선택한 글자의 볼드, 캔버스의 그리드, 목록의 필터. 컨트롤이고, 폼에는 들어가지 않습니다.
PlSwitch는 설정을 바꾸고, 그 변화 자체가 핵심입니다.PlCheckbox는 컨트롤이 아니라 폼 안의 답입니다.- 하나를 고르는 값이라면
PlSegmentedButton이나PlRadioGroup입니다.multiple없는PlToggleGroup은 그것처럼 보이지만 아닙니다. 담고 있는 것은 답이 아니라 상태입니다.
Examples
variant
꺼져 있을 때 키가 무엇으로 만들어졌는지입니다. 켜지면 어느 재질이든 색 계열이 나서고, 그때 내놓는 두 답은 PlSegmentedButton의 선택된 세그먼트가 내놓는 것과 같습니다. solid는 그러데이션과 on-fill 잉크를, glass와 ghost는 시트를 밝히고 라벨을 accent로 둡니다.
꺼져 있을 때 잉크는 셋 다 --plass-muted-fg이고, 어느 것에도 색이 들어가지 않습니다. 꺼진 토글은 맑은 유리 한 조각이고, 색 계열은 누름과 함께 도착하지 그전에는 오지 않습니다.
이는 포인터 아래에서도 그대로입니다. 잃어버리기 쉬운 쪽이 이쪽입니다. 호버도 여전히 꺼진 상태이므로 중립적인 유리 사다리를 오릅니다. solid와 glass가 이미 쓰고 있던 같은 두 칸이고, 색 계열 자신의 wash는 결코 아닙니다. 그것은 on이 칠해지는 색이기 때문입니다. 상태가 둘인 컨트롤은 거짓인 상태를 참인 상태의 색으로 그려 놓고 차이를 잉크에만 맡길 수 없습니다.
elevation
켜진 토글은 들어 올려진 토글이 아닙니다. elevation은 두 상태에서 같고 색만 바뀝니다. "켜짐"은 키가 페이지에서 얼마나 떠 있는지가 아니라 토글 옆에 있는 것에 대한 사실이기 때문입니다.
기본값은 0이고 PlButton보다 한 단계 아래인데, 같은 이유입니다.
size
컨트롤 사다리 그대로입니다. md 토글은 40px이고 옆의 필드·버튼과 줄이 맞습니다. density는 여백만 옮깁니다.
PlToggleGroup
두 가지가 일어나는데 그중 하나만 시각적입니다. 이웃을 마주하는 모서리가 각지는 것. 그것이 겉모습입니다. 나머지 절반은 세트가 값을 쥔다는 것입니다. 토글들이 하나의 배열로 보고하고, variant, size, color, density, elevation, disabled는 토글마다가 아니라 그룹에서 한 번 정해집니다.
값은 두 경우 모두 배열입니다. multiple을 켜도 타입이 바뀌지 않는 유일한 모양입니다.
아이콘만, 라벨 없이
children을 빼면 토글은 받은 아이콘 둘레로 정사각형이 됩니다. 툴바 토글이 바로 그것입니다. 그래도 aria-label은 필요합니다. 라벨이 통째로 그림인 컨트롤에는 접근 가능한 이름이 아예 없습니다.
React 빌드와 다른 점
| React | Flutter | 이유 |
|---|---|---|
Base UI의 aria-pressed | Semantics(toggled:) | 프레임워크 자신의 이름으로 된 같은 주장입니다: 무언가를 하는 버튼이 아니라 상태가 붙은 버튼. |
| 그룹 전체가 tab stop 하나, 안에서는 화살표 키 | 토글마다 focus stop 하나 | Base UI의 roving tab index에 대응하는 것이 widgets.dart에는 없고, 잘못 구현한 roving focus는 플랫폼 자신의 순회보다 나쁩니다. 값이 정말로 필요한 자리에서는 PlSegmentedButton이 진짜를 지니고 있습니다. |
자유롭게 조합하는 children | 그룹의 children: List<Widget> | 어느 구성원이 양 끝인지 알아야 올바른 모서리를 각지게 할 수 있고, 그룹이 셀 수 있는 것이 리스트입니다. |
loopFocus | — | 돌릴 roving focus가 없습니다. |
onPressedChange | onPressedChanged | Flutter의 이름입니다. |
aria-label | semanticLabel | Flutter의 이름입니다. |
className, style, 네이티브 속성 | — | 전달할 class 목록도 style 속성도 없습니다. |
Accessibility
- Base UI가
aria-pressed를 지닌 진짜<button>을 그립니다. "이건 무언가를 한다"가 아니라 "이건 상태다"라고 말하는 것이 그것입니다. PlToggleGroup은 tab stop 하나이고 화살표 키가 구성원 사이를 움직입니다. 토글 여덟 개짜리 툴바가 여덟 번이 아니라 두 번의 키 누름 깊이가 되는 이유입니다.loopFocus가 양 끝에서 화살표가 돌아가는지 정합니다.- 아이콘만 있는 토글에는
aria-label이 필요합니다. 다른 무엇도 그것에 이름을 주지 못합니다. disabled는 토글을 tab 순서에서 뺍니다. 그룹의disabled는 모든 구성원에 한 번에 그렇게 합니다.- 포인터 빛은 disabled인 동안 꺼집니다. 아무도 누를 수 없는 표면이 포인터에 답하지 않도록.
- 토글은
Semantics(button: true, toggled: …)이고, 반대쪽의aria-pressed와 같은 주장입니다. - 라벨 없이 아이콘만 있는 토글에는
semanticLabel이 필요합니다. 다른 무엇도 그것에 이름을 주지 못합니다. disabled는 토글을 focus 순서에서 빼고 포인터에 아예 답하지 않게 합니다. 빛도 함께 꺼집니다.- 그룹 안의 토글은 각자 하나의 focus stop입니다. roving focus는 여기에 없고, React 빌드에 있고 여기에 없는 유일한 것이 그것입니다.