본문으로 건너뛰기

PlAccordion

한 번에 하나씩 펼쳐지는 섹션 묶음입니다. 무엇을 읽을지 먼저 훑어보는 참고성 내용(설정 그룹, 사양표, FAQ)에 씁니다.

React
tsx
import { PlAccordion, PlAccordionItem } from 'plass-ui';

<PlAccordion defaultValue={['shipping']}>
  <PlAccordionItem value="shipping" title="Shipping">
    Three to five working days.
  </PlAccordionItem>
  <PlAccordionItem value="returns" title="Returns">
    Thirty days from delivery.
  </PlAccordionItem>
</PlAccordion>;
dart
import 'package:plass_ui/plass_ui.dart';

PlAccordion<String>(
  value: open,
  onChanged: (Set<String> next) => setState(() => open = next),
  items: const <PlAccordionItem<String>>[
    PlAccordionItem<String>(
      value: 'shipping',
      title: Text('Shipping'),
      child: Text('Three to five working days.'),
    ),
    PlAccordionItem<String>(
      value: 'returns',
      title: Text('Returns'),
      child: Text('Thirty days from delivery.'),
    ),
  ],
);

Props

Prop타입기본값설명
variant공통'solid' | 'glass' | 'ghost''glass'시트의 재질. solid는 가장 불투명한 유리, glass는 하이라이너가 있는 기본 시트, ghost는 표면 없음
size공통'xs' | 'sm' | 'md' | 'lg' | 'xl''md'제목과 본문의 타입 스케일, 그리고 그 둘을 감싸는 여백
color공통'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'의미론적 색 역할. 임의 색상값은 받지 않습니다
density공통'default' | 'compact''default'여백만 바꿉니다. 높이와 글자 크기는 그대로
elevation공통0 | 1 | 2 | 30그림자 깊이. 0은 그림자 없음
multiplebooleanfalse여러 섹션을 동시에 열 수 있게 합니다
value(string | number)[]열려 있는 섹션. onValueChange와 함께 controlled로 씁니다
defaultValue(string | number)[]uncontrolled일 때 처음부터 열려 있는 섹션
onValueChange(value: (string | number)[]) => void열린 섹션 집합이 바뀔 때 호출됩니다
dividersbooleantrue섹션 사이를 헤어라인으로 나눕니다. 끄면 각 섹션이 타일이 됩니다
disabledbooleanfalse모든 섹션이 반응하지 않습니다
hiddenUntilFoundbooleanfalse닫힌 패널을 DOM에 남겨 브라우저의 페이지 검색이 찾아 열 수 있게 합니다. keepMounted보다 우선합니다
keepMountedbooleanfalse닫힌 패널을 DOM에 남깁니다. 만드는 비용이 크거나 form 상태를 쥐고 있는 내용에
childrenReactNodePlAccordionItem 목록
Prop타입기본값설명
items * List<PlAccordionItem<T>>섹션들. children이 아니라 설명의 목록입니다 — accordion이 무엇이 열려 있고 사이의 선이 어디 가는지를 알아야 합니다
value * Set<T>열려 있는 섹션. multiple이 꺼져 있어도 집합입니다 — 닫힌 상태는 빈 집합입니다
onChangedValueChanged<Set<T>>?다음에 열려 있어야 할 집합으로 호출됩니다. 주지 않으면 열린 상태 그대로 굳습니다
multipleboolfalse여러 섹션을 동시에 열 수 있게 합니다
variant공통PlassVariantPlassVariant.glass시트의 재질. solid는 가장 불투명한 유리, glass는 하이라이너가 있는 기본 시트, ghost는 표면 없음
size공통PlassSizePlassSize.md제목과 본문의 타입 스케일, 그리고 그 둘을 감싸는 여백
color공통PlassColorPlassColor.primary의미론적 색 역할. 임의 색상값은 받지 않습니다
density공통PlassDensityPlassDensity.standard여백만 바꿉니다. 높이와 글자 크기는 그대로
elevation공통int0그림자 깊이. 0은 그림자 없음
dividersbooltrue섹션 사이를 헤어라인으로 나눕니다. 끄면 각 섹션이 타일이 됩니다
disabledboolfalse모든 섹션이 반응하지 않습니다

네이티브 <div> 속성은 그대로 전달됩니다. color는 위 표의 color와 충돌해서, defaultValueonChange는 accordion이 각각 배열형 defaultValueonValueChange로 쓰기 때문에 제외됩니다.

accordion은 섹션 값 타입에 대해 제네릭입니다(PlAccordion<String>, PlAccordion<Section>). 그래서 valueonChangeddynamic이 아니라 타입을 가지며, 패키지의 다른 컨트롤과 마찬가지로 controlled입니다. multiple이 꺼져 있어도 valueSet<T>입니다. 닫힌 상태도 집합이기 때문입니다. 빈 집합입니다.

PlAccordionItem

