- magnet-lines: move useEffect antes do early return (hooks não podem ser chamados condicionalmente), adiciona disabled aos deps - use-month-period: memoiza [...MONTH_NAMES] com useMemo - lancamentos-filters: handleFilterChange em useCallback - inbox-page: sortByTimestamp em useCallback, atualiza deps dos useMemo - note-card: remove formattedDate não utilizado do useMemo Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
113 lines
2.6 KiB
TypeScript
113 lines
2.6 KiB
TypeScript
"use client";
|
|
|
|
import type React from "react";
|
|
import { type CSSProperties, useEffect, useRef } from "react";
|
|
|
|
interface MagnetLinesProps {
|
|
rows?: number;
|
|
columns?: number;
|
|
containerSize?: string;
|
|
lineColor?: string;
|
|
lineWidth?: string;
|
|
lineHeight?: string;
|
|
baseAngle?: number;
|
|
className?: string;
|
|
style?: CSSProperties;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
const MagnetLines: React.FC<MagnetLinesProps> = ({
|
|
rows = 9,
|
|
columns = 9,
|
|
containerSize = "80vmin",
|
|
lineColor = "#efefef",
|
|
lineWidth = "1vmin",
|
|
lineHeight = "6vmin",
|
|
baseAngle = -10,
|
|
className = "",
|
|
style = {},
|
|
disabled = false,
|
|
}) => {
|
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (disabled) return;
|
|
const container = containerRef.current;
|
|
if (!container) return;
|
|
|
|
const items = container.querySelectorAll<HTMLSpanElement>("span");
|
|
|
|
const onPointerMove = (pointer: { x: number; y: number }) => {
|
|
items.forEach((item) => {
|
|
const rect = item.getBoundingClientRect();
|
|
const centerX = rect.x + rect.width / 2;
|
|
const centerY = rect.y + rect.height / 2;
|
|
|
|
const b = pointer.x - centerX;
|
|
const a = pointer.y - centerY;
|
|
const c = Math.sqrt(a * a + b * b) || 1;
|
|
const r =
|
|
((Math.acos(b / c) * 180) / Math.PI) * (pointer.y > centerY ? 1 : -1);
|
|
|
|
item.style.setProperty("--rotate", `${r}deg`);
|
|
});
|
|
};
|
|
|
|
const handlePointerMove = (e: PointerEvent) => {
|
|
onPointerMove({ x: e.x, y: e.y });
|
|
};
|
|
|
|
window.addEventListener("pointermove", handlePointerMove);
|
|
|
|
if (items.length) {
|
|
const middleIndex = Math.floor(items.length / 2);
|
|
const rect = items[middleIndex].getBoundingClientRect();
|
|
onPointerMove({ x: rect.x, y: rect.y });
|
|
}
|
|
|
|
return () => {
|
|
window.removeEventListener("pointermove", handlePointerMove);
|
|
};
|
|
}, [disabled]);
|
|
|
|
// Se magnetlines estiver desabilitado, não renderiza nada
|
|
if (disabled) {
|
|
return null;
|
|
}
|
|
|
|
const total = rows * columns;
|
|
const spans = Array.from({ length: total }, (_, i) => (
|
|
<span
|
|
key={i}
|
|
className="block origin-center"
|
|
style={{
|
|
backgroundColor: lineColor,
|
|
width: lineWidth,
|
|
height: lineHeight,
|
|
// @ts-expect-error -- CSS custom property para controlar a rotação inline
|
|
"--rotate": `${baseAngle}deg`,
|
|
transform: "rotate(var(--rotate))",
|
|
willChange: "transform",
|
|
}}
|
|
/>
|
|
));
|
|
|
|
return (
|
|
<div
|
|
ref={containerRef}
|
|
className={`grid place-items-center ${className}`}
|
|
style={{
|
|
gridTemplateColumns: `repeat(${columns}, 1fr)`,
|
|
gridTemplateRows: `repeat(${rows}, 1fr)`,
|
|
width: containerSize,
|
|
height: containerSize,
|
|
...style,
|
|
}}
|
|
>
|
|
{spans}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default MagnetLines;
|