"use client"; import { getCurrentWindow } from "@tauri-apps/api/window"; import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { GoPlus } from "react-icons/go"; import { LuChevronLeft, LuChevronRight, LuSearch, LuX } from "react-icons/lu"; import { getCurrentOS } from "@/lib/browser-utils"; import { cn } from "@/lib/utils"; import type { GroupWithCount } from "@/types"; import { Button } from "./ui/button"; import { Input } from "./ui/input"; import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; const HOLD_MS = 150; const DRAG_THRESHOLD_PX = 3; const isTextInputTarget = (target: EventTarget | null): boolean => { if (!(target instanceof Element)) return false; const el = target.closest( "input, select, textarea, [contenteditable=''], [contenteditable='true']", ); return el !== null; }; const ALL_FILTER_ID = "__all__"; interface Props { onCreateProfileDialogOpen: (open: boolean) => void; searchQuery: string; onSearchQueryChange: (query: string) => void; groups: GroupWithCount[]; totalProfiles: number; selectedGroupId: string | null; onGroupSelect: (groupId: string) => void; pageTitle?: string; } const HomeHeader = ({ onCreateProfileDialogOpen, searchQuery, onSearchQueryChange, groups, totalProfiles, selectedGroupId, onGroupSelect, pageTitle, }: Props) => { const { t } = useTranslation(); const [platform, setPlatform] = useState("macos"); useEffect(() => { setPlatform(getCurrentOS()); }, []); const isMacOS = platform === "macos"; const showProfileToolbar = !pageTitle; // Press-and-hold drag: any pixel of the sys-bar becomes a drag handle after // HOLD_MS, but quick clicks still reach buttons/inputs underneath. const holdTimeoutRef = useRef(null); const dragStartRef = useRef<{ x: number; y: number } | null>(null); const dragStartedRef = useRef(false); const activePointerIdRef = useRef(null); const dragRootRef = useRef(null); const clearHold = useCallback(() => { if (holdTimeoutRef.current !== null) { window.clearTimeout(holdTimeoutRef.current); holdTimeoutRef.current = null; } }, []); const beginDrag = useCallback(() => { if (dragStartedRef.current) return; dragStartedRef.current = true; clearHold(); void getCurrentWindow().startDragging(); }, [clearHold]); useEffect(() => { return () => { clearHold(); }; }, [clearHold]); const handlePointerDown = useCallback( (e: React.PointerEvent) => { if (e.button !== 0) return; if (isTextInputTarget(e.target)) return; dragStartedRef.current = false; dragStartRef.current = { x: e.clientX, y: e.clientY }; activePointerIdRef.current = e.pointerId; clearHold(); holdTimeoutRef.current = window.setTimeout(() => { holdTimeoutRef.current = null; beginDrag(); }, HOLD_MS); }, [beginDrag, clearHold], ); const handlePointerMove = useCallback( (e: React.PointerEvent) => { if ( dragStartedRef.current || dragStartRef.current === null || activePointerIdRef.current !== e.pointerId ) { return; } const dx = e.clientX - dragStartRef.current.x; const dy = e.clientY - dragStartRef.current.y; if (Math.hypot(dx, dy) > DRAG_THRESHOLD_PX) { beginDrag(); } }, [beginDrag], ); const handlePointerEnd = useCallback( (e: React.PointerEvent) => { if (activePointerIdRef.current !== e.pointerId) return; clearHold(); dragStartRef.current = null; activePointerIdRef.current = null; dragStartedRef.current = false; }, [clearHold], ); const handleDoubleClick = useCallback( (e: React.MouseEvent) => { if (isTextInputTarget(e.target)) return; if (e.target instanceof Element && e.target.closest("button")) return; clearHold(); void getCurrentWindow().toggleMaximize(); }, [clearHold], ); // Horizontal scroll fades for the group filter strip — when the user // has more groups than fit, the right edge fades to hint at overflow. const groupsScrollRef = useRef(null); const [groupsFadeLeft, setGroupsFadeLeft] = useState(false); const [groupsFadeRight, setGroupsFadeRight] = useState(false); useEffect(() => { const el = groupsScrollRef.current; if (!el) return; const update = () => { setGroupsFadeLeft(el.scrollLeft > 1); setGroupsFadeRight(el.scrollWidth - el.clientWidth - el.scrollLeft > 1); }; update(); el.addEventListener("scroll", update, { passive: true }); const ro = new ResizeObserver(update); ro.observe(el); return () => { el.removeEventListener("scroll", update); ro.disconnect(); }; }, []); const isWindows = platform === "windows"; return ( // biome-ignore lint/a11y/noStaticElementInteractions: titlebar drag surface; the interactive controls inside are real buttons/inputs
{isMacOS && (