컴포넌트
Status Meter
상태를 네 단계로 표시합니다.
설치
pnpm dlx ply-ui add status-meter button공통 설치 설정을 먼저 적용합니다.
사용법
value에 0–4 범위의 상태값을, label에 상태 이름을 전달합니다. startLabel과 endLabel은 양 끝의 설명입니다. tone은 수치와 독립적인 색상입니다.
구성
| 구성 요소 | 역할 |
|---|---|
StatusMeter | 0–4 범위의 값을 네 칸으로 표시합니다. 범위를 벗어난 값은 0–4로 제한됩니다. |
StatusCard | 제목과 상태 내용을 묶는 카드입니다. Meter 없이도 사용할 수 있습니다. |
Success
tone="success"로 채워진 칸의 색상을 지정합니다. 기본값입니다. 값의 의미는 사용하는 화면에서 정합니다.
Warning
tone="warning"로 채워진 칸의 색상을 지정합니다. 값의 의미는 사용하는 화면에서 정합니다.
Alert
tone="alert"로 채워진 칸의 색상을 지정합니다. 값의 의미는 사용하는 화면에서 정합니다.
Emphasis
tone="emphasis"로 채워진 칸의 색상을 지정합니다. 값의 의미는 사용하는 화면에서 정합니다.
속성
이 프로젝트에서 추가하거나 제한한 속성입니다.
StatusMeter
속성
타입
구현 코드
components/ui/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
.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);
}