Prop타입기본값설명
valuestring | numbervalue / defaultValue가 이 섹션을 가리키는 이름. 생략하면 Base UI가 만들어 줍니다
titleReactNode접힘 헤더의 제목
subtitleReactNode제목 아래 한 줄. 타입 스케일 한 단계 아래의 muted 텍스트
startIconReactNode제목 앞에 놓이는 내용 — 아이콘, 상태 점, 개수
actionReactNode헤더 끝, chevron 앞에 고정되는 컨트롤. trigger 바깥에 놓이므로 버튼을 넣어도 됩니다
truncatebooleanfalse제목과 부제를 각각 한 줄로 자르고 넘치면 말줄임합니다. 기본값이 false인 이유는 접힘 제목이 대개 한 문장이기 때문입니다
disabledbooleanfalse이 섹션만 접히지 않습니다. 나머지는 그대로 동작합니다
childrenReactNode패널의 내용
Prop타입기본값설명
value * TPlAccordion.value가 이 섹션을 가리키는 이름
titleWidget?접힘 헤더의 제목
subtitleWidget?제목 아래 한 줄. 타입 스케일 한 단계 아래의 muted 텍스트
startIconWidget?제목 앞에 놓이는 내용 — 아이콘, 상태 점, 개수
actionWidget?헤더 끝, chevron 앞에 고정되는 컨트롤. 접히는 부분 바깥이라 버튼을 넣어도 됩니다
truncateboolfalse제목과 부제를 각각 한 줄로 자르고 넘치면 말줄임합니다. 기본값이 false인 이유는 접힘 제목이 대개 한 문장이기 때문입니다
disabledboolfalse이 섹션만 접히지 않습니다. 나머지는 그대로 동작합니다
childWidget?패널의 내용

size, density, dividers는 item에 주는 prop이 아니라 감싸고 있는 PlAccordion에서 내려받습니다.

섹션은 위젯이 아니라 PlAccordionItem, 즉 설명입니다. accordion은 어느 섹션이 열려 있는지, 누르면 무엇이 닫혀야 하는지, 사이의 선이 어디 들어가는지를 알아야 하는데, 불투명한 Widget에게는 그중 어느 것도 물어볼 수 없습니다.

sizedensitydividers도 없고, 있을 수도 없습니다. 그것들은 accordion의 것이고, 타입 스케일이 두 개인 묶음은 한 장의 판이 아닙니다.

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

Examples

variant

세 가지 재질을 컨테이너 입장에서 읽은 것입니다. solid는 가장 불투명한 맑은 유리로, 주변보다 앞으로 나와 있어야 하는 판에 씁니다. glass는 Plass의 기본 시트이자 기본값입니다. ghost는 시트가 아예 없어서, 이미 시트인 PlCard 안에 넣을 때 씁니다. 사각형 안의 또 다른 사각형은 사각형 하나가 더 많은 것입니다.

셋 중 어느 것에도 색이 들어가지 않습니다. accordion이 담는 내용은 자기 색을 가지고 오기 때문에, 색 계열은 hover 틴트와 열린 섹션의 제목, focus ring까지만 닿고 거기서 멈춥니다.

React

multiple

기본값에서는 한 섹션을 열면 열려 있던 섹션이 닫힙니다. accordion이 collapsible을 쌓아 놓은 것과 다른 이유가 바로 이것으로, 다음을 열 때 앞의 것이 닫히는 덕분에 읽는 도중 페이지가 아래로 자라지 않습니다. multiple은 이 제약을 풉니다.

React

dividers

기본으로 켜져 있습니다. 양 끝까지 닿는 헤어라인이 여러 섹션을 한 장의 판으로 묶어 줍니다. 끄면 각 섹션이 자기 타일이 되고, 여백으로 구분됩니다.

React

title · subtitle · startIcon · action

제목과 부제는 줄바꿈됩니다. 접힘 제목은 대개 한 문장이고(FAQ는 질문의 목록입니다) 말줄임하면 독자는 문장의 끝을 잃습니다. 툴팁도 없고 확인할 방법도 없습니다. 반대로 줄바꿈이 치르는 값은 두 줄짜리 헤더인데, 높이가 변하는 것이 존재 이유인 컴포넌트에서는 그것이 값이라 하기 어렵습니다. truncate는 둘을 다시 한 줄로 되돌립니다. 데이터베이스에서 온 이름을 컨트롤 옆에 놓는 헤더를 위한 것입니다.

action은 접히는 부분 바깥에 그려집니다. 접히기도 하고 버튼도 쥐고 있는 헤더에는 누를 것이 두 개인데, 그중 하나를 다른 하나 안에 넣을 수는 없습니다.

브라우저가 <button> 안의 <button>을 파싱 단계에서 다시 쓰므로, 이것은 취향의 문제가 아닙니다.

여기서는 트리를 다시 쓰는 주체가 없지만, 컨트롤 안의 컨트롤은 한 번 누르면 두 번 발생하는 이벤트이고 스크린리더에는 버튼 안의 버튼입니다.

