본문으로 건너뛰기

PlRadioGroup

여러 옵션 중 정확히 하나를 고르는 묶음입니다. 묶음 전체가 tab stop 하나를 차지하고, 그 안에서는 방향키로 움직입니다.

React
tsx
import { PlRadio, PlRadioGroup } from 'plass-ui';

<PlRadioGroup label="Plan" defaultValue="team">
  <PlRadio value="starter" label="Starter" />
  <PlRadio value="team" label="Team" />
</PlRadioGroup>;
dart
import 'package:plass_ui/plass_ui.dart';

PlRadioGroup<String>(
  label: const Text('Plan'),
  value: plan,
  onChanged: (String next) => setState(() => plan = next),
  options: const <PlRadioOption<String>>[
    PlRadioOption<String>(value: 'starter', label: Text('Starter')),
    PlRadioOption<String>(value: 'team', label: Text('Team')),
  ],
);

Props

Prop타입기본값설명
size공통'xs' | 'sm' | 'md' | 'lg' | 'xl''md'모든 dot의 크기와 옆 글자의 타입 스케일. 그룹에 한 번 주면 전부가 물려받습니다
color공통'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'선택된 dot을 채우는 그러데이션
orientation공통'horizontal' | 'vertical''vertical'옵션이 쌓이는 방향. 세로가 기본입니다 — 라벨 하나가 길어지는 순간 가로줄은 읽기 어려워집니다
labelReactNode옵션들이 대답하는 질문. 그룹의 라벨로 렌더링됩니다
descriptionReactNode라벨 아래 보조 설명
errorReactNode옵션 아래의 오류 메시지. 존재 자체가 invalid 상태를 만듭니다
invalidboolean메시지 없이 invalid로 만듭니다. 기본값은 error에 내용이 있는지 여부
valueunknown선택된 옵션의 value. onValueChange와 함께 controlled로 씁니다
defaultValueunknownuncontrolled일 때 처음 선택된 값
onValueChange(value: unknown, details) => void선택이 바뀔 때 호출됩니다
readOnlybooleanfalse선택은 보이지만 바꿀 수 없습니다. 모든 옵션이 물려받습니다
disabledbooleanfalse모든 옵션이 반응하지 않습니다
name · requiredstring · boolean네이티브 form 제출을 위한 것들. Base UI가 그대로 받습니다
classNames{ label?, control?, description?, error?: string }className이 닿지 않는 부분에 붙는 class. control은 실제로 조작하는 부분입니다
Prop타입기본값설명
options * List<PlRadioOption<T>>옵션들. children이 아니라 설명의 목록입니다 — 그룹이 roving focus와 화살표 키를 소유하므로 어느 것이 선택됐고 그다음이 무엇인지 알아야 합니다
value * T?선택된 옵션, 또는 아무것도 선택되지 않았으면 null
onChangedValueChanged<T>?선택된 옵션을 알립니다. null이면 그룹이 비활성화됩니다
size공통PlassSizePlassSize.md모든 dot의 크기와 옆 글자의 타입 스케일. 그룹에 한 번 주면 전부가 물려받습니다
color공통PlassColorPlassColor.primary선택된 dot을 채우는 그러데이션
orientation공통PlassOrientationPlassOrientation.vertical옵션이 쌓이는 방향. 세로가 기본입니다 — 라벨 하나가 길어지는 순간 가로줄은 읽기 어려워집니다
labelWidget?옵션들이 대답하는 질문. 그룹의 라벨로 렌더링됩니다
descriptionWidget?라벨 아래 보조 설명
errorWidget?옵션 아래의 오류 메시지. 존재 자체가 invalid 상태를 만듭니다
invalidbool?메시지 없이 invalid로 만듭니다. 기본값은 error에 내용이 있는지 여부
readOnlyboolfalse선택은 보이지만 바꿀 수 없습니다. 모든 옵션이 물려받습니다
disabledboolfalse모든 옵션이 반응하지 않습니다

Base UI RadioGroup의 나머지 prop은 그대로 전달됩니다. classNamestyle은 field wrapper에 붙고, render는 제공하지 않습니다.

그 wrapper 안쪽 네 부분에 닿는 것이 classNames입니다: label, control(radio들이 늘어선 줄), description, error.

