From 580e798e16346eb894eb899edac82d2efdf40adc Mon Sep 17 00:00:00 2001 From: Christian Cleberg Date: Sat, 18 Apr 2026 22:32:53 -0500 Subject: initial commit --- src/components/PackageSwitcher.tsx | 125 +++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 src/components/PackageSwitcher.tsx (limited to 'src/components/PackageSwitcher.tsx') diff --git a/src/components/PackageSwitcher.tsx b/src/components/PackageSwitcher.tsx new file mode 100644 index 0000000..38c1967 --- /dev/null +++ b/src/components/PackageSwitcher.tsx @@ -0,0 +1,125 @@ +import { useState, useEffect } from 'react'; +import { createPortal } from 'react-dom'; +import type { PackageSlot } from '../hooks/usePackages'; + +interface Props { + slots: PackageSlot[]; + activeId: string; + onSwitch: (id: string) => void; + onCreate: () => void; + onRemove: (id: string) => void; + onRename: (id: string, name: string) => void; + onDuplicate: (id: string) => void; +} + +interface MenuState { + id: string; + top: number; + left: number; +} + +export function PackageSwitcher({ slots, activeId, onSwitch, onCreate, onRemove, onRename, onDuplicate }: Props) { + const [editingId, setEditingId] = useState(null); + const [draft, setDraft] = useState(''); + const [menu, setMenu] = useState(null); + + // Close dropdown on any outside click + useEffect(() => { + if (!menu) return; + const handler = () => setMenu(null); + document.addEventListener('click', handler); + return () => document.removeEventListener('click', handler); + }, [menu]); + + const openMenu = (id: string, e: React.MouseEvent) => { + e.stopPropagation(); + if (menu?.id === id) { setMenu(null); return; } + const rect = e.currentTarget.getBoundingClientRect(); + setMenu({ id, top: rect.bottom + 4, left: rect.left }); + }; + + const commitRename = (id: string) => { + const name = draft.trim(); + if (name) onRename(id, name); + setEditingId(null); + }; + + return ( +
+ {slots.map(slot => ( +
slot.id !== activeId && onSwitch(slot.id)} + > + {editingId === slot.id ? ( + setDraft(e.target.value)} + onBlur={() => commitRename(slot.id)} + onKeyDown={e => { + if (e.key === 'Enter') commitRename(slot.id); + if (e.key === 'Escape') setEditingId(null); + }} + onClick={e => e.stopPropagation()} + /> + ) : ( + { + e.stopPropagation(); + setDraft(slot.name); + setEditingId(slot.id); + }} + > + {slot.name} + + )} + +
+ + {slots.length > 1 && ( + + )} +
+
+ ))} + + + + {/* Dropdown rendered in a portal to escape overflow clipping */} + {menu && createPortal( +
e.stopPropagation()} + > + + + {slots.length > 1 && ( + + )} +
, + document.body + )} +
+ ); +} -- cgit v1.2.3