- Create StarRating component with interactive/readonly modes, amber stars, size sm/md - Add PersonalRating type and taskCount/avgCostUsd/lastUsedAt to SkillListItem - Add getRatings and addRating to skillRegistryApi - Add Rating System section to DesignGuide with all variants - Fix SkillCard fixture and DesignGuide examples to include new SkillListItem fields
65 lines
1.5 KiB
TypeScript
65 lines
1.5 KiB
TypeScript
import { Star } from "lucide-react";
|
|
import { cn } from "@/lib/utils";
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipTrigger,
|
|
} from "@/components/ui/tooltip";
|
|
|
|
interface StarRatingProps {
|
|
value: number;
|
|
onChange?: (v: number) => void;
|
|
readonly?: boolean;
|
|
size?: "sm" | "md";
|
|
}
|
|
|
|
export function StarRating({
|
|
value,
|
|
onChange,
|
|
readonly = false,
|
|
size = "md",
|
|
}: StarRatingProps) {
|
|
const iconClass = size === "sm" ? "h-3.5 w-3.5" : "h-5 w-5";
|
|
|
|
const stars = (
|
|
<span className="flex items-center gap-0.5">
|
|
{[1, 2, 3, 4, 5].map((star) => {
|
|
const filled = star <= value;
|
|
return (
|
|
<button
|
|
key={star}
|
|
type="button"
|
|
aria-label={`Rate ${star} star${star > 1 ? "s" : ""}`}
|
|
disabled={readonly}
|
|
onClick={() => onChange?.(star)}
|
|
className={cn(
|
|
"focus-visible:ring-ring focus-visible:ring-[3px] rounded outline-none",
|
|
"hover:bg-accent/10",
|
|
readonly ? "cursor-default" : "cursor-pointer",
|
|
)}
|
|
>
|
|
<Star
|
|
className={cn(
|
|
iconClass,
|
|
filled ? "fill-amber-400 text-amber-400" : "text-muted-foreground",
|
|
)}
|
|
/>
|
|
</button>
|
|
);
|
|
})}
|
|
</span>
|
|
);
|
|
|
|
if (readonly) {
|
|
return (
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
{stars}
|
|
</TooltipTrigger>
|
|
<TooltipContent>{value} out of 5 stars</TooltipContent>
|
|
</Tooltip>
|
|
);
|
|
}
|
|
|
|
return stars;
|
|
}
|