그룹은 옵션 값의 타입에 대해 제네릭입니다(PlRadioGroup<String>, PlRadioGroup<Plan>). 그래서 valueonChangeddynamic이 아니라 타입을 가지고, 묶음에 속하지 않는 값은 컴파일되지 않습니다.

그리고 controlled입니다. 패키지의 다른 모든 컨트롤과 같습니다.

PlRadio

Prop타입기본값설명
value * unknown이 옵션이 선택됐을 때 그룹이 갖는 값
labelReactNodedot 옆의 글자. Base UI의 Field가 엮어 주므로 눌러도 선택됩니다
descriptionReactNode라벨 아래 보조 설명
disabledbooleanfalse이 옵션만 고를 수 없습니다. 나머지는 그대로 동작합니다
readOnlyboolean그룹의 readOnly를 이 옵션에서만 덮어씁니다

Flutter 패키지에는 아직 PlRadio가 없습니다.

PlRadioOption

React 패키지에는 아직 PlRadioOption가 없습니다.

Prop타입기본값설명
value * T이 옵션이 선택됐을 때 그룹이 갖는 값
labelWidget?dot 옆의 글자. Base UI의 Field가 엮어 주므로 눌러도 선택됩니다
descriptionWidget?라벨 아래 보조 설명
disabledboolfalse이 옵션만 고를 수 없습니다. 나머지는 그대로 동작합니다

sizecolor는 옵션에 주는 것이 아니라 감싸는 PlRadioGroup에서 내려받습니다. radio button은 혼자서는 아무 말도 하지 않으므로, 어떻게 보이는지는 묶음의 몫입니다. 옵션마다 주는 것은 넷 중 하나를 틀릴 기회를 네 번 만드는 일입니다.

옵션은 **위젯이 아니라 설명인 PlRadioOption**이고, 여기서의 이유는 breadcrumb의 이유보다 더 분명합니다. 그룹이 roving focus와 화살표 키를 소유하므로, 어느 옵션이 선택되었는지, 어느 것을 고를 수 있는지, 각각의 다음이 무엇인지를 알아야 합니다. 그중 어느 것도 Widget에는 물어볼 수 없습니다.

sizecolor도 가지지 않으며, 가질 수도 없습니다. radio button은 혼자서는 아무 말도 하지 않으므로, 어떻게 보이는지는 묶음의 몫입니다.

점은 채움과 함께 켜지지 않고 고리 한가운데에서 자라며, 묶음의 다른 옵션이 값을 가져가면 다시 줄어듭니다. 커지는 것은 상자이지 transform이 아닙니다. 고리가 고정 크기 자식을 가운데 두므로 변화의 양쪽 끝이 같은 점을 중심으로 배치되고, 옵션 주위의 무엇도 움직이지 않습니다. 모션을 보세요.

라이브러리 전체에서 공유 축(size color orientation)이 뜻하는 바는 prop 규칙에 있습니다.

Examples

orientation

기본은 세로입니다. 세로로 늘어선 옵션은 개수가 늘어도 훑을 수 있지만, 가로줄은 라벨 하나가 예상보다 길어지는 순간 조용히 읽기 어려워집니다.

React

color

선택되면 dot이 색 계열의 그러데이션으로 채워지고, 안쪽 원은 계열 고유의 on-solid 잉크입니다. dot은 둥글고, 라이브러리에서 둥근 것 둘 중 하나입니다. 둥근 모양이 "이 중 하나"와 "이 중 아무거나"를 구분해 주고, 이 관습은 깨는 비용이 얻는 것보다 큰 정도로 오래된 것입니다.

React

size

그룹에 주면 모든 옵션이 물려받으므로, 한 묶음 안에 dot 크기가 두 가지가 되는 일이 없습니다.

각 단계의 안쪽 원은 바깥 원 content box와 홀짝이 같습니다(12/6, 14/6, 16/8, 18/8, 22/10). 그래서 둘레 여백이 정수 픽셀입니다. 1px 테두리가 붙은 18px 원 안의 7px 점은 사방으로 4.5px 떨어지는데, 네 변이 모두 절반만 칠해진 원은 왼쪽 위로 밀린 것처럼 보입니다. dot과 라벨이 함께 쓰는 line box가 정수인 것도 같은 이유입니다. 그 결과 비율은 38%에서 44% 사이를 오갑니다.

