Accordion
관련된 정보 섹션을 접고 펼쳐 화면을 간결하게 구성하는 컴포넌트입니다.
import "./styles";
import { root } from "@lynx-js/react";
import { Box, Text, useSeedClassName } from "@seed-design/lynx-react";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<Accordion className="accordion-preview">
<AccordionItem value="item-1">
<AccordionTrigger title="아코디언 항목 1" />
<AccordionContent>
<Box p="x4">
<Text textStyle="t4Regular">첫 번째 항목의 내용입니다.</Text>
</Box>
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger title="아코디언 항목 2" />
<AccordionContent>
<Box p="x4">
<Text textStyle="t4Regular">두 번째 항목의 내용입니다.</Text>
</Box>
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-3">
<AccordionTrigger title="아코디언 항목 3" />
<AccordionContent>
<Box p="x4">
<Text textStyle="t4Regular">세 번째 항목의 내용입니다.</Text>
</Box>
</AccordionContent>
</AccordionItem>
</Accordion>
</page>
);
}
root.render(<Root />);
npx @seed-design/cli@latest add ui:accordion
pnpm dlx @seed-design/cli@latest add ui:accordion
yarn dlx @seed-design/cli@latest add ui:accordion
bun x @seed-design/cli@latest add ui:accordion
의존성 설치
npm install @karrotmarket/lynx-monochrome-icon @seed-design/lynx-react
yarn add @karrotmarket/lynx-monochrome-icon @seed-design/lynx-react
pnpm add @karrotmarket/lynx-monochrome-icon @seed-design/lynx-react
bun add @karrotmarket/lynx-monochrome-icon @seed-design/lynx-react
아래 코드를 복사 후 붙여넣고 사용하세요
/**
* @file ui:accordion
* @requires @seed-design/lynx-react@>=0.5.0 <1.0.0
* @requires @seed-design/lynx-css@>=0.9.0 <1.0.0
* @requires @karrotmarket/lynx-monochrome-icon@>=1.20.0 <2.0.0
**/
import IconChevronDownSmallLine from "@karrotmarket/lynx-monochrome-icon/IconChevronDownSmallLine";
import * as React from "@lynx-js/react";
import { Accordion as SeedAccordion } from "@seed-design/lynx-react";
import type { LynxIconElementProps } from "@seed-design/lynx-react";
export interface AccordionProps extends SeedAccordion.RootProps {}
/**
* @see https://seed-design.io/lynx/components/accordion
*/
export const Accordion = React.forwardRef<unknown, AccordionProps>((props, ref) => {
const { children, ...otherProps } = props;
return (
<SeedAccordion.Root ref={ref} {...otherProps}>
{children}
</SeedAccordion.Root>
);
});
Accordion.displayName = "Accordion";
export interface AccordionItemProps extends SeedAccordion.ItemProps {}
/**
* @see https://seed-design.io/lynx/components/accordion
*/
export const AccordionItem = React.forwardRef<unknown, AccordionItemProps>((props, ref) => {
const { children, ...otherProps } = props;
return (
<SeedAccordion.Item ref={ref} {...otherProps}>
{children}
</SeedAccordion.Item>
);
});
AccordionItem.displayName = "AccordionItem";
export interface AccordionTriggerProps extends Omit<SeedAccordion.TriggerProps, "children"> {
title: React.ReactNode;
description?: React.ReactNode;
prefix?: React.ReactNode;
suffixIcon?: React.ReactElement<LynxIconElementProps>;
}
/**
* @see https://seed-design.io/lynx/components/accordion
*/
export const AccordionTrigger = React.forwardRef<unknown, AccordionTriggerProps>((props, ref) => {
const {
title,
description,
prefix,
suffixIcon = <IconChevronDownSmallLine />,
"accessibility-label": accessibilityLabel,
...otherProps
} = props;
return (
<SeedAccordion.Header>
<SeedAccordion.Trigger
ref={ref}
accessibility-label={accessibilityLabel ?? (typeof title === "string" ? title : undefined)}
{...otherProps}
>
{prefix != null ? <SeedAccordion.Prefix>{prefix}</SeedAccordion.Prefix> : null}
<SeedAccordion.Body>
<SeedAccordion.Title>{title}</SeedAccordion.Title>
{description != null ? (
<SeedAccordion.Description>{description}</SeedAccordion.Description>
) : null}
</SeedAccordion.Body>
<SeedAccordion.SuffixIcon icon={suffixIcon} />
</SeedAccordion.Trigger>
</SeedAccordion.Header>
);
});
AccordionTrigger.displayName = "AccordionTrigger";
export interface AccordionContentProps extends SeedAccordion.ContentProps {}
/**
* @see https://seed-design.io/lynx/components/accordion
*/
export const AccordionContent = React.forwardRef<unknown, AccordionContentProps>((props, ref) => {
const { children, ...otherProps } = props;
return (
<SeedAccordion.Content ref={ref} {...otherProps}>
{children}
</SeedAccordion.Content>
);
});
AccordionContent.displayName = "AccordionContent";
/**
* This file is a snippet from SEED Design, helping you get started quickly with @seed-design/* packages.
* You can extend this snippet however you want.
*/
values?string[] | undefined
defaultValues?string[] | undefined
onValuesChange?((values: string[]) => void) | undefined
disabled?boolean | undefined
multiple?boolean | undefined
style?CSSProperties | undefined
children?React.ReactNode
className?string | undefined
valuestring
disabled?boolean | undefined
style?CSSProperties | undefined
children?React.ReactNode
className?string | undefined
titleReact.ReactNode
description?React.ReactNode
prefix?React.ReactNode
suffixIcon?React.ReactElement<LynxIconElementProps, string | React.JSXElementConstructor<any>> | undefined
className?string | undefined
bindtap?EventHandler<BaseTouchEvent<Target>> | undefined
expandedAccessibilityValue?string | undefined
collapsedAccessibilityValue?string | undefined
style?CSSProperties | undefined
style?CSSProperties | undefined
children?React.ReactNode
className?string | undefined
multiple을 설정하면 여러 항목을 동시에 펼칠 수 있습니다.
import "./styles";
import { root } from "@lynx-js/react";
import { Box, Text, useSeedClassName } from "@seed-design/lynx-react";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<Accordion multiple defaultValues={["item-1", "item-2"]} variant="separated">
<AccordionItem value="item-1">
<AccordionTrigger title="아코디언 항목 1" />
<AccordionContent>
<Box p="x4">
<Text textStyle="t4Regular">여러 항목을 동시에 펼칠 수 있습니다.</Text>
</Box>
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2">
<AccordionTrigger title="아코디언 항목 2" />
<AccordionContent>
<Box p="x4">
<Text textStyle="t4Regular">각 항목은 다른 항목과 독립적으로 열고 닫힙니다.</Text>
</Box>
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-3">
<AccordionTrigger title="아코디언 항목 3" />
<AccordionContent>
<Box p="x4">
<Text textStyle="t4Regular">세 번째 항목의 내용입니다.</Text>
</Box>
</AccordionContent>
</AccordionItem>
</Accordion>
</page>
);
}
root.render(<Root />);
Accordion의 disabled는 전체 항목을 비활성화합니다. AccordionItem에 설정하면 해당 항목만 비활성화합니다.
import "./styles";
import { root } from "@lynx-js/react";
import { Box, Text, useSeedClassName } from "@seed-design/lynx-react";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<page className={seedClassName}>
<Accordion>
<AccordionItem value="item-1">
<AccordionTrigger title="활성화된 항목" />
<AccordionContent>
<Box p="x4">
<Text textStyle="t4Regular">이 항목은 활성화 상태입니다.</Text>
</Box>
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-2" disabled>
<AccordionTrigger title="비활성화된 항목" />
<AccordionContent>
<Box p="x4">
<Text textStyle="t4Regular">이 항목은 열 수 없습니다.</Text>
</Box>
</AccordionContent>
</AccordionItem>
<AccordionItem value="item-3">
<AccordionTrigger title="활성화된 항목" />
<AccordionContent>
<Box p="x4">
<Text textStyle="t4Regular">이 항목은 활성화 상태입니다.</Text>
</Box>
</AccordionContent>
</AccordionItem>
</Accordion>
</page>
);
}
root.render(<Root />);
values와 onValuesChange를 함께 사용해 펼쳐진 항목을 제어할 수 있습니다. 비제어 방식에서는 defaultValues를 사용합니다.
import "./styles";
import { root, useState } from "@lynx-js/react";
import { Box, Text, useSeedClassName } from "@seed-design/lynx-react";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
const DEFAULT_VALUES = ["shipping"];
function Root() {
const seedClassName = useSeedClassName({ colorMode: "system" });
const [values, setValues] = useState<string[]>(DEFAULT_VALUES);
return (
<page className={seedClassName}>
<Accordion multiple values={values} onValuesChange={setValues} variant="separated">
<AccordionItem value="shipping">
<AccordionTrigger title="배송 옵션" description="배송 방식과 예상 소요 시간" />
<AccordionContent>
<Box p="x4">
<Text textStyle="t4Regular">
빠른 배송, 새벽 배송, 방문 수령 옵션을 비교할 수 있습니다.
</Text>
</Box>
</AccordionContent>
</AccordionItem>
<AccordionItem value="payment">
<AccordionTrigger title="결제 수단" />
<AccordionContent>
<Box p="x4">
<Text textStyle="t4Regular">원하는 결제 수단을 확인할 수 있습니다.</Text>
</Box>
</AccordionContent>
</AccordionItem>
</Accordion>
<text className="accordion-preview__status">values: {JSON.stringify(values)}</text>
</page>
);
}
root.render(<Root />);
Lynx Accordion은 React 버전과 다음 차이가 있습니다.
- Native elements: HTML heading, button, region 대신 Lynx
<view>와 <text>를 렌더링합니다.
- Interaction: 키보드 탐색 대신 Trigger의 native tap 이벤트로 항목을 열고 닫습니다.
- Accessibility: DOM ARIA 대신
accessibility-heading, accessibility-role-description, accessibility-value를 사용합니다.
- Responsive size: Lynx preset은 viewport media query를 지원하지 않아
size="responsive"를 제공하지 않습니다.
- Content transition: React 버전은 CSS custom property로 높이를 전달하고, Lynx 버전은
layoutchange로 콘텐츠 높이를 측정해 같은 duration과 easing의 transition을 적용합니다.
| 기능 | 설명 |
|---|
asChild | Lynx에는 Slot 기반 polymorphic 렌더링이 없습니다. |
headingLevel | Lynx의 heading 접근성 속성은 HTML heading level을 받지 않습니다. |
| DOM ARIA 연결 | aria-controls, aria-labelledby, role="region" 대신 Lynx native 접근성 속성을 사용합니다. |
| Arrow / Home / End 키 탐색 | Lynx native 화면에는 웹과 같은 키보드 포커스 모델이 없습니다. |
size="responsive" | Lynx preset은 viewport media query를 지원하지 않습니다. |