@dataimago/interview 0.2.0-alpha.0 → 0.2.0-alpha.1

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/dist/index.js CHANGED
@@ -40,6 +40,28 @@ function ProgressBar({ current, total, estimatedMinutesRemaining }) {
40
40
  // src/QuestionCard.tsx
41
41
  import { useState, useEffect } from "react";
42
42
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
43
+ function defaultValueFor(question) {
44
+ switch (question.inputType) {
45
+ case "multi-select":
46
+ case "list":
47
+ return [];
48
+ case "composite":
49
+ return {};
50
+ case "repeater":
51
+ case "file-upload":
52
+ return [];
53
+ default:
54
+ return "";
55
+ }
56
+ }
57
+ function canAdvanceWith(question, value) {
58
+ if (!question.required) return true;
59
+ if (value === void 0 || value === null) return false;
60
+ if (typeof value === "string") return value.trim().length > 0;
61
+ if (Array.isArray(value)) return value.length > 0;
62
+ if (typeof value === "object") return Object.keys(value).length > 0;
63
+ return true;
64
+ }
43
65
  function QuestionCard({
44
66
  question,
45
67
  existingAnswer,
@@ -50,113 +72,28 @@ function QuestionCard({
50
72
  isLast
51
73
  }) {
52
74
  const [localValue, setLocalValue] = useState(
53
- existingAnswer ?? (question.inputType === "multi-select" || question.inputType === "list" ? [] : "")
75
+ existingAnswer ?? defaultValueFor(question)
54
76
  );
55
77
  useEffect(() => {
56
- const current = existingAnswer ?? (question.inputType === "multi-select" || question.inputType === "list" ? [] : "");
57
- setLocalValue(current);
78
+ setLocalValue(existingAnswer ?? defaultValueFor(question));
58
79
  }, [question.id, existingAnswer, question.inputType]);
59
80
  const handleSubmit = (e) => {
60
81
  e.preventDefault();
61
82
  onAnswerSubmit(question.id, localValue);
62
83
  onNext();
63
84
  };
64
- const canAdvance = question.required ? Array.isArray(localValue) ? localValue.length > 0 : localValue.trim().length > 0 : true;
85
+ const canAdvance = canAdvanceWith(question, localValue);
65
86
  return /* @__PURE__ */ jsxs2("form", { onSubmit: handleSubmit, className: "animate-slide-up", children: [
66
87
  /* @__PURE__ */ jsx2("h2", { className: "font-display text-3xl font-medium text-stone-900 sm:text-4xl", children: question.prompt }),
67
88
  question.subprompt && /* @__PURE__ */ jsx2("p", { className: "mt-4 text-lg text-stone-700", children: question.subprompt }),
68
- /* @__PURE__ */ jsxs2("div", { className: "mt-8", children: [
69
- question.inputType === "short-text" && /* @__PURE__ */ jsx2(
70
- "input",
71
- {
72
- type: "text",
73
- value: localValue,
74
- onChange: (e) => setLocalValue(e.target.value),
75
- autoFocus: true,
76
- className: "w-full rounded-lg border-2 border-stone-200 bg-stone-50 px-4 py-3 text-lg text-stone-900 focus:border-forest-700 focus:outline-none",
77
- placeholder: "Type your answer\u2026"
78
- }
79
- ),
80
- question.inputType === "long-text" && /* @__PURE__ */ jsx2(
81
- "textarea",
82
- {
83
- value: localValue,
84
- onChange: (e) => setLocalValue(e.target.value),
85
- autoFocus: true,
86
- rows: 5,
87
- className: "w-full rounded-lg border-2 border-stone-200 bg-stone-50 px-4 py-3 text-lg text-stone-900 focus:border-forest-700 focus:outline-none",
88
- placeholder: "Type your answer\u2026"
89
- }
90
- ),
91
- question.inputType === "select" && question.options && /* @__PURE__ */ jsx2("div", { className: "space-y-3", role: "radiogroup", "aria-labelledby": `q-${question.id}`, children: question.options.map((opt) => /* @__PURE__ */ jsxs2(
92
- "label",
93
- {
94
- className: `flex cursor-pointer items-start gap-3 rounded-lg border-2 p-4 transition-colors
95
- ${localValue === opt.value ? "border-forest-700 bg-forest-50" : "border-stone-200 bg-stone-50 hover:border-stone-400"}`,
96
- children: [
97
- /* @__PURE__ */ jsx2(
98
- "input",
99
- {
100
- type: "radio",
101
- name: question.id,
102
- value: opt.value,
103
- checked: localValue === opt.value,
104
- onChange: (e) => setLocalValue(e.target.value),
105
- className: "mt-1 h-4 w-4 text-forest-700"
106
- }
107
- ),
108
- /* @__PURE__ */ jsxs2("div", { children: [
109
- /* @__PURE__ */ jsx2("div", { className: "font-medium text-stone-900", children: opt.label }),
110
- opt.description && /* @__PURE__ */ jsx2("div", { className: "mt-1 text-sm text-stone-700", children: opt.description })
111
- ] })
112
- ]
113
- },
114
- opt.value
115
- )) }),
116
- question.inputType === "multi-select" && question.options && /* @__PURE__ */ jsx2("div", { className: "space-y-2", role: "group", "aria-labelledby": `q-${question.id}`, children: question.options.map((opt) => {
117
- const values = localValue || [];
118
- const checked = values.includes(opt.value);
119
- return /* @__PURE__ */ jsxs2(
120
- "label",
121
- {
122
- className: `flex cursor-pointer items-start gap-3 rounded-lg border-2 p-4 transition-colors
123
- ${checked ? "border-forest-700 bg-forest-50" : "border-stone-200 bg-stone-50 hover:border-stone-400"}`,
124
- children: [
125
- /* @__PURE__ */ jsx2(
126
- "input",
127
- {
128
- type: "checkbox",
129
- value: opt.value,
130
- checked,
131
- onChange: (e) => {
132
- if (e.target.checked) {
133
- setLocalValue([...values, opt.value]);
134
- } else {
135
- setLocalValue(values.filter((v) => v !== opt.value));
136
- }
137
- },
138
- className: "mt-1 h-4 w-4 text-forest-700"
139
- }
140
- ),
141
- /* @__PURE__ */ jsxs2("div", { children: [
142
- /* @__PURE__ */ jsx2("div", { className: "font-medium text-stone-900", children: opt.label }),
143
- opt.description && /* @__PURE__ */ jsx2("div", { className: "mt-1 text-sm text-stone-700", children: opt.description })
144
- ] })
145
- ]
146
- },
147
- opt.value
148
- );
149
- }) }),
150
- question.inputType === "list" && question.listRange && /* @__PURE__ */ jsx2(
151
- ListInput,
152
- {
153
- values: localValue || [],
154
- onChange: setLocalValue,
155
- min: question.listRange.min,
156
- max: question.listRange.max
157
- }
158
- )
159
- ] }),
89
+ /* @__PURE__ */ jsx2("div", { className: "mt-8", children: /* @__PURE__ */ jsx2(
90
+ InputForType,
91
+ {
92
+ question,
93
+ value: localValue,
94
+ onChange: setLocalValue
95
+ }
96
+ ) }),
160
97
  question.examples && question.examples.length > 0 && /* @__PURE__ */ jsxs2("details", { className: "mt-8", children: [
161
98
  /* @__PURE__ */ jsx2("summary", { className: "cursor-pointer text-sm font-medium text-forest-700 hover:text-forest-800", children: "Show examples from different fields" }),
162
99
  /* @__PURE__ */ jsx2("ul", { className: "mt-4 space-y-3 border-l-2 border-copper-200 pl-5", children: question.examples.map((ex, i) => /* @__PURE__ */ jsxs2("li", { className: "text-sm italic text-stone-700", children: [
@@ -188,13 +125,174 @@ function QuestionCard({
188
125
  ] })
189
126
  ] });
190
127
  }
128
+ function InputForType({ question, value, onChange }) {
129
+ switch (question.inputType) {
130
+ case "short-text":
131
+ return /* @__PURE__ */ jsx2(ShortTextInput, { value, onChange });
132
+ case "long-text":
133
+ return /* @__PURE__ */ jsx2(LongTextInput, { value, onChange });
134
+ case "single-select":
135
+ return /* @__PURE__ */ jsx2(SingleSelectInput, { question, value, onChange });
136
+ case "multi-select":
137
+ return /* @__PURE__ */ jsx2(MultiSelectInput, { question, value: value ?? [], onChange });
138
+ case "list":
139
+ return /* @__PURE__ */ jsx2(ListInput, { value: value ?? [], onChange, listRange: question.listRange });
140
+ case "scale":
141
+ return /* @__PURE__ */ jsx2(ScaleInput, { question, value, onChange });
142
+ case "date":
143
+ return /* @__PURE__ */ jsx2(DateInput, { value, onChange });
144
+ case "composite":
145
+ return /* @__PURE__ */ jsx2(CompositeInput, { question, value: value ?? {}, onChange });
146
+ case "repeater":
147
+ return /* @__PURE__ */ jsx2(RepeaterInput, { question, value: value ?? [], onChange });
148
+ case "file-upload":
149
+ return /* @__PURE__ */ jsx2(FileUploadInput, { question, value: value ?? [], onChange });
150
+ default: {
151
+ const _exhaustive = question.inputType;
152
+ return /* @__PURE__ */ jsx2(UnknownTypeFallback, { inputType: _exhaustive });
153
+ }
154
+ }
155
+ }
156
+ function ShortTextInput({ value, onChange }) {
157
+ return /* @__PURE__ */ jsx2(
158
+ "input",
159
+ {
160
+ type: "text",
161
+ value: value ?? "",
162
+ onChange: (e) => onChange(e.target.value),
163
+ autoFocus: true,
164
+ className: "w-full rounded-lg border-2 border-stone-200 bg-stone-50 px-4 py-3 text-lg text-stone-900 focus:border-forest-700 focus:outline-none",
165
+ placeholder: "Type your answer\u2026"
166
+ }
167
+ );
168
+ }
169
+ function LongTextInput({ value, onChange }) {
170
+ return /* @__PURE__ */ jsx2(
171
+ "textarea",
172
+ {
173
+ value: value ?? "",
174
+ onChange: (e) => onChange(e.target.value),
175
+ autoFocus: true,
176
+ rows: 5,
177
+ className: "w-full rounded-lg border-2 border-stone-200 bg-stone-50 px-4 py-3 text-lg text-stone-900 focus:border-forest-700 focus:outline-none",
178
+ placeholder: "Type your answer\u2026"
179
+ }
180
+ );
181
+ }
182
+ function SingleSelectInput({
183
+ question,
184
+ value,
185
+ onChange
186
+ }) {
187
+ return /* @__PURE__ */ jsx2("div", { className: "space-y-3", role: "radiogroup", "aria-labelledby": `q-${question.id}`, children: (question.options ?? []).map((opt) => /* @__PURE__ */ jsxs2(
188
+ "label",
189
+ {
190
+ className: `flex cursor-pointer items-start gap-3 rounded-lg border-2 p-4 transition-colors ${value === opt.value ? "border-forest-700 bg-forest-50" : "border-stone-200 bg-stone-50 hover:border-stone-400"}`,
191
+ children: [
192
+ /* @__PURE__ */ jsx2(
193
+ "input",
194
+ {
195
+ type: "radio",
196
+ name: question.id,
197
+ value: opt.value,
198
+ checked: value === opt.value,
199
+ onChange: (e) => onChange(e.target.value),
200
+ className: "mt-1 h-4 w-4 text-forest-700"
201
+ }
202
+ ),
203
+ /* @__PURE__ */ jsxs2("div", { children: [
204
+ /* @__PURE__ */ jsx2("div", { className: "font-medium text-stone-900", children: opt.label }),
205
+ opt.description && /* @__PURE__ */ jsx2("div", { className: "mt-1 text-sm text-stone-700", children: opt.description })
206
+ ] })
207
+ ]
208
+ },
209
+ opt.value
210
+ )) });
211
+ }
212
+ function MultiSelectInput({
213
+ question,
214
+ value,
215
+ onChange
216
+ }) {
217
+ return /* @__PURE__ */ jsx2("div", { className: "space-y-2", role: "group", "aria-labelledby": `q-${question.id}`, children: (question.options ?? []).map((opt) => {
218
+ const checked = value.includes(opt.value);
219
+ return /* @__PURE__ */ jsxs2(
220
+ "label",
221
+ {
222
+ className: `flex cursor-pointer items-start gap-3 rounded-lg border-2 p-4 transition-colors ${checked ? "border-forest-700 bg-forest-50" : "border-stone-200 bg-stone-50 hover:border-stone-400"}`,
223
+ children: [
224
+ /* @__PURE__ */ jsx2(
225
+ "input",
226
+ {
227
+ type: "checkbox",
228
+ value: opt.value,
229
+ checked,
230
+ onChange: (e) => {
231
+ if (e.target.checked) onChange([...value, opt.value]);
232
+ else onChange(value.filter((v) => v !== opt.value));
233
+ },
234
+ className: "mt-1 h-4 w-4 text-forest-700"
235
+ }
236
+ ),
237
+ /* @__PURE__ */ jsxs2("div", { children: [
238
+ /* @__PURE__ */ jsx2("div", { className: "font-medium text-stone-900", children: opt.label }),
239
+ opt.description && /* @__PURE__ */ jsx2("div", { className: "mt-1 text-sm text-stone-700", children: opt.description })
240
+ ] })
241
+ ]
242
+ },
243
+ opt.value
244
+ );
245
+ }) });
246
+ }
247
+ function ScaleInput({
248
+ question,
249
+ value,
250
+ onChange
251
+ }) {
252
+ const options = question.options ?? [];
253
+ return /* @__PURE__ */ jsx2("div", { className: "space-y-3", role: "radiogroup", "aria-labelledby": `q-${question.id}`, children: options.map((opt) => /* @__PURE__ */ jsxs2(
254
+ "label",
255
+ {
256
+ className: `flex cursor-pointer items-center gap-3 rounded-lg border-2 p-3 transition-colors ${value === opt.value ? "border-forest-700 bg-forest-50" : "border-stone-200 bg-stone-50 hover:border-stone-400"}`,
257
+ children: [
258
+ /* @__PURE__ */ jsx2(
259
+ "input",
260
+ {
261
+ type: "radio",
262
+ name: question.id,
263
+ value: opt.value,
264
+ checked: value === opt.value,
265
+ onChange: (e) => onChange(e.target.value),
266
+ className: "h-4 w-4 text-forest-700"
267
+ }
268
+ ),
269
+ /* @__PURE__ */ jsx2("span", { className: "font-mono text-sm text-stone-500", children: opt.value }),
270
+ /* @__PURE__ */ jsx2("span", { className: "text-stone-900", children: opt.label })
271
+ ]
272
+ },
273
+ opt.value
274
+ )) });
275
+ }
276
+ function DateInput({ value, onChange }) {
277
+ return /* @__PURE__ */ jsx2(
278
+ "input",
279
+ {
280
+ type: "date",
281
+ value: value ?? "",
282
+ onChange: (e) => onChange(e.target.value),
283
+ autoFocus: true,
284
+ className: "rounded-lg border-2 border-stone-200 bg-stone-50 px-4 py-3 text-lg text-stone-900 focus:border-forest-700 focus:outline-none"
285
+ }
286
+ );
287
+ }
191
288
  function ListInput({
192
- values,
289
+ value,
193
290
  onChange,
194
- min,
195
- max
291
+ listRange
196
292
  }) {
197
- const items = values.length > 0 ? values : [""];
293
+ const items = value.length > 0 ? value : [""];
294
+ const min = listRange?.min ?? 0;
295
+ const max = listRange?.max ?? Infinity;
198
296
  const setItem = (i, v) => {
199
297
  const next = [...items];
200
298
  next[i] = v;
@@ -221,7 +319,7 @@ function ListInput({
221
319
  value: item,
222
320
  onChange: (e) => setItem(i, e.target.value),
223
321
  className: "flex-grow rounded-lg border-2 border-stone-200 bg-stone-50 px-3 py-2 text-stone-900 focus:border-forest-700 focus:outline-none",
224
- placeholder: `Term ${i + 1}`
322
+ placeholder: `Item ${i + 1}`
225
323
  }
226
324
  ),
227
325
  /* @__PURE__ */ jsx2(
@@ -231,7 +329,7 @@ function ListInput({
231
329
  onClick: () => removeItem(i),
232
330
  disabled: items.length <= 1,
233
331
  className: "btn-ghost text-stone-500 disabled:opacity-30",
234
- "aria-label": `Remove term ${i + 1}`,
332
+ "aria-label": `Remove item ${i + 1}`,
235
333
  children: "\xD7"
236
334
  }
237
335
  )
@@ -244,19 +342,312 @@ function ListInput({
244
342
  onClick: addItem,
245
343
  disabled: items.length >= max,
246
344
  className: "text-sm font-medium text-forest-700 hover:text-forest-800 disabled:opacity-40",
247
- children: "+ Add term"
345
+ children: "+ Add item"
248
346
  }
249
347
  ),
250
- /* @__PURE__ */ jsxs2("span", { className: "font-mono text-xs text-stone-500", children: [
348
+ listRange && /* @__PURE__ */ jsxs2("span", { className: "font-mono text-xs text-stone-500", children: [
251
349
  validCount,
252
350
  " / ",
253
351
  min,
254
352
  "\u2013",
255
- max
353
+ max === Infinity ? "\u221E" : max
256
354
  ] })
257
355
  ] })
258
356
  ] });
259
357
  }
358
+ function CompositeInput({
359
+ question,
360
+ value,
361
+ onChange
362
+ }) {
363
+ const subQuestions = question.subQuestions ?? [];
364
+ return /* @__PURE__ */ jsx2("div", { className: "space-y-6 rounded-lg border-2 border-stone-200 bg-stone-50 p-4", children: subQuestions.map((sub) => /* @__PURE__ */ jsxs2("div", { children: [
365
+ /* @__PURE__ */ jsx2("div", { className: "text-sm font-medium text-stone-900", children: sub.prompt }),
366
+ sub.subprompt && /* @__PURE__ */ jsx2("div", { className: "mt-1 text-xs text-stone-700", children: sub.subprompt }),
367
+ /* @__PURE__ */ jsx2("div", { className: "mt-2", children: /* @__PURE__ */ jsx2(
368
+ InputForType,
369
+ {
370
+ question: sub,
371
+ value: value[sub.id],
372
+ onChange: (next) => onChange({ ...value, [sub.id]: next })
373
+ }
374
+ ) })
375
+ ] }, sub.id)) });
376
+ }
377
+ function RepeaterInput({
378
+ question,
379
+ value,
380
+ onChange
381
+ }) {
382
+ const rows = value.length > 0 ? value : [{}];
383
+ const itemSchema = question.itemSchema ?? [];
384
+ const min = question.listRange?.min ?? 1;
385
+ const max = question.listRange?.max ?? Infinity;
386
+ const updateRow = (i, patch) => {
387
+ const next = [...rows];
388
+ next[i] = { ...next[i], ...patch };
389
+ onChange(next);
390
+ };
391
+ const addRow = () => {
392
+ if (rows.length < max) onChange([...rows, {}]);
393
+ };
394
+ const removeRow = (i) => {
395
+ if (rows.length <= 1) return;
396
+ onChange(rows.filter((_, idx) => idx !== i));
397
+ };
398
+ return /* @__PURE__ */ jsxs2("div", { className: "space-y-4", children: [
399
+ rows.map((row, i) => /* @__PURE__ */ jsxs2("div", { className: "rounded-lg border-2 border-stone-200 bg-stone-50 p-4", children: [
400
+ /* @__PURE__ */ jsxs2("div", { className: "mb-3 flex items-center justify-between", children: [
401
+ /* @__PURE__ */ jsxs2("span", { className: "font-mono text-xs uppercase tracking-wider text-stone-500", children: [
402
+ "Item ",
403
+ i + 1
404
+ ] }),
405
+ /* @__PURE__ */ jsx2(
406
+ "button",
407
+ {
408
+ type: "button",
409
+ onClick: () => removeRow(i),
410
+ disabled: rows.length <= min,
411
+ className: "btn-ghost text-xs text-stone-500 disabled:opacity-30",
412
+ "aria-label": `Remove item ${i + 1}`,
413
+ children: "Remove"
414
+ }
415
+ )
416
+ ] }),
417
+ /* @__PURE__ */ jsx2("div", { className: "space-y-4", children: itemSchema.map((sub) => /* @__PURE__ */ jsxs2("div", { children: [
418
+ /* @__PURE__ */ jsx2("div", { className: "text-sm font-medium text-stone-900", children: sub.prompt }),
419
+ sub.subprompt && /* @__PURE__ */ jsx2("div", { className: "mt-1 text-xs text-stone-700", children: sub.subprompt }),
420
+ /* @__PURE__ */ jsx2("div", { className: "mt-2", children: /* @__PURE__ */ jsx2(
421
+ InputForType,
422
+ {
423
+ question: sub,
424
+ value: row[sub.id],
425
+ onChange: (next) => updateRow(i, { [sub.id]: next })
426
+ }
427
+ ) })
428
+ ] }, sub.id)) })
429
+ ] }, i)),
430
+ /* @__PURE__ */ jsx2(
431
+ "button",
432
+ {
433
+ type: "button",
434
+ onClick: addRow,
435
+ disabled: rows.length >= max,
436
+ className: "text-sm font-medium text-forest-700 hover:text-forest-800 disabled:opacity-40",
437
+ children: "+ Add another"
438
+ }
439
+ )
440
+ ] });
441
+ }
442
+ function FileUploadInput({
443
+ question,
444
+ value,
445
+ onChange
446
+ }) {
447
+ const itemSchema = question.itemSchema ?? [];
448
+ const onFiles = (files) => {
449
+ if (!files || files.length === 0) return;
450
+ const entries = Array.from(files).map((f) => ({
451
+ file: f,
452
+ metadata: {}
453
+ }));
454
+ onChange([...value, ...entries]);
455
+ };
456
+ const updateMetadata = (i, patch) => {
457
+ const next = [...value];
458
+ next[i] = { ...next[i], metadata: { ...next[i].metadata, ...patch } };
459
+ onChange(next);
460
+ };
461
+ const removeEntry = (i) => onChange(value.filter((_, idx) => idx !== i));
462
+ const onDrop = (e) => {
463
+ e.preventDefault();
464
+ onFiles(e.dataTransfer.files);
465
+ };
466
+ const onDragOver = (e) => e.preventDefault();
467
+ return /* @__PURE__ */ jsxs2("div", { className: "space-y-4", children: [
468
+ /* @__PURE__ */ jsxs2(
469
+ "div",
470
+ {
471
+ onDrop,
472
+ onDragOver,
473
+ className: "rounded-lg border-2 border-dashed border-stone-300 bg-stone-50 p-6 text-center",
474
+ children: [
475
+ /* @__PURE__ */ jsxs2("p", { className: "text-sm text-stone-700", children: [
476
+ "Drag files here, or",
477
+ " ",
478
+ /* @__PURE__ */ jsxs2("label", { className: "cursor-pointer font-medium text-forest-700 hover:text-forest-800", children: [
479
+ "browse",
480
+ /* @__PURE__ */ jsx2(
481
+ "input",
482
+ {
483
+ type: "file",
484
+ multiple: true,
485
+ className: "sr-only",
486
+ onChange: (e) => onFiles(e.target.files)
487
+ }
488
+ )
489
+ ] })
490
+ ] }),
491
+ /* @__PURE__ */ jsx2("p", { className: "mt-1 text-xs text-stone-500", children: "The package collects File objects + metadata; the consumer's pipeline handles upload." })
492
+ ]
493
+ }
494
+ ),
495
+ value.length > 0 && /* @__PURE__ */ jsx2("ul", { className: "space-y-4", children: value.map((entry, i) => {
496
+ const name = entry.file.name;
497
+ const size = "size" in entry.file ? entry.file.size : void 0;
498
+ return /* @__PURE__ */ jsxs2("li", { className: "rounded-lg border-2 border-stone-200 bg-stone-50 p-4", children: [
499
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between", children: [
500
+ /* @__PURE__ */ jsxs2("div", { children: [
501
+ /* @__PURE__ */ jsx2("div", { className: "font-medium text-stone-900", children: name }),
502
+ typeof size === "number" && /* @__PURE__ */ jsxs2("div", { className: "font-mono text-xs text-stone-500", children: [
503
+ (size / 1024).toFixed(1),
504
+ " KB"
505
+ ] })
506
+ ] }),
507
+ /* @__PURE__ */ jsx2(
508
+ "button",
509
+ {
510
+ type: "button",
511
+ onClick: () => removeEntry(i),
512
+ className: "btn-ghost text-xs text-stone-500",
513
+ "aria-label": `Remove ${name}`,
514
+ children: "Remove"
515
+ }
516
+ )
517
+ ] }),
518
+ itemSchema.length > 0 && /* @__PURE__ */ jsx2("div", { className: "mt-4 space-y-3", children: itemSchema.map((sub) => /* @__PURE__ */ jsxs2("div", { children: [
519
+ /* @__PURE__ */ jsx2("div", { className: "text-xs font-medium text-stone-900", children: sub.prompt }),
520
+ /* @__PURE__ */ jsx2("div", { className: "mt-1", children: /* @__PURE__ */ jsx2(
521
+ InputForType,
522
+ {
523
+ question: sub,
524
+ value: entry.metadata[sub.id],
525
+ onChange: (next) => updateMetadata(i, { [sub.id]: next })
526
+ }
527
+ ) })
528
+ ] }, sub.id)) })
529
+ ] }, i);
530
+ }) })
531
+ ] });
532
+ }
533
+ function UnknownTypeFallback({ inputType }) {
534
+ return /* @__PURE__ */ jsxs2("div", { className: "rounded-lg border-2 border-stone-300 bg-stone-50 p-4 text-sm text-stone-500", children: [
535
+ "Unsupported question input type:",
536
+ " ",
537
+ /* @__PURE__ */ jsx2("code", { className: "font-mono", children: String(inputType) })
538
+ ] });
539
+ }
540
+
541
+ // src/summarize.ts
542
+ function isAnswered(value) {
543
+ if (value === void 0 || value === null) return false;
544
+ if (typeof value === "string") return value.trim().length > 0;
545
+ if (Array.isArray(value)) return value.length > 0;
546
+ if (typeof value === "object") return Object.keys(value).length > 0;
547
+ return true;
548
+ }
549
+ function summarizeShort(question, value, maxLen = 80) {
550
+ if (!isAnswered(value)) return "";
551
+ switch (question.inputType) {
552
+ case "short-text":
553
+ case "long-text":
554
+ case "date":
555
+ case "scale":
556
+ return truncate(String(value), maxLen);
557
+ case "single-select": {
558
+ const v = String(value);
559
+ const label = question.options?.find((o) => o.value === v)?.label;
560
+ return truncate(label ?? v, maxLen);
561
+ }
562
+ case "multi-select":
563
+ case "list": {
564
+ const arr = value;
565
+ const labels = arr.map((v) => {
566
+ const found = question.options?.find((o) => o.value === v)?.label;
567
+ return found ?? v;
568
+ });
569
+ return truncate(labels.join(", "), maxLen);
570
+ }
571
+ case "composite": {
572
+ const obj = value;
573
+ const subs = question.subQuestions ?? [];
574
+ for (const sub of subs) {
575
+ const subVal = obj[sub.id];
576
+ if (isAnswered(subVal)) {
577
+ return truncate(summarizeShort(sub, subVal, maxLen), maxLen);
578
+ }
579
+ }
580
+ return "";
581
+ }
582
+ case "repeater": {
583
+ const rows = value;
584
+ return `${rows.length} item${rows.length === 1 ? "" : "s"}`;
585
+ }
586
+ case "file-upload": {
587
+ const files = value;
588
+ return `${files.length} file${files.length === 1 ? "" : "s"}`;
589
+ }
590
+ default:
591
+ return "";
592
+ }
593
+ }
594
+ function summarizeLong(question, value) {
595
+ if (!isAnswered(value)) return "";
596
+ switch (question.inputType) {
597
+ case "short-text":
598
+ case "long-text":
599
+ case "date":
600
+ case "scale":
601
+ return String(value);
602
+ case "single-select": {
603
+ const v = String(value);
604
+ const label = question.options?.find((o) => o.value === v)?.label;
605
+ return label ?? v;
606
+ }
607
+ case "multi-select":
608
+ case "list": {
609
+ const arr = value;
610
+ const labels = arr.map((v) => question.options?.find((o) => o.value === v)?.label ?? v);
611
+ return labels.join("\n\u2022 ").replace(/^/, "\u2022 ");
612
+ }
613
+ case "composite": {
614
+ const obj = value;
615
+ const subs = question.subQuestions ?? [];
616
+ const lines = [];
617
+ for (const sub of subs) {
618
+ const subVal = obj[sub.id];
619
+ if (isAnswered(subVal)) {
620
+ lines.push(`${sub.prompt}: ${summarizeShort(sub, subVal, 200)}`);
621
+ }
622
+ }
623
+ return lines.join("\n");
624
+ }
625
+ case "repeater": {
626
+ const rows = value;
627
+ const itemSchema = question.itemSchema ?? [];
628
+ const summary = rows.map((row, i) => {
629
+ const parts = [`Item ${i + 1}:`];
630
+ for (const sub of itemSchema) {
631
+ const subVal = row[sub.id];
632
+ if (isAnswered(subVal)) {
633
+ parts.push(` ${sub.prompt}: ${summarizeShort(sub, subVal, 100)}`);
634
+ }
635
+ }
636
+ return parts.join("\n");
637
+ });
638
+ return summary.join("\n");
639
+ }
640
+ case "file-upload": {
641
+ const files = value;
642
+ return files.map((f, i) => `${i + 1}. ${f.file.name}`).join("\n");
643
+ }
644
+ default:
645
+ return "";
646
+ }
647
+ }
648
+ function truncate(s, maxLen) {
649
+ return s.length > maxLen ? s.slice(0, maxLen - 1) + "\u2026" : s;
650
+ }
260
651
 
261
652
  // src/AnswerSidebar.tsx
262
653
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
@@ -270,21 +661,20 @@ function AnswerSidebar({ questions, currentIndex, answers, onSelectQuestion }) {
270
661
  /* @__PURE__ */ jsx3("h3", { className: "mb-3 font-mono text-xs uppercase tracking-wider text-stone-500", children: "Your answers" }),
271
662
  /* @__PURE__ */ jsx3("ol", { className: "space-y-3", children: questions.map((q, i) => {
272
663
  const answer = answers[q.id];
273
- const answered = answer !== void 0 && answer !== "" && !(Array.isArray(answer) && answer.length === 0);
664
+ const answered = isAnswered(answer);
274
665
  const isCurrent = i === currentIndex;
666
+ const preview = answered ? summarizeShort(q, answer) : "";
275
667
  return /* @__PURE__ */ jsx3("li", { children: /* @__PURE__ */ jsx3(
276
668
  "button",
277
669
  {
278
670
  type: "button",
279
671
  onClick: () => onSelectQuestion(i),
280
- className: `w-full rounded-lg border p-3 text-left transition-colors
281
- ${isCurrent ? "border-forest-700 bg-forest-50" : answered ? "border-stone-200 bg-stone-50 hover:border-stone-400" : "border-stone-200/50 bg-stone-50/50 hover:border-stone-300"}`,
672
+ className: `w-full rounded-lg border p-3 text-left transition-colors ${isCurrent ? "border-forest-700 bg-forest-50" : answered ? "border-stone-200 bg-stone-50 hover:border-stone-400" : "border-stone-200/50 bg-stone-50/50 hover:border-stone-300"}`,
282
673
  children: /* @__PURE__ */ jsxs3("div", { className: "flex items-start gap-2", children: [
283
674
  /* @__PURE__ */ jsxs3(
284
675
  "span",
285
676
  {
286
- className: `mt-0.5 font-mono text-xs
287
- ${isCurrent ? "text-forest-700" : answered ? "text-stone-500" : "text-stone-400"}`,
677
+ className: `mt-0.5 font-mono text-xs ${isCurrent ? "text-forest-700" : answered ? "text-stone-500" : "text-stone-400"}`,
288
678
  children: [
289
679
  String(i + 1).padStart(2, "0"),
290
680
  "."
@@ -295,12 +685,11 @@ function AnswerSidebar({ questions, currentIndex, answers, onSelectQuestion }) {
295
685
  /* @__PURE__ */ jsx3(
296
686
  "div",
297
687
  {
298
- className: `text-xs font-medium
299
- ${answered ? "text-stone-900" : "text-stone-500"}`,
688
+ className: `text-xs font-medium ${answered ? "text-stone-900" : "text-stone-500"}`,
300
689
  children: q.prompt.length > 50 ? q.prompt.slice(0, 50) + "\u2026" : q.prompt
301
690
  }
302
691
  ),
303
- answered && /* @__PURE__ */ jsx3("div", { className: "mt-1 text-xs italic text-stone-500 line-clamp-1", children: Array.isArray(answer) ? answer.join(", ") : answer })
692
+ answered && /* @__PURE__ */ jsx3("div", { className: "mt-1 text-xs italic text-stone-500 line-clamp-1", children: preview })
304
693
  ] })
305
694
  ] })
306
695
  }
@@ -319,13 +708,13 @@ function ReviewSummary({ questions, answers, onGenerate, onEdit, generating }) {
319
708
  /* @__PURE__ */ jsx4("p", { className: "mt-4 text-lg text-stone-700", children: "These become the seed content of your project. You can edit anything now, or continue and edit later through the platform." }),
320
709
  /* @__PURE__ */ jsx4("dl", { className: "mt-10 space-y-6 border-t border-stone-200 pt-6", children: questions.map((q, i) => {
321
710
  const answer = answers[q.id];
322
- const display = Array.isArray(answer) ? answer.length > 0 ? answer.join(", ") : "(not answered)" : answer || "(not answered)";
323
- const isEmpty = !answer || typeof answer === "string" && answer.trim() === "" || Array.isArray(answer) && answer.length === 0;
711
+ const answered = isAnswered(answer);
712
+ const display = answered ? summarizeLong(q, answer) : "";
324
713
  return /* @__PURE__ */ jsxs4("div", { className: "flex gap-4 border-b border-stone-200 pb-6", children: [
325
714
  /* @__PURE__ */ jsx4("span", { className: "font-mono text-sm text-stone-400 pt-1", children: String(i + 1).padStart(2, "0") }),
326
715
  /* @__PURE__ */ jsxs4("div", { className: "flex-grow", children: [
327
716
  /* @__PURE__ */ jsx4("dt", { className: "text-sm font-medium text-stone-900", children: q.prompt }),
328
- /* @__PURE__ */ jsx4("dd", { className: "mt-2 text-stone-900 whitespace-pre-wrap", children: isEmpty ? /* @__PURE__ */ jsx4("span", { className: "italic text-stone-400", children: "Not answered" }) : display })
717
+ /* @__PURE__ */ jsx4("dd", { className: "mt-2 text-stone-900 whitespace-pre-wrap", children: answered ? display : /* @__PURE__ */ jsx4("span", { className: "italic text-stone-400", children: "Not answered" }) })
329
718
  ] }),
330
719
  /* @__PURE__ */ jsx4(
331
720
  "button",
@@ -362,6 +751,9 @@ export {
362
751
  AnswerSidebar,
363
752
  ProgressBar,
364
753
  QuestionCard,
365
- ReviewSummary
754
+ ReviewSummary,
755
+ isAnswered,
756
+ summarizeLong,
757
+ summarizeShort
366
758
  };
367
759
  //# sourceMappingURL=index.js.map