d-ui logod-ui
フィードバック

Rating

クリックやホバーで選択できる星評価コンポーネント。

インストール

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

サンプル

使い方

"use client";
import { Rating } from "@/components/ui/rating";
import { useState } from "react";

export default function Example() {
  const [rating, setRating] = useState(3);
  return <Rating value={rating} onChange={setRating} />;
}

プロパティ

value任意
number

選択中の評価値

デフォルト:0
max任意
number

最大評価数

デフォルト:5
size任意
"sm" | "md" | "lg"

星のサイズ

デフォルト:"md"
readOnly任意
boolean

読み取り専用

デフォルト:false
onChange必須
(value: number) => void

変更ハンドラ

ソースコード

"use client";

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

interface RatingProps {
  value?: number;
  max?: number;
  size?: "sm" | "md" | "lg";
  readOnly?: boolean;
  onChange?: (value: number) => void;
  className?: string;
}

const sizes = {
  sm: "h-4 w-4",
  md: "h-5 w-5",
  lg: "h-7 w-7",
};

export function Rating({
  value = 0,
  max = 5,
  size = "md",
  readOnly = false,
  onChange,
  className,
}: RatingProps) {
  const [hovered, setHovered] = React.useState<number | null>(null);
  const display = hovered ?? value;

  return (
    <div
      className={cn("inline-flex items-center gap-0.5", className)}
      onMouseLeave={() => setHovered(null)}
    >
      {Array.from({ length: max }, (_, i) => {
        const filled = i + 1 <= display;
        return (
          <button
            key={i}
            type="button"
            disabled={readOnly}
            onClick={() => onChange?.(i + 1)}
            onMouseEnter={() => !readOnly && setHovered(i + 1)}
            className={cn(
              "transition-transform focus-visible:outline-none",
              !readOnly && "hover:scale-110 cursor-pointer",
              readOnly && "cursor-default"
            )}
          >
            <Star
              className={cn(
                sizes[size],
                "transition-colors duration-150",
                filled
                  ? "fill-amber-400 text-amber-400"
                  : "fill-transparent text-muted-foreground/40"
              )}
            />
          </button>
        );
      })}
    </div>
  );
}