라이브러리 파헤치기

아코디언(Accordion)에 대해 알아보자 (with. 라이브러리 구조 맛보기)

by GicoMomg 2024. 2. 4.

1. 들어가며…

  • 얼마 전에 컨텐츠를 접었다 펼칠 수 있는 UI가 필요해, 아코디언(accordion) 컴포넌트를 만들었다. 이때 라이브러리의 아코디언을 참고했는데, 공통으로 제공하는 기능이지만 구현 방식이 조금씩 다른 걸 볼 수 있었다.
  • 그래서 이번 시간에는 구현 시 참고했던 라이브러리들의 구조를 조금 더 살펴보는 시간을 가져보겠다.



2. 라이브러리별 아코디언 구조 맛보기

📌 라이브러리의 아코디언은 어떤 구조를 가질까?
여러 UI 라이브러리 중 MaterialUI, NextUI, Vuetify의 아코디언 구조를 확인해보았다.

1) 아코디언이 뭘까?

  • 아코디언은 사용자가 필요한 정보를 보거나 숨길 수 있는 UI이다.
  • 주로 Q&A나 FAQ와 같이 많은 정보를 효율적으로 표시할 필요가 있는 곳에서 사용된다.
  • 아코디언을 클릭하면 확장되어 내용이 표시되고, 다른 섹션을 클릭하여 기존 섹션을 접을 수도 있다.
  • 아래 예시는 애플의 베타 사이트의 자주 묻는 질문 화면이다.



2) 어떤 구조를 가질까?

(1) MaterialUI의 Accordion

컴포넌트 구성
  • MaterialUI는 리액트 기반 UI 라이브러리로, 90k 이상의 star를 받은 유명한 라이브러리이다.
  • 아코디온 관련 컴포넌트는 크게 4가지로 구성되어 있다.
컴포넌트 설명
Accordion 관련 컴포넌트를 그룹화하기 위한 래퍼
AccordionSummary Accordion 헤더를 위한 래퍼로, 클릭할 때 내용을 확장하거나 접음
AccordionDetails Accordion 내용을 위한 래퍼
AccordionActions 여러 버튼을 그룹화하는 옵셔널 래퍼

 

기본 사용법
  • 기본 형태는 아래와 같다.
<Accordion>
  <AccordionSummary>
    제목 영역
  </AccordionSummary>
  <AccordionDetails>
    내부 내용
  </AccordionDetails>
</Accordion>

 



내부 코드 구성 (버전: v5.15.7)
  • 그럼, 아코디온 코드 내부를 어떻게 구성되어 있을까?
  • 아래 이미지는 아코디언 관련 컴포넌트 중 Accordion 코드 내부이다.

번호 설명
(A) {… other}로 외부에서 여러 속성을 받는다.
(B) ContextAPI의 Provider로 데이터를 하위 컴포넌트로 전송한다.
(C) { children }으로 외부에서 컴포넌트를 주입할 수 있으며,
TransitionSlot으로 { chidren }에 애니메이션을 적용된다.

 

  • 아코디온 제목 영역의 컴포넌트는 어떻게 구성되어 있을까? AccordionSummary를 살펴보자

번호 설명
(A) forwardRef는 컴포넌트에 ref를 전달하는 함수다.
이 함수로 부모 컴포넌트가 자식 컴포넌트의 DOM 노드에 직접 접근할 수 있다.
(B) WAI-ARIA속성(aria-expanded, aria-controls)을 사용해 아코디언 컴포넌트의 접근성을 향상시켰다.
이 속성은 스크린 리더 사용자가 UI를 더 쉽게 탐색하고 이해할 수 있도록 도울 수 있다.
(C) { children }으로 외부에서 컴포넌트를 주입할 수 있다.
(D) 만약 외부에서 expandIcon을 지정했다면, 조건부로 expandIcon을 렌더링한다.

 

📌 [요약] materialUI의 Accordion은 ContextAPI의 Provider로 데이터를 하위 컴포넌트에 전송하고,
{ chidren }으로 외부 컴포넌트를 주입한다. 스크린 리더 사용자를 위해 WAI-ARIA 속성을 지정했다.
컴포넌트의 경우, 역할에 맞게 총 4개의 컴포넌트로 선언했다.

 

(2) NextUI의 Accordion

컴포넌트 구성
  • NextUI는 리액트 기반 UI 라이브러리이며, 2023년에 출시했다.
  • TailwindCSS 기반으로 구현됐으며, TailwindCSS와 다른 점은 컴포넌트 단위로 기능 확장이 가능하다는 점이다.
  • 아코디온 관련 컴포넌트는 크게 2가지로 구성되어 있다.
