158 lines
5.2 KiB
TypeScript
158 lines
5.2 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface RingData {
|
|
label: string;
|
|
value: number;
|
|
max: number;
|
|
color: string;
|
|
}
|
|
|
|
interface ChartCard18Props {
|
|
title?: string;
|
|
description?: string;
|
|
rings?: RingData[];
|
|
className?: string;
|
|
}
|
|
|
|
const numberFormatter = new Intl.NumberFormat("fa-IR", {
|
|
maximumFractionDigits: 0,
|
|
});
|
|
|
|
const defaultRings: RingData[] = [
|
|
{ label: "حرکت", value: 420, max: 500, color: "var(--chart-5)" },
|
|
{ label: "ورزش", value: 28, max: 30, color: "var(--chart-1)" },
|
|
{ label: "ایستادن", value: 10, max: 12, color: "var(--chart-2)" },
|
|
];
|
|
|
|
const ChartCard18 = ({
|
|
title = "حلقههای فعالیت",
|
|
description = "پیشرفت فعالیت روزانه",
|
|
rings = defaultRings,
|
|
className,
|
|
}: ChartCard18Props) => {
|
|
const [activeIndex, setActiveIndex] = useState<number | null>(null);
|
|
const size = 180;
|
|
const strokeWidth = 16;
|
|
const gap = 6;
|
|
|
|
return (
|
|
<Card
|
|
dir="rtl"
|
|
lang="fa"
|
|
className={cn("w-full max-w-2xl text-start", className)}
|
|
>
|
|
<CardHeader>
|
|
<CardTitle>{title}</CardTitle>
|
|
<CardDescription>{description}</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="flex items-center justify-center gap-8">
|
|
<div className="relative" style={{ width: size, height: size }}>
|
|
<svg width={size} height={size} className="-rotate-90">
|
|
{rings.map((ring, index) => {
|
|
const radius =
|
|
(size - strokeWidth) / 2 - index * (strokeWidth + gap);
|
|
const circumference = 2 * Math.PI * radius;
|
|
const percentage = Math.min(ring.value / ring.max, 1);
|
|
const strokeDashoffset = circumference * (1 - percentage);
|
|
const isActive = activeIndex === index;
|
|
const isInactive =
|
|
activeIndex !== null && activeIndex !== index;
|
|
|
|
return (
|
|
<g
|
|
key={ring.label}
|
|
style={{ cursor: "pointer" }}
|
|
onMouseEnter={() => setActiveIndex(index)}
|
|
onMouseLeave={() => setActiveIndex(null)}
|
|
>
|
|
<circle
|
|
cx={size / 2}
|
|
cy={size / 2}
|
|
r={radius}
|
|
fill="none"
|
|
stroke="var(--muted)"
|
|
strokeWidth={strokeWidth}
|
|
style={{
|
|
transition: "opacity 0.15s ease-out",
|
|
opacity: isInactive ? 0.3 : 1,
|
|
}}
|
|
/>
|
|
<circle
|
|
cx={size / 2}
|
|
cy={size / 2}
|
|
r={radius}
|
|
fill="none"
|
|
stroke={ring.color}
|
|
strokeWidth={strokeWidth}
|
|
strokeLinecap="round"
|
|
strokeDasharray={circumference}
|
|
strokeDashoffset={strokeDashoffset}
|
|
style={{
|
|
transition:
|
|
"stroke-dashoffset 0.5s ease-in-out, opacity 0.15s ease-out",
|
|
opacity: isInactive ? 0.3 : 1,
|
|
}}
|
|
/>
|
|
</g>
|
|
);
|
|
})}
|
|
</svg>
|
|
</div>
|
|
|
|
<div className="flex flex-col gap-4">
|
|
{rings.map((ring, index) => {
|
|
const percentage = Math.round((ring.value / ring.max) * 100);
|
|
const isActive = activeIndex === index;
|
|
const isInactive = activeIndex !== null && activeIndex !== index;
|
|
|
|
return (
|
|
<div
|
|
key={ring.label}
|
|
className={cn(
|
|
"flex cursor-pointer items-center gap-3 rounded-md px-2 py-1.5 transition-all duration-150 ease-out",
|
|
isActive && "bg-muted",
|
|
!isActive && "hover:bg-muted/50",
|
|
isInactive && "opacity-40",
|
|
)}
|
|
onMouseEnter={() => setActiveIndex(index)}
|
|
onMouseLeave={() => setActiveIndex(null)}
|
|
>
|
|
<div
|
|
className={cn(
|
|
"size-3 rounded-full transition-transform duration-150 ease-out",
|
|
isActive && "scale-125",
|
|
)}
|
|
style={{ backgroundColor: ring.color }}
|
|
/>
|
|
<div>
|
|
<div className="text-sm font-medium">{ring.label}</div>
|
|
<div className="text-xs text-muted-foreground tabular-nums">
|
|
{numberFormatter.format(ring.value)}/
|
|
{numberFormatter.format(ring.max)} (
|
|
{numberFormatter.format(percentage)}٪)
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
};
|
|
|
|
export { ChartCard18 };
|