List
가로 행으로 구성된 콘텐츠를 표현하는 컴포넌트입니다.
import "./styles";
import IconILowercaseSerifCircleLine from "@karrotmarket/lynx-monochrome-icon/IconILowercaseSerifCircleLine";
import IconPersonCircleLine from "@karrotmarket/lynx-monochrome-icon/IconPersonCircleLine";
import { PrefixIcon, SuffixIcon, useSeedClassName } from "@seed-design/lynx-react";
import { ListHeader } from "@/components/ui/list-header";
import { List, ListDivider, ListItem } from "@/components/ui/list";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<view className={`${seedClassName} docs-lynx-list-root`}>
<view className="list-preview">
<ListHeader>리스트 헤더</ListHeader>
<List>
<ListItem title="기본 리스트 아이템" />
<ListDivider />
<ListItem
prefix={<PrefixIcon icon={<IconPersonCircleLine />} />}
title="아이콘이 있는 리스트 아이템"
detail="부가 정보가 포함된 설명"
suffix={<SuffixIcon icon={<IconILowercaseSerifCircleLine />} />}
/>
</List>
</view>
</view>
);
}Installation
npx @seed-design/cli@latest add ui:listpnpm dlx @seed-design/cli@latest add ui:listyarn dlx @seed-design/cli@latest add ui:listbun x @seed-design/cli@latest add ui:list의존성 설치
npm install @karrotmarket/lynx-monochrome-icon @seed-design/lynx-reactyarn add @karrotmarket/lynx-monochrome-icon @seed-design/lynx-reactpnpm add @karrotmarket/lynx-monochrome-icon @seed-design/lynx-reactbun add @karrotmarket/lynx-monochrome-icon @seed-design/lynx-react아래 코드를 복사 후 붙여넣고 사용하세요
/**
* @file ui:list
* @requires @seed-design/lynx-react@>=0.7.0 <1.0.0
* @requires @seed-design/lynx-css@>=0.11.0 <1.0.0
* @requires @karrotmarket/lynx-monochrome-icon@>=1.20.0 <2.0.0
**/
import * as React from "@lynx-js/react";
import IconCheckmarkFatFill from "@karrotmarket/lynx-monochrome-icon/IconCheckmarkFatFill";
import IconMinusFatFill from "@karrotmarket/lynx-monochrome-icon/IconMinusFatFill";
import {
Checkbox as SeedCheckbox,
Divider as SeedDivider,
type DividerProps as SeedDividerProps,
List as SeedList,
RadioGroup as SeedRadioGroup,
Switch as SeedSwitch,
} from "@seed-design/lynx-react";
export interface ListProps extends SeedList.RootProps {}
/**
* @see https://seed-design.io/lynx/components/list
*/
export const List = SeedList.Root;
interface ListItemContentProps {
title: React.ReactNode;
detail?: React.ReactNode;
prefix?: React.ReactNode;
suffix?: React.ReactNode;
}
function ListItemContent({ title, detail, prefix, suffix }: ListItemContentProps) {
return (
<>
{prefix != null ? <SeedList.Prefix>{prefix}</SeedList.Prefix> : null}
<SeedList.Content>
<SeedList.Title>{title}</SeedList.Title>
{detail != null ? <SeedList.Detail>{detail}</SeedList.Detail> : null}
</SeedList.Content>
{suffix != null ? <SeedList.Suffix>{suffix}</SeedList.Suffix> : null}
</>
);
}
function inferAccessibilityLabel(title: React.ReactNode) {
return typeof title === "string" ? title : undefined;
}
export interface ListItemProps extends Omit<SeedList.ItemProps, "children">, ListItemContentProps {}
/**
* @see https://seed-design.io/lynx/components/list
*/
export const ListItem = React.forwardRef<unknown, ListItemProps>(
({ title, detail, prefix, suffix, ...props }, ref) => {
return (
<SeedList.Item ref={ref} {...props}>
<ListItemContent title={title} detail={detail} prefix={prefix} suffix={suffix} />
</SeedList.Item>
);
},
);
ListItem.displayName = "ListItem";
export interface ListButtonItemProps
extends Omit<SeedList.ButtonItemProps, "children">,
ListItemContentProps {}
/**
* @see https://seed-design.io/lynx/components/list
*/
export const ListButtonItem = React.forwardRef<unknown, ListButtonItemProps>(
({ title, detail, prefix, suffix, "accessibility-label": accessibilityLabel, ...props }, ref) => {
return (
<SeedList.ButtonItem
ref={ref}
accessibility-label={accessibilityLabel ?? inferAccessibilityLabel(title)}
{...props}
>
<ListItemContent title={title} detail={detail} prefix={prefix} suffix={suffix} />
</SeedList.ButtonItem>
);
},
);
ListButtonItem.displayName = "ListButtonItem";
export interface ListCheckItemProps
extends Omit<SeedList.CheckboxItemProps, "children">,
ListItemContentProps {}
/**
* @see https://seed-design.io/lynx/components/list
*/
export const ListCheckItem = React.forwardRef<unknown, ListCheckItemProps>(
({ title, detail, prefix, suffix, "accessibility-label": accessibilityLabel, ...props }, ref) => {
const defaultSuffix = (
<SeedCheckbox.Control>
<SeedCheckbox.Indicator
checked={<IconCheckmarkFatFill />}
indeterminate={<IconMinusFatFill />}
/>
</SeedCheckbox.Control>
);
return (
<SeedList.CheckboxItem
ref={ref}
accessibility-label={accessibilityLabel ?? inferAccessibilityLabel(title)}
{...props}
>
<ListItemContent
title={title}
detail={detail}
prefix={prefix}
suffix={suffix === undefined ? defaultSuffix : suffix}
/>
</SeedList.CheckboxItem>
);
},
);
ListCheckItem.displayName = "ListCheckItem";
export interface ListRadioItemProps
extends Omit<SeedList.RadioItemProps, "children">,
ListItemContentProps {}
/**
* @see https://seed-design.io/lynx/components/list
*/
export const ListRadioItem = React.forwardRef<unknown, ListRadioItemProps>(
({ title, detail, prefix, suffix, "accessibility-label": accessibilityLabel, ...props }, ref) => {
const defaultSuffix = (
<SeedRadioGroup.ItemControl>
<SeedRadioGroup.ItemIndicator />
</SeedRadioGroup.ItemControl>
);
return (
<SeedList.RadioItem
ref={ref}
accessibility-label={accessibilityLabel ?? inferAccessibilityLabel(title)}
{...props}
>
<ListItemContent
title={title}
detail={detail}
prefix={prefix}
suffix={suffix === undefined ? defaultSuffix : suffix}
/>
</SeedList.RadioItem>
);
},
);
ListRadioItem.displayName = "ListRadioItem";
export interface ListSwitchItemProps
extends Omit<SeedList.SwitchItemProps, "children">,
ListItemContentProps {}
/**
* @see https://seed-design.io/lynx/components/list
*/
export const ListSwitchItem = React.forwardRef<unknown, ListSwitchItemProps>(
({ title, detail, prefix, suffix, "accessibility-label": accessibilityLabel, ...props }, ref) => {
const defaultSuffix = (
<SeedSwitch.Control>
<SeedSwitch.Thumb />
</SeedSwitch.Control>
);
return (
<SeedList.SwitchItem
ref={ref}
accessibility-label={accessibilityLabel ?? inferAccessibilityLabel(title)}
{...props}
>
<ListItemContent
title={title}
detail={detail}
prefix={prefix}
suffix={suffix === undefined ? defaultSuffix : suffix}
/>
</SeedList.SwitchItem>
);
},
);
ListSwitchItem.displayName = "ListSwitchItem";
export interface ListDividerProps extends SeedDividerProps {}
/**
* @see https://seed-design.io/lynx/components/list
*/
export const ListDivider = React.forwardRef<unknown, ListDividerProps>((props, ref) => {
return <SeedDivider ref={ref} {...props} accessibility-element={false} />;
});
ListDivider.displayName = "ListDivider";
/**
* 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.
*/
/**
* @file ui:list
* @requires @seed-design/lynx-react@>=0.7.0 <1.0.0
* @requires @seed-design/lynx-css@>=0.11.0 <1.0.0
**/
import {
ListHeader as SeedListHeader,
type ListHeaderProps as SeedListHeaderProps,
} from "@seed-design/lynx-react";
export interface ListHeaderProps extends SeedListHeaderProps {}
/**
* @see https://seed-design.io/lynx/components/list
*/
export const ListHeader = SeedListHeader;
/**
* 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.
*/
Usage
import { ListHeader } from "@/components/ui/list-header";
import { List, ListDivider, ListItem } from "@/components/ui/list";
<view>
<ListHeader>리스트 헤더</ListHeader>
<List>
<ListItem title="리스트 아이템 1" />
<ListDivider />
<ListItem title="리스트 아이템 2" />
</List>
</view>Props
ListHeader
Prop
Type
style?CSSProperties | undefinedchildren?ReactNodeclassName?string | undefinedList
Prop
Type
style?CSSProperties | undefinedchildren?React.ReactNodeclassName?string | undefinedList Items
Prop
Type
className?string | undefineddisabled?boolean | undefinedstyle?CSSProperties | undefinedtitleReact.ReactNodedetail?React.ReactNodeprefix?React.ReactNodesuffix?React.ReactNodeProp
Type
bindtap?EventHandler<BaseTouchEvent<Target>> | undefinedmain-thread:bindtap?EventHandler<BaseTouchEvent<Element>> | undefinedclassName?string | undefineddisabled?boolean | undefinedstyle?CSSProperties | undefinedtitleReact.ReactNodedetail?React.ReactNodeprefix?React.ReactNodesuffix?React.ReactNodeLynx에는 HTML anchor에 해당하는 기본 요소가 없습니다. 링크 이동은 ListButtonItem의 bindtap에서 앱 라우터를 호출해 처리합니다.
Prop
Type
className?string | undefineddisabled?boolean | undefinedstyle?CSSProperties | undefineddefaultChecked?boolean | undefinedonCheckedChange?((checked: boolean) => void) | undefinedtitleReact.ReactNodedetail?React.ReactNodeprefix?React.ReactNodesuffix?React.ReactNodeProp
Type
className?string | undefineddisabled?boolean | undefinedstyle?CSSProperties | undefinedchecked?boolean | undefineddefaultChecked?boolean | undefinedonCheckedChange?((checked: boolean) => void) | undefinedindeterminate?boolean | undefinedtitleReact.ReactNodedetail?React.ReactNodeprefix?React.ReactNodesuffix?React.ReactNodeProp
Type
className?string | undefineddisabled?boolean | undefinedstyle?CSSProperties | undefinedvaluestringtitleReact.ReactNodedetail?React.ReactNodeprefix?React.ReactNodesuffix?React.ReactNodeProp
Type
className?string | undefinedstyle?CSSProperties | undefinedExamples
Using ListHeader
ListHeader는 List 밖에 위치합니다. Lynx ListHeader는 <text>를 렌더링하므로 액션은 Header의 형제로 배치합니다.
import "./styles";
import IconChevronRightLine from "@karrotmarket/lynx-monochrome-icon/IconChevronRightLine";
import IconLockLine from "@karrotmarket/lynx-monochrome-icon/IconLockLine";
import IconPersonCircleLine from "@karrotmarket/lynx-monochrome-icon/IconPersonCircleLine";
import IconQuestionmarkCircleFill from "@karrotmarket/lynx-monochrome-icon/IconQuestionmarkCircleFill";
import { ActionButton, PrefixIcon, SuffixIcon, useSeedClassName } from "@seed-design/lynx-react";
import { List, ListButtonItem, ListDivider } from "@/components/ui/list";
import { ListHeader } from "@/components/ui/list-header";
function AccountList() {
return (
<List>
<ListButtonItem
title="내 계정"
detail="이메일과 연락처, 본인 인증 관리"
prefix={<PrefixIcon icon={<IconPersonCircleLine />} />}
suffix={<SuffixIcon icon={<IconChevronRightLine />} />}
/>
<ListButtonItem
title="보안 · 인증 관리"
detail="비밀번호, 생체 인증 사용을 관리해요"
prefix={<PrefixIcon icon={<IconLockLine />} />}
suffix={<SuffixIcon icon={<IconChevronRightLine />} />}
/>
</List>
);
}
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<view className={`${seedClassName} docs-lynx-list-root`}>
<view className="list-preview__sections">
<view className="list-preview__section">
<ListHeader variant="mediumWeak">variant="mediumWeak"</ListHeader>
<AccountList />
</view>
<ListDivider />
<view className="list-preview__section">
<ListHeader variant="boldSolid">variant="boldSolid"</ListHeader>
<AccountList />
</view>
<ListDivider />
<view className="list-preview__section">
<view className="list-preview__row list-preview__header-row">
<ListHeader>List Header with Action Button</ListHeader>
<ActionButton variant="ghost" size="small">
<PrefixIcon icon={<IconQuestionmarkCircleFill />} />
도움말
</ActionButton>
</view>
<AccountList />
</view>
</view>
</view>
);
}Affixes (Prefix/Suffix)
import "./styles";
import IconArrowUpBracketDownFill from "@karrotmarket/lynx-monochrome-icon/IconArrowUpBracketDownFill";
import IconILowercaseSerifCircleLine from "@karrotmarket/lynx-monochrome-icon/IconILowercaseSerifCircleLine";
import { useState } from "@lynx-js/react";
import { ActionButton, Icon, PrefixIcon, useSeedClassName } from "@seed-design/lynx-react";
import { List, ListDivider, ListItem } from "@/components/ui/list";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
const [pressed, setPressed] = useState(false);
return (
<view className={`${seedClassName} docs-lynx-list-root`}>
<List>
<ListItem
prefix={
<view className="list-preview__avatar">
<text className="list-preview__avatar-label">A</text>
</view>
}
title="Prefix에 Avatar 넣기"
detail="Amet elit ullamco magna."
/>
<ListDivider />
<ListItem
title="Prefix에 아이콘 넣기"
detail="Deserunt nulla elit est."
prefix={<PrefixIcon icon={<IconILowercaseSerifCircleLine />} />}
/>
<ListDivider />
<ListItem
title="Suffix에 Action Button 넣기"
detail="Veniam non est non ut consequat."
suffix={
<ActionButton size="xsmall" variant="neutralWeak">
액션 버튼
</ActionButton>
}
/>
<ListDivider />
<ListItem
title="Suffix에 Action Button (Ghost) 넣기"
detail="Deserunt nulla elit est."
suffix={
<ActionButton size="small" variant="ghost" layout="iconOnly" accessibility-label="공유">
<Icon icon={<IconArrowUpBracketDownFill />} />
</ActionButton>
}
/>
<ListDivider />
<ListItem
title="Suffix에 토글 액션 넣기"
detail="Sit eu incididunt aute ea elit ex."
suffix={
<ActionButton
size="xsmall"
variant={pressed ? "neutralSolid" : "neutralWeak"}
bindtap={() => {
"background only";
setPressed((value) => !value);
}}
>
{pressed ? "선택됨" : "토글 버튼"}
</ActionButton>
}
/>
</List>
</view>
);
}Clickable List Items
ListButtonItem을 사용하면 리스트 항목 전체를 탭할 수 있습니다. Lynx에는 ListLinkItem이 없으므로 화면 이동은 bindtap에서 앱 라우터를 호출합니다.
import "./styles";
import IconArrowUpRightLine from "@karrotmarket/lynx-monochrome-icon/IconArrowUpRightLine";
import IconCheckmarkFill from "@karrotmarket/lynx-monochrome-icon/IconCheckmarkFill";
import IconChevronRightLine from "@karrotmarket/lynx-monochrome-icon/IconChevronRightLine";
import IconPenHorizlineFill from "@karrotmarket/lynx-monochrome-icon/IconPenHorizlineFill";
import IconPlusFill from "@karrotmarket/lynx-monochrome-icon/IconPlusFill";
import IconSquare2StackedFill from "@karrotmarket/lynx-monochrome-icon/IconSquare2StackedFill";
import { useState } from "@lynx-js/react";
import { ActionButton, PrefixIcon, SuffixIcon, useSeedClassName } from "@seed-design/lynx-react";
import { List, ListButtonItem, ListDivider, ListItem } from "@/components/ui/list";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
const [status, setStatus] = useState("아직 탭하지 않았어요");
const [subscribed, setSubscribed] = useState(false);
const [copied, setCopied] = useState(false);
return (
<view className={`${seedClassName} docs-lynx-list-root`}>
<List>
<ListItem
title="ListItem은 클릭할 수 없어요. 눌러보세요."
detail="우측의 Action Button만 클릭할 수 있어요"
suffix={
<ActionButton
variant="ghost"
size="xsmall"
bindtap={() => {
"background only";
setStatus("편집 클릭됨");
}}
>
<PrefixIcon icon={<IconPenHorizlineFill />} />
편집
</ActionButton>
}
/>
<ListDivider />
<ListButtonItem
title="ListButtonItem은 클릭할 수 있어요. 눌러보세요."
detail="리스트 항목 전체와 우측의 토글 액션을 각각 탭할 수 있어요"
bindtap={() => {
"background only";
setStatus("리스트 아이템 클릭됨");
}}
suffix={
<view className="list-preview__suffix">
<view catchtap={() => {}}>
<ActionButton
variant={subscribed ? "neutralSolid" : "neutralWeak"}
size="xsmall"
bindtap={() => {
"background only";
setSubscribed((value) => !value);
setStatus("모아보기 상태 변경됨");
}}
>
<PrefixIcon icon={subscribed ? <IconCheckmarkFill /> : <IconPlusFill />} />
{subscribed ? "모아보는 중" : "모아보기"}
</ActionButton>
</view>
<SuffixIcon icon={<IconChevronRightLine />} />
</view>
}
/>
<ListDivider />
<ListButtonItem
title="ListButtonItem은 클릭할 수 있어요. 눌러보세요."
detail="리스트 항목 전체와 우측의 커스텀 버튼을 각각 탭할 수 있어요"
bindtap={() => {
"background only";
setStatus("리스트 아이템 클릭됨");
}}
suffix={
<view className="list-preview__suffix">
<view
className="list-preview__custom-button"
accessibility-element={true}
accessibility-traits="button"
catchtap={() => {
"background only";
setStatus("커스텀 버튼 클릭됨");
}}
>
<text className="list-preview__custom-button-label">커스텀 버튼</text>
</view>
<SuffixIcon icon={<IconChevronRightLine />} />
</view>
}
/>
<ListDivider />
<ListButtonItem
title="링크 이동도 ListButtonItem으로 처리해요."
detail="탭 이벤트에서 앱 라우터를 호출합니다"
bindtap={() => {
"background only";
setStatus("링크 이동 요청됨");
}}
suffix={
<view className="list-preview__suffix">
<view catchtap={() => {}}>
<ActionButton
variant="neutralWeak"
size="xsmall"
bindtap={() => {
"background only";
setCopied(true);
setStatus("URL 복사됨");
}}
>
<PrefixIcon icon={copied ? <IconCheckmarkFill /> : <IconSquare2StackedFill />} />
{copied ? "복사됨" : "URL 복사"}
</ActionButton>
</view>
<SuffixIcon icon={<IconArrowUpRightLine />} />
</view>
}
/>
</List>
<text className="list-preview__status">{status}</text>
</view>
);
}inputs in List Items
ListSwitchItem, ListCheckItem, ListRadioItem을 사용하면 항목 전체를 탭해 값을 바꿀 수 있습니다. snippet은 각 컨트롤을 기본 suffix로 렌더링합니다. suffix={null}을 전달하면 기본 컨트롤을 숨길 수 있습니다.
import "./styles";
import IconTrashcanLine from "@karrotmarket/lynx-monochrome-icon/IconTrashcanLine";
import { PrefixIcon, useSeedClassName } from "@seed-design/lynx-react";
import { List, ListDivider, ListSwitchItem } from "@/components/ui/list";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<view className={`${seedClassName} docs-lynx-list-root`}>
<view className="list-preview list-preview--centered">
<List>
<ListSwitchItem
title="삭제하기 전에 확인"
prefix={<PrefixIcon icon={<IconTrashcanLine />} />}
/>
<ListDivider />
<ListSwitchItem
title="메시지 요약"
detail="핵심 내용만 빠르게 확인해보세요."
defaultChecked
/>
</List>
</view>
</view>
);
}import "./styles";
import IconCheckmarkFatFill from "@karrotmarket/lynx-monochrome-icon/IconCheckmarkFatFill";
import { Badge, Checkbox, HStack, useSeedClassName } from "@seed-design/lynx-react";
import { List, ListCheckItem, ListDivider } from "@/components/ui/list";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<view className={`${seedClassName} docs-lynx-list-root`}>
<view className="list-preview list-preview--centered">
<List>
<ListCheckItem
title={
<HStack gap="x1_5" align="center">
<text>알림 수신 동의</text>
<Badge variant="weak">권장</Badge>
</HStack>
}
detail="푸시 알림을 받으시겠습니까?"
defaultChecked
/>
<ListDivider />
<ListCheckItem
title="마케팅 정보 수신 동의"
detail="마케팅 정보를 받으시겠습니까?"
prefix={
<Checkbox.Control tone="neutral" size="large">
<Checkbox.Indicator checked={<IconCheckmarkFatFill />} />
</Checkbox.Control>
}
suffix={null}
defaultChecked
/>
<ListDivider />
<ListCheckItem
prefix={
<Checkbox.Control tone="neutral" size="large" variant="ghost">
<Checkbox.Indicator
unchecked={<IconCheckmarkFatFill />}
checked={<IconCheckmarkFatFill />}
/>
</Checkbox.Control>
}
suffix={null}
title="Ghost Variant"
/>
</List>
</view>
</view>
);
}import "./styles";
import { useSeedClassName } from "@seed-design/lynx-react";
import { List, ListDivider, ListRadioItem } from "@/components/ui/list";
import { RadioGroup, Radiomark } from "@/components/ui/radio-group";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<view className={`${seedClassName} docs-lynx-list-root`}>
<view className="list-preview list-preview--centered">
<RadioGroup
defaultValue="option1"
accessibility-label="옵션 선택"
tone="neutral"
size="large"
>
<List>
<ListRadioItem
value="option1"
title="옵션 1"
detail="첫 번째 선택지"
suffix={<Radiomark />}
/>
<ListDivider />
<ListRadioItem
value="option2"
title="옵션 2"
detail="두 번째 선택지"
prefix={<Radiomark />}
suffix={null}
/>
<ListDivider />
<ListRadioItem
value="option3"
title="옵션 3"
detail="세 번째 선택지"
prefix={<Radiomark />}
suffix={null}
/>
</List>
</RadioGroup>
</view>
</view>
);
}Accessibility
ListButtonItem은 기본적으로 button 역할과 문자열 title 기반의 접근성 이름을 제공합니다. 선택 항목은 현재 선택 여부를 accessibility-value로 전달합니다. ListRadioItem은 반드시 RadioGroup.Root 안에서 사용하고 그룹의 접근성 이름도 지정하세요.
import { List, ListRadioItem } from "@/components/ui/list";
import { RadioGroup } from "@/components/ui/radio-group";
<RadioGroup defaultValue="짜장" accessibility-label="점심 메뉴">
<List>
<ListRadioItem value="짜장" title="짜장" />
<ListRadioItem value="짬뽕" title="짬뽕" />
</List>
</RadioGroup>Disabled
import "./styles";
import IconChevronRightLine from "@karrotmarket/lynx-monochrome-icon/IconChevronRightLine";
import IconPersonCircleLine from "@karrotmarket/lynx-monochrome-icon/IconPersonCircleLine";
import IconSlashCircleLine from "@karrotmarket/lynx-monochrome-icon/IconSlashCircleLine";
import { PrefixIcon, SuffixIcon, useSeedClassName } from "@seed-design/lynx-react";
import { List, ListButtonItem, ListCheckItem, ListRadioItem } from "@/components/ui/list";
import { RadioGroup } from "@/components/ui/radio-group";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
return (
<view className={`${seedClassName} docs-lynx-list-root`}>
<view className="list-preview__sections">
<List>
<ListButtonItem
prefix={<PrefixIcon icon={<IconPersonCircleLine />} />}
title="활성화된 ListButtonItem"
detail="Cupidatat et pariatur amet."
suffix={<SuffixIcon icon={<IconChevronRightLine />} />}
/>
<ListCheckItem
prefix={<PrefixIcon icon={<IconPersonCircleLine />} />}
title="활성화된 ListCheckItem"
/>
</List>
<RadioGroup defaultValue="enabled" accessibility-label="활성화 옵션">
<List>
<ListRadioItem
prefix={<PrefixIcon icon={<IconPersonCircleLine />} />}
title="활성화된 ListRadioItem"
value="enabled"
/>
</List>
</RadioGroup>
<List>
<ListButtonItem
disabled
prefix={<PrefixIcon icon={<IconSlashCircleLine />} />}
title="비활성화된 ListButtonItem"
detail="Cupidatat et pariatur amet."
suffix={<SuffixIcon icon={<IconChevronRightLine />} />}
/>
<ListCheckItem
disabled
prefix={<PrefixIcon icon={<IconSlashCircleLine />} />}
title="비활성화된 ListCheckItem"
/>
</List>
<RadioGroup defaultValue="disabled" accessibility-label="비활성화 옵션">
<List>
<ListRadioItem
disabled
prefix={<PrefixIcon icon={<IconSlashCircleLine />} />}
title="비활성화된 ListRadioItem"
value="disabled"
/>
</List>
</RadioGroup>
</view>
</view>
);
}Variants
Highlighted
import "./styles";
import IconPersonCircleLine from "@karrotmarket/lynx-monochrome-icon/IconPersonCircleLine";
import { useState } from "@lynx-js/react";
import { PrefixIcon, useSeedClassName } from "@seed-design/lynx-react";
import { List, ListButtonItem, ListDivider, ListItem } from "@/components/ui/list";
import { Switch } from "@/components/ui/switch";
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
const [highlighted, setHighlighted] = useState(true);
return (
<view className={`${seedClassName} docs-lynx-list-root`}>
<view className="list-preview__sections">
<List>
<ListButtonItem
prefix={<PrefixIcon icon={<IconPersonCircleLine />} />}
title="버튼"
detail="Enim aute duis magna mollit aute sit aliquip duis ut tempor sunt."
/>
<ListDivider />
<ListButtonItem
highlighted
prefix={<PrefixIcon icon={<IconPersonCircleLine />} />}
title="하이라이트된 버튼"
detail="Enim aute duis magna mollit aute sit aliquip duis ut tempor sunt."
/>
<ListDivider />
<ListButtonItem
highlighted
disabled
prefix={<PrefixIcon icon={<IconPersonCircleLine />} />}
title="하이라이트 및 비활성화된 버튼"
detail="Enim aute duis magna mollit aute sit aliquip duis ut tempor sunt."
/>
</List>
<List>
<ListItem
prefix={<PrefixIcon icon={<IconPersonCircleLine />} />}
title="하이라이트"
highlighted={highlighted}
/>
</List>
<view className="list-preview__row">
<text>highlight</text>
<Switch checked={highlighted} onCheckedChange={setHighlighted} />
</view>
</view>
</view>
);
}With Bottom Sheet
import "./styles";
import IconArrowClockwiseCircularFill from "@karrotmarket/lynx-monochrome-icon/IconArrowClockwiseCircularFill";
import { useRef, useState } from "@lynx-js/react";
import {
ActionButton,
type BottomSheetRootRef,
PrefixIcon,
useSeedClassName,
} from "@seed-design/lynx-react";
import {
BottomSheetBody,
BottomSheetContent,
BottomSheetFooter,
BottomSheetRoot,
BottomSheetTrigger,
} from "@/components/ui/bottom-sheet";
import { List, ListCheckItem } from "@/components/ui/list";
const TYPES = ["버스", "지하철", "택시", "자전거", "도보"] as const;
export default function Example() {
const seedClassName = useSeedClassName({ colorMode: "system" });
const sheetRef = useRef<BottomSheetRootRef>(null);
const [selectedTypes, setSelectedTypes] = useState<(typeof TYPES)[number][]>([]);
return (
<view className={`${seedClassName} docs-lynx-list-root`}>
<view className="list-preview list-preview--centered">
<BottomSheetRoot ref={sheetRef}>
<BottomSheetTrigger>
<ActionButton variant="neutralSolid">BottomSheet 열기</ActionButton>
</BottomSheetTrigger>
<BottomSheetContent title="교통수단" description="이동할 교통수단을 선택해주세요.">
<BottomSheetBody style={{ flex: "none", height: "280px" }}>
<List>
{TYPES.map((type) => (
<ListCheckItem
key={type}
title={type}
checked={selectedTypes.includes(type)}
onCheckedChange={() => {
setSelectedTypes((previous) =>
previous.includes(type)
? previous.filter((item) => item !== type)
: [...previous, type],
);
}}
/>
))}
</List>
</BottomSheetBody>
<BottomSheetFooter>
<view className="list-preview__bottom-sheet-actions">
<ActionButton
size="large"
variant="neutralSolid"
disabled={selectedTypes.length === 0}
bindtap={() => {
"background only";
sheetRef.current?.close();
}}
>
경로 찾기
</ActionButton>
<ActionButton
size="small"
variant="ghost"
disabled={selectedTypes.length === 0}
bindtap={() => {
"background only";
setSelectedTypes([]);
}}
>
<PrefixIcon icon={<IconArrowClockwiseCircularFill />} />
초기화
</ActionButton>
</view>
</BottomSheetFooter>
</BottomSheetContent>
</BottomSheetRoot>
</view>
</view>
);
}Alignment
React의 alignItems prop은 Lynx List에서 지원하지 않습니다. Lynx에서는 Recipe가 prefix, content, suffix를 가운데에 정렬합니다. 상단 정렬이 필요하면 저수준 슬롯을 직접 배치한 별도 레이아웃을 사용하세요.
Border Radius
React의 itemBorderRadius prop은 Lynx List에서 지원하지 않습니다. Lynx 항목의 눌림 반경은 Recipe가 정한 값을 사용합니다. 둥근 카드 안에서는 바깥 컨테이너에 border-radius와 overflow: hidden을 적용하세요.
Customization and Composition
Usage
@seed-design/lynx-react에서 제공하는 List와 ListHeader 컴포넌트는 다음 구조로 사용합니다.
ListHeader
List.Root
└── List.Item
├── List.Prefix (선택사항)
├── List.Content
│ ├── List.Title
│ └── List.Detail (선택사항)
└── List.Suffix (선택사항)
└── List.Item
├── ...import IconChevronRightLine from "@karrotmarket/lynx-monochrome-icon/IconChevronRightLine";
import IconPersonCircleLine from "@karrotmarket/lynx-monochrome-icon/IconPersonCircleLine";
import { List, ListHeader, PrefixIcon, SuffixIcon } from "@seed-design/lynx-react";
<view>
<ListHeader>내 정보</ListHeader>
<List.Root>
<List.Item>
<List.Prefix>
<PrefixIcon icon={<IconPersonCircleLine />} />
</List.Prefix>
<List.Content>
<List.Title>내 프로필</List.Title>
<List.Detail>다른 사람들에게 보이는 내 정보를 관리합니다.</List.Detail>
</List.Content>
<List.Suffix>
<SuffixIcon icon={<IconChevronRightLine />} />
</List.Suffix>
</List.Item>
</List.Root>
</view>ListHeader: 리스트의 제목이나 설명을 표시하는 헤더List.Root: 모든 리스트 항목을 감싸는 컨테이너List.Item: 상호작용하지 않는 개별 항목List.ButtonItem: 탭할 수 있는 개별 항목List.CheckboxItem,List.RadioItem,List.SwitchItem: 선택 상태를 가진 개별 항목List.Prefix: 아이콘이나 Avatar 등을 표시하는 시작 영역List.Content: 제목과 설명이 들어가는 중앙 영역List.Suffix: 아이콘이나 Action Button 등을 표시하는 끝 영역
asChild prop으로 적절한 시맨틱 요소와 조합하기
Lynx에는 DOM 기반 asChild prop이 없습니다. 역할에 맞는 상호작용 항목을 직접 사용합니다.
Using List.ButtonItem instead of asChild in List.Content
항목 전체를 탭할 때는 List.ButtonItem을 사용합니다.
import { List } from "@seed-design/lynx-react";
<List.ButtonItem bindtap={handleTap} accessibility-label="사용자, 항목 6개">
<List.Content>
<List.Title>사용자</List.Title>
<List.Detail>항목 6개</List.Detail>
</List.Content>
</List.ButtonItem>snippet의 ListButtonItem은 같은 구조를 간단히 작성할 수 있게 돕습니다.
import { ListButtonItem } from "@/components/ui/list";
<ListButtonItem bindtap={handleTap} title="사용자" detail="항목 6개" />Using selection items instead of asChild in List.Item
선택 상태가 있는 항목은 각각 List.CheckboxItem, List.RadioItem, List.SwitchItem을 사용합니다. snippet의 선택 항목은 접근성 값과 기본 컨트롤을 함께 구성합니다.
import { ListCheckItem } from "@/components/ui/list";
<ListCheckItem defaultChecked title="동의" />웹 버전과의 차이
| 영역 | React Web | Lynx |
|---|---|---|
| 요소 | ul, li, button, input | view, text |
| 이벤트 | onClick | bindtap, main-thread:bindtap |
| 접근성 | HTML 시맨틱과 ARIA | accessibility-* |
| 링크 항목 | ListLinkItem | ListButtonItem에서 앱 라우터 호출 |
| 다형 렌더링 | as, asChild | 미지원 |
| 선택 컨트롤 | hidden input | Lynx 상태와 접근성 값 공유 |
Lynx 미지원 기능
| 기능 | 웹 대응 | 대체 방법 |
|---|---|---|
ListLinkItem | <a> 링크 항목 | ListButtonItem의 bindtap에서 앱 라우터 호출 |
itemBorderRadius | 목록별 눌림 반경 | 바깥 컨테이너에 반경과 clipping 적용 |
alignItems | prefix와 suffix 정렬 | 저수준 슬롯을 사용한 별도 레이아웃 구성 |
| Header 내부 복합 콘텐츠 | 제목과 액션을 한 Header에 배치 | Header와 액션을 형제 요소로 배치 |
Last updated on