React

size

제목과 본문, 그리고 둘을 감싸는 여백이 함께 움직입니다. accordion에 주면 모든 섹션이 내려받으므로, 한 묶음 안에 타입 스케일이 두 개가 되는 일이 없습니다.

본문은 아래쪽뿐 아니라 위쪽에도 자기 여백을 둡니다. 열린 헤더는 아래 모서리가 붙은 색 띠이고, 그 모서리에서 바로 시작하는 본문은 첫 줄이 제목 밑 half leading 자리에 놓입니다. 제목과 그것을 설명하는 문단이 색만 바뀐 한 덩어리 글로 읽히게 됩니다. 헤더의 여백이 사는 것은 제목 둘레의 자리이고, 본문의 자리는 본문이 삽니다.

React

Controlled

valueonValueChange를 함께 넘기면 열린 섹션 집합을 직접 쥘 수 있습니다. multiple이 꺼져 있어도 둘 다 배열입니다. 전부 닫힌 상태는 []입니다.

uncontrolled 모드는 없습니다. accordion을 움직이는 방법은 언제나 valueonChanged입니다. multiple이 꺼져 있어도 valueSet<T>이고(전부 닫힌 상태는 <String>{}입니다) onChanged를 주지 않으면 열려 있는 상태 그대로 굳습니다. 읽기 전용 요약은 그렇게 씁니다.

React

Accessibility

  • 각 헤더는 aria-expanded가 붙은 진짜 <button>이고, aria-controls로 자기 패널을 가리킵니다. 패널은 헤더가 이름을 붙여 주는 region입니다.
  • EnterSpace로 섹션을 접고 폅니다. Tab은 헤더 사이와 열린 패널 안으로 이동합니다.
  • hiddenUntilFound는 닫힌 패널을 hidden="until-found"로 렌더링하므로, 브라우저의 페이지 검색이 그 안의 글자를 찾아 해당 섹션을 열어 줍니다.
  • chevron은 장식이라 aria-hidden입니다. 열림 상태는 aria-expanded가 나르며, 회전만으로 전달되는 정보는 없습니다.
  • action에 넣은 것은 자기 tab stop이 붙은 별개의 컨트롤이므로, 접근 가능한 이름도 따로 필요합니다.
  • 패널은 transform이 아니라 height를 애니메이션합니다. 글자가 다시 샘플링되지 않고, 열리는 동안 패널 안의 내용이 밀리지도 않습니다.
  • 각 헤더는 펼쳐졌는지 접혔는지가 표시된 버튼으로 읽힙니다. 그 상태는 플래그가 나르며, chevron의 회전만으로 전달되는 정보는 없습니다.
  • EnterSpace로 섹션을 접고 폅니다. Tab은 헤더 사이와 열린 패널 안으로 이동합니다. 헤더는 저마다 자기 focus stop이 있습니다. accordion은 버튼 묶음이지 roving 그룹이 아닙니다.
  • 닫힌 패널은 트리에 아예 없습니다. 열리기 전까지 그 안의 어떤 것도 닿거나 포커스되거나 읽히지 않습니다.
  • chevron은 그려지되 이름이 없고, 비활성 섹션은 포인터에도 키보드에도 답하지 않습니다.
  • action에 넣은 것은 자기 focus stop이 붙은 별개의 컨트롤이므로, 이름도 따로 필요합니다.
  • 패널은 transform이 아니라 높이를 애니메이션합니다. 글자가 다시 샘플링되지 않고, 열리는 동안 패널 안의 내용이 밀리지도 않습니다. OS에서 애니메이션을 끄면 즉시 펼쳐집니다.

React 빌드와 다른 점

ReactFlutter이유
<PlAccordionItem> children설명 목록인 itemsaccordion은 어느 섹션이 열려 있는지, 누르면 무엇이 닫히는지, 선이 어디 들어가는지를 알아야 합니다. 불투명한 위젯에게는 물어볼 수 없습니다.
defaultValue / onValueChangevalue / onChangedFlutter의 컨트롤은 controlled이고, 콜백 이름도 Flutter의 것입니다.
string제네릭 TDart에는 제네릭이 있으니 섹션 타입은 관습이 아니라 타입 검사로 지켜집니다.
배열인 valueSet<T>value열린 섹션은 순서도 중복도 없는 집합이고, Dart에는 그 자료형이 있습니다.
hiddenUntilFound섹션을 대신 열어 줄 브라우저 페이지 검색이 없습니다. 닫힌 패널은 그냥 만들어지지 않습니다.
aria-expanded, aria-controls, region펼쳐짐이 표시된 버튼과, 있거나 없는 패널Flutter는 상태를 노드 자체에 적습니다. 가리킬 id가 없습니다.
childrenchildFlutter의 이름입니다.
className, style전달할 클래스 목록도 style 속성도 없습니다.

Released under the MIT License