d-ui logod-ui
データ表示

Progress Ring

SVGで描画する円形プログレスインジケーター。

インストール

npx shadcn add https://d-ui.daigo-suhara.com/registry/progress-ring.json

サンプル

72%

使い方

import { ProgressRing } from "@/components/ui/progress-ring";

export default function Example() {
  return <ProgressRing value={72} color="success" size={100} />;
}

プロパティ

value必須
number

現在値

max任意
number

最大値

デフォルト:100
size任意
number

直径(px)

デフォルト:80
strokeWidth任意
number

線の太さ

デフォルト:8
color任意
"primary" | "success" | "warning" | "danger"

デフォルト:"primary"
showLabel任意
boolean

中央にパーセントを表示

デフォルト:true
label必須
string

カスタムラベル

ソースコード

import * as React from "react";
import { cn } from "@/lib/utils";

interface ProgressRingProps {
  value: number;
  max?: number;
  size?: number;
  strokeWidth?: number;
  color?: "primary" | "success" | "warning" | "danger" | string;
  showLabel?: boolean;
  label?: string;
  className?: string;
}

const colorMap: Record<string, string> = {
  primary: "stroke-primary",
  success: "stroke-emerald-500",
  warning: "stroke-amber-500",
  danger: "stroke-red-500",
};

export function ProgressRing({
  value,
  max = 100,
  size = 80,
  strokeWidth = 8,
  color = "primary",
  showLabel = true,
  label,
  className,
}: ProgressRingProps) {
  const pct = Math.min(Math.max(value / max, 0), 1);
  const radius = (size - strokeWidth) / 2;
  const circumference = 2 * Math.PI * radius;
  const offset = circumference * (1 - pct);
  const colorClass = colorMap[color] ?? "";
  const inlineColor = colorMap[color] ? undefined : color;

  return (
    <div
      className={cn("relative inline-flex items-center justify-center", className)}
      style={{ width: size, height: size }}
    >
      <svg
        width={size}
        height={size}
        viewBox={`0 0 ${size} ${size}`}
        className="-rotate-90"
      >
        {/* Track */}
        <circle
          cx={size / 2}
          cy={size / 2}
          r={radius}
          fill="none"
          strokeWidth={strokeWidth}
          className="stroke-muted"
        />
        {/* Progress */}
        <circle
          cx={size / 2}
          cy={size / 2}
          r={radius}
          fill="none"
          strokeWidth={strokeWidth}
          strokeDasharray={circumference}
          strokeDashoffset={offset}
          strokeLinecap="round"
          className={cn("transition-all duration-700", colorClass)}
          style={inlineColor ? { stroke: inlineColor } : undefined}
        />
      </svg>
      {showLabel && (
        <div className="absolute inset-0 flex flex-col items-center justify-center">
          <span className="text-sm font-semibold tabular-nums leading-none">
            {label ?? `${Math.round(pct * 100)}%`}
          </span>
        </div>
      )}
    </div>
  );
}