# Import Export Popover (`import-export-popover`) - SignalOS widget

> Popover combining import/export selects behind one trigger, plus an optional generic secondary action (label/icon/handler) with its own customize surface - not tied to any specific action like print.

- **Version:** 0.1.0
- **Kind:** widget · **Category:** inputs
- **Install:** `npx shadcn@4.1.2 add @signalos/import-export-popover`
- **Registry dependencies (pulled automatically):** @signalos/tokens, @signalos/utils, @signalos/button, @signalos/popover, @signalos/select
- **npm dependencies:** lucide-react@^1.7.0
- **Files installed:** `src/components/widgets/import-export-popover/ImportExportPopover.tsx`, `src/components/widgets/import-export-popover/ImportExportPopover.types.ts`, `src/components/widgets/import-export-popover/ImportExportPopover.constants.ts`, `src/components/widgets/import-export-popover/components/SectionParts.tsx`, `src/components/widgets/import-export-popover/components/PopoverHeader.tsx`, `src/components/widgets/import-export-popover/components/SecondaryActionSection.tsx`, `src/components/widgets/import-export-popover/components/ImportSection.tsx`, `src/components/widgets/import-export-popover/components/ExportSection.tsx`

## Access

This is a private registry: pulling source requires a `SIGNALOS_REGISTRY_TOKEN`
(GitHub fine-grained PAT with read access to the signal-widgets repo) and an
`@signalos` entry in components.json `"registries"`. Previews and this document are public.

## Usage

```tsx
import { ImportExportPopover } from "@/components/widgets/import-export-popover/ImportExportPopover"
```

## Example

```tsx
// Example: the snippet shown as public example code in the catalog.
import { RefreshCw } from "lucide-react"

import { ImportExportPopover } from "@/components/widgets/import-export-popover/ImportExportPopover"

export default function Example() {
  return (
    <ImportExportPopover
      importOptions={[
        { value: "csv", label: "CSV" },
        { value: "json", label: "JSON" },
      ]}
      exportOptions={[
        { value: "pdf", label: "PDF" },
        { value: "xlsx", label: "XLSX" },
      ]}
      onImport={(value) => console.info("import", value)}
      onDownload={({ importValue, exportValue }) =>
        console.info("download", { importValue, exportValue })
      }
      secondaryAction={{
        label: "Sync now",
        icon: <RefreshCw className="size-4" />,
        onRun: async () => {
          await fetch("/api/sync", { method: "POST" })
        },
      }}
    />
  )
}
```

## Props

### `ImportExportOption`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `value` | `string` | yes | - |  |
| `label` | `string` | yes | - |  |

### `ImportExportPopoverProps`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `importOptions` | `ImportExportOption[] \| undefined` | no | - |  |
| `exportOptions` | `ImportExportOption[] \| undefined` | no | - |  |
| `onImport` | `((value: string) => void) \| undefined` | no | - |  |
| `onExport` | `((value: string) => void) \| undefined` | no | - |  |
| `onDownload` | `((params: { importValue?: string \| undefined; exportValue?: string \| undefined; }) => void \| Promise<void>) \| undefined` | no | - |  |
| `disabled` | `boolean \| undefined` | no | - |  |
| `isDownloading` | `boolean \| undefined` | no | - |  |
| `renderExportExtraFields` | `((exportValue: string) => ReactNode) \| undefined` | no | - |  |
| `renderSecondaryActionFields` | `(() => ReactNode) \| undefined` | no | - | Renders a secondary action's own customize surface (e.g. a `CustomizeFieldsPanel`) above that action's button. The widget carries no field vocabulary itself - pass whatever the action needs to render. |
| `secondaryAction` | `{ label: string; icon?: ReactNode; onRun: () => void \| Promise<void>; isRunning?: boolean \| undefined; runningLabel?: string \| undefined; } \| undefined` | no | - | A secondary action shown in its own section (e.g. print, sync, preview). Generic over label/icon so this widget never assumes what the action is. |
| `secondaryActionAsExportOption` | `boolean \| undefined` | no | - | When true, the secondary action is offered as an export option (value `"__secondary-action__"`) instead of its own section. |
| `data-testid` | `string \| undefined` | no | - |  |
| `onOpenChange` | `((open: boolean) => void) \| undefined` | no | - |  |
| `title` | `string \| undefined` | no | - | Trigger label and popover heading. Defaults to "Import & Export", but falls back to "Export" (or "Import") when only one side is wired up for the current render - pass an explicit value to override either way. |
| `description` | `string \| undefined` | no | - | Popover heading description. Defaults based on which of import/export/secondary action are wired up. |
| `icon` | `ReactNode` | no | `an import/export icon based on what's wired up` | Icon shown on the trigger and popover header. |
| `classNames` | `{ trigger?: string \| undefined; triggerIcon?: string \| undefined; triggerChevron?: string \| undefined; popover?: string \| undefined; header?: string \| undefined; headerIcon?: string \| undefined; headerTitle?: string \| undefined; headerDescription?: string \| undefined; sectionHeading?: string \| undefined; secondaryActionButton?: string \| undefined; selectTrigger?: string \| undefined; selectContent?: string \| undefined; selectItem?: string \| undefined; customizeFields?: string \| undefined; exportExtraFields?: string \| undefined; downloadButton?: string \| undefined; } \| undefined` | no | - |  |

## Changelog

# import-export-popover

## 0.1.0

- Initial release: a popover combining import/export selects behind one
  trigger, generic over a `secondaryAction` (label/icon/handler, plus
  `renderSecondaryActionFields` for that action's own customize surface -
  e.g. a `CustomizeFieldsPanel`) instead of a built-in "print" concept.
  `secondaryActionAsExportOption` folds that action into the export select;
  otherwise it gets its own section. Extracted from an informal
  `ImportExportPopover` that hard-coded print vocabulary and cross-imported
  another widget's types without a declared registry dependency.