컴포넌트 설명
Accordion 아코디언 항목 목록을 표시하는 주요 컴포넌트
AccordionItem 단일 아코디언 항목을 표시하는 데 사용되는 아이템 컴포넌트

 

기본 사용법
  • 기본 형태는 아래와 같다.
<Accordion>
  <AccordionItem key="1" aria-label="Accordion 1" title="제목 영역">
    내용 영역
  </AccordionItem>
</Accordion>

 

 

내부 코드 구성 (버전: accordion@2.0.28)
  • 아래 이미지는 Accordion 컴포넌트의 내부 코드이다. 렌더링 영역의 코드를 살펴보았다.

번호 설명
(A) useMemo 훅으로 배열 내부의 값이 변할 때, content를 재계산한다.
(B) 반복문으로 state.collection을 순회해 < AccordionItem />컴포넌트를 렌더링한다.
(C) isSplitted 값에 따라 < Divider />로 아이템 간의 구분선을 추가한다.
(D) disableAnimation prop에 따라 content를 직접 렌더링하거나 < LayoutGroup /> 컴포넌트 내부에서 렌더링하여 애니메이션을 적용한다.

 

  • AccordionItem는 아래와 같은 구조를 가지고 있다.

번호 설명
(A) 헤더 영역을, button 태그로 선언했다.
(B) startContent가 있는 경우, startContent을 보여준다.
(C) 헤더 영역의 내용을 렌더링하는 코드로, title, subTitle값이 있다면 각각 렌더링한다.
(D) useMemo 훅으로 배열 내부의 값(ex. isOpen, children)이 변할 때, 내용 영역을 재계산한다.
{ children } 으로 외부 컴포넌트 주입을 가능토록 한다.

 

📌 [요약] NextUI는 useMemo로 컴포넌트 렌더링을 최적화했다. 헤더 영역은 자유도에 제한이 있으나, 내용 영역은 { chidren }으로 외부 컴포넌트 주입을 가능토록 했다.

 

(3) Vuetify의 Expansion panel

컴포넌트 구성
  • Vuetify는 뷰 기반 UI 라이브러리로, 38k 이상의 star를 받은 유명한 라이브러리이다.
  • vuetify은 아코디온 형태의 UI를 Expansion panels(확장 패널)로 정의했다.
  • 확장 패널 관련 컴포넌트는 크게 4가지로 구성되어 있다.
컴포넌트 설명 슬롯
v-expansion-panels 기본 컴포넌트 -
v-expansion-panel v-expansion-panel-text
v-expansion-panel-title을 포함하는 하위 컴포넌트
-
v-expansion-panel-title 확장 패널의 제목을 표시하는 데 사용되는 하위 컴포넌트 #title
v-expansion-panel-text 확장 패널의 텍스트를 표시하는 데 사용하는 하위 컴포넌트 #text

 

기본 사용법
  • 기본 형태는 아래와 같다.
<v-expansion-panels>
  <v-expansion-panel title="제목 영역" text="내용 영역" />
</v-expansion-panels>

 

 

내부 코드 구성 (버전: v3.5.2)
  • 아래 이미지는 ExpansionPanel 컴포넌트의 내부 코드이다. 렌더링 영역의 코드를 살펴보았다.

번호 설명
(A) 컴포넌트 상태에 따라, 클래스나 스타일을 조건부로 적용하며, 클래스 형식은 BEM 방식을 쓴다.
(B) expansion-panel에 그림자 효과를 추가하기 위한 요소이다.
(C) title propstitle slot을 지정한 경우 패널 제목 영역을 렌더링하며,
레이아웃은 < ExpansionPanelTitle />을 사용한다.
(D) text propstext slot을 지정한 경우, 패널 내용 영역을 렌더링하며,
레이아웃은 < ExpansionPanelText />을 사용한다.
(E) 사용자가 expansion-panel 내부에 추가적인 내용을 삽입할 수 있도록 하는 기본 슬롯이다.

 

📌 [요약] vuetify은 아코디온 형태의 UI를 Expansion panels로 정의했다.
material-ui처럼 컴포넌트를 역할에 따라 4가지 컴포넌트로 구성했다.
클래스명의 경우 독립성과 커스터마이징을 위해 BEM을 사용했으며, slot과 props로 내용을 수정할 수 있다.



3) 어떤 기능을 제공할까?

  • 각 라이브러리 별로 제공하는 기능 중, 핵심 기능을 제공하는지 여부를 체크해보았다.
