# Vector Export Panel

Vector Export Panel [#vector-export-panel]

The **Vector Export Panel** is the counterpart to the [Vector Import Flow](/docs/local-first/vector-import-flow): a record/dimension count line, a row per export format (native JSON with vectors, CSV, JSONL — each with a label, description, and vectors-included / text-only indicator), per-format export actions emitting `onExport(formatId)`, a busy state that disables all actions (with a spinner on the active format), a zero-records disabled state, and an optional last-export banner (format, records, human-readable size, filename).

Preview [#preview]

```tsx
'use client';

import { useState } from 'react';

import {
  VectorExportPanel,
  type ExportFormat,
  type LastExportSummary,
} from '@/components/vector-export-panel';

const FORMATS: ExportFormat[] = [
  {
    id: 'native-json',
    label: 'Native JSON',
    description: 'Full fidelity, re-importable',
    vectors: true,
  },
  {
    id: 'csv',
    label: 'CSV',
    description: 'Spreadsheet-friendly rows',
    vectors: false,
  },
  {
    id: 'jsonl',
    label: 'JSONL',
    description: 'One record per line',
    vectors: false,
  },
];

const BYTES: Record<string, number> = {
  'native-json': 2_842_624,
  csv: 49_664,
  jsonl: 61_440,
};

/**
 * Demo for VectorExportPanel. Fixture-driven with a simulated busy state —
 * activating a format spins its action for a moment, then shows the
 * last-export banner. No network, no model. Wire `onExport` to
 * useImportExport (`exportCSV`, `exportJSONL`) in your app.
 */
export default function VectorExportPanelDemo() {
  const [exporting, setExporting] = useState<string | false>(false);
  const [lastExport, setLastExport] = useState<LastExportSummary | null>(null);

  const handleExport = (formatId: string) => {
    setExporting(formatId);
    setTimeout(() => {
      setExporting(false);
      setLastExport({
        formatId,
        records: 1024,
        bytes: BYTES[formatId] ?? 49_664,
        filename: `vectors.${formatId === 'native-json' ? 'json' : formatId}`,
        at: 'just now',
      });
    }, 1200);
  };

  return (
    <VectorExportPanel
      formats={FORMATS}
      recordCount={1024}
      dimensions={384}
      exporting={exporting}
      lastExport={lastExport}
      onExport={handleExport}
    />
  );
}
```

Installation [#installation]

```bash
npx shadcn@latest add @localmode/ui/local-first/vector-export-panel
```

Data source & dependencies [#data-source--dependencies]

**Data source:** renders the formats/counts/exporting/last-export state you pass and emits `onExport(formatId)` — works with any backend. Recommended producer: `useImportExport` (`exportCSV`, `exportJSONL`) from `@localmode/react` plus a native JSON export (on-device, optional).

* `lucide-react` — icons
* `clsx` + `tailwind-merge` — via the shared `cn()` util (installed automatically as a registry dependency)

Files installed [#files-installed]

* `vector-export-panel.tsx` — the `VectorExportPanel` component
* `lib/utils.ts` — the `cn()` helper (if not already present)

Props [#props]

**VectorExportPanel**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `formats` | `array` | — | **Required.** Export formats to offer, one action per format. |
| `recordCount` | `number` | — | **Required.** Number of records currently in the corpus. Zero disables all actions. |
| `dimensions` | `number` | — | Vector dimensions of the corpus (shown next to the record count). |
| `exporting` | `boolean \| string` | — | Busy state: `true` disables all actions; a format id additionally renders a spinner on that format's action. |
| `lastExport` | `object \| null` | — | Summary of the last completed export (shown as a result banner). |
| `onExport` | `function` | — | **Required.** Fired with the format id when the user activates a format's export action. |
| `disabled` | `boolean` | — | Disables all actions regardless of state. |

**ExportFormat**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` | — | **Required.** Stable format id (passed to `onExport`, e.g. `"native-json"`, `"csv"`, `"jsonl"`). |
| `label` | `string` | — | **Required.** Display label (e.g. "Native JSON"). |
| `description` | `string` | — | One-line description (e.g. "Full fidelity, re-importable"). |
| `vectors` | `boolean` | — | Whether the format includes vectors (renders a "Vectors" / "Text only" indicator when set). |

**LastExportSummary**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `formatId` | `string` | — | **Required.** The format that was exported. |
| `records` | `number` | — | **Required.** Number of records exported. |
| `bytes` | `number` | — | **Required.** Export size in bytes. |
| `filename` | `string` | — | Downloaded filename. |
| `at` | `string` | — | When the export finished (preformatted display string, e.g. "just now"). |

Examples [#examples]

Wiring to export functions [#wiring-to-export-functions]

Works with any export backend — pass a busy format id while the export runs, then a summary when it completes:

```tsx
const { exportCSV, exportJSONL } = useImportExport({ db });
const [exporting, setExporting] = useState<string | false>(false);
const [lastExport, setLastExport] = useState<LastExportSummary | null>(null);

const runExport = async (formatId: string) => {
  setExporting(formatId);
  try {
    const blob = formatId === 'csv' ? await exportCSV() : await exportJSONL();
    downloadBlob(blob, `vectors.${formatId}`);
    setLastExport({ formatId, records: count, bytes: blob.size, filename: `vectors.${formatId}` });
  } finally {
    setExporting(false);
  }
};

<VectorExportPanel
  formats={[
    { id: 'csv', label: 'CSV', vectors: false },
    { id: 'jsonl', label: 'JSONL', vectors: false },
  ]}
  recordCount={count}
  exporting={exporting}
  lastExport={lastExport}
  onExport={runExport}
/>
```

Disabling while other work runs [#disabling-while-other-work-runs]

```tsx
<VectorExportPanel formats={formats} recordCount={count} disabled={isImporting} onExport={runExport} />
```

Customization [#customization]

`exporting` accepts `true` (disable everything) or a format id (also spin that format's action). The vectors-included / text-only indicator renders only when a format sets `vectors`; the zero-records note and disabled state kick in at `recordCount === 0`. The size in the last-export banner is formatted by the local `formatBytes` helper — edit its thresholds or units freely. Styled entirely with shadcn/ui CSS-variable utilities so it inherits your theme — because you own the copied file, every class and threshold is yours to change.