style: replace animated grid with subtle horizontal lines in chart

Simple repeating horizontal lines at 40px intervals with 50% opacity,
faded at top/bottom edges via CSS mask. No extra components needed.
This commit is contained in:
Usman Baig
2026-03-13 13:05:24 +01:00
parent 9a54d93c79
commit 453a596eaf
3 changed files with 10 additions and 160 deletions

View File

@@ -11,7 +11,6 @@ import { Checkbox } from '@ciphera-net/ui'
import { ArrowUpRight, ArrowDownRight } from '@phosphor-icons/react'
import { motion } from 'framer-motion'
import { cn } from '@/lib/utils'
import { AnimatedGridPattern } from '@/components/ui/animated-grid-pattern'
const ANNOTATION_COLORS: Record<string, string> = {
deploy: '#3b82f6',
@@ -386,16 +385,15 @@ export default function Chart({
</CardHeader>
<CardContent className="px-2.5 py-4 relative overflow-hidden">
<AnimatedGridPattern
numSquares={30}
maxOpacity={0.1}
duration={3}
repeatDelay={1}
width={30}
height={30}
x={-1}
y={-1}
className="fill-neutral-400/10 stroke-neutral-400/15 dark:fill-neutral-500/10 dark:stroke-neutral-500/15 [mask-image:linear-gradient(to_bottom,transparent,white_20%,white_80%,transparent)]"
{/* Subtle horizontal lines background */}
<div
className="pointer-events-none absolute inset-0"
style={{
backgroundImage: 'repeating-linear-gradient(to bottom, transparent, transparent 39px, var(--chart-grid) 39px, var(--chart-grid) 40px)',
opacity: 0.5,
maskImage: 'linear-gradient(to bottom, transparent, black 15%, black 85%, transparent)',
WebkitMaskImage: 'linear-gradient(to bottom, transparent, black 15%, black 85%, transparent)',
}}
/>
{/* Toolbar */}
<div className="flex items-center justify-between gap-3 mb-4 px-2">

View File

@@ -1,148 +0,0 @@
"use client";
import { useEffect, useId, useRef, useState } from "react";
import { motion } from "framer-motion";
import { cn } from "@/lib/utils";
interface AnimatedGridPatternProps {
width?: number;
height?: number;
x?: number;
y?: number;
strokeDasharray?: any;
numSquares?: number;
className?: string;
maxOpacity?: number;
duration?: number;
repeatDelay?: number;
}
export function AnimatedGridPattern({
width = 40,
height = 40,
x = -1,
y = -1,
strokeDasharray = 0,
numSquares = 50,
className,
maxOpacity = 0.5,
duration = 4,
repeatDelay = 0.5,
...props
}: AnimatedGridPatternProps) {
const id = useId();
const containerRef = useRef(null);
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const [squares, setSquares] = useState(() => generateSquares(numSquares));
function getPos() {
return [
Math.floor((Math.random() * dimensions.width) / width),
Math.floor((Math.random() * dimensions.height) / height),
];
}
// Adjust the generateSquares function to return objects with an id, x, and y
function generateSquares(count: number) {
return Array.from({ length: count }, (_, i) => ({
id: i,
pos: getPos(),
}));
}
// Function to update a single square's position
const updateSquarePosition = (id: number) => {
setSquares((currentSquares) =>
currentSquares.map((sq) =>
sq.id === id
? {
...sq,
pos: getPos(),
}
: sq,
),
);
};
// Update squares to animate in
useEffect(() => {
if (dimensions.width && dimensions.height) {
setSquares(generateSquares(numSquares));
}
}, [dimensions, numSquares]);
// Resize observer to update container dimensions
useEffect(() => {
const resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
setDimensions({
width: entry.contentRect.width,
height: entry.contentRect.height,
});
}
});
if (containerRef.current) {
resizeObserver.observe(containerRef.current);
}
return () => {
if (containerRef.current) {
resizeObserver.unobserve(containerRef.current);
}
};
}, [containerRef]);
return (
<svg
ref={containerRef}
aria-hidden="true"
className={cn(
"pointer-events-none absolute inset-0 h-full w-full fill-gray-400/30 stroke-gray-400/30",
className,
)}
{...props}
>
<defs>
<pattern
id={id}
width={width}
height={height}
patternUnits="userSpaceOnUse"
x={x}
y={y}
>
<path
d={`M.5 ${height}V.5H${width}`}
fill="none"
strokeDasharray={strokeDasharray}
/>
</pattern>
</defs>
<rect width="100%" height="100%" fill={`url(#${id})`} />
<svg x={x} y={y} className="overflow-visible">
{squares.map(({ pos: [x, y], id }, index) => (
<motion.rect
initial={{ opacity: 0 }}
animate={{ opacity: maxOpacity }}
transition={{
duration,
repeat: 1,
delay: index * 0.1,
repeatType: "reverse",
}}
onAnimationComplete={() => updateSquarePosition(id)}
key={`${x}-${y}-${index}`}
width={width - 1}
height={height - 1}
x={x * width + 1}
y={y * height + 1}
fill="currentColor"
strokeWidth="0"
/>
))}
</svg>
</svg>
);
}