Column mapper
ProMatch file columns to the object's fields. Exact name matches, the model's suggestions (with how sure it is) and your own choices are marked apart, so a guess never passes for a match. Required fields left unmapped, and two columns sent to one field, are flagged before anything loads.
Preview
Install
With the Candor CLI
First time? Get started sets up the registry and theme. Or with shadcn directly: npx shadcn@latest add @candor/column-mapper
Brings in: data, locale, select, utils · npm: lucide-react@^1.67.0
Usage
"use client"
import * as React from "react"
import { ColumnMapper } from "@/components/column-mapper"
import type { ColumnMapping, ImportColumn, TargetField } from "@/lib/data"
const columns: ImportColumn[] = [
{ header: "Flag", samples: ["new-billing-page", "dark-mode", "fast-search"] },
{ header: "Repo", samples: ["acme/web", "acme/web", "acme/api"] },
{ header: "Owner Email", samples: ["dana@acme.example", "jonas@acme", "mei@acme.example"] },
{ header: "Rollout %", samples: ["25", "100", "n/a"] },
{ header: "Notes", samples: ["Remove after Q4", "", "Behind a beta"] },
]
const fields: TargetField[] = [
{ name: "key", label: "Flag key", type: "text", required: true },
{ name: "repository", label: "Repository", type: "choice" },
{ name: "owner_id", label: "Owner", type: "id", required: true },
{ name: "rollout", label: "Rollout", type: "number" },
{ name: "description", label: "Description", type: "text" },
]
// Keel's first pass: one exact match, its own suggestions with confidence, and a column it couldn't place.
const suggested: ColumnMapping[] = [
{ header: "Flag", field: "key", by: "model", confidence: 0.91 },
{ header: "Repo", field: "repository", by: "match" },
{ header: "Owner Email", field: null, by: "model", confidence: 0 },
{ header: "Rollout %", field: "rollout", by: "model", confidence: 0.82 },
{ header: "Notes", field: "description", by: "model", confidence: 0.58 },
]
export default function Example() {
const [mapping, setMapping] = React.useState(suggested)
return <ColumnMapper className="max-w-2xl" columns={columns} fields={fields} mapping={mapping} onChange={setMapping} />
}<ColumnMapper>
| Prop | Type | Default | Description |
|---|---|---|---|
| columnsrequired | ImportColumn[] | — | The file's columns, with sample values. |
| fieldsrequired | TargetField[] | — | The fields a column can map to; required ones are marked and checked. |
| mappingrequired | ColumnMapping[] | — | The current match for each column, and who made it: exact name, the model (with confidence) or you. |
| onChangerequired | (mapping: ColumnMapping[]) => void | — | Called with the whole new mapping when a person changes a column's field. |
| className | string | undefined | — | Extra classes for the outer element. |
Strings
The fixed words, in English by default. Change any of them under columnMapper in <LocaleProvider strings={{ … }}>; the table is exported as columnMapperStrings.
| Key | English |
|---|---|
| mapColumns | Map columns |
| mapped | {mapped} of {total} mapped |
| suggestedCount | {count} suggested |
| requiredMissing | Required and not mapped: {fields} |
| noValues | No values |
| fieldFor | Field for {column} |
| dontImport | Don’t import |
| clash | Another column also maps here |
| suggested | Suggested · {percent}% sure |
| nameMatches | Name matches |
| yourChoice | Your choice |
| skipped | Skipped |