용도 MaterialUI NextUI Vuetify
비활성화 처리가 가능할까 disabled 속성 제공 isDisabled 속성 제공 disabled 속성 제공
펼치기를 기본 설정할 수 있을까 defaultExpanded 속성 제공 defaultSelectedKeys 속성 제공 v-model로 가능
펼치기, 접기를 컨트롤할 수 있을까 expanded 속성 제공 selectedKeys 속성 제공 v-model로 가능
아이콘 변경이 가능할까 expandIcon 속성 제공 indicator 속성 제공 expand-icon 속성 제공
열기, 닫기 동작을 감지하는 이벤트가 있을까 onChange 이벤트 제공 onSelectionChange이벤트 제공 v-model로 감지 가능
콘텐츠 영역을 동적으로 변경할 수 있을까 children으로 가능 children으로 가능 slot이나 props로 가능
애니메이션 변경이 될까 props로 가능 MotionProps로 가능 v-expand-transition API 제공
아이템 그룹에서 한 아이템만 열리도록 할 수 있을까 가능 selectionMode 속성 제공 가능
스타일 변경이 될까 가능 가능 가능
여러 스타일 타입을 제공할까 X variant 속성 제공 variant 속성 제공



4) 왜 details 태그를 쓰지 않았을까?

  • materialUI는 div, nextUI는 button + div, Vuetify는 button + div로 아코디언을 구현했다.
  • 어? 그런데 <details>를 사용하면, 쉽게 아코디언을 구현할 수 있고 심지어 내부 콘텐츠 검색도 가능한데..

See the Pen detail tag default by KumJungMin (@kumjungmin) on CodePen.

 

💡 왜 라이브러리는 아코디언을 구현할 때 <details> 대신 <div>, <button>을 사용했을까?

 

(1) 일부 브라우저 호환 문제가 있다.

  • can i use 사이트<details />의 브라우저 호환성, 일부 문제를 확인했다.
  • 브라우저 호환성의 경우, Firefox, Opera 이전 버전에서 <details />를 제공하지 않고 있었다.
  • 또한, <details />는 일부 브라우저에서 기본 폰트가 작아보이는 현상, toggle 이벤트가 작동하지 않는 문제가 있다.

 

(2) 스타일 커스텀이 용이하지 않다.

  • 두 번째, 스타일 커스텀이 <div><button>보다 자유도가 낮다.
  • <div><button>에서는 아이콘의 위치나 스타일을 쉽게 변형할 수 있다.
  • 하지만 <details>의 경우, list-style, list-style-type, list-style-image 로 스타일을 변경해야한다.

See the Pen custom details tag by KumJungMin (@kumjungmin) on CodePen.




5) 만약 Accordion 컴포넌트를 만든다면?

📌 만약 Accordion 컴포넌트를 만든다면, 라이브러리를 참고하여 아래 리스트 적용을 고려해볼 수 있다!
(단, 요구사항 이상의 구현은 오버엔지니어링이 될 수 있기에 확장성만 열어둬도 됨)

  • 외부에서 스타일링이 되도록 BEM 방식으로 클래스 선언하기
vg-accordion--active
vg-accordion--disabled
vg-accordion__shadow

...
  • 여러 기능을 제공한다면 Accordion 컴포넌트 하나만 제공하는 게 아닌, 역할에 따라 컴포넌트 분리하기
|- Accordion
|-- AccordionSummary
|-- AccordionDetails

...

 

  • 제목, 내용 영역의 정보는 텍스트를 넘기는 방식과 컴포넌트를 주입하는 방식 2가지 제공하기
<!-- 텍스트 넘기는 방식 -->
<AccordionSummary text="제목 영역" />

<!-- 컴포넌트 주입 방식 -->
<AccordionSummary >
	<div>제목 영역</div>
<AccordionSummary />

 

 

  • 컴포넌트 주입의 경우 리액트는 { children }를, 뷰는 slot으로 구현하기
  • 리액트는 useMemo로 UI 컴포넌트가 특정 값이 바뀔 때만 렌더링하여 최적화하기
  • 외부에서 열기, 닫기를 컨트롤할 수 있어야 하며, 기본값 설정도 가능하도록 제공하기
  • 컨텐츠 내부 검색이 되어야 한다면, hidden=until-found 적용하기
  • 아코디온을 열거나 닫을 때, 이를 감지할 수 있는 이벤트 제공하기
onChange event

v-model  // vue의 경우 v-model로 가능

 

  • 아이콘을 컴포넌트로 변경할 수 있게 하기
<AccordionSummary expand-Icon={<ArrowIcon />} />

 

  • 아코디언 그룹에서 하나의 아코디온만 열리도록 하거나 동시에 열릴 수 있게 인터페이스 제공하기
  • 여러 디자인이나 애니메이션 타입 제공하기(ex. variants)
  • 비활성화 제공하고, 비활성화 기본 스타일 제공하기 (ex. disabled)
  • 접근성을 위해 aria- 속성을 기본값으로 지정하기

 

 

 

 

 



반응형

댓글