krz/brand-bench

A dynamic brand documentation generator with Ollama integration.

clone: git clone https://gitbay.org/krz/brand-bench.git

main: src/components/PackageSwitcher.tsx · raw

  1import { useState, useEffect } from 'react';
  2import { createPortal } from 'react-dom';
  3import type { PackageSlot } from '../hooks/usePackages';
  4
  5interface Props {
  6  slots: PackageSlot[];
  7  activeId: string;
  8  onSwitch: (id: string) => void;
  9  onCreate: () => void;
 10  onRemove: (id: string) => void;
 11  onRename: (id: string, name: string) => void;
 12  onDuplicate: (id: string) => void;
 13}
 14
 15interface MenuState {
 16  id: string;
 17  top: number;
 18  left: number;
 19}
 20
 21export function PackageSwitcher({ slots, activeId, onSwitch, onCreate, onRemove, onRename, onDuplicate }: Props) {
 22  const [editingId, setEditingId] = useState<string | null>(null);
 23  const [draft, setDraft] = useState('');
 24  const [menu, setMenu] = useState<MenuState | null>(null);
 25
 26  // Close dropdown on any outside click
 27  useEffect(() => {
 28    if (!menu) return;
 29    const handler = () => setMenu(null);
 30    document.addEventListener('click', handler);
 31    return () => document.removeEventListener('click', handler);
 32  }, [menu]);
 33
 34  const openMenu = (id: string, e: React.MouseEvent<HTMLButtonElement>) => {
 35    e.stopPropagation();
 36    if (menu?.id === id) { setMenu(null); return; }
 37    const rect = e.currentTarget.getBoundingClientRect();
 38    setMenu({ id, top: rect.bottom + 4, left: rect.left });
 39  };
 40
 41  const commitRename = (id: string) => {
 42    const name = draft.trim();
 43    if (name) onRename(id, name);
 44    setEditingId(null);
 45  };
 46
 47  return (
 48    <div className="pkg-switcher">
 49      {slots.map(slot => (
 50        <div
 51          key={slot.id}
 52          className={`pkg-tab${slot.id === activeId ? ' active' : ''}`}
 53          onClick={() => slot.id !== activeId && onSwitch(slot.id)}
 54        >
 55          {editingId === slot.id ? (
 56            <input
 57              autoFocus
 58              className="pkg-tab-input"
 59              value={draft}
 60              onChange={e => setDraft(e.target.value)}
 61              onBlur={() => commitRename(slot.id)}
 62              onKeyDown={e => {
 63                if (e.key === 'Enter') commitRename(slot.id);
 64                if (e.key === 'Escape') setEditingId(null);
 65              }}
 66              onClick={e => e.stopPropagation()}
 67            />
 68          ) : (
 69            <span
 70              className="pkg-tab-name"
 71              onDoubleClick={e => {
 72                e.stopPropagation();
 73                setDraft(slot.name);
 74                setEditingId(slot.id);
 75              }}
 76            >
 77              {slot.name}
 78            </span>
 79          )}
 80
 81          <div className="pkg-tab-actions">
 82            <button
 83              className="pkg-tab-menu-btn"
 84              title="Options"
 85              onClick={e => openMenu(slot.id, e)}
 86            >
 87              ···
 88            </button>
 89            {slots.length > 1 && (
 90              <button
 91                className="pkg-tab-close"
 92                title="Remove"
 93                onClick={e => { e.stopPropagation(); onRemove(slot.id); }}
 94              >
 95                ×
 96              </button>
 97            )}
 98          </div>
 99        </div>
100      ))}
101
102      <button className="pkg-new-btn" onClick={onCreate} title="New package">+</button>
103
104      {/* Dropdown rendered in a portal to escape overflow clipping */}
105      {menu && createPortal(
106        <div
107          className="pkg-tab-dropdown"
108          style={{ position: 'fixed', top: menu.top, left: menu.left }}
109          onClick={e => e.stopPropagation()}
110        >
111          <button onClick={() => {
112            const slot = slots.find(s => s.id === menu.id);
113            if (slot) { setDraft(slot.name); setEditingId(slot.id); }
114            setMenu(null);
115          }}>Rename</button>
116          <button onClick={() => { onDuplicate(menu.id); setMenu(null); }}>Duplicate</button>
117          {slots.length > 1 && (
118            <button className="danger" onClick={() => { onRemove(menu.id); setMenu(null); }}>Delete</button>
119          )}
120        </div>,
121        document.body
122      )}
123    </div>
124  );
125}