# CSV Import Kit (`csv-import-kit`) - SignalOS widget

> Generic modal shell and building blocks for a CSV bulk-import flow: drop zone, review-mode toggles, banners, success state, and a labeled select for review-row fields.

- **Version:** 0.1.1
- **Kind:** widget · **Category:** inputs
- **Install:** `npx shadcn@4.1.2 add @signalos/csv-import-kit`
- **Registry dependencies (pulled automatically):** @signalos/tokens, @signalos/utils, @signalos/button, @signalos/switch, @signalos/select, @signalos/confirm-dialog
- **npm dependencies:** lucide-react@^1.7.0
- **Files installed:** `src/components/widgets/csv-import-kit/CsvImportKit.tsx`, `src/components/widgets/csv-import-kit/CsvImportKit.types.ts`, `src/components/widgets/csv-import-kit/CsvImportKit.constants.ts`, `src/components/widgets/csv-import-kit/CsvImportKit.utils.ts`, `src/components/widgets/csv-import-kit/components/CsvImportKitHeader.tsx`, `src/components/widgets/csv-import-kit/components/CsvImportKitFooter.tsx`, `src/components/widgets/csv-import-kit/components/CsvImportKitDropZone.tsx`, `src/components/widgets/csv-import-kit/components/CsvImportKitSelectedFile.tsx`, `src/components/widgets/csv-import-kit/components/CsvImportKitBanner.tsx`, `src/components/widgets/csv-import-kit/components/CsvImportKitSuccessState.tsx`, `src/components/widgets/csv-import-kit/components/CsvImportKitModeToggle.tsx`, `src/components/widgets/csv-import-kit/components/CsvImportKitUpdatesToggle.tsx`, `src/components/widgets/csv-import-kit/components/CsvImportKitValuesBanner.tsx`, `src/components/widgets/csv-import-kit/components/CsvImportKitSelect.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 { CsvImportKit } from "@/components/widgets/csv-import-kit/CsvImportKit"
```

## Example

```tsx
// Example: a minimal CSV import flow - upload, then a success state.
import { useState } from "react"

import { CsvImportKitDropZone } from "@/components/widgets/csv-import-kit/components/CsvImportKitDropZone"
import { CsvImportKitSuccessState } from "@/components/widgets/csv-import-kit/components/CsvImportKitSuccessState"
import { CsvImportKit } from "@/components/widgets/csv-import-kit/CsvImportKit"

export default function Example() {
  const [open, setOpen] = useState(true)
  const [file, setFile] = useState<File | null>(null)

  return (
    <CsvImportKit
      open={open}
      onClose={() => setOpen(false)}
      title="Import Users"
      description="Add multiple users from a CSV file."
    >
      {file ? (
        <CsvImportKitSuccessState
          title="Import completed successfully"
          description={`${file.name} was imported successfully.`}
          onReset={() => setFile(null)}
        />
      ) : (
        <CsvImportKitDropZone
          isProcessing={false}
          hint="Required: full_name, email, role"
          onFile={setFile}
        />
      )}
    </CsvImportKit>
  )
}
```

## Props

### `CsvImportKitProps`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `open` | `boolean` | yes | - | Whether the modal is open. Renders nothing when `false`. |
| `onClose` | `() => void` | yes | - | Fired to request the modal close immediately (no dirty-close guard). |
| `title` | `ReactNode` | yes | - | Modal title, e.g. "Import Users". |
| `description` | `ReactNode` | no | - | Modal subtitle shown under the title. |
| `isWide` | `boolean \| undefined` | no | `false` | Widens the modal for wide content such as a review grid. |
| `isSubmitting` | `boolean \| undefined` | no | `false` | Disables the close/expand controls while a submission is in flight. |
| `isDirty` | `boolean \| undefined` | no | `false` | When `true`, closing the modal first asks the user to confirm via the built-in discard-changes dialog instead of closing immediately. |
| `discardTitle` | `string \| undefined` | no | `"Discard unsaved changes?"` |  |
| `discardDescription` | `ReactNode` | no | `"Your changes will be lost."` |  |
| `discardConfirmLabel` | `ReactNode` | no | `"Discard changes"` |  |
| `discardCancelLabel` | `ReactNode` | no | `"Keep editing"` |  |
| `children` | `ReactNode` | no | - | Step content rendered in the scrollable body. |
| `footer` | `ReactNode` | no | - | Sticky footer content, e.g. `CsvImportKitFooter`. Omit to render no footer. |
| `className` | `string \| undefined` | no | - | Extra classes merged onto the root overlay element. |
| `data-testid` | `string \| undefined` | no | - | Test identifier rendered as `data-testid` on the dialog element. |

