import React, { useCallback, useEffect, useState } from "react"; import { useReducedMotion } from "../shared/useReducedMotion"; export type FlowLayoutItem = { key: React.Key; content: React.ReactNode; }; export interface FlowLayoutProps { items: FlowLayoutItem[]; justifyContent?: React.CSSProperties["justifyContent"]; alignItems?: React.CSSProperties["alignItems"]; rowGap?: number; columnGap?: number; padding?: string; outerPadding?: string; className?: string; style?: React.CSSProperties; } export default function FlowLayout({ items, justifyContent = "center", alignItems = "center", rowGap = 16, columnGap = 16, padding = "0px", outerPadding, className = "", style, }: FlowLayoutProps): React.ReactElement { const [activeIndex, setActiveIndex] = useState(null); const [supportsHover, setSupportsHover] = useState(false); const reducedMotion = useReducedMotion(); useEffect(() => { if ( typeof window === "undefined" || typeof window.matchMedia !== "function" ) { return; } const query = window.matchMedia("(hover: hover) and (pointer: fine)"); const update = (): void => setSupportsHover(query.matches); update(); query.addEventListener?.("change", update); return () => query.removeEventListener?.("change", update); }, []); const getTransform = useCallback( (index: number): React.CSSProperties => { if (!supportsHover || reducedMotion || activeIndex === null) return {}; const delta = index - activeIndex; const distance = Math.abs(delta); if (delta === 0) { return { transform: "translateZ(0) scale(1.15)", zIndex: 100, opacity: 1, }; } return { transform: `perspective(1000px) rotateY(${delta * 8}deg) translateX(${delta * 8}px) scale(${Math.max(0.85, 1 - distance * 0.08)})`, zIndex: Math.max(1, 100 - distance * 10), opacity: Math.max(0.6, 1 - distance * 0.15), }; }, [activeIndex, reducedMotion, supportsHover], ); const content = (
{items.map((item, index) => (
setActiveIndex(index)} onMouseLeave={() => setActiveIndex(null)} onFocus={() => setActiveIndex(index)} onBlur={() => setActiveIndex(null)} > {item.content}
))}
); return outerPadding ? (
{content}
) : ( content ); }