"use client"; import { useEffect, useRef, useState, type ComponentProps } from "react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { motion, useReducedMotion } from "motion/react"; import { cn } from "@/lib/utils"; const CORNER = 6; const DASH = "repeating-linear-gradient(to top, transparent 0 2px, currentColor 2px 4px)"; export type HookSidebarItem = | string | { label: string; href?: string; onClick?: (e: React.MouseEvent) => void; }; export type HookSidebarProps = Omit, "onChange"> & { items: HookSidebarItem[]; label?: string; value?: number; defaultValue?: number; onChange?: (index: number) => void; color?: string; dashed?: boolean; }; const hrefOf = (item: HookSidebarItem) => typeof item === "string" ? undefined : item.href; const labelOf = (item: HookSidebarItem) => typeof item === "string" ? item : item.label; const Rail = ({ from = 0, y, visible, color, dashed, className, }: { from?: number; y: number | null; visible: boolean; color?: string; dashed: boolean; className?: string; }) => { const reduced = useReducedMotion(); const travel = reduced ? { duration: 0 } : { type: "spring" as const, stiffness: 420, damping: 34, mass: 0.7 }; return ( ); }; export function HookSidebar({ items, label, value, defaultValue = 0, onChange, color = "#FC4C01", dashed = true, className, ...props }: HookSidebarProps) { const pathname = usePathname(); const listRef = useRef(null); const itemRefs = useRef<(HTMLElement | null)[]>([]); const [centers, setCenters] = useState([]); const [internalValue, setInternalValue] = useState(defaultValue); const [hoverIndex, setHoverIndex] = useState(null); const [pointerInside, setPointerInside] = useState(false); const [focusInside, setFocusInside] = useState(false); const routeIndex = items.findIndex((item) => hrefOf(item) === pathname); const activeIndex = value ?? (routeIndex >= 0 ? routeIndex : internalValue); useEffect(() => { const list = listRef.current; if (!list) return; const measure = () => setCenters( itemRefs.current.map((el) => el ? el.offsetTop + el.offsetHeight / 2 : 0, ), ); measure(); const rafId = requestAnimationFrame(measure); const observer = new ResizeObserver(measure); observer.observe(list); return () => { cancelAnimationFrame(rafId); observer.disconnect(); }; }, [items.length]); const activeY = activeIndex < 0 ? null : (centers[activeIndex] ?? null); const hoverY = hoverIndex === null ? null : (centers[hoverIndex] ?? null); const hoverFrom = activeY !== null && hoverY !== null && hoverY <= activeY ? Math.max(0, hoverY - CORNER) : (activeY ?? 0); const select = (index: number) => { if (value === undefined) setInternalValue(index); onChange?.(index); }; return ( ); }