React

readOnly · disabled · error

그룹의 disabled는 모든 옵션을 멈추고, PlRadio 하나의 disabled는 그 옵션만 멈춥니다. 그래도 목록에는 남습니다. 고를 수 없다고 사라지는 옵션은 읽는 사람이 계속 찾게 되는 옵션입니다.

그룹의 error는 invalid 상태도 만들고, 그러면 색 계열 전체가 danger를 가리킵니다.

React

Controlled

valueonValueChange를 함께 넘기세요. 값은 PlRadio에 준 것 그대로입니다. 보통은 문자열이지만, Base UI가 identity로 비교하므로 렌더 사이에 안정적이기만 하면 무엇이든 됩니다.

controlled 형태 하나뿐입니다. valueonChanged. 옵션은 ==로 비교하므로, 합리적인 동등성이 붙은 값 타입(String, enum, @immutable인 것 무엇이든)이면 빌드 사이에 같은 인스턴스를 유지하지 않아도 됩니다.

React

Accessibility

  • Base UI가 진짜 radio들을 담은 role="radiogroup"을 렌더링하고 aria-checked를 맞춰 주며, roving tab index를 소유합니다. 묶음이 tab stop 하나를 차지하고 로 그 안에서 움직입니다. radio group이 <div>에 input을 담은 것이 아니라 컴포넌트여야 하는 이유가 바로 이것입니다.
  • 그룹의 label, description, error는 Base UI의 Field가 엮어 주고, 각 옵션의 라벨도 마찬가지입니다. 라벨을 누르면 그 옵션이 선택됩니다.
  • 각 dot은 자기 라벨의 첫 줄에 맞춰 중앙에 놓이므로, 라벨이 줄바꿈되어도 자리를 지킵니다.
  • 선택된 dot은 색이 바뀌기만 하는 것이 아니라 채워진 원입니다. 채움을 볼 수 없는 사람에게는 모양이 상태를 나릅니다.
  • name을 주면 Base UI가 hidden input을 렌더링해서 선택이 네이티브 form 제출에 포함됩니다.
  • 각 옵션은 서로 배타적인 묶음의 하나로, 선택 여부와 함께 알려집니다.
  • 묶음은 focus stop 하나를 차지합니다. 정확히 한 옵션만 tab 순서에 있고 나머지는 ExcludeFocus로 감싸여 있는데, 그것이 위젯 하나로 쓴 roving tab index입니다. 가 선택을 옮기고, 양 끝에서 순환하며, 고를 수 없는 옵션은 건너뜁니다.
  • 순환은 radio group에서 화살표 키가 하는 일이고 목록에서는 하지 않는 일입니다. 묶음은 시작이 없는 대안들의 고리입니다.
  • 라벨을 누르면 그 옵션이 선택됩니다. 대상은 행 전체입니다.
  • 각 dot은 자기 라벨의 첫 줄에 맞춰 중앙에 놓이므로, 라벨이 줄바꿈되어도 자리를 지킵니다.
  • 선택된 dot은 색이 바뀌기만 하는 것이 아니라 채워진 원입니다. 채움을 볼 수 없는 사람에게는 모양이 상태를 나릅니다.

React 빌드와 다른 점

ReactFlutter이유
<PlRadio> children설명으로서의 options그룹이 roving focus와 화살표 키를 소유하므로, 어느 옵션이 선택되었고 그다음이 무엇인지 알아야 합니다. Widget은 불투명합니다.
defaultValue / onValueChangevalue / onChangedFlutter 자신의 컨트롤이 controlled이고, 콜백 이름도 Flutter의 것입니다.
identity로 비교하는 unknown==로 비교하는 제네릭 TDart에는 제네릭이 있어 타입이 검사됩니다. 그리고 합리적인 동등성이 붙은 값은 빌드 사이에 같은 인스턴스일 필요가 없습니다.
name과 hidden input포함될 네이티브 form 제출이 없습니다.
className, style전달할 클래스 목록도 style 속성도 없습니다.

Released under the MIT License