@expresscsv/react 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +384 -240
- package/dist/index.d.mts +1 -10
- package/package.json +2 -2
- package/dist/.dts/CSVImporter.d.ts +0 -27
- package/dist/.dts/index.d.ts +0 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ExpressCSV
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @expresscsv/react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@expresscsv/react)
|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
React hook for embedding the [ExpressCSV](https://expresscsv.com) CSV import widget. Wraps [`@expresscsv/sdk`](https://www.npmjs.com/package/@expresscsv/sdk) with automatic lifecycle management and reactive state.
|
|
4
7
|
|
|
5
8
|
## Installation
|
|
6
9
|
|
|
@@ -15,47 +18,45 @@ npm install @expresscsv/react
|
|
|
15
18
|
yarn add @expresscsv/react
|
|
16
19
|
```
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
The React SDK provides a `useExpressCSV` hook for easy integration into your React components.
|
|
21
|
+
> **Peer dependency:** React 18+
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
The React SDK provides a `useExpressCSV` hook for easy integration:
|
|
23
|
+
## Quick Start
|
|
25
24
|
|
|
26
25
|
```tsx
|
|
27
|
-
import { useExpressCSV, x
|
|
26
|
+
import { useExpressCSV, x } from "@expresscsv/react";
|
|
28
27
|
|
|
29
28
|
const schema = x.row({
|
|
30
|
-
name: x.string().label(
|
|
31
|
-
email: x.string().email().label(
|
|
32
|
-
age: x.number().label(
|
|
29
|
+
name: x.string().label("Full Name"),
|
|
30
|
+
email: x.string().email().label("Email Address"),
|
|
31
|
+
age: x.number().label("Age").min(18),
|
|
33
32
|
});
|
|
34
33
|
|
|
35
34
|
function App() {
|
|
36
|
-
const { open, isOpen
|
|
35
|
+
const { open, isOpen } = useExpressCSV({
|
|
37
36
|
schema,
|
|
38
|
-
|
|
37
|
+
publishableKey: "your-publishable-key",
|
|
39
38
|
importIdentifier: "user-import",
|
|
39
|
+
title: "Import Users",
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
const handleImport = () => {
|
|
43
43
|
open({
|
|
44
|
-
chunkSize:
|
|
44
|
+
chunkSize: 500,
|
|
45
45
|
onData: async (chunk, next) => {
|
|
46
|
-
console.log(
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
console.log(
|
|
47
|
+
`Chunk ${chunk.currentChunkIndex + 1}/${chunk.totalChunks}`
|
|
48
|
+
);
|
|
49
|
+
console.log("Records:", chunk.records);
|
|
49
50
|
next();
|
|
50
51
|
},
|
|
51
52
|
onComplete: () => {
|
|
52
|
-
console.log(
|
|
53
|
+
console.log("All chunks processed");
|
|
53
54
|
},
|
|
54
55
|
onCancel: () => {
|
|
55
|
-
console.log(
|
|
56
|
+
console.log("User cancelled");
|
|
56
57
|
},
|
|
57
58
|
onError: (error) => {
|
|
58
|
-
console.error(
|
|
59
|
+
console.error("Import error:", error);
|
|
59
60
|
},
|
|
60
61
|
});
|
|
61
62
|
};
|
|
@@ -68,226 +69,299 @@ function App() {
|
|
|
68
69
|
}
|
|
69
70
|
```
|
|
70
71
|
|
|
71
|
-
|
|
72
|
+
Your `publishableKey` is available from the [ExpressCSV dashboard](https://expresscsv.com). Two key types are available: **production** keys for live usage, and **dev/testing** keys that provide unlimited test imports.
|
|
73
|
+
|
|
74
|
+
## Preloading
|
|
72
75
|
|
|
73
|
-
By default
|
|
76
|
+
By default the widget preloads in a hidden iframe so it appears instantly when `open()` is called:
|
|
74
77
|
|
|
75
78
|
```tsx
|
|
76
|
-
|
|
79
|
+
const { open } = useExpressCSV({
|
|
80
|
+
schema,
|
|
81
|
+
publishableKey: "your-publishable-key",
|
|
82
|
+
importIdentifier: "user-import",
|
|
83
|
+
});
|
|
77
84
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
85
|
+
// Widget displays instantly
|
|
86
|
+
const handleImport = () => {
|
|
87
|
+
open({
|
|
88
|
+
onData: (chunk, next) => {
|
|
89
|
+
console.log("Records:", chunk.records);
|
|
90
|
+
next();
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
To disable preloading (there will be a brief loading screen instead):
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
const { open } = useExpressCSV({
|
|
100
|
+
schema,
|
|
101
|
+
publishableKey: "your-publishable-key",
|
|
102
|
+
importIdentifier: "user-import",
|
|
103
|
+
preload: false,
|
|
81
104
|
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Theming and Styling
|
|
108
|
+
|
|
109
|
+
Customize the widget's appearance with the `theme`, `colorMode`, `customCSS`, and `fonts` options.
|
|
110
|
+
|
|
111
|
+
### Theme
|
|
112
|
+
|
|
113
|
+
Use the `theme` option to override CSS variables (colors, radius, typography). Pass either a single theme (applies to both light and dark) or a dual-mode theme with separate light/dark values:
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
import { useExpressCSV, x, type ECSVTheme } from "@expresscsv/react";
|
|
117
|
+
|
|
118
|
+
// Single theme (both modes)
|
|
119
|
+
const theme: ECSVTheme = {
|
|
120
|
+
primary: "#4F46E5",
|
|
121
|
+
"primary-foreground": "#ffffff",
|
|
122
|
+
background: "#ffffff",
|
|
123
|
+
foreground: "#0f172a",
|
|
124
|
+
border: "#e5e7eb",
|
|
125
|
+
ring: "#A5B4FC",
|
|
126
|
+
radius: "0.5rem",
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Dual-mode (light and dark)
|
|
130
|
+
const dualTheme: ECSVTheme = {
|
|
131
|
+
modes: {
|
|
132
|
+
light: {
|
|
133
|
+
primary: "#4F46E5",
|
|
134
|
+
background: "#ffffff",
|
|
135
|
+
foreground: "#0f172a",
|
|
136
|
+
},
|
|
137
|
+
dark: {
|
|
138
|
+
primary: "#a5b4fc",
|
|
139
|
+
background: "#09090b",
|
|
140
|
+
foreground: "#fafafa",
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
};
|
|
82
144
|
|
|
83
145
|
function App() {
|
|
84
|
-
// Preload is enabled by default
|
|
85
146
|
const { open } = useExpressCSV({
|
|
86
147
|
schema,
|
|
87
|
-
|
|
148
|
+
publishableKey: "your-publishable-key",
|
|
88
149
|
importIdentifier: "user-import",
|
|
150
|
+
theme,
|
|
89
151
|
});
|
|
152
|
+
// ...
|
|
153
|
+
}
|
|
154
|
+
```
|
|
90
155
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
156
|
+
Theme variables (light mode defaults):
|
|
157
|
+
|
|
158
|
+
| Variable | Default |
|
|
159
|
+
|---|---|
|
|
160
|
+
| `radius` | `0.625rem` |
|
|
161
|
+
| `background` | `oklch(1 0 0)` |
|
|
162
|
+
| `foreground` | `oklch(0.145 0 0)` |
|
|
163
|
+
| `card` | `oklch(1 0 0)` |
|
|
164
|
+
| `card-foreground` | `oklch(0.145 0 0)` |
|
|
165
|
+
| `popover` | `oklch(1 0 0)` |
|
|
166
|
+
| `popover-foreground` | `oklch(0.145 0 0)` |
|
|
167
|
+
| `primary` | `oklch(0.205 0 0)` |
|
|
168
|
+
| `primary-foreground` | `oklch(0.985 0 0)` |
|
|
169
|
+
| `secondary` | `oklch(0.97 0 0)` |
|
|
170
|
+
| `secondary-foreground` | `oklch(0.205 0 0)` |
|
|
171
|
+
| `muted` | `oklch(0.97 0 0)` |
|
|
172
|
+
| `muted-foreground` | `oklch(0.556 0 0)` |
|
|
173
|
+
| `accent` | `oklch(0.7 0.2 145)` |
|
|
174
|
+
| `accent-foreground` | `oklch(0.985 0 0)` |
|
|
175
|
+
| `destructive` | `oklch(0.577 0.245 27.325)` |
|
|
176
|
+
| `destructive-foreground` | `oklch(0.985 0 0)` |
|
|
177
|
+
| `success` | `oklch(0.7 0.2 145)` |
|
|
178
|
+
| `success-foreground` | `oklch(0.985 0 0)` |
|
|
179
|
+
| `warning` | `oklch(0.769 0.188 70)` |
|
|
180
|
+
| `warning-foreground` | `oklch(0.985 0 0)` |
|
|
181
|
+
| `border` | `oklch(0.922 0 0)` |
|
|
182
|
+
| `input` | `oklch(0.922 0 0)` |
|
|
183
|
+
| `ring` | `oklch(0.708 0 0)` |
|
|
184
|
+
| `font-title` | `inherit` |
|
|
185
|
+
| `font-body` | `inherit` |
|
|
186
|
+
|
|
187
|
+
### Color Mode
|
|
188
|
+
|
|
189
|
+
Control light/dark mode with `colorMode`:
|
|
99
190
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
191
|
+
```tsx
|
|
192
|
+
const { open } = useExpressCSV({
|
|
193
|
+
schema,
|
|
194
|
+
publishableKey: "your-publishable-key",
|
|
195
|
+
importIdentifier: "user-import",
|
|
196
|
+
colorMode: "system", // 'light' | 'dark' | 'system'
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Custom CSS
|
|
201
|
+
|
|
202
|
+
Inject custom CSS for fine-grained styling overrides.
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
const { open } = useExpressCSV({
|
|
206
|
+
schema,
|
|
207
|
+
publishableKey: "your-publishable-key",
|
|
208
|
+
importIdentifier: "user-import",
|
|
209
|
+
customCSS: `
|
|
210
|
+
.ecsv [data-step="upload"] {
|
|
211
|
+
border-radius: 1rem;
|
|
212
|
+
}
|
|
213
|
+
.ecsv button {
|
|
214
|
+
font-weight: 600;
|
|
215
|
+
}
|
|
216
|
+
`,
|
|
217
|
+
});
|
|
106
218
|
```
|
|
107
219
|
|
|
108
|
-
|
|
220
|
+
### Custom Fonts
|
|
221
|
+
|
|
222
|
+
Load custom fonts via the `fonts` option:
|
|
109
223
|
|
|
110
224
|
```tsx
|
|
111
225
|
const { open } = useExpressCSV({
|
|
112
226
|
schema,
|
|
113
|
-
|
|
227
|
+
publishableKey: "your-publishable-key",
|
|
114
228
|
importIdentifier: "user-import",
|
|
115
|
-
|
|
229
|
+
fonts: {
|
|
230
|
+
title: { source: "google", name: "Space Grotesk", weights: [400, 600, 700] },
|
|
231
|
+
body: { source: "custom", url: "https://example.com/font.woff2", format: "woff2" },
|
|
232
|
+
},
|
|
233
|
+
theme: {
|
|
234
|
+
"font-title": "'Space Grotesk', sans-serif",
|
|
235
|
+
"font-body": "'Custom Font', sans-serif",
|
|
236
|
+
},
|
|
116
237
|
});
|
|
117
238
|
```
|
|
118
239
|
|
|
119
|
-
|
|
240
|
+
## Webhook Delivery
|
|
120
241
|
|
|
121
|
-
|
|
242
|
+
Deliver data to a server endpoint instead of (or in addition to) processing locally:
|
|
122
243
|
|
|
123
244
|
```tsx
|
|
124
|
-
import { useExpressCSV, x
|
|
245
|
+
import { useExpressCSV, x } from "@expresscsv/react";
|
|
125
246
|
|
|
126
247
|
const schema = x.row({
|
|
127
|
-
name: x.string().label(
|
|
128
|
-
email: x.string().email().label(
|
|
248
|
+
name: x.string().label("Full Name"),
|
|
249
|
+
email: x.string().email().label("Email Address"),
|
|
129
250
|
});
|
|
130
251
|
|
|
131
252
|
function App() {
|
|
132
253
|
const { open } = useExpressCSV({
|
|
133
254
|
schema,
|
|
134
|
-
|
|
255
|
+
publishableKey: "your-publishable-key",
|
|
135
256
|
importIdentifier: "user-import",
|
|
257
|
+
title: "Import Users",
|
|
136
258
|
});
|
|
137
259
|
|
|
138
260
|
const handleImport = () => {
|
|
139
261
|
open({
|
|
140
|
-
chunkSize: 500, // Optional: chunk size for webhook delivery (default: 1000)
|
|
141
262
|
webhook: {
|
|
142
|
-
url:
|
|
143
|
-
method:
|
|
263
|
+
url: "https://api.example.com/webhooks/csv-import",
|
|
264
|
+
method: "POST",
|
|
144
265
|
headers: {
|
|
145
|
-
|
|
146
|
-
'X-Custom-Header': 'custom-value',
|
|
266
|
+
Authorization: "Bearer your-api-token",
|
|
147
267
|
},
|
|
148
268
|
metadata: {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
userId: 'user-123',
|
|
269
|
+
source: "react-app",
|
|
270
|
+
userId: "user-123",
|
|
152
271
|
},
|
|
153
272
|
},
|
|
154
273
|
onComplete: () => {
|
|
155
|
-
console.log(
|
|
274
|
+
console.log("Webhook delivery initiated");
|
|
156
275
|
},
|
|
157
276
|
onError: (error) => {
|
|
158
|
-
console.error(
|
|
277
|
+
console.error("Delivery error:", error);
|
|
159
278
|
},
|
|
160
279
|
});
|
|
161
280
|
};
|
|
162
281
|
|
|
163
|
-
return
|
|
164
|
-
<button onClick={handleImport}>
|
|
165
|
-
Import CSV via Webhook
|
|
166
|
-
</button>
|
|
167
|
-
);
|
|
282
|
+
return <button onClick={handleImport}>Import CSV via Webhook</button>;
|
|
168
283
|
}
|
|
169
284
|
```
|
|
170
285
|
|
|
171
286
|
### Combined Local Callback and Webhook
|
|
172
287
|
|
|
173
|
-
You can use both `onData` callback and webhook delivery simultaneously:
|
|
174
|
-
|
|
175
288
|
```tsx
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
289
|
+
open({
|
|
290
|
+
chunkSize: 500,
|
|
291
|
+
onData: async (chunk, next) => {
|
|
292
|
+
await saveToLocalDatabase(chunk.records);
|
|
293
|
+
next();
|
|
294
|
+
},
|
|
295
|
+
webhook: {
|
|
296
|
+
url: "https://api.example.com/webhooks/csv-import",
|
|
297
|
+
headers: { Authorization: "Bearer your-api-token" },
|
|
298
|
+
},
|
|
299
|
+
onComplete: () => {
|
|
300
|
+
console.log("Local processing and webhook delivery complete");
|
|
301
|
+
},
|
|
181
302
|
});
|
|
182
|
-
|
|
183
|
-
function App() {
|
|
184
|
-
const { open } = useExpressCSV({
|
|
185
|
-
schema,
|
|
186
|
-
title: "Import Users",
|
|
187
|
-
importIdentifier: "user-import",
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
const handleImport = () => {
|
|
191
|
-
open({
|
|
192
|
-
chunkSize: 100,
|
|
193
|
-
// Process locally
|
|
194
|
-
onData: async (chunk, next) => {
|
|
195
|
-
console.log(`Processing chunk ${chunk.currentChunkIndex + 1}/${chunk.totalChunks}`);
|
|
196
|
-
// Your local processing logic
|
|
197
|
-
await processChunkLocally(chunk.records);
|
|
198
|
-
next();
|
|
199
|
-
},
|
|
200
|
-
// Also deliver to webhook
|
|
201
|
-
webhook: {
|
|
202
|
-
url: 'https://api.example.com/webhooks/csv-import',
|
|
203
|
-
headers: {
|
|
204
|
-
'Authorization': 'Bearer your-api-token',
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
onComplete: () => {
|
|
208
|
-
console.log('All chunks processed and webhook delivery initiated');
|
|
209
|
-
},
|
|
210
|
-
});
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
return (
|
|
214
|
-
<button onClick={handleImport}>
|
|
215
|
-
Import CSV
|
|
216
|
-
</button>
|
|
217
|
-
);
|
|
218
|
-
}
|
|
219
303
|
```
|
|
220
304
|
|
|
221
|
-
|
|
305
|
+
## Advanced Example
|
|
222
306
|
|
|
223
307
|
```tsx
|
|
224
|
-
import { useExpressCSV, x, type Infer } from
|
|
225
|
-
import { useState } from
|
|
308
|
+
import { useExpressCSV, x, type Infer } from "@expresscsv/react";
|
|
309
|
+
import { useState } from "react";
|
|
226
310
|
|
|
227
311
|
const candidateSchema = x.row({
|
|
228
|
-
firstName: x.string().label(
|
|
229
|
-
lastName: x.string().label(
|
|
230
|
-
email: x.string().email().label(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
312
|
+
firstName: x.string().label("First Name"),
|
|
313
|
+
lastName: x.string().label("Last Name"),
|
|
314
|
+
email: x.string().email().label("Email"),
|
|
315
|
+
level: x
|
|
316
|
+
.select([
|
|
317
|
+
{ label: "Entry Level", value: "entry" },
|
|
318
|
+
{ label: "Mid Level", value: "mid" },
|
|
319
|
+
{ label: "Senior Level", value: "senior" },
|
|
320
|
+
])
|
|
321
|
+
.label("Experience Level"),
|
|
322
|
+
salary: x.number().currency("USD").min(30000).label("Expected Salary"),
|
|
237
323
|
});
|
|
238
324
|
|
|
239
325
|
function CandidateImporter() {
|
|
240
|
-
const [
|
|
241
|
-
const [error, setError] = useState<string | null>(null);
|
|
326
|
+
const [status, setStatus] = useState<string | null>(null);
|
|
242
327
|
|
|
243
|
-
const { open } = useExpressCSV({
|
|
328
|
+
const { open, isOpen } = useExpressCSV({
|
|
244
329
|
schema: candidateSchema,
|
|
245
|
-
|
|
330
|
+
publishableKey: "your-publishable-key",
|
|
246
331
|
importIdentifier: "candidate-import",
|
|
247
|
-
|
|
248
|
-
|
|
332
|
+
title: "Import Candidates",
|
|
333
|
+
developerMode: process.env.NODE_ENV === "development",
|
|
249
334
|
});
|
|
250
335
|
|
|
251
336
|
const handleImport = () => {
|
|
252
|
-
|
|
253
|
-
setError(null);
|
|
337
|
+
setStatus(null);
|
|
254
338
|
|
|
255
339
|
open({
|
|
256
340
|
onData: async (chunk, next) => {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
setError('Failed to import candidates. Please try again.');
|
|
263
|
-
throw err;
|
|
264
|
-
}
|
|
341
|
+
setStatus(
|
|
342
|
+
`Processing chunk ${chunk.currentChunkIndex + 1}/${chunk.totalChunks}...`
|
|
343
|
+
);
|
|
344
|
+
await importCandidates(chunk.records);
|
|
345
|
+
next();
|
|
265
346
|
},
|
|
266
347
|
onComplete: () => {
|
|
267
|
-
|
|
268
|
-
alert('Successfully imported all candidates!');
|
|
348
|
+
setStatus("Import complete!");
|
|
269
349
|
},
|
|
270
350
|
onError: (error) => {
|
|
271
|
-
|
|
272
|
-
setError(`Import failed: ${error.message}`);
|
|
351
|
+
setStatus(`Error: ${error.message}`);
|
|
273
352
|
},
|
|
274
353
|
onCancel: () => {
|
|
275
|
-
|
|
354
|
+
setStatus(null);
|
|
276
355
|
},
|
|
277
356
|
});
|
|
278
357
|
};
|
|
279
358
|
|
|
280
359
|
return (
|
|
281
360
|
<div>
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
<button
|
|
285
|
-
onClick={handleImport}
|
|
286
|
-
disabled={isLoading}
|
|
287
|
-
className="import-button"
|
|
288
|
-
>
|
|
289
|
-
{isLoading ? 'Processing...' : 'Import Candidates'}
|
|
361
|
+
<button onClick={handleImport} disabled={isOpen}>
|
|
362
|
+
{isOpen ? "Importing..." : "Import Candidates"}
|
|
290
363
|
</button>
|
|
364
|
+
{status && <p>{status}</p>}
|
|
291
365
|
</div>
|
|
292
366
|
);
|
|
293
367
|
}
|
|
@@ -295,126 +369,196 @@ function CandidateImporter() {
|
|
|
295
369
|
|
|
296
370
|
## API Reference
|
|
297
371
|
|
|
298
|
-
### useExpressCSV
|
|
299
|
-
|
|
300
|
-
A custom hook that provides access to the CSV importer functionality.
|
|
301
|
-
|
|
302
|
-
#### Parameters
|
|
303
|
-
|
|
304
|
-
```tsx
|
|
305
|
-
interface UseExpressCSVOptions<TSchema> {
|
|
306
|
-
schema: TSchema;
|
|
307
|
-
title?: string;
|
|
308
|
-
importIdentifier: string;
|
|
309
|
-
publishableKey: string;
|
|
310
|
-
debug?: boolean;
|
|
311
|
-
developerMode?: boolean;
|
|
312
|
-
preload?: boolean; // Defaults to true
|
|
313
|
-
theme?: ECSVTheme;
|
|
314
|
-
colorMode?: ColorModeConfig;
|
|
315
|
-
customCSS?: string;
|
|
316
|
-
fonts?: Record<string, ECSVFontSource>;
|
|
317
|
-
stepDisplay?: 'progressBar' | 'segmented' | 'numbered';
|
|
318
|
-
}
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
#### Returns
|
|
372
|
+
### `useExpressCSV(options)`
|
|
322
373
|
|
|
323
|
-
```
|
|
324
|
-
|
|
374
|
+
```typescript
|
|
375
|
+
function useExpressCSV<TSchema>(
|
|
376
|
+
options: UseExpressCSVOptions<TSchema>
|
|
377
|
+
): {
|
|
325
378
|
open: (options: OpenOptions<Infer<TSchema>>) => void;
|
|
326
379
|
widgetState: WidgetState;
|
|
327
380
|
isInitialising: boolean;
|
|
328
381
|
isOpen: boolean;
|
|
329
|
-
}
|
|
382
|
+
};
|
|
330
383
|
```
|
|
331
384
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
385
|
+
#### Options
|
|
386
|
+
|
|
387
|
+
| Option | Type | Required | Default | Description |
|
|
388
|
+
|---|---|---|---|---|
|
|
389
|
+
| `schema` | Schema | Yes | - | Schema definition created with `x.row()` |
|
|
390
|
+
| `publishableKey` | `string` | Yes | - | Your publishable key from the [dashboard](https://expresscsv.com) |
|
|
391
|
+
| `importIdentifier` | `string` | Yes | - | Unique identifier for this import type |
|
|
392
|
+
| `title` | `string` | No | - | Title shown in the widget header |
|
|
393
|
+
| `preload` | `boolean` | No | `true` | Preload widget for instant display |
|
|
394
|
+
| `debug` | `boolean` | No | `false` | Enable debug logging |
|
|
395
|
+
| `developerMode` | `boolean` | No | `false` | Enable developer mode features |
|
|
396
|
+
| `theme` | `ECSVTheme` | No | - | Custom theme configuration |
|
|
397
|
+
| `colorMode` | `ColorModePref` | No | - | Light/dark mode (`'light'`, `'dark'`, or `'system'`) |
|
|
398
|
+
| `customCSS` | `string` | No | - | Custom CSS to inject into the widget |
|
|
399
|
+
| `fonts` | `Record<string, ECSVFontSource>` | No | - | Custom font sources |
|
|
400
|
+
| `stepDisplay` | `'progressBar' \| 'segmented' \| 'numbered'` | No | `'progressBar'` | Step indicator style |
|
|
401
|
+
| `previewSchemaBeforeUpload` | `boolean` | No | `true` | Show schema preview before upload |
|
|
402
|
+
| `templateDownload` | `TemplateDownloadConfig` | No | - | Template download configuration |
|
|
403
|
+
| `saveSession` | `boolean` | No | - | Persist session state |
|
|
404
|
+
| `locale` | `DeepPartial<ExpressCSVLocaleInput>` | No | - | Localization overrides |
|
|
405
|
+
|
|
406
|
+
#### Return Value
|
|
407
|
+
|
|
408
|
+
| Property | Type | Description |
|
|
409
|
+
|---|---|---|
|
|
410
|
+
| `open` | `(options: OpenOptions) => void` | Opens the widget. Requires at least one of `onData` or `webhook`. |
|
|
411
|
+
| `widgetState` | `WidgetState` | Current widget state (reactive) |
|
|
412
|
+
| `isInitialising` | `boolean` | `true` while the widget is initializing or opening |
|
|
413
|
+
| `isOpen` | `boolean` | `true` while the widget is open |
|
|
414
|
+
|
|
415
|
+
### `OpenOptions<T>`
|
|
416
|
+
|
|
417
|
+
Options passed to `open()`. At least one of `onData` or `webhook` must be provided.
|
|
418
|
+
|
|
419
|
+
| Option | Type | Required | Description |
|
|
420
|
+
|---|---|---|---|
|
|
421
|
+
| `onData` | `(chunk: RecordsChunk<T>, next: () => void) => void` | * | Callback for each chunk. Call `next()` to continue. |
|
|
422
|
+
| `webhook` | `WebhookConfig` | * | Webhook endpoint for server-side delivery |
|
|
423
|
+
| `chunkSize` | `number` | No | Records per chunk (default: 1000) |
|
|
424
|
+
| `onComplete` | `() => void` | No | Called when all chunks have been processed |
|
|
425
|
+
| `onCancel` | `() => void` | No | Called when the user cancels the import |
|
|
426
|
+
| `onError` | `(error: Error) => void` | No | Called when an error occurs |
|
|
427
|
+
| `onWidgetOpen` | `() => void` | No | Called when the widget opens |
|
|
428
|
+
| `onWidgetClose` | `(reason: string) => void` | No | Called when the widget closes |
|
|
429
|
+
| `onStepChange` | `(stepId, previousStepId?) => void` | No | Called when the wizard step changes |
|
|
430
|
+
|
|
431
|
+
\* At least one of `onData` or `webhook` is required.
|
|
432
|
+
|
|
433
|
+
### `RecordsChunk<T>`
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
interface RecordsChunk<T> {
|
|
437
|
+
records: T[]; // Automatically typed to your schema
|
|
438
|
+
totalChunks: number;
|
|
439
|
+
currentChunkIndex: number;
|
|
440
|
+
totalRecords: number;
|
|
347
441
|
}
|
|
348
442
|
```
|
|
349
443
|
|
|
350
|
-
### WebhookConfig
|
|
444
|
+
### `WebhookConfig`
|
|
351
445
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
```tsx
|
|
446
|
+
```typescript
|
|
355
447
|
interface WebhookConfig {
|
|
356
|
-
url: string;
|
|
357
|
-
headers?: Record<string, string>;
|
|
358
|
-
method?:
|
|
359
|
-
timeout?: number;
|
|
360
|
-
retries?: number;
|
|
361
|
-
metadata?: Record<string, unknown>;
|
|
448
|
+
url: string;
|
|
449
|
+
headers?: Record<string, string>;
|
|
450
|
+
method?: "POST" | "PUT" | "PATCH";
|
|
451
|
+
timeout?: number;
|
|
452
|
+
retries?: number;
|
|
453
|
+
metadata?: Record<string, unknown>;
|
|
362
454
|
}
|
|
363
455
|
```
|
|
364
456
|
|
|
365
|
-
**Note:** The `chunkSize` option in `OpenOptions` controls the chunk size for both `onData` callbacks and webhook delivery. The backend will deliver webhooks in chunks of this size (or default 1000 if not specified).
|
|
366
|
-
|
|
367
457
|
### Schema Builder
|
|
368
458
|
|
|
369
|
-
The schema builder
|
|
459
|
+
The `x` schema builder provides a type-safe, fluent API for defining your CSV structure.
|
|
460
|
+
|
|
461
|
+
#### Field Types
|
|
370
462
|
|
|
371
463
|
```tsx
|
|
464
|
+
import { x } from "@expresscsv/react";
|
|
465
|
+
|
|
372
466
|
const schema = x.row({
|
|
373
|
-
//
|
|
374
|
-
name: x.string().label(
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
//
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
//
|
|
467
|
+
// Strings with validation
|
|
468
|
+
name: x.string().label("Full Name").min(2).max(100),
|
|
469
|
+
email: x.string().email().label("Email"),
|
|
470
|
+
website: x.string().url().label("Website").optional(),
|
|
471
|
+
|
|
472
|
+
// Numbers with constraints
|
|
473
|
+
age: x.number().label("Age").min(0).max(150).integer(),
|
|
474
|
+
salary: x.number().currency("USD").label("Salary").min(0),
|
|
475
|
+
|
|
476
|
+
// Boolean
|
|
477
|
+
isActive: x.boolean().label("Active"),
|
|
478
|
+
|
|
479
|
+
// Dates and times
|
|
480
|
+
startDate: x.date().label("Start Date"),
|
|
481
|
+
createdAt: x.datetime().label("Created At"),
|
|
482
|
+
|
|
483
|
+
// Single selection (requires { label, value } objects)
|
|
386
484
|
role: x.select([
|
|
387
|
-
{ label:
|
|
388
|
-
{ label:
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
485
|
+
{ label: "Admin", value: "admin" },
|
|
486
|
+
{ label: "Editor", value: "editor" },
|
|
487
|
+
{ label: "Viewer", value: "viewer" },
|
|
488
|
+
]).label("Role"),
|
|
489
|
+
|
|
490
|
+
// Multi-selection
|
|
491
|
+
tags: x.multiselect([
|
|
492
|
+
{ label: "Engineering", value: "eng" },
|
|
493
|
+
{ label: "Design", value: "design" },
|
|
494
|
+
{ label: "Marketing", value: "mkt" },
|
|
495
|
+
]).label("Tags"),
|
|
396
496
|
});
|
|
397
497
|
```
|
|
398
498
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
499
|
+
#### Common Modifiers
|
|
500
|
+
|
|
501
|
+
All field types support:
|
|
502
|
+
|
|
503
|
+
| Modifier | Description |
|
|
504
|
+
|---|---|
|
|
505
|
+
| `.label(text)` | User-facing label shown in the widget |
|
|
506
|
+
| `.description(text)` | Help text for the field |
|
|
507
|
+
| `.example(text)` | Example value shown as placeholder |
|
|
508
|
+
| `.optional()` | Makes the field optional (default is required) |
|
|
509
|
+
| `.refine(fn)` | Custom validation function |
|
|
510
|
+
|
|
511
|
+
#### String Modifiers
|
|
512
|
+
|
|
513
|
+
| Modifier | Description |
|
|
514
|
+
|---|---|
|
|
515
|
+
| `.email()` | Validates email format |
|
|
516
|
+
| `.url()` | Validates URL format |
|
|
517
|
+
| `.uuid()` | Validates UUID format |
|
|
518
|
+
| `.ip()` | Validates IP address |
|
|
519
|
+
| `.phone()` | Validates phone number |
|
|
520
|
+
| `.regex(pattern)` | Matches a regular expression |
|
|
521
|
+
| `.min(n)` | Minimum string length |
|
|
522
|
+
| `.max(n)` | Maximum string length |
|
|
523
|
+
| `.length(n)` | Exact string length |
|
|
524
|
+
| `.includes(str)` | Must contain substring |
|
|
525
|
+
| `.startsWith(str)` | Must start with prefix |
|
|
526
|
+
| `.endsWith(str)` | Must end with suffix |
|
|
527
|
+
|
|
528
|
+
#### Number Modifiers
|
|
529
|
+
|
|
530
|
+
| Modifier | Description |
|
|
531
|
+
|---|---|
|
|
532
|
+
| `.min(n)` | Minimum value |
|
|
533
|
+
| `.max(n)` | Maximum value |
|
|
534
|
+
| `.integer()` | Must be a whole number |
|
|
535
|
+
| `.multipleOf(n)` | Must be a multiple of n |
|
|
536
|
+
| `.currency(code)` | Formats as currency (e.g. `"USD"`) |
|
|
537
|
+
| `.percentage()` | Formats as percentage |
|
|
538
|
+
|
|
539
|
+
## TypeScript
|
|
540
|
+
|
|
541
|
+
Full type inference from your schema is built in:
|
|
402
542
|
|
|
403
543
|
```tsx
|
|
544
|
+
import { x, type Infer } from "@expresscsv/react";
|
|
545
|
+
|
|
404
546
|
const schema = x.row({
|
|
405
547
|
name: x.string(),
|
|
406
548
|
age: x.number(),
|
|
407
549
|
});
|
|
408
550
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
results.forEach(row => {
|
|
412
|
-
console.log(row.name); // string
|
|
413
|
-
console.log(row.age); // number
|
|
414
|
-
});
|
|
415
|
-
};
|
|
551
|
+
type Row = Infer<typeof schema>;
|
|
552
|
+
// { name: string; age: number }
|
|
416
553
|
```
|
|
417
554
|
|
|
555
|
+
The `onData` callback receives `RecordsChunk<Row>` automatically -- no manual type annotations needed.
|
|
556
|
+
|
|
557
|
+
## Resources
|
|
558
|
+
|
|
559
|
+
- [ExpressCSV Dashboard](https://expresscsv.com) -- manage your imports and API keys
|
|
560
|
+
- [`@expresscsv/sdk`](https://www.npmjs.com/package/@expresscsv/sdk) -- vanilla JS SDK (no React dependency)
|
|
561
|
+
|
|
418
562
|
## License
|
|
419
563
|
|
|
420
|
-
|
|
564
|
+
[MIT](./LICENSE)
|
package/dist/index.d.mts
CHANGED
|
@@ -4,15 +4,6 @@ declare interface BICOptions {
|
|
|
4
4
|
|
|
5
5
|
declare type BooleanControlType = 'toggle' | 'checkbox' | 'dropdown';
|
|
6
6
|
|
|
7
|
-
/**
|
|
8
|
-
* Color mode configuration
|
|
9
|
-
*/
|
|
10
|
-
export declare interface ColorModeConfig {
|
|
11
|
-
default?: ColorModePref;
|
|
12
|
-
persist?: boolean;
|
|
13
|
-
onChange?: (mode: ColorModePref) => void;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
7
|
/**
|
|
17
8
|
* Color mode preference
|
|
18
9
|
*/
|
|
@@ -3744,7 +3735,7 @@ export declare interface UseExpressCSVOptions<TSchema extends ExType<unknown, Ex
|
|
|
3744
3735
|
developerMode?: boolean;
|
|
3745
3736
|
preload?: boolean;
|
|
3746
3737
|
theme?: ECSVTheme;
|
|
3747
|
-
colorMode?:
|
|
3738
|
+
colorMode?: ColorModePref;
|
|
3748
3739
|
customCSS?: string;
|
|
3749
3740
|
fonts?: Record<string, ECSVFontSource>;
|
|
3750
3741
|
stepDisplay?: 'progressBar' | 'segmented' | 'numbered';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expresscsv/react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "React component wrapper for ExpressCSV SDK",
|
|
5
5
|
"module": "dist/index.mjs",
|
|
6
6
|
"types": "dist/index.d.mts",
|
|
@@ -25,6 +25,6 @@
|
|
|
25
25
|
"access": "public"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"react": "
|
|
28
|
+
"react": ">=16.8.0"
|
|
29
29
|
}
|
|
30
30
|
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { DeepPartial, ExpressCSVLocaleInput, TemplateDownloadConfig } from '@expresscsv/core';
|
|
2
|
-
import { type ColorModeConfig, type ECSVFontSource, type ECSVTheme, type ExBaseDef, type ExType, type Infer, type OpenOptions, WidgetState } from '@expresscsv/sdk';
|
|
3
|
-
export type { ColorModeConfig, ColorModePref, ECSVTheme, TailwindThemeVars, } from '@expresscsv/sdk';
|
|
4
|
-
export interface UseExpressCSVOptions<TSchema extends ExType<unknown, ExBaseDef, unknown>> {
|
|
5
|
-
schema: TSchema;
|
|
6
|
-
title?: string;
|
|
7
|
-
importIdentifier: string;
|
|
8
|
-
publishableKey: string;
|
|
9
|
-
debug?: boolean;
|
|
10
|
-
developerMode?: boolean;
|
|
11
|
-
preload?: boolean;
|
|
12
|
-
theme?: ECSVTheme;
|
|
13
|
-
colorMode?: ColorModeConfig;
|
|
14
|
-
customCSS?: string;
|
|
15
|
-
fonts?: Record<string, ECSVFontSource>;
|
|
16
|
-
stepDisplay?: 'progressBar' | 'segmented' | 'numbered';
|
|
17
|
-
previewSchemaBeforeUpload?: boolean;
|
|
18
|
-
templateDownload?: TemplateDownloadConfig;
|
|
19
|
-
saveSession?: boolean;
|
|
20
|
-
locale?: DeepPartial<ExpressCSVLocaleInput>;
|
|
21
|
-
}
|
|
22
|
-
export declare function useExpressCSV<TSchema extends ExType<unknown, ExBaseDef, unknown>>({ schema, title, importIdentifier, publishableKey, debug, developerMode, preload, theme, colorMode, customCSS, fonts, stepDisplay, previewSchemaBeforeUpload, templateDownload, saveSession, locale, }: UseExpressCSVOptions<TSchema>): {
|
|
23
|
-
open: (options: OpenOptions<Infer<TSchema>>) => void;
|
|
24
|
-
widgetState: WidgetState;
|
|
25
|
-
isInitialising: boolean;
|
|
26
|
-
isOpen: boolean;
|
|
27
|
-
};
|
package/dist/.dts/index.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export { useExpressCSV, type UseExpressCSVOptions, } from './CSVImporter';
|
|
2
|
-
export { x, WidgetState, WidgetMode, ImportCancelledError, } from '@expresscsv/sdk';
|
|
3
|
-
export type { Infer, ECSVFontSource, ECSVTheme, TailwindThemeVars, ColorModeConfig, ColorModePref, ExpressCSVStep, RecordsChunk, OpenOptions, WebhookConfig, DeliveryOptions, } from '@expresscsv/sdk';
|
|
4
|
-
export type { TemplateDownloadConfig, TemplateDownloadFormat, ExpressCSVLocaleInput, DeepPartial, } from '@expresscsv/core';
|