### `CsvImportKitDropZoneProps`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `title` | `string \| undefined` | no | `"Upload your CSV file"` |  |
| `description` | `string \| undefined` | no | `"Drag and drop or browse from your computer"` |  |
| `hint` | `string \| undefined` | no | - | Short hint about required/optional columns, shown under the drop area. |
| `isProcessing` | `boolean` | yes | - | Disables the drop zone and shows a processing indicator. |
| `onFile` | `(file: File) => void` | yes | - | Fired with the dropped or browsed file once its extension passes the `.csv` check. |
| `onInvalidFile` | `((reason: string) => void) \| undefined` | no | - | Fired instead of `onFile` when the selected file fails the `.csv` extension check. |
| `onDownloadTemplate` | `(() => void) \| undefined` | no | - | Renders a "Download template" action when provided. |
| `formatNote` | `{ title: string; description: ReactNode; } \| undefined` | no | - | Expands the template action into a labeled note above the drop zone. |
| `className` | `string \| undefined` | no | - |  |
| `data-testid` | `string \| undefined` | no | - |  |

### `CsvImportKitSelectedFileProps`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `file` | `File` | yes | - | The selected file, used for its name only. |
| `onRemove` | `() => void` | yes | - | Fired when the remove action is activated. |
| `fileTypeLabel` | `string \| undefined` | no | `"CSV file"` | Label shown under the file name. |
| `className` | `string \| undefined` | no | - |  |
| `data-testid` | `string \| undefined` | no | - |  |

### `CsvImportKitBannerTone`

```ts
export type CsvImportKitBannerTone = "error" | "warning"
```

### `CsvImportKitBannerProps`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `message` | `ReactNode` | yes | - | Body message. |
| `onDismiss` | `() => void` | yes | - | Fired when the dismiss action is activated. |
| `tone` | `CsvImportKitBannerTone \| undefined` | no | `"error"` |  |
| `title` | `ReactNode` | no | - | Overrides the tone's default heading. |
| `className` | `string \| undefined` | no | - |  |
| `data-testid` | `string \| undefined` | no | - |  |

### `CsvImportKitSuccessStat`

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

### `CsvImportKitSuccessStateProps`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `title` | `ReactNode` | yes | - |  |
| `description` | `ReactNode` | no | - |  |
| `onReset` | `() => void` | yes | - | Fired when the reset action is activated. |
| `resetLabel` | `ReactNode` | no | `"Upload another file"` |  |
| `stats` | `CsvImportKitSuccessStat[] \| undefined` | no | - | Optional stat tiles summarizing the completed import. |
| `className` | `string \| undefined` | no | - |  |
| `data-testid` | `string \| undefined` | no | - |  |

### `CsvImportKitFooterProps`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `onCancel` | `() => void` | yes | - | Fired when the cancel action is activated. |
| `onSubmit` | `() => void` | yes | - | Fired when the submit action is activated. |
| `isSubmitting` | `boolean` | yes | - | Disables cancel and shows a spinner + `submittingLabel` on submit. |
| `disabled` | `boolean` | yes | - | Disables the submit action independent of `isSubmitting`. |
| `submitLabel` | `ReactNode` | yes | - | Submit action label when idle. |
| `cancelLabel` | `ReactNode` | no | `"Cancel"` |  |
| `submittingLabel` | `ReactNode` | no | `"Importing..."` | Submit action label while `isSubmitting`. |
| `className` | `string \| undefined` | no | - |  |
| `data-testid` | `string \| undefined` | no | - |  |

### `CsvImportKitReviewMode`

```ts
export type CsvImportKitReviewMode = "fix" | "skip"
```

