nexus/ui/src/components/StarRating.tsx
Mikkel Georgsen 86d4de87e3 feat(12-02): StarRating component, API extensions, DesignGuide entry
- 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
2026-04-04 03:55:42 +00:00

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;
}