available-elements-panel.tsx 3.62 KB
import React from 'react';
import { Lock, Unlock, X } from 'lucide-react';
import { cn } from '../../ui/utils';
import type { LabelElement } from '../../../types/labelTemplate';
import {
  elementEditorDisplayName,
  readElementPositionLocked,
  sortTemplateElementsForDisplay,
} from '../../../types/labelTemplate';

interface AvailableElementsPanelProps {
  elements: LabelElement[];
  selectedId: string | null;
  onSelect: (id: string) => void;
  onTogglePositionLock: (id: string, locked: boolean) => void;
  onClose: () => void;
}

export function AvailableElementsPanel({
  elements,
  selectedId,
  onSelect,
  onTogglePositionLock,
  onClose,
}: AvailableElementsPanelProps) {
  const sorted = sortTemplateElementsForDisplay(elements);

  return (
    <div className="absolute left-0 top-0 z-20 flex h-full w-56 flex-col border-r border-gray-200 bg-white shadow-lg">
      <div className="flex shrink-0 items-center justify-between border-b border-gray-200 px-3 py-2">
        <h3 className="text-sm font-semibold text-gray-800">Available Elements</h3>
        <button
          type="button"
          onClick={onClose}
          className="rounded p-1 text-gray-500 hover:bg-gray-100 hover:text-gray-800"
          title="Close"
          aria-label="Close Available Elements"
        >
          <X className="h-4 w-4" />
        </button>
      </div>
      <div className="min-h-0 flex-1 overflow-y-auto overscroll-contain py-1">
        {sorted.length === 0 ? (
          <p className="px-3 py-4 text-xs text-gray-500">No elements on this label yet.</p>
        ) : (
          <ul className="space-y-0.5 px-1">
            {sorted.map((el) => {
              const locked = readElementPositionLocked(el);
              const isSelected = selectedId === el.id;
              const label = elementEditorDisplayName(el, elements);
              return (
                <li key={el.id}>
                  <div
                    className={cn(
                      'flex items-center gap-1 rounded-md pr-1 transition-colors',
                      isSelected ? 'bg-blue-50' : 'hover:bg-gray-50',
                    )}
                  >
                    <button
                      type="button"
                      onClick={() => onSelect(el.id)}
                      className={cn(
                        'min-w-0 flex-1 truncate px-2 py-2 text-left text-xs',
                        isSelected ? 'font-medium text-blue-900' : 'text-gray-800',
                      )}
                      title={label}
                    >
                      {label}
                    </button>
                    <button
                      type="button"
                      onClick={(e) => {
                        e.stopPropagation();
                        onTogglePositionLock(el.id, !locked);
                      }}
                      className={cn(
                        'shrink-0 rounded p-1.5 transition-colors',
                        locked
                          ? 'text-gray-700 hover:bg-gray-200'
                          : 'text-gray-400 hover:bg-gray-100 hover:text-gray-700',
                      )}
                      title={locked ? 'Unlock position' : 'Lock position'}
                      aria-label={locked ? 'Unlock position' : 'Lock position'}
                    >
                      {locked ? (
                        <Lock className="h-3.5 w-3.5" />
                      ) : (
                        <Unlock className="h-3.5 w-3.5" />
                      )}
                    </button>
                  </div>
                </li>
              );
            })}
          </ul>
        )}
      </div>
    </div>
  );
}