컴포넌트

Status Meter

상태를 네 단계로 표시합니다.

설치

pnpm dlx ply-ui add status-meter button

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

사용법

value에 0–4 범위의 상태값을, label에 상태 이름을 전달합니다. startLabelendLabel은 양 끝의 설명입니다. tone은 수치와 독립적인 색상입니다.

구성

구성 요소역할
StatusMeter0–4 범위의 값을 네 칸으로 표시합니다. 범위를 벗어난 값은 0–4로 제한됩니다.
StatusCard제목과 상태 내용을 묶는 카드입니다. Meter 없이도 사용할 수 있습니다.

Success

tone="success"로 채워진 칸의 색상을 지정합니다. 기본값입니다. 값의 의미는 사용하는 화면에서 정합니다.

Warning

tone="warning"로 채워진 칸의 색상을 지정합니다. 값의 의미는 사용하는 화면에서 정합니다.

Alert

tone="alert"로 채워진 칸의 색상을 지정합니다. 값의 의미는 사용하는 화면에서 정합니다.

Emphasis

tone="emphasis"로 채워진 칸의 색상을 지정합니다. 값의 의미는 사용하는 화면에서 정합니다.

속성

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

StatusMeter

속성

타입

구현 코드

components/ui/status-meter.tsx
status-meter.tsx
"use client";
import type { ComponentProps, ReactNode } from "react";
import { Meter } from "@base-ui/react/meter";
import { cx, withClassName } from "../../lib/cx";
import "./status-meter.css";
export type StatusMeterProps = Omit<
  ComponentProps<typeof Meter.Root>,
  "children" | "min" | "max"
> & {
  label: string;
  startLabel?: string;
  endLabel?: string;
  tone?: "success" | "warning" | "alert" | "emphasis";
};
/** Account Status에서 본 4칸 표시. 숫자 값의 의미와 문구는 소비자가 정합니다. */
export function StatusMeter({
  value,
  label,
  tone = "success",
  startLabel,
  endLabel,
  className,
  ...props
}: StatusMeterProps) {
  const normalized = Math.min(
    4,
    Math.max(0, Number.isFinite(value) ? value : 0),
  );
  return (
    <Meter.Root
      {...props}
      value={normalized}
      min={0}
      max={4}
      aria-label={label}
      data-tone={tone}
      className={withClassName("rbx-status-meter", className)}
    >
      <div className="rbx-status-segments" aria-hidden="true">
        {[0, 1, 2, 3].map((index) => (
          <span key={index} data-filled={index < normalized || undefined} />
        ))}
      </div>
      {(startLabel || endLabel) && (
        <div className="rbx-status-labels" aria-hidden="true">
          <span>{startLabel}</span>
          <span>{endLabel}</span>
        </div>
      )}
    </Meter.Root>
  );
}
export function StatusCard({
  title,
  children,
  className,
  ...props
}: Omit<ComponentProps<"section">, "title"> & { title: ReactNode }) {
  return (
    <section {...props} className={cx("rbx-status-card", className)}>
      <h2 className="rbx-status-card-title">{title}</h2>
      {children}
    </section>
  );
}
components/ui/status-meter.css
status-meter.css
.rbx-status-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 20px;
  padding: 24px;
  border: 0;
  border-radius: 16px;
  background: var(--rbx-color-surface-100);
  color: var(--rbx-color-content-emphasis);
}
.rbx-status-card-title {
  margin: 0;
  font: var(--rbx-typography-heading-medium-font);
  letter-spacing: var(--rbx-typography-heading-medium-letter-spacing);
}
.rbx-status-meter {
  --rbx-status-color: var(--rbx-color-system-success);
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.rbx-status-meter[data-tone="warning"] {
  --rbx-status-color: var(--rbx-color-system-warning);
}
.rbx-status-meter[data-tone="alert"] {
  --rbx-status-color: var(--rbx-color-system-alert);
}
.rbx-status-meter[data-tone="emphasis"] {
  --rbx-status-color: var(--rbx-color-system-emphasis);
}
.rbx-status-segments {
  display: flex;
  gap: 2px;
  height: 8px;
}
.rbx-status-segments > span {
  flex: 1;
  background: var(--rbx-color-shift-200);
}
.rbx-status-segments > span[data-filled] {
  background: var(--rbx-status-color);
}
.rbx-status-segments > span:first-child {
  border-radius: 4px 0 0 4px;
}
.rbx-status-segments > span:last-child {
  border-radius: 0 4px 4px 0;
}
.rbx-status-labels {
  display: flex;
  justify-content: space-between;
  color: var(--rbx-color-content-muted);
  font: var(--rbx-typography-caption-medium-font);
}

목차