Files
mintel.me/apps/web/src/components/WaterfallChart.tsx
Marc Mintel b15c8408ff
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 5s
Build & Deploy / 🏗️ Build (push) Failing after 14s
Build & Deploy / 🧪 QA (push) Failing after 1m48s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 2s
fix(blog): optimize component share logic, typography, and modal layouts
2026-02-22 11:41:28 +01:00

101 lines
5.8 KiB
TypeScript

"use client";
import React from 'react';
import { ComponentShareButton } from './ComponentShareButton';
import { Reveal } from './Reveal';
interface WaterfallEvent {
name: string;
start: number;
duration: number;
}
interface WaterfallChartProps {
title?: string;
events: WaterfallEvent[];
totalDuration?: number;
showShare?: boolean;
}
export const WaterfallChart: React.FC<WaterfallChartProps> = ({ title = 'Resource Waterfall', events, totalDuration }) => {
const maxTime = totalDuration || Math.max(...events.map(e => e.start + e.duration));
const getDefaultColor = (name: string) => {
const n = name.toLowerCase();
if (n.includes('html') || n.includes('document')) return 'bg-slate-800';
if (n.includes('js') || n.includes('script')) return 'bg-amber-400';
if (n.includes('css') || n.includes('style')) return 'bg-blue-400';
if (n.includes('img') || n.includes('image')) return 'bg-emerald-400';
if (n.includes('font')) return 'bg-pink-400';
return 'bg-slate-300';
};
// Generate stable hash for share button
const shareId = `waterfall-${title.toLowerCase().replace(/[^a-z0-9]/g, '-')}`;
return (
<Reveal direction="up" delay={0.1}>
<figure id={shareId} className="my-16 not-prose font-sans group relative transition-all duration-500 ease-out z-10">
{/* Ambient Background Glow matching the homepage feel */}
<div className="absolute -inset-1 bg-gradient-to-r from-blue-100/30 to-slate-100/30 rounded-[2rem] blur opacity-20 group-hover:opacity-40 transition duration-1000 -z-10" />
<div className="glass bg-white/80 backdrop-blur-xl border border-slate-100 rounded-2xl shadow-sm group-hover:shadow-md group-hover:border-slate-200 transition-all duration-500 overflow-hidden relative">
{/* Subtle top shine */}
<div className="absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-white to-transparent opacity-80" />
<div className="p-6 md:p-8 lg:p-10 relative z-10">
{/* Share Button top right */}
<div className="absolute top-6 right-6 md:opacity-0 group-hover:opacity-100 transition-opacity duration-500 z-50">
<ComponentShareButton targetId={shareId} title={title} />
</div>
<header className="mb-8 flex flex-col md:flex-row md:justify-between md:items-end gap-2 border-b border-slate-100 pb-4">
<h3 className="text-xl md:text-2xl font-bold text-slate-900 tracking-tight m-0">{title}</h3>
<div className="font-mono text-xs text-slate-400 bg-slate-50 px-3 py-1 rounded-full border border-slate-100 inline-flex items-center gap-2">
<span className="w-1.5 h-1.5 bg-green-500 rounded-full animate-pulse" />
Total Time: {maxTime}ms
</div>
</header>
<div className="relative pt-6 pb-2">
{/* Elegant Grid Lines */}
<div className="absolute inset-x-0 top-0 bottom-0 flex justify-between pointer-events-none z-0 ml-[100px] md:ml-[160px]">
{[0, 0.25, 0.5, 0.75, 1].map((tick) => (
<div key={tick} className="w-px h-full bg-slate-100 flex flex-col justify-start">
<span className="text-[9px] text-slate-400 font-mono -ml-3 -mt-5 bg-white/80 backdrop-blur-sm px-1.5 py-0.5 rounded border border-slate-100 shadow-sm">
{Math.round(maxTime * tick)}
</span>
</div>
))}
</div>
<div className="relative z-10 space-y-3">
{events.map((event, i) => {
const left = (event.start / maxTime) * 100;
const width = Math.max((event.duration / maxTime) * 100, 0.5);
return (
<div key={i} className="group/row relative flex items-center h-10 w-full hover:bg-slate-50/50 rounded-lg transition-colors px-2 -mx-2">
<div className="w-[100px] md:w-[160px] shrink-0 pr-4 flex flex-col justify-center z-20">
<span className="font-bold text-slate-700 text-[10px] md:text-xs truncate uppercase tracking-widest">{event.name}</span>
</div>
<div className="flex-1 relative h-full flex items-center">
<div
className={`h-2.5 md:h-3 rounded-full relative shadow-sm transition-all duration-300 group-hover/row:scale-y-110 group-hover/row:brightness-110 ${getDefaultColor(event.name)}`}
style={{ left: `${left}%`, width: `${width}%` }}
/>
</div>
<span className="w-12 text-right font-mono text-[9px] md:text-xs text-slate-400 group-hover/row:text-slate-900 transition-colors z-20">{event.duration}ms</span>
</div>
);
})}
</div>
</div>
</div>
</div>
</figure>
</Reveal>
);
};