@bernierllc/csv-ui
v0.8.1
Published
React components for CSV import interfaces with drag-and-drop, column mapping, validation, and real-time feedback
Downloads
1,376
Readme
@bernierllc/csv-ui
React components for CSV import interfaces with drag-and-drop, column mapping, validation, and real-time feedback.
Installation
npm install @bernierllc/csv-uiFeatures
- File Upload - Drag-and-drop CSV file upload with validation
- Column Mapping - Visual column-to-field mapping with auto-suggestions
- Data Preview - Interactive data preview with validation highlighting
- Import Wizard - Complete step-by-step import workflow
- Real-time Validation - Live validation feedback with error correction
- Accessibility - WCAG 2.1 AA compliant with full keyboard navigation
- TypeScript - Full TypeScript support with comprehensive type definitions
Quick Start
import { CSVImportWizard } from '@bernierllc/csv-ui';
const schema = {
fields: [
{ name: 'name', type: 'string', required: true },
{ name: 'email', type: 'string', required: true },
{ name: 'age', type: 'number', required: false }
]
};
function MyComponent() {
const handleComplete = (result) => {
console.log('Import completed:', result);
};
return (
<CSVImportWizard
onComplete={handleComplete}
initialSchema={schema}
title="Import Users"
/>
);
}Components
CSVImportWizard
Complete wizard component that handles the entire CSV import flow:
<CSVImportWizard
onComplete={(result) => console.log(result)}
onCancel={() => console.log('cancelled')}
initialSchema={schema}
allowedFileTypes={['.csv', '.txt']}
maxFileSize={10 * 1024 * 1024}
validationEnabled={true}
/>FileUpload
Drag-and-drop file upload component:
<FileUpload
onFileSelect={(file) => console.log(file)}
acceptedTypes={['.csv', '.txt']}
maxSize={5 * 1024 * 1024}
/>ColumnMapper
Visual column mapping interface:
<ColumnMapper
headers={['Name', 'Email', 'Age']}
schema={schema}
mapping={mapping}
onMappingChange={setMapping}
onAutoMap={() => console.log('auto-map')}
/>DataPreview
Interactive data preview with validation:
<DataPreview
data={csvData}
schema={schema}
mapping={mapping}
validationErrors={errors}
maxRows={50}
/>ValidationDisplay
Validation results with error details:
<ValidationDisplay
errors={validationErrors}
showSuggestions={true}
onErrorClick={(error) => console.log(error)}
/>MatchReview
Headless component for operator review of tier === 'review' match candidates produced by @bernierllc/entity-resolver. Renders a side-by-side comparison of the incoming CSV row against the best candidate (with per-field rationale), a selectable list of alternatives, and action buttons (accept / create-new / skip / undo). All UI strings are overridable via the labels prop for full i18n support. Styling is applied only via semantic BEM class names and the className prop — bring your own CSS (Tailwind, modules, etc.).
import { MatchReview } from '@bernierllc/csv-ui';
import { useMatchReview } from '@bernierllc/csv-ui';
import type { MatchReviewQueueItem } from '@bernierllc/csv-ui';
import type { MatchResult } from '@bernierllc/entity-resolver';
// Build queue from entity-resolver output
const queue: MatchReviewQueueItem<User>[] = reviewResults.map((item) => ({
rowIndex: item.rowIndex,
incomingRow: item.incomingRow,
matchResult: item.matchResult, // MatchResult<User> with tier === 'review'
}));
function MatchReviewScreen() {
const { currentItem, currentIndex, totalItems, accept, createNew, skip, undo, canUndo, isComplete } =
useMatchReview({ queue });
if (isComplete) {
// all items decided — pass decisions to your merge pipeline
return <div>Review complete</div>;
}
return currentItem ? (
<MatchReview
item={currentItem}
currentIndex={currentIndex}
totalItems={totalItems}
onAccept={accept}
onCreateNew={createNew}
onSkip={skip}
onUndo={undo}
canUndo={canUndo}
/>
) : null;
}MatchReview labels/i18n override
Pass any subset of the labels prop to override English defaults:
<MatchReview
item={currentItem}
currentIndex={currentIndex}
totalItems={totalItems}
onAccept={accept}
onCreateNew={createNew}
onSkip={skip}
labels={{
heading: 'Überprüfung',
acceptLabel: 'Übernehmen',
createNewLabel: 'Neu anlegen',
skipLabel: 'Überspringen',
undoLabel: 'Rückgängig',
queuePosition: 'Eintrag {current} von {total}',
}}
/>ConflictResolution
Headless component for side-by-side field-level conflict resolution. Given a RecordResolution from @bernierllc/merge-planner, renders a table of conflicting fields with the incoming and existing values and radio buttons for keep-existing, use-incoming, or skip-field. Also includes bulk "Prefer all incoming" / "Prefer all existing" buttons. All UI strings are overridable via labels.
import { ConflictResolution } from '@bernierllc/csv-ui';
import { useConflictResolution } from '@bernierllc/csv-ui';
import type { RecordResolution } from '@bernierllc/merge-planner';
function ConflictScreen({ resolution }: { resolution: RecordResolution<User> }) {
const { decisions, setDecision, bulkPrefer, isComplete, toRecordResolution } =
useConflictResolution({ resolution });
const handleSave = () => {
if (isComplete) {
const resolved = toRecordResolution();
// pass resolved to buildMergePlan(...)
}
};
return (
<>
<ConflictResolution
resolution={resolution}
decisions={decisions}
onDecisionChange={setDecision}
onBulkPrefer={bulkPrefer}
/>
<button onClick={handleSave} disabled={!isComplete}>
Apply
</button>
</>
);
}ConflictResolution labels/i18n override
<ConflictResolution
resolution={resolution}
decisions={decisions}
onDecisionChange={setDecision}
onBulkPrefer={bulkPrefer}
labels={{
heading: 'Konflikte lösen',
keepExistingLabel: 'Bestand behalten',
useIncomingLabel: 'Importwert verwenden',
skipFieldLabel: 'Feld überspringen',
preferAllIncomingLabel: 'Alle importieren',
preferAllExistingLabel: 'Alle behalten',
}}
/>Hooks
useMatchReview
Manages a queue of tier === 'review' MatchResults from @bernierllc/entity-resolver. Tracks operator decisions (accept / create-new / skip) with full undo support. Purely synchronous — no I/O.
const {
currentItem, // MatchReviewQueueItem | undefined
currentIndex, // 1-based position for display
totalItems, // queue length
remaining, // items left (including current)
isComplete, // true when all items are decided
canUndo, // true when undo is available
decisions, // Map<rowIndex, MatchDecision>
accept, // (record: Existing) => void
createNew, // () => void
skip, // () => void
undo, // () => void
} = useMatchReview({ queue });useConflictResolution
Manages per-field FieldDecision choices for a single RecordResolution from @bernierllc/merge-planner. Provides setDecision, bulkPrefer, isComplete, and toRecordResolution(). Purely synchronous — no I/O.
const {
decisions, // Record<string, FieldDecision>
setDecision, // (field: string, decision: FieldDecision) => void
bulkPrefer, // (preference: 'use-incoming' | 'keep-existing') => void
isComplete, // true when all conflict fields have a decision
toRecordResolution, // () => RecordResolution<Existing>
} = useConflictResolution({ resolution, initialDecisions });useCSVImport
Complete CSV import state management:
const {
currentStep,
file,
data,
schema,
mapping,
validationErrors,
isProcessing,
setFile,
setMapping,
nextStep,
startImport
} = useCSVImport({
initialSchema: schema,
onComplete: (result) => console.log(result)
});useColumnMapping
Column mapping utilities:
const {
mapping,
suggestions,
autoMap,
mapColumn,
clearMapping
} = useColumnMapping({
headers: csvHeaders,
schema: importSchema,
onMappingChange: setMapping
});TypeScript
All components are fully typed. Import types as needed:
import type {
ImportSchema,
ColumnMapping,
ValidationError,
ImportResult,
FileUploadProps,
CSVImportWizardProps
} from '@bernierllc/csv-ui';Styling
Components use Tailwind CSS classes. Include Tailwind in your project or provide custom styles.
Accessibility
- Full keyboard navigation support
- Screen reader compatible
- WCAG 2.1 AA compliant
- Focus management and ARIA labels
- High contrast support
License
Copyright (c) 2025 Bernier LLC. All rights reserved.