### `CsvImportKitModeOption`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `value` | `CsvImportKitReviewMode` | yes | - |  |
| `title` | `ReactNode` | yes | - |  |
| `description` | `ReactNode` | yes | - |  |

### `CsvImportKitModeToggleProps`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `mode` | `CsvImportKitReviewMode` | yes | - |  |
| `onChange` | `(mode: CsvImportKitReviewMode) => void` | yes | - |  |
| `options` | `[CsvImportKitModeOption, CsvImportKitModeOption]` | yes | - | The two options rendered, in order. The caller composes their own copy (e.g. counts of invalid/existing rows) rather than the toggle knowing about review-row shapes. |
| `ariaLabel` | `string \| undefined` | no | `"How to handle rows with errors"` | Accessible label for the radio group. |
| `disabled` | `boolean \| undefined` | no | - |  |
| `className` | `string \| undefined` | no | - |  |
| `data-testid` | `string \| undefined` | no | - |  |

### `CsvImportKitUpdatesToggleProps`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `title` | `ReactNode` | yes | - | Heading, e.g. "Also update 3 existing records". |
| `description` | `ReactNode` | yes | - | Helper copy shown under the heading; typically differs by `checked`. |
| `checked` | `boolean` | yes | - |  |
| `onCheckedChange` | `(checked: boolean) => void` | yes | - |  |
| `icon` | `ReactNode` | no | - | Icon rendered in the leading badge. Omit for no icon. |
| `disabled` | `boolean \| undefined` | no | - |  |
| `className` | `string \| undefined` | no | - |  |
| `data-testid` | `string \| undefined` | no | - |  |

### `CsvImportKitValuesBannerProps`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `targetIds` | `readonly string[]` | yes | - | Ids of the rows missing a value, e.g. row ids with a blank password cell. |
| `title` | `ReactNode` | yes | - | Heading, e.g. "3 rows need a password". Recomputed by the caller as `targetIds` changes. |
| `description` | `ReactNode` | yes | - | Helper copy explaining what the action does. |
| `actionLabel` | `ReactNode` | yes | - | Label for the action button when idle, e.g. "Generate all". |
| `icon` | `ReactNode` | no | - | Icon rendered in the leading badge and on the action button. |
| `onGenerate` | `(ids: readonly string[]) => void \| Promise<void>` | yes | - | Produces the value for each target id and applies them in one update. Deliberately synchronous-per-id, single async apply: filling values in a loop against a single-row update callback tends to revalidate against a stale row list, so this hands back every id/value pair at once. |
| `disabled` | `boolean \| undefined` | no | - |  |
| `className` | `string \| undefined` | no | - |  |
| `data-testid` | `string \| undefined` | no | - |  |

### `CsvImportKitSelectOption`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `value` | `string` | yes | - | Unique option value. |
| `label` | `string \| undefined` | no | - | Rendered label. Falls back to `value` when omitted. |
| `source` | `string \| undefined` | no | - | Short badge shown after the label, e.g. "from CSV". |

### `CsvImportKitSelectProps`

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `value` | `string \| undefined` | no | - |  |
| `onChange` | `(value: string) => void` | yes | - |  |
| `options` | `readonly CsvImportKitSelectOption[]` | yes | - | Options in display order. De-duplicated by `value` (case-insensitive), first occurrence wins. |
| `placeholder` | `string \| undefined` | no | `"Select value"` |  |
| `clearLabel` | `string \| undefined` | no | - | Rendered as a value-less option and used as the "cleared" placeholder. Omit to require a value. |
| `error` | `string \| undefined` | no | - |  |
| `disabled` | `boolean \| undefined` | no | - |  |
| `className` | `string \| undefined` | no | - |  |
| `data-testid` | `string \| undefined` | no | - |  |

## Changelog

# csv-import-kit

## 0.1.1

- Initial release: a generic modal shell and building-block set for a CSV
  bulk-import flow - drop zone, selected-file chip, error/warning banner,
  success state, sticky footer, fix/skip mode toggle, apply-updates toggle,
  bulk-fill-blank-field banner, and a labeled/deduplicated select for review
  rows - plus CSV escaping/export and secure temporary-value helpers.
