SPB Git forge
28commits 1branches 0releases
7.7 MBsize
maindefault branch
10 days agolast push
Python 66.3% TypeScript 22.7% JavaScript 8.6% HTML 1.4% CSS 0.7%
3.2 KB · 75 lines tsx
Raw Blame History
1'use client';2import { Plus, X } from 'lucide-react';3import { useRouter } from 'next/navigation';4import { useEffect, useState } from 'react';5import { clientApi } from '@/lib/client-api';6import { routes } from '@/lib/site';7import type { Suggestion } from '@/lib/types';89/** Add/remove companies on /company/compare via `/search/suggest` (2–6 slugs in the URL). */10export function ComparePicker({ slugs, names }: { slugs: string[]; names: Record<string, string> }) {11  const router = useRouter();12  const [q, setQ] = useState('');13  const [items, setItems] = useState<Suggestion[]>([]);14  useEffect(() => {15    if (q.trim().length < 1) {16      setItems([]);17      return;18    }19    const ctrl = new AbortController();20    const t = setTimeout(() => {21      clientApi22        .suggest(q.trim(), ctrl.signal)23        .then((r) => setItems(r.items.filter((s) => s.kind === 'company')))24        .catch(() => setItems([]));25    }, 120);26    return () => {27      clearTimeout(t);28      ctrl.abort();29    };30  }, [q]);31  const slugOf = (href: string) => href.replace(/^\/company\//, '').split('?')[0] ?? '';32  const add = (s: string) => {33    if (!s || slugs.includes(s) || slugs.length >= 6) return;34    router.push(routes.compare([...slugs, s]));35    setQ('');36    setItems([]);37  };38  const remove = (s: string) => router.push(routes.compare(slugs.filter((x) => x !== s)));39  return (40    <div className="flex flex-wrap items-center gap-2" data-compare-picker>41      {slugs.map((s) => (42        <span key={s} className="inline-flex h-10 items-center gap-1 border border-rule bg-surface pl-2.5 pr-0.5 text-sm">43          {names[s] ?? s}44          <button type="button" onClick={() => remove(s)} className="flex size-10 items-center justify-center text-ink-3 hover:text-danger" aria-label={`Remove ${names[s] ?? s}`}>45            <X className="size-3.5" aria-hidden />46          </button>47        </span>48      ))}49      {slugs.length < 6 && (50        <div className="relative">51          <label className="sr-only" htmlFor="compare-add">52            Add a company53          </label>54          <span className="flex min-h-10 items-center gap-1 border border-rule-strong bg-surface px-2">55            <Plus className="size-4 text-ink-3" aria-hidden />56            <input id="compare-add" value={q} onChange={(e) => setQ(e.target.value)} placeholder="Add company…" className="h-10 w-40 bg-transparent text-sm focus:outline-none" autoComplete="off" data-compare-input onKeyDown={(e) => e.key === 'Enter' && items[0] && add(slugOf(items[0].href))} />57          </span>58          {items.length > 0 && (59            <ul className="panel absolute left-0 top-full z-20 mt-1 w-64 py-1 shadow-lg" role="listbox">60              {items.slice(0, 8).map((s) => (61                <li key={s.href}>62                  <button type="button" onClick={() => add(slugOf(s.href))} className="flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-surface-2" role="option" aria-selected={false}>63                    <span className="flex-1 truncate">{s.label}</span>64                    <span className="truncate text-xs text-ink-3">{s.sublabel}</span>65                  </button>66                </li>67              ))}68            </ul>69          )}70        </div>71      )}72    </div>73  );74}75