diff --git a/apps/web/src/components/common/side-by-side-comparison.tsx b/apps/web/src/components/common/side-by-side-comparison.tsx new file mode 100644 index 00000000..f484e960 --- /dev/null +++ b/apps/web/src/components/common/side-by-side-comparison.tsx @@ -0,0 +1,116 @@ +import { useState } from "react"; + +interface SideBySideComparisonProps { + beforeSrc: string; + afterSrc: string; + beforeSize?: number; + afterSize?: number; +} + +function formatSize(bytes: number): string { + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; + return `${(bytes / (1024 * 1024)).toFixed(2)} MB`; +} + +export function SideBySideComparison({ + beforeSrc, + afterSrc, + beforeSize, + afterSize, +}: SideBySideComparisonProps) { + const [beforeDims, setBeforeDims] = useState<{ w: number; h: number } | null>(null); + const [afterDims, setAfterDims] = useState<{ w: number; h: number } | null>(null); + + const savingsPercent = + beforeSize && afterSize && beforeSize > 0 + ? ((1 - afterSize / beforeSize) * 100).toFixed(1) + : null; + + const checkerboard = { + backgroundImage: `linear-gradient(45deg, #ccc 25%, transparent 25%), + linear-gradient(-45deg, #ccc 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, #ccc 75%), + linear-gradient(-45deg, transparent 75%, #ccc 75%)`, + backgroundSize: "16px 16px", + backgroundPosition: "0 0, 0 8px, 8px -8px, -8px 0px", + }; + + return ( +
+ {beforeDims.w} × {beforeDims.h} +
+ )} + {beforeSize != null &&{formatSize(beforeSize)}
} ++ {afterDims.w} × {afterDims.h} +
+ )} + {afterSize != null &&{formatSize(afterSize)}
} +0 ? "text-green-600 dark:text-green-400" : "text-red-500"}`} + > + {Number(savingsPercent) > 0 + ? `${savingsPercent}% smaller` + : `${Math.abs(Number(savingsPercent))}% larger`} +
+ )} +