File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
08-14
08-14
08-18
08-18
08-14
08-18
08-18
08-18
File name
Commit message
Commit date
"use client";
import { useId, useState, type ReactNode } from "react";
import { cx } from "../../utils";
/** `box`는 색을 깐 상자, `line`은 위쪽 선 하나로 나뉘는 목록형이다. */
export type FoxAccordionType = "box" | "line";
export type FoxAccordionSize = "lg" | "md";
export interface FoxAccordionProps {
title: ReactNode;
children?: ReactNode;
type?: FoxAccordionType;
size?: FoxAccordionSize;
/** 제목 앞자리. 시안은 배지 묶음이다. */
badge?: ReactNode;
/** 주면 호출부가 열림을 소유한다. 없으면 컴포넌트가 스스로 여닫는다. */
open?: boolean;
defaultOpen?: boolean;
onOpenChange?: (open: boolean) => void;
/** 참이면 렌더하지 않는다(DOM에 남지 않는다). */
hidden?: boolean;
/** 배치 조정용. */
className?: string;
}
/**
* @fox 아코디언. 제목을 누르면 아래 내용이 열린다.
*
* 열림을 밖에서 쥐면 여러 개 중 하나만 열리게 하는 것도 호출부가 정할 수 있다. 넘기지
* 않으면 스스로 여닫는다.
*
* ⚠️ 스타일은 이 파일이 import하지 않는다 — 호스트 앱이 `@use "@fox/styles/components"`
* (또는 개별 파티셜)로 한 번 불러와야 한다.
*/
export function FoxAccordion({
title,
children,
type = "box",
size = "lg",
badge,
open,
defaultOpen = false,
onOpenChange,
hidden = false,
className,
}: FoxAccordionProps) {
const autoId = useId();
const [inner, setInner] = useState(defaultOpen);
if (hidden) {
return null;
}
const expanded = open ?? inner;
const panelId = `${autoId}-panel`;
const toggle = () => {
if (open === undefined) {
setInner(!expanded);
}
onOpenChange?.(!expanded);
};
return (
<div
className={cx(
"fox-accordion",
`fox-accordion--${type}`,
`fox-accordion--${size}`,
expanded && "fox-accordion--open",
className
)}
>
<button
className="fox-accordion__tit"
type="button"
aria-expanded={expanded}
aria-controls={panelId}
onClick={toggle}
>
{badge && <span className="fox-accordion__badge">{badge}</span>}
<span className="fox-accordion__title">{title}</span>
<span className="fox-accordion__icon" aria-hidden="true">
{expanded ? <i className="fox-ico fox-ico-CaretUp" aria-hidden="true" /> : <i className="fox-ico fox-ico-CaretDown" aria-hidden="true" />}
</span>
</button>
{/* 닫혔을 때는 DOM에서 뺀다 — 안의 초점 가능한 것들이 탭 순서에 남으면 안 된다. */}
{expanded && (
<div className="fox-accordion__contents" id={panelId}>
{children}
</div>
)}
</div>
);
}