collapse —— 폴더 패널
폴더 패널은 ‘콘텐츠가 너무 많아 기본은 접고 필요할 때 펼치는’ 표준 방안입니다: FAQ, 설정 항목 그룹, 긴 폼의 영역 나누기에서 흔히 봅니다. 상태 모델은 극도로 단순합니다. ’어떤 패널이 열려 있는지’의 배열이며, 난점은 아코디언 모드(동시에 하나만 열기)와 제어 값 정규화에만 있습니다.
언제 사용할까
섹션 제목: “언제 사용할까”- 각 단말이
LhCollapse를 구현할 때: FAQ 질문/답변, 설정 그룹, 복잡한 폼의 영역 폴딩, 사이드바 메뉴 그룹. - 콘텐츠 블록을 독립적으로 접기/펼칠 수 있고, 세로 공간을 절약해야 하는 장면.
- 사용하면 안 되는 곳: 고정 항목이 두세 개뿐인 경우(그냥 보여주는 것이 더 수월); 엄격한 선형 단계가 필요한 경우(
steps사용).
가져오기
섹션 제목: “가져오기”import { normalizeActiveKeys, toggleCollapseKey, getCollapseItemClassNames,} from '@lh-ui/share/core/collapse'import type { LhCollapseKey } from '@lh-ui/share/core/collapse'설계 의도
섹션 제목: “설계 의도”normalizeActiveKeys의 3상태 정규화: 제어하는 v-model이 단일 값('1'), 배열(['1','2']), undefined(모두 접기)을 넘길 수 있습니다. 배열로 통일 정규화한 후 하위의 includes 판정은 한 가지 형태만 다루면 됩니다. 이것은 ’입력은 관대하게, 내부는 엄격하게’의 또 다른 예입니다.
아코디언의 ‘0개 열림 허용’: toggleCollapseKey(keys, key, true)는 keys.includes(key)일 때 다른 항목을 유지하지 않고 []를 반환합니다. 아코디언에서 열려 있는 패널을 다시 누르면 모두 접힙니다. 이것은 직관에 맞는 동작입니다(다른 것으로 전환되는 것이 아님). 아코디언에 단일 값을 넘길 때 UI 계층의 v-model은 배열을 받게 되므로 직접 타입을 처리해야 합니다(또는 UI 계층이 아코디언 모드에 대해 추가로 단일 값 API를 제공).
불변 업데이트: 두 함수는 모두 새 배열을 반환하여 반응형 상태에 직접 먹이고, 입력 매개변수를 수정하지 않습니다.
함수 표
섹션 제목: “함수 표”| 함수 | 시그니처 | 반환 | 역할 |
|---|---|---|---|
normalizeActiveKeys |
(value: LhCollapseKey | LhCollapseKey[] | undefined) => LhCollapseKey[] |
키 배열 | 3상태 정규화 |
toggleCollapseKey |
(keys, key, accordion: boolean) => LhCollapseKey[] |
새 키 배열 | 아코디언 / 일반 |
getCollapseItemClassNames |
(state: { active, disabled }) => string[] |
클래스명 | 펼침 / 비활성 |
타입 정의
섹션 제목: “타입 정의”export type LhCollapseKey = string | number접근성
섹션 제목: “접근성”collapse는 APG accordion / disclosure 패턴의 전형적인 구현이며, 의미 규칙은 UI 계층이 조립합니다(core는 속성 묶음 함수를 제공하지 않습니다. 패턴이 단순하고 각 단말의 DOM 차이가 크기 때문):
<!-- 각 item은 하나의 disclosure --><div class="lh-collapse__item"> <h3> <button type="button" aria-expanded="true" aria-controls="panel-1" id="trigger-1"> 제목 </button> </h3> <div id="panel-1" role="region" aria-labelledby="trigger-1">콘텐츠</div></div>aria-expanded는 트리거 버튼에 붙고active와 같은 출처입니다. 이것이 접근성 폴더 패널의 핵심 속성입니다.- 패널 컨테이너의
role="region"+aria-labelledby가 트리거를 역으로 참조하여 양방향 연결을 만듭니다. - 비활성 항목: 트리거 버튼에
disabled를 붙입니다(단순한 클래스명이 아닌 진짜 disabled 속성). 키보드와 스크린 리더 모두 인식합니다. - 키보드: Tab으로 트리거 사이를 이동하고, Enter/Space로 펼침을 전환합니다.
- 아코디언 모드에서 접힌 패널을
aria-hidden="true"로 숨기지 마세요. 콘텐츠가 여전히 DOM에 있습니다(애니메이션 중에 보임), CSS 높이 애니메이션만으로 충분합니다; 정말로 DOM을 제거한다면aria-expanded의 상태를 잘 관리해야 합니다.
사용 예
섹션 제목: “사용 예”import { normalizeActiveKeys, toggleCollapseKey } from '@lh-ui/share/core/collapse'
normalizeActiveKeys('1') // ['1']normalizeActiveKeys(['1', '2']) // ['1', '2']normalizeActiveKeys(undefined) // []
toggleCollapseKey([], '1', false) // ['1']toggleCollapseKey(['1'], '1', false) // [] (다시 누르면 접힘)toggleCollapseKey(['1'], '2', false) // ['1', '2'] (일반 모드는 여러 개 열기 가능)toggleCollapseKey(['1'], '1', true) // [] (아코디언: 다시 누르면 모두 접힘)toggleCollapseKey(['1'], '2', true) // ['2'] (아코디언: 새로 열린 것만 유지)<!-- Vue3 --><div class="lh-collapse"> <div v-for="item in items" :key="item.key" :class="getCollapseItemClassNames({ active: activeKeys.includes(item.key), disabled: item.disabled })" > <h4 class="lh-collapse__header"> <button type="button" class="lh-collapse__trigger" :disabled="item.disabled" :aria-expanded="activeKeys.includes(item.key)" :aria-controls="`lh-panel-${item.key}`" :id="`lh-trigger-${item.key}`" @click="onToggle(item)" > <span class="lh-collapse__arrow">{{ activeKeys.includes(item.key) ? '▾' : '▸' }}</span> {{ item.title }} </button> </h4> <div v-show="activeKeys.includes(item.key)" class="lh-collapse__panel" :id="`lh-panel-${item.key}`" role="region" :aria-labelledby="`lh-trigger-${item.key}`" > <slot :name="item.key" :item="item" /> </div> </div></div>// 제어 + 아코디언const activeKeys = computed(() => normalizeActiveKeys(props.modelValue))function onToggle(item) { if (item.disabled) return emit('update:modelValue', toggleCollapseKey(activeKeys.value, item.key, props.accordion))}// React: 아코디언 모드에서 v-model이 단일 값인지 배열인지는 UI 계층의 API가 결정합니다;// core는 항상 배열을 다루고, UI 계층이 emit할 때 필요에 따라 변환합니다(아코디언이고 단일 값만 필요할 때 [0]을 취함)// uniapp: <view> + v-show; 펼침 애니메이션은 CSS max-height transition 사용(<view>가 지원)스타일과 토큰
섹션 제목: “스타일과 토큰”| 클래스명 | 의미 |
|---|---|
.lh-collapse |
기본 클래스 |
.lh-collapse__item |
단일 패널 항목 |
.lh-collapse__item--active |
펼침 상태 |
.lh-collapse__item--disabled |
비활성 |
.lh-collapse__header / __trigger |
머리글 / 트리거 버튼 |
.lh-collapse__arrow |
펼침 화살표(회전 애니메이션) |
.lh-collapse__panel |
패널 콘텐츠 영역 |
머리글 배경색 --lh-fill-color, 화살표 --lh-text-color-secondary, 비활성 항목 --lh-text-disabled, 패널 테두리 --lh-border-color.
주의사항
섹션 제목: “주의사항”v-showvsv-if: 펼침 애니메이션에는 DOM이 상주해야 합니다(v-show+ 높이/max-height transition).v-if를 쓰면 파괴 시 접힘 애니메이션을 재생할 수 없어 경험이 뻣뻣해집니다.- 제어 값 타입과 아코디언: 아코디언은 논리상 펼침 항목이 하나여야 하지만,
toggleCollapseKey가 반환하는 것은 배열입니다([]또는[key]일 수 있음). UI 계층이 아코디언에 단일 값 API를 노출한다면 emit할 때[0] ?? undefined를 취하세요; 배열 API를 유지해도 되지만, 그 경우 호출 쪽이 스스로 제약해야 합니다. normalizeActiveKeys의 호출 시점: 매 렌더링마다 한 번 정규화하는 비용은 극히 낮습니다(Array.isArray판정 한 번); 걱정된다면computed로 캐시하세요.- 중첩 폴더 패널: 내부의
activeKeys는 바깥과 독립적이며, DOM이 중첩될 때 내부 패널의role="region"이 다른 region 안에 들어갑니다. 이것은 허용되지만,aria-labelledby의 id는 전역적으로 유일해야 합니다(key에 접두어를 붙여 구성). - 애니메이션 중의 높이:
max-height방식은 충분히 큰 상한(예:1000px)을 주어야 합니다. 그렇지 않으면 콘텐츠가 너무 높을 때 애니메이션 속도가 왜곡됩니다; 더 정확한 방식은 JS로scrollHeight를 측정해 설정하는 것이며, core는 관여하지 않고 UI 계층이 직접 구현합니다. - id의 유일성:
aria-controls/aria-labelledby는 id에 의존하고, collapse 인스턴스가 여러 개 공존하는 페이지에서는 key에 컴포넌트 수준의 접두어를 붙여야 합니다(UI 계층이useId나 인스턴스 uid 사용).
@lh-ui/share/core/collapse → toggleCollapseKey( )(填好参数后点运行)Copyright © 2024-2026 FengMo WenXue