Disclosure
Accordion
관련 콘텐츠를 펼치고 접어 화면의 정보 밀도를 조절합니다.
이 컴포넌트는 무엇인가요?
여러 콘텐츠 영역을 펼치거나 접는 컴포넌트입니다.
여러 항목을 열 수 있나요?
기본값은 multiple이며 동시에 여러 항목을 열 수 있습니다.
Usage
AccordionRoot 안에 AccordionItem, AccordionTitle, AccordionContent를 조합합니다. 열린 항목은 values로 초기화합니다.
Examples
Single
multiple={false}를 사용하면 한 번에 하나의 항목만 펼칩니다.
한 번에 하나만 열기
multiple을 false로 지정합니다.
Rounded
rounded를 지정하면 첫 항목과 마지막 항목의 바깥 모서리가 둥글게 표시됩니다. 위 기본 예제에서 확인할 수 있습니다.
Props
values: string[]— 처음 열 항목multiple?: boolean— 여러 항목 동시 열기, 기본값truerounded?: boolean— 바깥 모서리 처리onChange?: (values: string[]) => void— 열린 항목 변경 콜백
Source
accordion-root
import {
createContext,
PropsWithChildren,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from 'react'
type AccordionRootProps = {
multiple?: boolean
values: string[]
onChange?: (values: string[]) => void
rounded?: boolean
}
type TStateContext = {
values: string[]
rounded: boolean
}
type TActionContext = {
setter: (item: string) => void
}
const AccordionStateContext = createContext<TStateContext>({ values: [], rounded: false })
const AccordionActionsContext = createContext<TActionContext>({ setter: () => null })
export const AccordionRoot = ({
children,
multiple = true,
values = [],
onChange,
rounded = false,
}: PropsWithChildren<AccordionRootProps>) => {
const [items, setItems] = useState<string[]>(values)
const setter = useCallback(
(item: string) => {
let newItem = new Set([item])
if (multiple) {
newItem = new Set(items)
if (newItem.has(item)) {
newItem.delete(item)
} else {
newItem.add(item)
}
}
setItems(Array.from(newItem))
},
[items, multiple],
)
const memoizedItems = useMemo(() => items, [items])
const memoizedRounded = useMemo(() => rounded, [rounded])
const memoizedSetter = useMemo(() => setter, [setter])
useEffect(() => {
if (onChange && typeof onChange === 'function') {
onChange(memoizedItems)
}
}, [memoizedItems, onChange])
return (
<AccordionActionsContext.Provider value={{ setter: memoizedSetter }}>
<AccordionStateContext.Provider value={{ values: memoizedItems, rounded: memoizedRounded }}>
<div>{children}</div>
</AccordionStateContext.Provider>
</AccordionActionsContext.Provider>
)
}
export const useAccordionState = () => useContext(AccordionStateContext)
export const useAccordionActions = () => useContext(AccordionActionsContext)
accordion-examples
'use client'
import {
AccordionContent,
AccordionItem,
AccordionRoot,
AccordionTitle,
} from '@/shared/ui/Accordion'
export function AccordionBasicExample() {
return (
<div className="w-full max-w-xl">
<AccordionRoot values={['intro']} rounded>
<AccordionItem value="intro">
<AccordionTitle>이 컴포넌트는 무엇인가요?</AccordionTitle>
<AccordionContent>여러 콘텐츠 영역을 펼치거나 접는 컴포넌트입니다.</AccordionContent>
</AccordionItem>
<AccordionItem value="usage">
<AccordionTitle>여러 항목을 열 수 있나요?</AccordionTitle>
<AccordionContent>
기본값은 multiple이며 동시에 여러 항목을 열 수 있습니다.
</AccordionContent>
</AccordionItem>
</AccordionRoot>
</div>
)
}
export function AccordionSingleExample() {
return (
<div className="w-full max-w-xl">
<AccordionRoot values={[]} multiple={false}>
<AccordionItem value="one">
<AccordionTitle>한 번에 하나만 열기</AccordionTitle>
<AccordionContent>multiple을 false로 지정합니다.</AccordionContent>
</AccordionItem>
</AccordionRoot>
</div>
)
}
Known limitations
현재 AccordionTitle은 div 클릭으로 동작해 키보드와 ARIA 상태를 제공하지 않습니다. 현행 UI 보존 범위에서는 수정하지 않았으며, 접근성 개선 작업에서 button 기반 trigger로 교체해야 합니다.