컴포넌트

List

콘텐츠를 목록으로 표시합니다.

설치

pnpm dlx ply-ui add list badge icon

공통 설치 설정을 먼저 적용합니다.

사용법

항목마다 ItemContent를 구성합니다. 추가 설명은 Description에 넣고, 클릭할 행에만 Action을 사용합니다.

구성

구성 요소역할
Root목록 전체입니다. variant로 테두리와 구분선을 지정합니다.
Item하나의 목록 행입니다.
Content행의 본문과 leading, trailing 영역을 배치합니다.
Title행의 주된 정보입니다.
Description행의 보조 정보입니다. 필요할 때만 넣습니다.
Action클릭할 수 있는 행에 사용합니다. 본문을 감싸는 버튼입니다.

Standard

Root variant="standard"는 기본 목록입니다.

Boxed

Root variant="boxed"는 테두리로 목록을 감쌉니다.

Inset

Root variant="inset"는 구분선 시작점을 안쪽으로 들여 표시합니다. 앞쪽 아이콘이 있는 행에 사용할 수 있습니다.

작업이 있는 행

Action은 버튼입니다. onClick에 동작을 연결하고 내부에는 다른 버튼이나 링크를 넣지 않습니다.

속성

이 프로젝트에서 추가하거나 제한한 속성입니다.

Root

속성

타입

Content

속성

타입

구현 코드

components/ui/list.tsx
list.tsx
"use client";
import type { ComponentProps, ReactNode } from "react";
import { Button } from "@base-ui/react/button";
import { cx, withClassName } from "../../lib/cx";
import "./list.css";
export type ListRootProps = ComponentProps<"ul"> & {
  /** 목록 테두리와 구분선입니다. @defaultValue "standard" */
  variant?: "standard" | "boxed" | "inset";
};
function ListRoot({
  variant = "standard",
  className,
  ...props
}: ListRootProps) {
  return (
    <ul
      {...props}
      data-variant={variant}
      className={cx("rbx-list", className)}
    />
  );
}
function ListItem({ className, ...props }: ComponentProps<"li">) {
  return <li {...props} className={cx("rbx-list-item", className)} />;
}
/** 상호작용이 있는 행만 Button을 씁니다. 버튼 안에 다른 버튼을 넣지 마세요. */
function ListAction({ className, ...props }: ComponentProps<typeof Button>) {
  return (
    <Button
      {...props}
      className={withClassName("rbx-list-action", className)}
    />
  );
}
export type ListContentProps = ComponentProps<"div"> & {
  /** 내용 앞에 표시할 요소입니다. */
  leading?: ReactNode;
  /** 내용 뒤에 표시할 요소입니다. */
  trailing?: ReactNode;
};
function ListContent({
  leading,
  trailing,
  children,
  className,
  ...props
}: ListContentProps) {
  return (
    <div {...props} className={cx("rbx-list-content", className)}>
      {leading && <span className="rbx-list-leading">{leading}</span>}
      <div className="rbx-list-body">{children}</div>
      {trailing && <span className="rbx-list-trailing">{trailing}</span>}
    </div>
  );
}
function ListTitle({ className, ...props }: ComponentProps<"div">) {
  return <div {...props} className={cx("rbx-list-title", className)} />;
}
function ListDescription({ className, ...props }: ComponentProps<"div">) {
  return <div {...props} className={cx("rbx-list-description", className)} />;
}
export const List = {
  Root: ListRoot,
  Item: ListItem,
  Action: ListAction,
  Content: ListContent,
  Title: ListTitle,
  Description: ListDescription,
};
components/ui/list.css
list.css
/* Security list: outer 12px horizontal inset, inner 16px vertical inset, 12px gaps. */
.rbx-list {
  list-style: none;
  margin: 0;
  padding: 0;
  width: 100%;
}
.rbx-list-item {
  border-bottom: 1px solid var(--rbx-color-stroke-default);
}
.rbx-list-action {
  display: block;
  width: 100%;
  background: transparent;
  color: inherit;
  border: 0;
  padding: 0;
  text-align: left;
  cursor: pointer;
}
.rbx-list-action:hover {
  background: var(--rbx-color-state-hover);
}
.rbx-list-action:active {
  background: var(--rbx-color-state-press);
}
.rbx-list-content {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 16px 12px;
}
.rbx-list-body {
  flex: 1;
  min-width: 0;
}
.rbx-list-leading,
.rbx-list-trailing {
  display: inline-flex;
  flex-shrink: 0;
}
.rbx-list-title {
  color: var(--rbx-color-content-emphasis);
  font: var(--rbx-typography-title-medium-font);
}
.rbx-list-description {
  margin-top: 4px;
  color: var(--rbx-color-content-default);
  font: var(--rbx-typography-body-medium-font);
}

.rbx-list[data-variant="boxed"] {
  border: 1px solid var(--rbx-color-stroke-default);
  border-radius: 16px;
  overflow: hidden;
}
.rbx-list[data-variant="boxed"] .rbx-list-content {
  padding-inline: 20px;
}
.rbx-list[data-variant="boxed"] .rbx-list-item:last-child {
  border-bottom: 0;
}
.rbx-list[data-variant="inset"] .rbx-list-item {
  border-bottom: 0;
  position: relative;
}
.rbx-list[data-variant="inset"] .rbx-list-item:not(:last-child)::after {
  content: "";
  display: block;
  margin-inline: 56px 12px;
  border-bottom: 1px solid var(--rbx-color-stroke-default);
}

목차