chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,341 @@
|
||||
import { motion } from "framer-motion";
|
||||
import { Button } from "../Buttons";
|
||||
import { Paragraph } from "../Paragraph";
|
||||
import { Spinner } from "../Spinner";
|
||||
import { useDateRange } from "./DateRangeContext";
|
||||
import { useMemo } from "react";
|
||||
import { ClientOnly } from "remix-utils/client-only";
|
||||
|
||||
export function ChartBarLoading() {
|
||||
return (
|
||||
<div className="relative grid h-full place-items-center p-2 pt-0">
|
||||
<ChartBarLoadingBackground />
|
||||
<div className="absolute z-10 mb-16 flex items-center gap-2">
|
||||
<Spinner className="size-6" />
|
||||
<Paragraph variant="base/bright">Loading data…</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChartBarNoData() {
|
||||
const dateRange = useDateRange();
|
||||
|
||||
return (
|
||||
<div className="relative grid h-full place-items-center p-2 pt-0">
|
||||
<ChartBarLoadingBackground />
|
||||
<div className="absolute z-10 mb-16 flex flex-col items-center gap-2">
|
||||
<Paragraph variant="small/bright">No data</Paragraph>
|
||||
<Paragraph variant="small" spacing>
|
||||
There's no data available for the filters you've set.
|
||||
</Paragraph>
|
||||
{dateRange && (
|
||||
<Button variant="secondary/small" onClick={dateRange.resetDateRange}>
|
||||
Clear filters
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChartBarInvalid() {
|
||||
const dateRange = useDateRange();
|
||||
|
||||
return (
|
||||
<div className="relative grid h-full place-items-center p-2 pt-0">
|
||||
<ChartBarLoadingBackground />
|
||||
<div className="absolute z-10 mb-16 flex flex-col items-center gap-2">
|
||||
<Paragraph variant="small/bright">Chart not applicable</Paragraph>
|
||||
<Paragraph variant="small" spacing>
|
||||
Your current filter settings don't apply to this chart's data type.
|
||||
</Paragraph>
|
||||
{dateRange && (
|
||||
<Button variant="secondary/small" onClick={dateRange.resetDateRange}>
|
||||
Clear filters
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChartLineLoading() {
|
||||
return (
|
||||
<div className="relative mb-16 grid h-full place-items-center p-2 pt-0">
|
||||
<ChartLineLoadingBackground />
|
||||
<div className="absolute z-10 flex items-center gap-2">
|
||||
<Spinner className="size-6" />
|
||||
<Paragraph variant="base/bright">Loading data…</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChartLineNoData() {
|
||||
const dateRange = useDateRange();
|
||||
|
||||
return (
|
||||
<div className="relative grid h-full place-items-center p-2 pt-0">
|
||||
<ChartLineLoadingBackground />
|
||||
<div className="absolute z-10 mb-16 flex flex-col items-center gap-2">
|
||||
<Paragraph variant="small/bright">No data</Paragraph>
|
||||
<Paragraph variant="small" spacing>
|
||||
There's no data available for the filters you've set.
|
||||
</Paragraph>
|
||||
{dateRange && (
|
||||
<Button variant="secondary/small" onClick={dateRange.resetDateRange}>
|
||||
Clear filters
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChartLineInvalid() {
|
||||
const dateRange = useDateRange();
|
||||
|
||||
return (
|
||||
<div className="relative grid h-full place-items-center p-2 pt-0">
|
||||
<ChartLineLoadingBackground />
|
||||
<div className="absolute z-10 mb-16 flex flex-col items-center gap-2">
|
||||
<Paragraph variant="small/bright">Chart not applicable</Paragraph>
|
||||
<Paragraph variant="small" spacing>
|
||||
Your current filter settings don't apply to this chart's data type.
|
||||
</Paragraph>
|
||||
{dateRange && (
|
||||
<Button variant="secondary/small" onClick={dateRange.resetDateRange}>
|
||||
Clear filters
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ChartBarLoadingBackground() {
|
||||
return (
|
||||
<motion.div
|
||||
className="flex h-full w-full flex-col"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<ClientOnly fallback={<div />}>
|
||||
{() => (
|
||||
<>
|
||||
<motion.div
|
||||
className="flex flex-1 items-end gap-1"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.6, delay: 0.1 }}
|
||||
>
|
||||
{Array.from({ length: 30 }).map((_, i) => {
|
||||
const height = Math.max(15, Math.floor(Math.random() * 100));
|
||||
return (
|
||||
<motion.div
|
||||
key={i}
|
||||
className="flex-1 rounded-sm bg-background-hover/50"
|
||||
style={{ height: `${height}%` }}
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: `${height}%`, opacity: 1 }}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 120,
|
||||
damping: 14,
|
||||
mass: 1,
|
||||
delay: 0.1 + i * 0.02,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</motion.div>
|
||||
<motion.div
|
||||
className="mt-2 flex flex-col justify-center gap-1"
|
||||
initial={{ y: 10, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 100,
|
||||
damping: 15,
|
||||
delay: 0.2,
|
||||
}}
|
||||
>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<motion.div
|
||||
className="flex items-center gap-1"
|
||||
key={i}
|
||||
initial={{ x: -10, opacity: 0 }}
|
||||
animate={{ x: 0, opacity: 1 }}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 100,
|
||||
damping: 12,
|
||||
delay: 0.3 + i * 0.08,
|
||||
}}
|
||||
>
|
||||
<div className="h-5 w-2 rounded-sm bg-background-hover/50" />
|
||||
<div className="h-5 w-full rounded-sm bg-background-hover/50" />
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
</>
|
||||
)}
|
||||
</ClientOnly>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
function ChartLineLoadingBackground() {
|
||||
// Generate line points with configurable starting position and constraints
|
||||
const generateLinePoints = (startY: number, minY: number, maxY: number) => {
|
||||
const numPoints = 10;
|
||||
const points = [];
|
||||
let lastY = startY;
|
||||
|
||||
for (let i = 0; i < numPoints; i++) {
|
||||
// Calculate x value that spreads points across the full width
|
||||
const x = i * (9 / (numPoints - 1));
|
||||
|
||||
// Create less extreme variations that move smoothly
|
||||
const change = Math.random() * 6 - 3; // Range from -3 to +3
|
||||
const y = Math.max(minY, Math.min(maxY, lastY + change)); // Apply constraints
|
||||
|
||||
points.push({ x, y });
|
||||
lastY = y;
|
||||
}
|
||||
|
||||
return points;
|
||||
};
|
||||
|
||||
// Generate points for both lines
|
||||
const points = useMemo(() => generateLinePoints(30, 10, 90), []);
|
||||
const secondPoints = useMemo(() => generateLinePoints(40, 30, 90), []);
|
||||
|
||||
const generateSmoothPath = (points: Array<{ x: number; y: number }>) => {
|
||||
if (points.length < 2) return "";
|
||||
|
||||
let path = `M0,${50 - points[0].y}`;
|
||||
|
||||
// Use curve command for smooth lines
|
||||
for (let i = 0; i < points.length - 1; i++) {
|
||||
const x1 = points[i].x;
|
||||
const y1 = 50 - points[i].y;
|
||||
const x2 = points[i + 1].x;
|
||||
const y2 = 50 - points[i + 1].y;
|
||||
|
||||
// Bezier control points (create smooth curve)
|
||||
const cx1 = (x1 + x2) / 2;
|
||||
const cy1 = y1;
|
||||
const cx2 = (x1 + x2) / 2;
|
||||
const cy2 = y2;
|
||||
|
||||
path += ` C${cx1},${cy1} ${cx2},${cy2} ${x2},${y2}`;
|
||||
}
|
||||
|
||||
return path;
|
||||
};
|
||||
|
||||
const generateAreaPath = (points: Array<{ x: number; y: number }>) => {
|
||||
const curvePath = generateSmoothPath(points);
|
||||
const lastX = 9;
|
||||
return `${curvePath} L${lastX},50 L0,50 Z`;
|
||||
};
|
||||
|
||||
// Component to render a line with area fill and animation
|
||||
const AnimatedLine = ({
|
||||
points,
|
||||
gradientId,
|
||||
delay = 0,
|
||||
}: {
|
||||
points: Array<{ x: number; y: number }>;
|
||||
gradientId: string;
|
||||
delay?: number;
|
||||
}) => (
|
||||
<>
|
||||
<motion.path
|
||||
d={generateAreaPath(points)}
|
||||
fill={`url(#${gradientId})`}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 1, ease: "easeInOut", delay }}
|
||||
/>
|
||||
<motion.path
|
||||
d={generateSmoothPath(points)}
|
||||
stroke="var(--color-background-hover)"
|
||||
strokeWidth="1"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
fill="none"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 0.8 }}
|
||||
transition={{ duration: 1.5, ease: "easeInOut", delay }}
|
||||
vectorEffect="non-scaling-stroke"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className="flex h-full w-full flex-col"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<div className="relative flex-1">
|
||||
<svg
|
||||
className="absolute inset-0 h-full w-full"
|
||||
viewBox="0 0 9 50"
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="areaGradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="var(--color-background-hover)" stopOpacity={0.3} />
|
||||
<stop offset="100%" stopColor="var(--color-background-hover)" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
<linearGradient id="secondAreaGradient" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="var(--color-background-hover)" stopOpacity={0.3} />
|
||||
<stop offset="100%" stopColor="var(--color-background-hover)" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<g>
|
||||
<AnimatedLine points={points} gradientId="areaGradient" />
|
||||
<AnimatedLine points={secondPoints} gradientId="secondAreaGradient" delay={0.2} />
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
className="mt-2 flex flex-col justify-center gap-1"
|
||||
initial={{ y: 10, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 100,
|
||||
damping: 15,
|
||||
delay: 0.2,
|
||||
}}
|
||||
>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<motion.div
|
||||
className="flex items-center gap-1"
|
||||
key={i}
|
||||
initial={{ x: -10, opacity: 0 }}
|
||||
animate={{ x: 0, opacity: 1 }}
|
||||
transition={{
|
||||
type: "spring",
|
||||
stiffness: 100,
|
||||
damping: 12,
|
||||
delay: 0.3 + i * 0.08,
|
||||
}}
|
||||
>
|
||||
<div className="h-5 w-2 rounded-sm bg-background-hover/50" />
|
||||
<div className="h-5 w-full rounded-sm bg-background-hover/50" />
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user