Files
webkulisse-saas-tanstack/src/dev-component/Devtool.tsx
T

132 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useRef, useState, type ReactNode, type CSSProperties } from 'react';
import './devtool.css';
interface CrosshairProps {
children: ReactNode;
/** Optionales Label, das vor den Dimensionen angezeigt wird, z. B. "Header" */
label?: string;
/** Beliebige CSS-Farbe für Rahmen/Kreuz, überschreibt --crosshair-color */
color?: string;
/** Schraffur-Hintergrund aktivieren (.hatch Utility) */
hatch?: boolean;
/** Größe der Schraffur in px, überschreibt --hatch-size */
hatchSize?: number;
/** Live-Breite/Höhe oben links einblenden (per ResizeObserver) */
showDimensions?: boolean;
className?: string;
}
export function Crosshair({
children,
label,
color,
hatch = false,
hatchSize,
showDimensions = true,
className = '',
}: CrosshairProps) {
const ref = useRef<HTMLDivElement>(null);
const [size, setSize] = useState({ w: 0, h: 0 });
useEffect(() => {
const el = ref.current;
if (!el || !showDimensions) return;
const ro = new ResizeObserver(([entry]) => {
const { width, height } = entry.contentRect;
setSize({ w: Math.round(width), h: Math.round(height) });
});
ro.observe(el);
return () => ro.disconnect();
}, [showDimensions]);
const style: CSSProperties = {
...(color ? { ['--crosshair-color' as string]: color } : {}),
...(hatchSize ? { ['--hatch-size' as string]: `${hatchSize}px` } : {}),
};
return (
<div
ref={ref}
className={`crosshair ${hatch ? 'hatch' : ''} ${className}`.trim()}
style={style}
>
{showDimensions && (
<span className="pointer-events-none absolute left-1 top-1 z-50 rounded bg-black/70 px-1.5 py-0.5 font-mono text-[10px] leading-none text-white">
{label ? `${label} · ` : ''}
{size.w}×{size.h}
</span>
)}
{children}
</div>
);
}
/** Zeigt den berechneten z-index eines Elements als Badge oben rechts an. */
export function ZIndexBadge({
children,
className = '',
}: {
children: ReactNode;
className?: string;
}) {
const ref = useRef<HTMLDivElement>(null);
const [z, setZ] = useState('auto');
useEffect(() => {
const el = ref.current;
if (!el) return;
setZ(getComputedStyle(el).zIndex);
}, []);
return (
<div ref={ref} className={`relative ${className}`.trim()}>
<span className="pointer-events-none absolute right-1 top-1 z-[9999] rounded bg-black/70 px-1.5 py-0.5 font-mono text-[10px] leading-none text-white">
z: {z}
</span>
{children}
</div>
);
}
/**
* Definiert die SVG-Filter für die colorblind-* Utilities aus dev-utilities.css.
* Einmal im App-Root rendern (z. B. direkt unter <body>/<App>) — erzeugt selbst
* kein sichtbares Element, nur die <filter>-Definitionen im DOM.
*/
export function ColorblindFilters() {
return (
<svg aria-hidden="true" className="absolute h-0 w-0 overflow-hidden">
<defs>
<filter id="protanopia">
<feColorMatrix
type="matrix"
values="0.567 0.433 0 0 0
0.558 0.442 0 0 0
0 0.242 0.758 0 0
0 0 0 1 0"
/>
</filter>
<filter id="deuteranopia">
<feColorMatrix
type="matrix"
values="0.625 0.375 0 0 0
0.7 0.3 0 0 0
0 0.3 0.7 0 0
0 0 0 1 0"
/>
</filter>
<filter id="tritanopia">
<feColorMatrix
type="matrix"
values="0.95 0.05 0 0 0
0 0.433 0.567 0 0
0 0.475 0.525 0 0
0 0 0 1 0"
/>
</filter>
</defs>
</svg>
);
}