98 lines
4.0 KiB
TypeScript
98 lines
4.0 KiB
TypeScript
import { type Component, For } from "solid-js";
|
|
import { CheckCircle2 } from "lucide-solid";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface StatusCircleProps {
|
|
status: number;
|
|
size?: number;
|
|
className?: string;
|
|
recurrenceProgress?: number | null;
|
|
}
|
|
|
|
export const StatusCircle: Component<StatusCircleProps> = (props) => {
|
|
const size = () => props.size || 24;
|
|
const radius = () => (size() / 2) - 2; // Subtract stroke width/padding
|
|
const circumference = () => 2 * Math.PI * radius();
|
|
|
|
// Status 0: Empty Circle
|
|
// Status 10: CheckCircle2 (Green)
|
|
// Status 1-9: Progress Ring
|
|
|
|
const strokeDashoffset = () => {
|
|
// Invert to fill clockwise?
|
|
// Progress 1-9.
|
|
// 1 = 10%, 9 = 90%
|
|
const percentage = Math.max(0, Math.min(10, props.status)) / 10;
|
|
return circumference() * (1 - percentage);
|
|
};
|
|
|
|
const dotCount = () => 12;
|
|
const dotRadius = () => Math.max(1.4, size() * 0.06);
|
|
const dotRingRadius = () => radius() - 1;
|
|
const filledDots = () => {
|
|
const progress = props.recurrenceProgress ?? 0;
|
|
return Math.floor(Math.max(0, Math.min(1, progress)) * dotCount());
|
|
};
|
|
|
|
return (
|
|
<div class={cn("relative flex items-center justify-center", props.className)} style={{ width: `${size()}px`, height: `${size()}px` }}>
|
|
{props.status >= 10 ? (
|
|
props.recurrenceProgress != null ? (
|
|
<svg width={size()} height={size()}>
|
|
<For each={Array.from({ length: dotCount() })}>
|
|
{(_, index) => {
|
|
const angle = (index() / dotCount()) * Math.PI * 2 - Math.PI / 2;
|
|
const cx = size() / 2 + Math.cos(angle) * dotRingRadius();
|
|
const cy = size() / 2 + Math.sin(angle) * dotRingRadius();
|
|
const isFilled = index() < filledDots();
|
|
return (
|
|
<circle
|
|
cx={cx}
|
|
cy={cy}
|
|
r={dotRadius()}
|
|
class={isFilled ? "fill-green-500" : "fill-muted-foreground/30"}
|
|
/>
|
|
);
|
|
}}
|
|
</For>
|
|
</svg>
|
|
) : (
|
|
<CheckCircle2 size={size()} class="text-green-500" />
|
|
)
|
|
) : (
|
|
// Progress Circle
|
|
<div class="relative group">
|
|
{/* Background Circle (Ghost) */}
|
|
<svg width={size()} height={size()} class="rotate-[-90deg]">
|
|
<circle
|
|
cx={size() / 2}
|
|
cy={size() / 2}
|
|
r={radius()}
|
|
fill="transparent"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
class="text-muted-foreground/20"
|
|
/>
|
|
{/* Progress Arc */}
|
|
<circle
|
|
cx={size() / 2}
|
|
cy={size() / 2}
|
|
r={radius()}
|
|
fill="transparent"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-dasharray={circumference().toString()}
|
|
stroke-dashoffset={strokeDashoffset()}
|
|
stroke-linecap="round"
|
|
class="text-primary transition-all duration-300"
|
|
/>
|
|
</svg>
|
|
{/* Hover indicator (maybe show check on hover?)
|
|
User didn't ask for generic hover, just visual update.
|
|
*/}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|