{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "import-wizard",
  "title": "Import Wizard",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-react/blocks/import-wizard/ImportWizard.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { CheckCircle2, ChevronLeft, ChevronRight, FileSpreadsheet, Upload } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Alert } from '@/components/ui/alert'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { SectionCard } from '@/components/ui/section-card'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'\nimport { Stepper } from '@/components/ui/stepper'\n\ntype TargetField = 'Full name' | 'Email' | 'Department' | 'Job title' | 'Start date' | 'Skip'\n\ninterface CsvRow {\n  full_name: string\n  email_address: string\n  dept: string\n  job_title: string\n  start_date: string\n  manager: string\n}\n\nexport interface ImportWizardProps {\n  /** Jump straight to a step (1 Upload · 2 Map · 3 Preview · 4 Result). */\n  initialStep?: number\n  /** Show the upload step with the file already parsed. */\n  initialFileSelected?: boolean\n  /** Override the auto-mapping so demos can show unmapped required fields. */\n  initialMapping?: Record<string, TargetField>\n  className?: string\n}\n\nconst steps = [\n  { id: 1, title: 'Upload' },\n  { id: 2, title: 'Map fields' },\n  { id: 3, title: 'Preview' },\n  { id: 4, title: 'Result' },\n]\n\nconst columns = ['full_name', 'email_address', 'dept', 'job_title', 'start_date', 'manager'] as const\n\nconst defaultMapping: Record<string, TargetField> = {\n  full_name: 'Full name',\n  email_address: 'Email',\n  dept: 'Department',\n  job_title: 'Job title',\n  start_date: 'Start date',\n  manager: 'Skip',\n}\n\nconst confidence: Record<string, 'high' | 'medium'> = {\n  full_name: 'high',\n  email_address: 'high',\n  dept: 'medium',\n  job_title: 'high',\n  start_date: 'medium',\n  manager: 'high',\n}\n\nconst targetOptions: TargetField[] = ['Full name', 'Email', 'Department', 'Job title', 'Start date', 'Skip']\nconst requiredTargets: TargetField[] = ['Full name', 'Email']\n\nconst rows: CsvRow[] = [\n  {\n    full_name: 'Amara Osei',\n    email_address: 'amara@acme.com',\n    dept: 'Engineering',\n    job_title: 'Backend engineer',\n    start_date: '2026-09-01',\n    manager: 'j.weber@acme.com',\n  },\n  {\n    full_name: 'Marcus Lee',\n    email_address: 'marcus.lee@acme.com',\n    dept: 'Design',\n    job_title: 'Product designer',\n    start_date: '2026-09-01',\n    manager: 'a.osei@acme.com',\n  },\n  {\n    full_name: '',\n    email_address: 'no-name@acme.com',\n    dept: 'Sales',\n    job_title: 'Account exec',\n    start_date: '2026-09-08',\n    manager: 'p.nair@acme.com',\n  },\n  {\n    full_name: 'Priya Nair',\n    email_address: 'not-an-email',\n    dept: 'People',\n    job_title: 'Recruiter',\n    start_date: '2026-09-15',\n    manager: 'a.osei@acme.com',\n  },\n  {\n    full_name: 'Jonas Weber',\n    email_address: 'jonas@acme.com',\n    dept: 'Engineering',\n    job_title: 'Eng manager',\n    start_date: '2026-08-24',\n    manager: '',\n  },\n]\n\nexport function ImportWizard({\n  initialStep = 1,\n  initialFileSelected = false,\n  initialMapping,\n  className,\n}: ImportWizardProps) {\n  const [step, setStep] = React.useState(initialStep)\n  const [fileSelected, setFileSelected] = React.useState(initialFileSelected || initialStep > 1)\n  const [mapping, setMapping] = React.useState<Record<string, TargetField>>(initialMapping ?? { ...defaultMapping })\n\n  const mappedTargets: TargetField[] = Object.values(mapping).filter((t) => t !== 'Skip')\n  const missingRequired = requiredTargets.filter((target) => !mappedTargets.includes(target))\n\n  function cellInvalid(row: CsvRow, column: string): boolean {\n    if (mapping[column] === 'Full name') return row.full_name.trim() === ''\n    if (mapping[column] === 'Email') return !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(row.email_address)\n    return false\n  }\n\n  const flaggedCount = rows.filter((row) => columns.some((col) => cellInvalid(row, col))).length\n  const importedCount = 245\n\n  function onStepperInput(value: number) {\n    // Free backward navigation; forward goes through the footer button only.\n    if (value < step && step !== 4) setStep(value)\n  }\n\n  function goNext() {\n    if (step === 1 && !fileSelected) setFileSelected(true)\n    if (step === 2 && missingRequired.length > 0) return\n    if (step < 4) setStep(step + 1)\n  }\n\n  function reset() {\n    setStep(1)\n    setFileSelected(false)\n    setMapping({ ...defaultMapping })\n  }\n\n  return (\n    <SectionCard\n      data-slot=\"import-wizard\"\n      title=\"Import employees\"\n      description=\"Bring your people data in as CSV — map, review, then commit.\"\n      className={className}\n      footer={\n        <div className=\"flex items-center justify-between\">\n          {step > 1 && step < 4 ? (\n            <Button variant=\"ghost\" size=\"sm\" onClick={() => setStep(step - 1)}>\n              <ChevronLeft aria-hidden=\"true\" />\n              Back\n            </Button>\n          ) : (\n            <span></span>\n          )}\n          {step < 4 && (\n            <Button size=\"sm\" disabled={step === 2 && missingRequired.length > 0} onClick={goNext}>\n              {step === 1 ? 'Continue' : step === 2 ? 'Review' : 'Import'}\n              <ChevronRight aria-hidden=\"true\" />\n            </Button>\n          )}\n        </div>\n      }\n    >\n      <Stepper steps={steps} value={step} onValueChange={onStepperInput} className=\"mb-6\" />\n\n      {step === 1 && (\n        <div className=\"space-y-4\">\n          {!fileSelected ? (\n            <button\n              type=\"button\"\n              className=\"border-border hover:border-primary/50 hover:bg-accent/50 focus-visible:ring-ring flex w-full cursor-pointer flex-col items-center gap-2 rounded-lg border border-dashed px-6 py-10 transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n              onClick={() => setFileSelected(true)}\n            >\n              <Upload className=\"text-muted-foreground size-6\" aria-hidden=\"true\" />\n              <span className=\"text-sm font-medium\">Drop your CSV here or browse</span>\n              <span className=\"text-muted-foreground text-xs\">Up to 10 MB · comma or semicolon separated</span>\n            </button>\n          ) : (\n            <div className=\"border-border flex flex-wrap items-center gap-3 rounded-lg border p-4\">\n              <span\n                className=\"bg-muted text-muted-foreground flex size-9 items-center justify-center rounded-md\"\n                aria-hidden=\"true\"\n              >\n                <FileSpreadsheet className=\"size-4\" />\n              </span>\n              <div className=\"min-w-0 flex-1\">\n                <p className=\"truncate text-sm font-medium\">employees.csv</p>\n                <p className=\"text-muted-foreground text-xs\">248 rows · 6 columns · 38 KB</p>\n              </div>\n              <Badge wrap className=\"\" variant=\"success\">\n                Parsed\n              </Badge>\n            </div>\n          )}\n        </div>\n      )}\n\n      {step === 2 && (\n        <div className=\"space-y-4\">\n          {missingRequired.length > 0 && (\n            <Alert\n              icon=\"warning\"\n              title=\"Required fields unmapped\"\n              text={`Map ${missingRequired.join(' and ')} before continuing.`}\n            />\n          )}\n          <ul className=\"divide-y rounded-lg border\">\n            {columns.map((column) => (\n              <li key={column} className=\"flex flex-wrap items-center gap-3 p-3\">\n                <code className=\"w-32 shrink-0 truncate font-mono text-xs\">{column}</code>\n                <ChevronRight className=\"text-muted-foreground size-4 shrink-0\" aria-hidden=\"true\" />\n                <Select\n                  value={mapping[column]}\n                  onValueChange={(value) => setMapping((m) => ({ ...m, [column]: value as TargetField }))}\n                >\n                  <SelectTrigger className=\"flex-1\" size=\"sm\">\n                    <SelectValue />\n                  </SelectTrigger>\n                  <SelectContent>\n                    {targetOptions.map((option) => (\n                      <SelectItem key={option} value={option}>\n                        {option}\n                      </SelectItem>\n                    ))}\n                  </SelectContent>\n                </Select>\n                {mapping[column] !== 'Skip' && (\n                  <Badge wrap className=\"\" variant={confidence[column] === 'high' ? 'success' : 'info'}>\n                    {confidence[column] === 'high' ? 'Auto · high' : 'Auto · medium'}\n                  </Badge>\n                )}\n              </li>\n            ))}\n          </ul>\n        </div>\n      )}\n\n      {step === 3 && (\n        <div className=\"space-y-3\">\n          <div className=\"overflow-x-auto rounded-lg border\">\n            <table className=\"w-full max-w-[640px] min-w-full text-sm\">\n              <thead>\n                <tr className=\"bg-muted/50 border-b\">\n                  {columns.map((column) => (\n                    <th key={column} className=\"px-3 py-2 text-left font-medium\">\n                      {mapping[column] === 'Skip' ? '—' : mapping[column]}\n                    </th>\n                  ))}\n                </tr>\n              </thead>\n              <tbody>\n                {rows.slice(0, 5).map((row, i) => (\n                  <tr key={i} className=\"border-b last:border-b-0\">\n                    {columns.map((column) => (\n                      <td\n                        key={column}\n                        title={cellInvalid(row, column) ? 'This value will be rejected' : undefined}\n                        className={cn(\n                          'px-3 py-2',\n                          cellInvalid(row, column) && 'bg-destructive/10 text-destructive font-medium',\n                        )}\n                      >\n                        {mapping[column] === 'Skip' ? '·' : row[column]}\n                      </td>\n                    ))}\n                  </tr>\n                ))}\n              </tbody>\n            </table>\n          </div>\n          <p className=\"text-muted-foreground text-xs\">\n            Showing first 5 of 248 rows ·{' '}\n            <span className={flaggedCount > 0 ? 'text-warning font-medium' : 'text-success font-medium'}>\n              {importedCount} valid · {flaggedCount} flagged\n            </span>\n          </p>\n        </div>\n      )}\n\n      {step === 4 && (\n        <div className=\"space-y-4 py-4 text-center\">\n          <CheckCircle2 className=\"text-success mx-auto size-10\" aria-hidden=\"true\" />\n          <div>\n            <p className=\"text-lg font-semibold\">{importedCount} records imported</p>\n            <p className=\"text-muted-foreground mt-1 text-sm\">\n              {flaggedCount} rows were skipped — fix them and re-import anytime.\n            </p>\n          </div>\n          <Button variant=\"outline\" size=\"sm\" onClick={reset}>\n            Import another file\n          </Button>\n        </div>\n      )}\n    </SectionCard>\n  )\n}\n",
      "type": "registry:block",
      "target": "~/components/blocks/ImportWizard.tsx"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/alert.json",
    "https://uipkge.dev/r/react/badge.json",
    "https://uipkge.dev/r/react/button.json",
    "https://uipkge.dev/r/react/section-card.json",
    "https://uipkge.dev/r/react/select.json",
    "https://uipkge.dev/r/react/stepper.json"
  ],
  "description": "CSV import flow in a SectionCard: four-step state machine (Upload → Map fields → Preview → Result) with an interactive Stepper, auto-mapped column targets carrying confidence badges, required-field validation that gates the footer action, flagged-cell highlighting in the preview table, and a success/partial result screen. Data is stubbed post-parse — wire the file reader of your choice.",
  "categories": [
    "devops",
    "dashboard",
    "data"
  ]
}