@dowel-ui/react 0.1.1 → 0.3.0
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 +1 -1
- package/README.md +193 -0
- package/dist/components/ai-action-ledger/ai-action-ledger.d.ts +77 -0
- package/dist/components/ai-action-ledger/ai-action-ledger.d.ts.map +1 -0
- package/dist/components/ai-action-ledger/ai-action-ledger.js +224 -0
- package/dist/components/ai-action-ledger/ai-action-ledger.js.map +1 -0
- package/dist/components/ai-action-ledger/index.d.ts +2 -0
- package/dist/components/ai-action-ledger/index.js +2 -0
- package/dist/components/ai-action-ledger/meta.js +17 -0
- package/dist/components/ai-action-ledger/meta.js.map +1 -0
- package/dist/components/ai-agent-plan/ai-agent-plan.d.ts +50 -0
- package/dist/components/ai-agent-plan/ai-agent-plan.d.ts.map +1 -0
- package/dist/components/ai-agent-plan/ai-agent-plan.js +156 -0
- package/dist/components/ai-agent-plan/ai-agent-plan.js.map +1 -0
- package/dist/components/ai-agent-plan/index.d.ts +2 -0
- package/dist/components/ai-agent-plan/index.js +2 -0
- package/dist/components/ai-agent-plan/meta.js +17 -0
- package/dist/components/ai-agent-plan/meta.js.map +1 -0
- package/dist/components/ai-agent-status/ai-agent-status.d.ts +1 -1
- package/dist/components/ai-approval-request/ai-approval-request.d.ts +67 -0
- package/dist/components/ai-approval-request/ai-approval-request.d.ts.map +1 -0
- package/dist/components/ai-approval-request/ai-approval-request.js +218 -0
- package/dist/components/ai-approval-request/ai-approval-request.js.map +1 -0
- package/dist/components/ai-approval-request/index.d.ts +2 -0
- package/dist/components/ai-approval-request/index.js +2 -0
- package/dist/components/ai-approval-request/meta.js +17 -0
- package/dist/components/ai-approval-request/meta.js.map +1 -0
- package/dist/components/ai-inline-completion/ai-inline-completion.d.ts +45 -0
- package/dist/components/ai-inline-completion/ai-inline-completion.d.ts.map +1 -0
- package/dist/components/ai-inline-completion/ai-inline-completion.js +110 -0
- package/dist/components/ai-inline-completion/ai-inline-completion.js.map +1 -0
- package/dist/components/ai-inline-completion/index.d.ts +2 -0
- package/dist/components/ai-inline-completion/index.js +2 -0
- package/dist/components/ai-inline-completion/meta.js +17 -0
- package/dist/components/ai-inline-completion/meta.js.map +1 -0
- package/dist/components/ai-response/ai-response.d.ts.map +1 -1
- package/dist/components/ai-response/ai-response.js +1 -0
- package/dist/components/ai-response/ai-response.js.map +1 -1
- package/dist/components/ai-structured-output/ai-structured-output.d.ts +75 -0
- package/dist/components/ai-structured-output/ai-structured-output.d.ts.map +1 -0
- package/dist/components/ai-structured-output/ai-structured-output.js +137 -0
- package/dist/components/ai-structured-output/ai-structured-output.js.map +1 -0
- package/dist/components/ai-structured-output/index.d.ts +2 -0
- package/dist/components/ai-structured-output/index.js +2 -0
- package/dist/components/ai-structured-output/meta.js +17 -0
- package/dist/components/ai-structured-output/meta.js.map +1 -0
- package/dist/components/ai-tool/ai-tool.d.ts +1 -1
- package/dist/components/alert/alert.d.ts +1 -1
- package/dist/components/badge/badge.d.ts +1 -1
- package/dist/components/button/button.d.ts +1 -1
- package/dist/components/file-upload/file-upload.d.ts +46 -0
- package/dist/components/file-upload/file-upload.d.ts.map +1 -0
- package/dist/components/file-upload/file-upload.js +204 -0
- package/dist/components/file-upload/file-upload.js.map +1 -0
- package/dist/components/file-upload/index.d.ts +3 -0
- package/dist/components/file-upload/index.js +3 -0
- package/dist/components/file-upload/meta.js +17 -0
- package/dist/components/file-upload/meta.js.map +1 -0
- package/dist/components/file-upload/upload-queue.d.ts +87 -0
- package/dist/components/file-upload/upload-queue.d.ts.map +1 -0
- package/dist/components/file-upload/upload-queue.js +249 -0
- package/dist/components/file-upload/upload-queue.js.map +1 -0
- package/dist/components/meter/index.d.ts +2 -0
- package/dist/components/meter/index.js +2 -0
- package/dist/components/meter/meta.js +17 -0
- package/dist/components/meter/meta.js.map +1 -0
- package/dist/components/meter/meter.d.ts +69 -0
- package/dist/components/meter/meter.d.ts.map +1 -0
- package/dist/components/meter/meter.js +144 -0
- package/dist/components/meter/meter.js.map +1 -0
- package/dist/components/metric-delta/index.d.ts +2 -0
- package/dist/components/metric-delta/index.js +2 -0
- package/dist/components/metric-delta/meta.js +17 -0
- package/dist/components/metric-delta/meta.js.map +1 -0
- package/dist/components/metric-delta/metric-delta.d.ts +59 -0
- package/dist/components/metric-delta/metric-delta.d.ts.map +1 -0
- package/dist/components/metric-delta/metric-delta.js +82 -0
- package/dist/components/metric-delta/metric-delta.js.map +1 -0
- package/dist/components/progress/progress.d.ts +1 -1
- package/dist/components/progress/progress.js +1 -0
- package/dist/components/progress/progress.js.map +1 -1
- package/dist/components/record-diff/index.d.ts +2 -0
- package/dist/components/record-diff/index.js +2 -0
- package/dist/components/record-diff/meta.js +17 -0
- package/dist/components/record-diff/meta.js.map +1 -0
- package/dist/components/record-diff/record-diff.d.ts +56 -0
- package/dist/components/record-diff/record-diff.d.ts.map +1 -0
- package/dist/components/record-diff/record-diff.js +199 -0
- package/dist/components/record-diff/record-diff.js.map +1 -0
- package/dist/components/spinner/spinner.js +1 -0
- package/dist/components/spinner/spinner.js.map +1 -1
- package/dist/components/tags-input/index.d.ts +2 -0
- package/dist/components/tags-input/index.js +2 -0
- package/dist/components/tags-input/meta.js +17 -0
- package/dist/components/tags-input/meta.js.map +1 -0
- package/dist/components/tags-input/tags-input.d.ts +47 -0
- package/dist/components/tags-input/tags-input.d.ts.map +1 -0
- package/dist/components/tags-input/tags-input.js +182 -0
- package/dist/components/tags-input/tags-input.js.map +1 -0
- package/dist/components/toast/toast.d.ts +1 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +12 -1
- package/dist/registry/components.d.ts.map +1 -1
- package/dist/registry/components.js +65 -45
- package/dist/registry/components.js.map +1 -1
- package/package.json +36 -4
- package/src/components/ai-action-ledger/ai-action-ledger.tsx +381 -0
- package/src/components/ai-action-ledger/index.ts +14 -0
- package/src/components/ai-action-ledger/meta.ts +19 -0
- package/src/components/ai-agent-plan/ai-agent-plan.tsx +0 -0
- package/src/components/ai-agent-plan/index.ts +10 -0
- package/src/components/ai-agent-plan/meta.ts +19 -0
- package/src/components/ai-approval-request/ai-approval-request.tsx +368 -0
- package/src/components/ai-approval-request/index.ts +7 -0
- package/src/components/ai-approval-request/meta.ts +21 -0
- package/src/components/ai-inline-completion/ai-inline-completion.tsx +214 -0
- package/src/components/ai-inline-completion/index.ts +1 -0
- package/src/components/ai-inline-completion/meta.ts +20 -0
- package/src/components/ai-response/ai-response.tsx +1 -0
- package/src/components/ai-structured-output/ai-structured-output.tsx +268 -0
- package/src/components/ai-structured-output/index.ts +10 -0
- package/src/components/ai-structured-output/meta.ts +18 -0
- package/src/components/file-upload/file-upload.tsx +322 -0
- package/src/components/file-upload/index.ts +20 -0
- package/src/components/file-upload/meta.ts +21 -0
- package/src/components/file-upload/upload-queue.ts +337 -0
- package/src/components/meter/index.ts +8 -0
- package/src/components/meter/meta.ts +20 -0
- package/src/components/meter/meter.tsx +219 -0
- package/src/components/metric-delta/index.ts +6 -0
- package/src/components/metric-delta/meta.ts +18 -0
- package/src/components/metric-delta/metric-delta.tsx +180 -0
- package/src/components/progress/progress.tsx +3 -0
- package/src/components/record-diff/index.ts +10 -0
- package/src/components/record-diff/meta.ts +19 -0
- package/src/components/record-diff/record-diff.tsx +341 -0
- package/src/components/spinner/spinner.tsx +5 -0
- package/src/components/tags-input/index.ts +1 -0
- package/src/components/tags-input/meta.ts +23 -0
- package/src/components/tags-input/tags-input.tsx +300 -0
- package/src/index.ts +10 -0
- package/src/registry/components.ts +20 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-inline-completion.d.ts","names":[],"sources":["../../../src/components/ai-inline-completion/ai-inline-completion.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;UAwCiB,8BAA8B,KAC7C;EAGA;EACA,gBAAgB;;;;;EAKhB;;EAEA,YAAY;;EAEZ;;EAEA;;iBASc,mBACd,WACA,OACA,eACA,YACA,UACA,WACA,YACA,WACA,aACG,SACF,wCAAqB,IAAA"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../../lib/utils.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { useId, useRef, useState } from "react";
|
|
5
|
+
//#region src/components/ai-inline-completion/ai-inline-completion.tsx
|
|
6
|
+
/** The next word of a suggestion, including its leading space. */
|
|
7
|
+
function nextWord(suggestion) {
|
|
8
|
+
const match = /^\s*\S+/.exec(suggestion);
|
|
9
|
+
return match ? match[0] : suggestion;
|
|
10
|
+
}
|
|
11
|
+
function InlineCompletion({ className, value, onValueChange, suggestion = "", onAccept, onDismiss, singleLine = false, onKeyDown, onScroll, ...props }) {
|
|
12
|
+
const fieldRef = useRef(null);
|
|
13
|
+
const mirrorRef = useRef(null);
|
|
14
|
+
const describedById = useId();
|
|
15
|
+
const [dismissed, setDismissed] = useState(false);
|
|
16
|
+
const [composing, setComposing] = useState(false);
|
|
17
|
+
const [caretAtEnd, setCaretAtEnd] = useState(true);
|
|
18
|
+
const visible = suggestion.length > 0 && !dismissed && !composing && caretAtEnd;
|
|
19
|
+
function syncCaret() {
|
|
20
|
+
const field = fieldRef.current;
|
|
21
|
+
if (!field) return;
|
|
22
|
+
setCaretAtEnd(field.selectionStart === value.length && field.selectionEnd === value.length);
|
|
23
|
+
}
|
|
24
|
+
function accept(text) {
|
|
25
|
+
const next = value + text;
|
|
26
|
+
onValueChange(next);
|
|
27
|
+
onAccept?.(next);
|
|
28
|
+
setDismissed(false);
|
|
29
|
+
}
|
|
30
|
+
function handleKeyDown(event) {
|
|
31
|
+
onKeyDown?.(event);
|
|
32
|
+
if (event.defaultPrevented) return;
|
|
33
|
+
if (!visible) return;
|
|
34
|
+
if (event.key === "ArrowRight" && (event.metaKey || event.altKey || event.ctrlKey)) {
|
|
35
|
+
event.preventDefault();
|
|
36
|
+
accept(nextWord(suggestion));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (event.key === "Tab") {
|
|
40
|
+
event.preventDefault();
|
|
41
|
+
accept(suggestion);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (event.key === "Escape") {
|
|
45
|
+
event.preventDefault();
|
|
46
|
+
setDismissed(true);
|
|
47
|
+
onDismiss?.();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function handleChange(event) {
|
|
51
|
+
setDismissed(false);
|
|
52
|
+
onValueChange(event.target.value);
|
|
53
|
+
queueMicrotask(syncCaret);
|
|
54
|
+
}
|
|
55
|
+
function handleScroll(event) {
|
|
56
|
+
onScroll?.(event);
|
|
57
|
+
if (mirrorRef.current) {
|
|
58
|
+
mirrorRef.current.scrollTop = event.currentTarget.scrollTop;
|
|
59
|
+
mirrorRef.current.scrollLeft = event.currentTarget.scrollLeft;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function handleComposition(event) {
|
|
63
|
+
setComposing(event.type === "compositionstart");
|
|
64
|
+
}
|
|
65
|
+
const shared = cn("w-full rounded-md border border-input px-3 py-2 text-sm", singleLine ? "overflow-x-auto whitespace-pre" : "break-words whitespace-pre-wrap");
|
|
66
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
67
|
+
"data-slot": "inline-completion",
|
|
68
|
+
className: cn("relative", className),
|
|
69
|
+
children: [
|
|
70
|
+
/* @__PURE__ */ jsxs("div", {
|
|
71
|
+
ref: mirrorRef,
|
|
72
|
+
"aria-hidden": "true",
|
|
73
|
+
"data-slot": "inline-completion-ghost",
|
|
74
|
+
className: cn(shared, "pointer-events-none absolute inset-0 overflow-hidden border-transparent text-transparent select-none"),
|
|
75
|
+
children: [value, visible ? /* @__PURE__ */ jsx("span", {
|
|
76
|
+
"data-slot": "inline-completion-suggestion",
|
|
77
|
+
className: "text-muted-foreground",
|
|
78
|
+
children: suggestion
|
|
79
|
+
}) : null]
|
|
80
|
+
}),
|
|
81
|
+
/* @__PURE__ */ jsx("textarea", {
|
|
82
|
+
ref: fieldRef,
|
|
83
|
+
"data-slot": "inline-completion-field",
|
|
84
|
+
"data-suggesting": visible || void 0,
|
|
85
|
+
rows: singleLine ? 1 : props.rows,
|
|
86
|
+
value,
|
|
87
|
+
onChange: handleChange,
|
|
88
|
+
onKeyDown: handleKeyDown,
|
|
89
|
+
onScroll: handleScroll,
|
|
90
|
+
onSelect: syncCaret,
|
|
91
|
+
onClick: syncCaret,
|
|
92
|
+
onCompositionStart: handleComposition,
|
|
93
|
+
onCompositionEnd: handleComposition,
|
|
94
|
+
"aria-describedby": visible ? describedById : void 0,
|
|
95
|
+
className: cn(shared, "relative resize-none bg-transparent", "placeholder:text-muted-foreground", "outline-none focus-visible:ring-2 focus-visible:ring-ring/55", singleLine && "resize-none"),
|
|
96
|
+
...props
|
|
97
|
+
}),
|
|
98
|
+
/* @__PURE__ */ jsx("span", {
|
|
99
|
+
id: describedById,
|
|
100
|
+
"aria-live": "polite",
|
|
101
|
+
className: "sr-only",
|
|
102
|
+
children: visible ? `Suggestion: ${suggestion}. Press Tab to accept, Escape to dismiss.` : ""
|
|
103
|
+
})
|
|
104
|
+
]
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
export { InlineCompletion };
|
|
109
|
+
|
|
110
|
+
//# sourceMappingURL=ai-inline-completion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-inline-completion.js","names":[],"sources":["../../../src/components/ai-inline-completion/ai-inline-completion.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n useId,\n useRef,\n useState,\n type ChangeEvent,\n type ComponentPropsWithRef,\n type CompositionEvent,\n type KeyboardEvent,\n type UIEvent,\n} from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Ghost-text suggestion inside a real textarea or input.\n *\n * Outside chat this is the most-copied AI affordance in software — Smart\n * Compose, Copilot's grey continuation, Notion and Linear's inline suggestions —\n * and every team rebuilds the same overlay-mirror trick from the same handful of\n * blog posts.\n *\n * Two deliberate limits, both stated rather than discovered:\n *\n * 1. It works on `textarea` and `input`, not on contenteditable. Ghost text in\n * contenteditable means owning selection, undo and inline formatting, which\n * is an editor, not a component. Anyone in that territory already has\n * ProseMirror or Lexical and should extend it there.\n * 2. The suggestion completes the END of the value. Mid-caret insertion needs\n * per-character measurement of wrapped text; the ghost hides whenever the\n * caret is not at the end rather than rendering in the wrong place.\n *\n * There is no WAI-ARIA pattern for generative ghost text. Combobox is the\n * nearest and does not fit: the suggestion is not one of a known set of options,\n * so a listbox would be a lie. What the standard does require is that the\n * suggestion be perceivable and that the keyboard is never trapped — so the text\n * is announced through a live description, and Escape always restores plain Tab.\n */\n\nexport interface InlineCompletionProps extends Omit<\n ComponentPropsWithRef<\"textarea\">,\n \"value\" | \"onChange\" | \"children\"\n> {\n value: string;\n onValueChange: (value: string) => void;\n /**\n * The continuation to show after the value. Empty or undefined shows nothing.\n * The caller supplies this; the component never requests it.\n */\n suggestion?: string;\n /** Called with the full text after the suggestion is taken. */\n onAccept?: (value: string) => void;\n /** Called when the reader rejects the suggestion outright. */\n onDismiss?: () => void;\n /** Renders a single-line input instead of a textarea. */\n singleLine?: boolean;\n}\n\n/** The next word of a suggestion, including its leading space. */\nfunction nextWord(suggestion: string): string {\n const match = /^\\s*\\S+/.exec(suggestion);\n return match ? match[0] : suggestion;\n}\n\nexport function InlineCompletion({\n className,\n value,\n onValueChange,\n suggestion = \"\",\n onAccept,\n onDismiss,\n singleLine = false,\n onKeyDown,\n onScroll,\n ...props\n}: InlineCompletionProps) {\n const fieldRef = useRef<HTMLTextAreaElement | null>(null);\n const mirrorRef = useRef<HTMLDivElement | null>(null);\n const describedById = useId();\n\n const [dismissed, setDismissed] = useState(false);\n const [composing, setComposing] = useState(false);\n const [caretAtEnd, setCaretAtEnd] = useState(true);\n\n // A suggestion is only shown when it can be shown honestly: not mid-word of\n // an IME composition, not after the reader has rejected it, and not when the\n // caret sits somewhere the ghost would render in the wrong place.\n const visible = suggestion.length > 0 && !dismissed && !composing && caretAtEnd;\n\n function syncCaret() {\n const field = fieldRef.current;\n if (!field) return;\n setCaretAtEnd(field.selectionStart === value.length && field.selectionEnd === value.length);\n }\n\n function accept(text: string) {\n const next = value + text;\n onValueChange(next);\n onAccept?.(next);\n setDismissed(false);\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLTextAreaElement>) {\n onKeyDown?.(event);\n if (event.defaultPrevented) return;\n\n if (!visible) return;\n\n // Word-at-a-time, before the plain Tab case so the modifier wins.\n if (event.key === \"ArrowRight\" && (event.metaKey || event.altKey || event.ctrlKey)) {\n event.preventDefault();\n accept(nextWord(suggestion));\n return;\n }\n\n if (event.key === \"Tab\") {\n event.preventDefault();\n accept(suggestion);\n return;\n }\n\n if (event.key === \"Escape\") {\n // The escape hatch. Without it, a keyboard user facing a suggestion has\n // no way to leave the field: Tab would accept instead of moving focus.\n event.preventDefault();\n setDismissed(true);\n onDismiss?.();\n }\n }\n\n function handleChange(event: ChangeEvent<HTMLTextAreaElement>) {\n // Any edit invalidates the previous rejection: the reader has moved on and\n // the next suggestion is a different one.\n setDismissed(false);\n onValueChange(event.target.value);\n queueMicrotask(syncCaret);\n }\n\n function handleScroll(event: UIEvent<HTMLTextAreaElement>) {\n onScroll?.(event);\n // The mirror has to follow the field, or the ghost detaches from the text\n // as soon as the content is taller than the box.\n if (mirrorRef.current) {\n mirrorRef.current.scrollTop = event.currentTarget.scrollTop;\n mirrorRef.current.scrollLeft = event.currentTarget.scrollLeft;\n }\n }\n\n function handleComposition(event: CompositionEvent<HTMLTextAreaElement>) {\n setComposing(event.type === \"compositionstart\");\n }\n\n // Typography must match exactly between field and mirror or the ghost lands\n // in the wrong place. Both take the same class string for that reason.\n const shared = cn(\n \"w-full rounded-md border border-input px-3 py-2 text-sm\",\n singleLine ? \"overflow-x-auto whitespace-pre\" : \"break-words whitespace-pre-wrap\",\n );\n\n return (\n <div data-slot=\"inline-completion\" className={cn(\"relative\", className)}>\n {/* The mirror sits behind the field and holds the value in transparent\n text purely to push the ghost to the caret position. */}\n <div\n ref={mirrorRef}\n aria-hidden=\"true\"\n data-slot=\"inline-completion-ghost\"\n className={cn(\n shared,\n \"pointer-events-none absolute inset-0 overflow-hidden border-transparent text-transparent select-none\",\n )}\n >\n {value}\n {visible ? (\n <span data-slot=\"inline-completion-suggestion\" className=\"text-muted-foreground\">\n {suggestion}\n </span>\n ) : null}\n </div>\n\n <textarea\n ref={fieldRef}\n data-slot=\"inline-completion-field\"\n data-suggesting={visible || undefined}\n rows={singleLine ? 1 : props.rows}\n value={value}\n onChange={handleChange}\n onKeyDown={handleKeyDown}\n onScroll={handleScroll}\n onSelect={syncCaret}\n onClick={syncCaret}\n onCompositionStart={handleComposition}\n onCompositionEnd={handleComposition}\n aria-describedby={visible ? describedById : undefined}\n className={cn(\n shared,\n \"relative resize-none bg-transparent\",\n \"placeholder:text-muted-foreground\",\n \"outline-none focus-visible:ring-2 focus-visible:ring-ring/55\",\n singleLine && \"resize-none\",\n )}\n {...props}\n />\n\n {/* The suggestion as text, for anyone who cannot see grey glyphs behind a\n field. Polite, so it does not interrupt typing, and it states the keys\n because a gesture nobody knows about is not an affordance. */}\n <span id={describedById} aria-live=\"polite\" className=\"sr-only\">\n {visible ? `Suggestion: ${suggestion}. Press Tab to accept, Escape to dismiss.` : \"\"}\n </span>\n </div>\n );\n}\n"],"mappings":";;;;;;AA4DA,SAAS,SAAS,YAA4B;CAC5C,MAAM,QAAQ,UAAU,KAAK,UAAU;CACvC,OAAO,QAAQ,MAAM,KAAK;AAC5B;AAEA,SAAgB,iBAAiB,EAC/B,WACA,OACA,eACA,aAAa,IACb,UACA,WACA,aAAa,OACb,WACA,UACA,GAAG,SACqB;CACxB,MAAM,WAAW,OAAmC,IAAI;CACxD,MAAM,YAAY,OAA8B,IAAI;CACpD,MAAM,gBAAgB,MAAM;CAE5B,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAChD,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAChD,MAAM,CAAC,YAAY,iBAAiB,SAAS,IAAI;CAKjD,MAAM,UAAU,WAAW,SAAS,KAAK,CAAC,aAAa,CAAC,aAAa;CAErE,SAAS,YAAY;EACnB,MAAM,QAAQ,SAAS;EACvB,IAAI,CAAC,OAAO;EACZ,cAAc,MAAM,mBAAmB,MAAM,UAAU,MAAM,iBAAiB,MAAM,MAAM;CAC5F;CAEA,SAAS,OAAO,MAAc;EAC5B,MAAM,OAAO,QAAQ;EACrB,cAAc,IAAI;EAClB,WAAW,IAAI;EACf,aAAa,KAAK;CACpB;CAEA,SAAS,cAAc,OAA2C;EAChE,YAAY,KAAK;EACjB,IAAI,MAAM,kBAAkB;EAE5B,IAAI,CAAC,SAAS;EAGd,IAAI,MAAM,QAAQ,iBAAiB,MAAM,WAAW,MAAM,UAAU,MAAM,UAAU;GAClF,MAAM,eAAe;GACrB,OAAO,SAAS,UAAU,CAAC;GAC3B;EACF;EAEA,IAAI,MAAM,QAAQ,OAAO;GACvB,MAAM,eAAe;GACrB,OAAO,UAAU;GACjB;EACF;EAEA,IAAI,MAAM,QAAQ,UAAU;GAG1B,MAAM,eAAe;GACrB,aAAa,IAAI;GACjB,YAAY;EACd;CACF;CAEA,SAAS,aAAa,OAAyC;EAG7D,aAAa,KAAK;EAClB,cAAc,MAAM,OAAO,KAAK;EAChC,eAAe,SAAS;CAC1B;CAEA,SAAS,aAAa,OAAqC;EACzD,WAAW,KAAK;EAGhB,IAAI,UAAU,SAAS;GACrB,UAAU,QAAQ,YAAY,MAAM,cAAc;GAClD,UAAU,QAAQ,aAAa,MAAM,cAAc;EACrD;CACF;CAEA,SAAS,kBAAkB,OAA8C;EACvE,aAAa,MAAM,SAAS,kBAAkB;CAChD;CAIA,MAAM,SAAS,GACb,2DACA,aAAa,mCAAmC,iCAClD;CAEA,OACE,qBAAC,OAAD;EAAK,aAAU;EAAoB,WAAW,GAAG,YAAY,SAAS;EAAtE,UAAA;GAGE,qBAAC,OAAD;IACE,KAAK;IACL,eAAY;IACZ,aAAU;IACV,WAAW,GACT,QACA,sGACF;IAPF,UAAA,CASG,OACA,UACC,oBAAC,QAAD;KAAM,aAAU;KAA+B,WAAU;KACtD,UAAA;IACG,CAAA,IACJ,IACD;;GAEL,oBAAC,YAAD;IACE,KAAK;IACL,aAAU;IACV,mBAAiB,WAAW,KAAA;IAC5B,MAAM,aAAa,IAAI,MAAM;IACtB;IACP,UAAU;IACV,WAAW;IACX,UAAU;IACV,UAAU;IACV,SAAS;IACT,oBAAoB;IACpB,kBAAkB;IAClB,oBAAkB,UAAU,gBAAgB,KAAA;IAC5C,WAAW,GACT,QACA,uCACA,qCACA,gEACA,cAAc,aAChB;IACA,GAAI;GACL,CAAA;GAKD,oBAAC,QAAD;IAAM,IAAI;IAAe,aAAU;IAAS,WAAU;IACnD,UAAA,UAAU,eAAe,WAAW,6CAA6C;GAC9E,CAAA;EACH;;AAET"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineMeta } from "../../registry/schema.js";
|
|
2
|
+
//#region src/components/ai-inline-completion/meta.ts
|
|
3
|
+
const meta = defineMeta({
|
|
4
|
+
name: "ai-inline-completion",
|
|
5
|
+
title: "AI Inline Completion",
|
|
6
|
+
description: "Ghost-text suggestion inside a real textarea, accepted with Tab.",
|
|
7
|
+
category: "ai",
|
|
8
|
+
status: "beta",
|
|
9
|
+
dependencies: [],
|
|
10
|
+
registryDependencies: [],
|
|
11
|
+
files: ["ai-inline-completion.tsx"],
|
|
12
|
+
a11y: "No WAI-ARIA pattern covers generative ghost text — combobox is the nearest and does not fit, because the suggestion is not one of a known set of options and a listbox would misdescribe it. So the suggestion is announced through a polite live description that names the keys, since a gesture nobody knows about is not an affordance, and the grey text itself is aria-hidden. Escape always dismisses and restores plain Tab, so the keyboard is never trapped in the field. The ghost hides during IME composition and whenever the caret is not at the end, rather than rendering somewhere it does not belong."
|
|
13
|
+
});
|
|
14
|
+
//#endregion
|
|
15
|
+
export { meta };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta.js","names":[],"sources":["../../../src/components/ai-inline-completion/meta.ts"],"sourcesContent":["import { defineMeta } from \"@/registry/schema\";\n\nexport const meta = defineMeta({\n name: \"ai-inline-completion\",\n title: \"AI Inline Completion\",\n description: \"Ghost-text suggestion inside a real textarea, accepted with Tab.\",\n category: \"ai\",\n status: \"beta\",\n dependencies: [],\n registryDependencies: [],\n files: [\"ai-inline-completion.tsx\"],\n a11y:\n \"No WAI-ARIA pattern covers generative ghost text — combobox is the nearest and does not fit, \" +\n \"because the suggestion is not one of a known set of options and a listbox would misdescribe \" +\n \"it. So the suggestion is announced through a polite live description that names the keys, \" +\n \"since a gesture nobody knows about is not an affordance, and the grey text itself is \" +\n \"aria-hidden. Escape always dismisses and restores plain Tab, so the keyboard is never \" +\n \"trapped in the field. The ghost hides during IME composition and whenever the caret is not \" +\n \"at the end, rather than rendering somewhere it does not belong.\",\n});\n"],"mappings":";;AAEA,MAAa,OAAO,WAAW;CAC7B,MAAM;CACN,OAAO;CACP,aAAa;CACb,UAAU;CACV,QAAQ;CACR,cAAc,CAAC;CACf,sBAAsB,CAAC;CACvB,OAAO,CAAC,0BAA0B;CAClC,MACE;AAOJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-response.d.ts","names":[],"sources":["../../../src/components/ai-response/ai-response.tsx"],"mappings":";;;;;;;;;;;;;;UAkBiB,sBAAsB;;EAErC;;iBAGc,WAAW,WAAW,WAAW,aAAa,SAAS,gCAAa,IAAA;;;iBAwBpE,gBAAgB,cAAc,SAAS,gDAA6B,IAAA;;;;;;;;
|
|
1
|
+
{"version":3,"file":"ai-response.d.ts","names":[],"sources":["../../../src/components/ai-response/ai-response.tsx"],"mappings":";;;;;;;;;;;;;;UAkBiB,sBAAsB;;EAErC;;iBAGc,WAAW,WAAW,WAAW,aAAa,SAAS,gCAAa,IAAA;;;iBAwBpE,gBAAgB,cAAc,SAAS,gDAA6B,IAAA;;;;;;;;UAsBnE,+BAA+B;EAC9C;;iBAGc,oBACd,WACA,UACG,SACF,yCAAsB,IAAA"}
|
|
@@ -17,6 +17,7 @@ function ResponseCaret({ className, ...props }) {
|
|
|
17
17
|
return /* @__PURE__ */ jsx("span", {
|
|
18
18
|
"data-slot": "response-caret",
|
|
19
19
|
"aria-hidden": "true",
|
|
20
|
+
"data-motion": "indicator",
|
|
20
21
|
className: cn("ml-0.5 inline-block h-[1em] w-[2px] translate-y-[0.15em] animate-caret bg-current align-baseline", className),
|
|
21
22
|
...props
|
|
22
23
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-response.js","names":[],"sources":["../../../src/components/ai-response/ai-response.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ComponentPropsWithRef } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * The text of an assistant response.\n *\n * Renders plain text with paragraph whitespace preserved. Markdown is\n * deliberately not parsed here: a Markdown renderer is a dependency, a security\n * decision about raw HTML, and a styling surface all at once, and every product\n * makes those differently. Pass already-rendered content as children when you\n * need it — the prose styling below applies either way.\n *\n * Not a live region, and not by omission. See the note on `Conversation`:\n * announcing streamed text token by token is unusable with a screen reader.\n */\nexport interface ResponseProps extends ComponentPropsWithRef<\"div\"> {\n /** Shows the caret. Purely visual — it carries no announcement. */\n streaming?: boolean;\n}\n\nexport function Response({ className, streaming, children, ...props }: ResponseProps) {\n return (\n <div\n data-slot=\"response\"\n data-streaming={streaming || undefined}\n className={cn(\n \"text-sm leading-relaxed whitespace-pre-wrap text-foreground\",\n // Minimal prose styling for consumers who pass rendered Markdown.\n \"[&_a]:text-primary [&_a]:underline [&_a]:underline-offset-4\",\n \"[&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-[0.9em]\",\n \"[&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-5 [&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-5\",\n \"[&_p+p]:mt-3\",\n className,\n )}\n {...props}\n >\n {children}\n {streaming ? <ResponseCaret /> : null}\n </div>\n );\n}\n\n/** The trailing cursor. Decorative — the streaming state is announced, if at\n * all, through ConversationStatus. */\nexport function ResponseCaret({ className, ...props }: ComponentPropsWithRef<\"span\">) {\n return (\n <span\n data-slot=\"response-caret\"\n aria-hidden=\"true\"\n className={cn(\n \"ml-0.5 inline-block h-[1em] w-[2px] translate-y-[0.15em] animate-caret bg-current align-baseline\",\n className,\n )}\n {...props}\n />\n );\n}\n\n/**\n * Shown while waiting for the first token.\n *\n * A distinct state from streaming: nothing has arrived yet, so there is no text\n * to show a caret after. Labelled for assistive technology because, unlike the\n * caret, this is the only thing on screen.\n */\nexport interface ThinkingIndicatorProps extends ComponentPropsWithRef<\"div\"> {\n label?: string;\n}\n\nexport function ThinkingIndicator({\n className,\n label = \"Thinking\",\n ...props\n}: ThinkingIndicatorProps) {\n return (\n <div\n data-slot=\"thinking-indicator\"\n className={cn(\"flex items-center gap-1.5 text-sm text-muted-foreground\", className)}\n {...props}\n >\n <span className=\"sr-only\">{label}</span>\n {[0, 1, 2].map((index) => (\n <span\n key={index}\n aria-hidden=\"true\"\n className=\"size-1.5 animate-pulse-soft rounded-full bg-current\"\n style={{ animationDelay: `${String(index * 160)}ms` }}\n />\n ))}\n </div>\n );\n}\n"],"mappings":";;;;AAuBA,SAAgB,SAAS,EAAE,WAAW,WAAW,UAAU,GAAG,SAAwB;CACpF,OACE,qBAAC,OAAD;EACE,aAAU;EACV,kBAAgB,aAAa,KAAA;EAC7B,WAAW,GACT,+DAEA,+DACA,6GACA,wFACA,gBACA,SACF;EACA,GAAI;EAZN,UAAA,CAcG,UACA,YAAY,oBAAC,eAAD,CAAgB,CAAA,IAAI,IAC9B;;AAET;;;AAIA,SAAgB,cAAc,EAAE,WAAW,GAAG,SAAwC;CACpF,OACE,oBAAC,QAAD;EACE,aAAU;EACV,eAAY;EACZ,WAAW,GACT,oGACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAaA,SAAgB,kBAAkB,EAChC,WACA,QAAQ,YACR,GAAG,SACsB;CACzB,OACE,qBAAC,OAAD;EACE,aAAU;EACV,WAAW,GAAG,2DAA2D,SAAS;EAClF,GAAI;EAHN,UAAA,CAKE,oBAAC,QAAD;GAAM,WAAU;GAAW,UAAA;EAAY,CAAA,GACtC;GAAC;GAAG;GAAG;EAAC,CAAC,CAAC,KAAK,UACd,oBAAC,QAAD;GAEE,eAAY;GACZ,WAAU;GACV,OAAO,EAAE,gBAAgB,GAAG,OAAO,QAAQ,GAAG,EAAE,IAAI;EACrD,GAJM,KAIN,CACF,CACE;;AAET"}
|
|
1
|
+
{"version":3,"file":"ai-response.js","names":[],"sources":["../../../src/components/ai-response/ai-response.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ComponentPropsWithRef } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * The text of an assistant response.\n *\n * Renders plain text with paragraph whitespace preserved. Markdown is\n * deliberately not parsed here: a Markdown renderer is a dependency, a security\n * decision about raw HTML, and a styling surface all at once, and every product\n * makes those differently. Pass already-rendered content as children when you\n * need it — the prose styling below applies either way.\n *\n * Not a live region, and not by omission. See the note on `Conversation`:\n * announcing streamed text token by token is unusable with a screen reader.\n */\nexport interface ResponseProps extends ComponentPropsWithRef<\"div\"> {\n /** Shows the caret. Purely visual — it carries no announcement. */\n streaming?: boolean;\n}\n\nexport function Response({ className, streaming, children, ...props }: ResponseProps) {\n return (\n <div\n data-slot=\"response\"\n data-streaming={streaming || undefined}\n className={cn(\n \"text-sm leading-relaxed whitespace-pre-wrap text-foreground\",\n // Minimal prose styling for consumers who pass rendered Markdown.\n \"[&_a]:text-primary [&_a]:underline [&_a]:underline-offset-4\",\n \"[&_code]:rounded [&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-[0.9em]\",\n \"[&_ol]:my-2 [&_ol]:list-decimal [&_ol]:pl-5 [&_ul]:my-2 [&_ul]:list-disc [&_ul]:pl-5\",\n \"[&_p+p]:mt-3\",\n className,\n )}\n {...props}\n >\n {children}\n {streaming ? <ResponseCaret /> : null}\n </div>\n );\n}\n\n/** The trailing cursor. Decorative — the streaming state is announced, if at\n * all, through ConversationStatus. */\nexport function ResponseCaret({ className, ...props }: ComponentPropsWithRef<\"span\">) {\n return (\n <span\n data-slot=\"response-caret\"\n aria-hidden=\"true\"\n data-motion=\"indicator\"\n className={cn(\n \"ml-0.5 inline-block h-[1em] w-[2px] translate-y-[0.15em] animate-caret bg-current align-baseline\",\n className,\n )}\n {...props}\n />\n );\n}\n\n/**\n * Shown while waiting for the first token.\n *\n * A distinct state from streaming: nothing has arrived yet, so there is no text\n * to show a caret after. Labelled for assistive technology because, unlike the\n * caret, this is the only thing on screen.\n */\nexport interface ThinkingIndicatorProps extends ComponentPropsWithRef<\"div\"> {\n label?: string;\n}\n\nexport function ThinkingIndicator({\n className,\n label = \"Thinking\",\n ...props\n}: ThinkingIndicatorProps) {\n return (\n <div\n data-slot=\"thinking-indicator\"\n className={cn(\"flex items-center gap-1.5 text-sm text-muted-foreground\", className)}\n {...props}\n >\n <span className=\"sr-only\">{label}</span>\n {[0, 1, 2].map((index) => (\n <span\n key={index}\n aria-hidden=\"true\"\n className=\"size-1.5 animate-pulse-soft rounded-full bg-current\"\n style={{ animationDelay: `${String(index * 160)}ms` }}\n />\n ))}\n </div>\n );\n}\n"],"mappings":";;;;AAuBA,SAAgB,SAAS,EAAE,WAAW,WAAW,UAAU,GAAG,SAAwB;CACpF,OACE,qBAAC,OAAD;EACE,aAAU;EACV,kBAAgB,aAAa,KAAA;EAC7B,WAAW,GACT,+DAEA,+DACA,6GACA,wFACA,gBACA,SACF;EACA,GAAI;EAZN,UAAA,CAcG,UACA,YAAY,oBAAC,eAAD,CAAgB,CAAA,IAAI,IAC9B;;AAET;;;AAIA,SAAgB,cAAc,EAAE,WAAW,GAAG,SAAwC;CACpF,OACE,oBAAC,QAAD;EACE,aAAU;EACV,eAAY;EACZ,eAAY;EACZ,WAAW,GACT,oGACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAaA,SAAgB,kBAAkB,EAChC,WACA,QAAQ,YACR,GAAG,SACsB;CACzB,OACE,qBAAC,OAAD;EACE,aAAU;EACV,WAAW,GAAG,2DAA2D,SAAS;EAClF,GAAI;EAHN,UAAA,CAKE,oBAAC,QAAD;GAAM,WAAU;GAAW,UAAA;EAAY,CAAA,GACtC;GAAC;GAAG;GAAG;EAAC,CAAC,CAAC,KAAK,UACd,oBAAC,QAAD;GAEE,eAAY;GACZ,WAAU;GACV,OAAO,EAAE,gBAAgB,GAAG,OAAO,QAAQ,GAAG,EAAE,IAAI;EACrD,GAJM,KAIN,CACF,CACE;;AAET"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { ComponentPropsWithRef, ReactNode } from "react";
|
|
2
|
+
//#region src/components/ai-structured-output/ai-structured-output.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* An object arriving field by field from the model.
|
|
5
|
+
*
|
|
6
|
+
* Most AI features inside real software are not chatbots. They are invoice
|
|
7
|
+
* extraction, CRM enrichment, resume parsing, form autofill — a schema goes in
|
|
8
|
+
* and a filled object comes back. Every one of them streams a partial object
|
|
9
|
+
* into a layout, and almost every one reflows on each token because the fields
|
|
10
|
+
* appear as they arrive.
|
|
11
|
+
*
|
|
12
|
+
* The honest limitation, stated here rather than discovered later: there is no
|
|
13
|
+
* per-field completion signal anywhere in the stack. A streaming API hands you
|
|
14
|
+
* successive partial snapshots, and a string that has stopped growing is
|
|
15
|
+
* indistinguishable from one still arriving. So this component does not pretend
|
|
16
|
+
* to know. It takes the field list up front, reserves the layout from it, and
|
|
17
|
+
* lets the caller say which fields are settled — defaulting to the rule that a
|
|
18
|
+
* field is settled once a later field has appeared, which is true of
|
|
19
|
+
* depth-first JSON streaming and is documented rather than assumed silently.
|
|
20
|
+
*/
|
|
21
|
+
type FieldState = "pending" | "streaming" | "settled" | "error";
|
|
22
|
+
interface OutputField {
|
|
23
|
+
/** Key in the streamed object. */
|
|
24
|
+
name: string;
|
|
25
|
+
label: string;
|
|
26
|
+
/** Rough height reserved before the value arrives, in lines. */
|
|
27
|
+
lines?: number;
|
|
28
|
+
}
|
|
29
|
+
interface StructuredOutputProps extends Omit<ComponentPropsWithRef<"dl">, "children"> {
|
|
30
|
+
/** Declared up front, so layout is reserved before anything arrives. */
|
|
31
|
+
fields: OutputField[];
|
|
32
|
+
/** The latest partial snapshot. */
|
|
33
|
+
value: Record<string, unknown>;
|
|
34
|
+
/**
|
|
35
|
+
* True while more of the object is still expected. When false every present
|
|
36
|
+
* field is settled, which is the only completion signal that is ever certain.
|
|
37
|
+
*/
|
|
38
|
+
streaming?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Fields the caller knows are finished. Supply this when the API gives a
|
|
41
|
+
* real signal; otherwise the default rule applies.
|
|
42
|
+
*/
|
|
43
|
+
settled?: string[];
|
|
44
|
+
/** Fields that failed to extract, with the reason shown in place. */
|
|
45
|
+
errors?: Record<string, string>;
|
|
46
|
+
formatValue?: (value: unknown, name: string) => string;
|
|
47
|
+
children?: ReactNode;
|
|
48
|
+
}
|
|
49
|
+
declare function StructuredOutput({ className, fields, value, streaming, settled, errors, formatValue, children, ...props }: StructuredOutputProps): import("react").JSX.Element;
|
|
50
|
+
interface StructuredFieldProps extends Omit<ComponentPropsWithRef<"div">, "children"> {
|
|
51
|
+
name: string;
|
|
52
|
+
children?: ReactNode;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* One field: its label, its value, and the space it will occupy before it has
|
|
56
|
+
* one.
|
|
57
|
+
*
|
|
58
|
+
* The reserved height is what stops the layout jumping as the object fills in.
|
|
59
|
+
*/
|
|
60
|
+
declare function StructuredField({ className, name, children, ...props }: StructuredFieldProps): import("react").JSX.Element;
|
|
61
|
+
/**
|
|
62
|
+
* A confidence score for one field.
|
|
63
|
+
*
|
|
64
|
+
* Rendered as text with the number, never as a bare colour, because "how much
|
|
65
|
+
* should I trust this" is exactly the thing a colour cannot answer.
|
|
66
|
+
*/
|
|
67
|
+
declare function StructuredConfidence({ className, value: confidence, lowBelow, ...props }: ComponentPropsWithRef<"p"> & {
|
|
68
|
+
value: number;
|
|
69
|
+
lowBelow?: number;
|
|
70
|
+
}): import("react").JSX.Element;
|
|
71
|
+
/** The reason a field could not be extracted, shown where the value would be. */
|
|
72
|
+
declare function StructuredFieldError({ className, ...props }: ComponentPropsWithRef<"p">): import("react").JSX.Element;
|
|
73
|
+
//#endregion
|
|
74
|
+
export { FieldState, OutputField, StructuredConfidence, StructuredField, StructuredFieldError, StructuredFieldProps, StructuredOutput, StructuredOutputProps };
|
|
75
|
+
//# sourceMappingURL=ai-structured-output.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-structured-output.d.ts","names":[],"sources":["../../../src/components/ai-structured-output/ai-structured-output.tsx"],"mappings":";;;;;;;;;;;;;;;;;;;;KAgCY;UAEK;;EAEf;EACA;;EAEA;;UA6Be,8BAA8B,KAAK;;EAElD,QAAQ;;EAER,OAAO;;;;;EAKP;;;;;EAKA;;EAEA,SAAS;EACT,eAAe,gBAAgB;EAC/B,WAAW;;iBAGG,mBACd,WACA,QACA,OACA,WACA,SACA,QACA,aACA,aACG,SACF,wCAAqB,IAAA;UAiDP,6BAA6B,KAAK;EACjD;EACA,WAAW;;;;;;;;iBASG,kBAAkB,WAAW,MAAM,aAAa,SAAS,uCAAoB,IAAA;;;;;;;iBA2E7E,uBACd,WACA,OAAO,YACP,aACG,SACF;EAA+B;EAAe;oBAAmB,IAAA;;iBAoBpD,uBAAuB,cAAc,SAAS,6CAA0B,IAAA"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../../lib/utils.js";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { createContext, useContext, useMemo, useState } from "react";
|
|
5
|
+
//#region src/components/ai-structured-output/ai-structured-output.tsx
|
|
6
|
+
const StructuredOutputContext = createContext(null);
|
|
7
|
+
function useStructuredOutputContext(component) {
|
|
8
|
+
const context = useContext(StructuredOutputContext);
|
|
9
|
+
if (!context) throw new Error(`${component} must be rendered inside <StructuredOutput>.`);
|
|
10
|
+
return context;
|
|
11
|
+
}
|
|
12
|
+
function defaultFormat(value) {
|
|
13
|
+
if (value === null || value === void 0) return "";
|
|
14
|
+
if (typeof value === "string") return value;
|
|
15
|
+
if (typeof value === "number" || typeof value === "bigint") return String(value);
|
|
16
|
+
if (typeof value === "boolean") return value ? "Yes" : "No";
|
|
17
|
+
if (Array.isArray(value)) return value.map(defaultFormat).join(", ");
|
|
18
|
+
return JSON.stringify(value);
|
|
19
|
+
}
|
|
20
|
+
function StructuredOutput({ className, fields, value, streaming = false, settled, errors, formatValue = defaultFormat, children, ...props }) {
|
|
21
|
+
const stateOf = useMemo(() => {
|
|
22
|
+
const explicit = settled ? new Set(settled) : null;
|
|
23
|
+
const lastPresentIndex = fields.reduce((last, field, index) => value[field.name] !== void 0 ? index : last, -1);
|
|
24
|
+
return (name) => {
|
|
25
|
+
if (errors?.[name]) return "error";
|
|
26
|
+
const index = fields.findIndex((field) => field.name === name);
|
|
27
|
+
if (!(value[name] !== void 0)) return "pending";
|
|
28
|
+
if (explicit) return explicit.has(name) ? "settled" : "streaming";
|
|
29
|
+
if (!streaming) return "settled";
|
|
30
|
+
return index < lastPresentIndex ? "settled" : "streaming";
|
|
31
|
+
};
|
|
32
|
+
}, [
|
|
33
|
+
fields,
|
|
34
|
+
value,
|
|
35
|
+
streaming,
|
|
36
|
+
settled,
|
|
37
|
+
errors
|
|
38
|
+
]);
|
|
39
|
+
const context = useMemo(() => ({
|
|
40
|
+
fields,
|
|
41
|
+
value,
|
|
42
|
+
stateOf,
|
|
43
|
+
formatValue
|
|
44
|
+
}), [
|
|
45
|
+
fields,
|
|
46
|
+
value,
|
|
47
|
+
stateOf,
|
|
48
|
+
formatValue
|
|
49
|
+
]);
|
|
50
|
+
return /* @__PURE__ */ jsx(StructuredOutputContext.Provider, {
|
|
51
|
+
value: context,
|
|
52
|
+
children: /* @__PURE__ */ jsx("dl", {
|
|
53
|
+
"data-slot": "structured-output",
|
|
54
|
+
"data-streaming": streaming || void 0,
|
|
55
|
+
"aria-live": "polite",
|
|
56
|
+
"aria-busy": streaming,
|
|
57
|
+
className: cn("flex flex-col gap-3", className),
|
|
58
|
+
...props,
|
|
59
|
+
children: children ?? fields.map((field) => /* @__PURE__ */ jsx(StructuredField, { name: field.name }, field.name))
|
|
60
|
+
})
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* One field: its label, its value, and the space it will occupy before it has
|
|
65
|
+
* one.
|
|
66
|
+
*
|
|
67
|
+
* The reserved height is what stops the layout jumping as the object fills in.
|
|
68
|
+
*/
|
|
69
|
+
function StructuredField({ className, name, children, ...props }) {
|
|
70
|
+
const { fields, value, stateOf, formatValue } = useStructuredOutputContext("StructuredField");
|
|
71
|
+
const field = fields.find((candidate) => candidate.name === name);
|
|
72
|
+
if (!field) throw new Error(`StructuredField "${name}" is not declared in the fields prop.`);
|
|
73
|
+
const state = stateOf(name);
|
|
74
|
+
const raw = value[name];
|
|
75
|
+
const [seenState, setSeenState] = useState(state);
|
|
76
|
+
const [hasSettled, setHasSettled] = useState(false);
|
|
77
|
+
if (seenState !== state) {
|
|
78
|
+
setSeenState(state);
|
|
79
|
+
if (state === "settled" && seenState !== "settled") setHasSettled(true);
|
|
80
|
+
}
|
|
81
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
82
|
+
"data-slot": "structured-field",
|
|
83
|
+
"data-state": state,
|
|
84
|
+
"data-settled": hasSettled || void 0,
|
|
85
|
+
"data-field": name,
|
|
86
|
+
className: cn("flex flex-col gap-1", className),
|
|
87
|
+
...props,
|
|
88
|
+
children: [/* @__PURE__ */ jsx("dt", {
|
|
89
|
+
className: "text-xs text-muted-foreground",
|
|
90
|
+
children: field.label
|
|
91
|
+
}), /* @__PURE__ */ jsx("dd", {
|
|
92
|
+
className: cn("m-0 text-sm", state === "pending" && "text-muted-foreground", state === "error" && "text-destructive"),
|
|
93
|
+
style: { minHeight: `${String((field.lines ?? 1) * 1.25)}rem` },
|
|
94
|
+
children: state === "pending" ? /* @__PURE__ */ jsx("span", {
|
|
95
|
+
"data-slot": "structured-field-placeholder",
|
|
96
|
+
"aria-hidden": "true",
|
|
97
|
+
className: "block h-4 w-24 animate-pulse-soft rounded bg-muted"
|
|
98
|
+
}) : /* @__PURE__ */ jsx("span", {
|
|
99
|
+
"data-slot": "structured-field-value",
|
|
100
|
+
className: cn(hasSettled && "inline-block animate-settle"),
|
|
101
|
+
children: children ?? formatValue(raw, name)
|
|
102
|
+
})
|
|
103
|
+
})]
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* A confidence score for one field.
|
|
108
|
+
*
|
|
109
|
+
* Rendered as text with the number, never as a bare colour, because "how much
|
|
110
|
+
* should I trust this" is exactly the thing a colour cannot answer.
|
|
111
|
+
*/
|
|
112
|
+
function StructuredConfidence({ className, value: confidence, lowBelow = .7, ...props }) {
|
|
113
|
+
const low = confidence < lowBelow;
|
|
114
|
+
const percent = new Intl.NumberFormat(void 0, {
|
|
115
|
+
style: "percent",
|
|
116
|
+
maximumFractionDigits: 0
|
|
117
|
+
}).format(confidence);
|
|
118
|
+
return /* @__PURE__ */ jsx("p", {
|
|
119
|
+
"data-slot": "structured-confidence",
|
|
120
|
+
"data-low": low || void 0,
|
|
121
|
+
className: cn("text-xs", low ? "text-warning" : "text-muted-foreground", className),
|
|
122
|
+
...props,
|
|
123
|
+
children: low ? `Low confidence, ${percent} — worth checking` : `${percent} confidence`
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/** The reason a field could not be extracted, shown where the value would be. */
|
|
127
|
+
function StructuredFieldError({ className, ...props }) {
|
|
128
|
+
return /* @__PURE__ */ jsx("p", {
|
|
129
|
+
"data-slot": "structured-field-error",
|
|
130
|
+
className: cn("text-xs text-destructive", className),
|
|
131
|
+
...props
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
export { StructuredConfidence, StructuredField, StructuredFieldError, StructuredOutput };
|
|
136
|
+
|
|
137
|
+
//# sourceMappingURL=ai-structured-output.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-structured-output.js","names":[],"sources":["../../../src/components/ai-structured-output/ai-structured-output.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useContext,\n useMemo,\n useState,\n type ComponentPropsWithRef,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\n/**\n * An object arriving field by field from the model.\n *\n * Most AI features inside real software are not chatbots. They are invoice\n * extraction, CRM enrichment, resume parsing, form autofill — a schema goes in\n * and a filled object comes back. Every one of them streams a partial object\n * into a layout, and almost every one reflows on each token because the fields\n * appear as they arrive.\n *\n * The honest limitation, stated here rather than discovered later: there is no\n * per-field completion signal anywhere in the stack. A streaming API hands you\n * successive partial snapshots, and a string that has stopped growing is\n * indistinguishable from one still arriving. So this component does not pretend\n * to know. It takes the field list up front, reserves the layout from it, and\n * lets the caller say which fields are settled — defaulting to the rule that a\n * field is settled once a later field has appeared, which is true of\n * depth-first JSON streaming and is documented rather than assumed silently.\n */\n\nexport type FieldState = \"pending\" | \"streaming\" | \"settled\" | \"error\";\n\nexport interface OutputField {\n /** Key in the streamed object. */\n name: string;\n label: string;\n /** Rough height reserved before the value arrives, in lines. */\n lines?: number;\n}\n\ninterface StructuredOutputContextValue {\n fields: OutputField[];\n value: Record<string, unknown>;\n stateOf: (name: string) => FieldState;\n formatValue: (value: unknown, name: string) => string;\n}\n\nconst StructuredOutputContext = createContext<StructuredOutputContextValue | null>(null);\n\nfunction useStructuredOutputContext(component: string): StructuredOutputContextValue {\n const context = useContext(StructuredOutputContext);\n if (!context) {\n throw new Error(`${component} must be rendered inside <StructuredOutput>.`);\n }\n return context;\n}\n\nfunction defaultFormat(value: unknown): string {\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\" || typeof value === \"bigint\") return String(value);\n if (typeof value === \"boolean\") return value ? \"Yes\" : \"No\";\n if (Array.isArray(value)) return value.map(defaultFormat).join(\", \");\n return JSON.stringify(value);\n}\n\nexport interface StructuredOutputProps extends Omit<ComponentPropsWithRef<\"dl\">, \"children\"> {\n /** Declared up front, so layout is reserved before anything arrives. */\n fields: OutputField[];\n /** The latest partial snapshot. */\n value: Record<string, unknown>;\n /**\n * True while more of the object is still expected. When false every present\n * field is settled, which is the only completion signal that is ever certain.\n */\n streaming?: boolean;\n /**\n * Fields the caller knows are finished. Supply this when the API gives a\n * real signal; otherwise the default rule applies.\n */\n settled?: string[];\n /** Fields that failed to extract, with the reason shown in place. */\n errors?: Record<string, string>;\n formatValue?: (value: unknown, name: string) => string;\n children?: ReactNode;\n}\n\nexport function StructuredOutput({\n className,\n fields,\n value,\n streaming = false,\n settled,\n errors,\n formatValue = defaultFormat,\n children,\n ...props\n}: StructuredOutputProps) {\n const stateOf = useMemo(() => {\n const explicit = settled ? new Set(settled) : null;\n const lastPresentIndex = fields.reduce(\n (last, field, index) => (value[field.name] !== undefined ? index : last),\n -1,\n );\n\n return (name: string): FieldState => {\n if (errors?.[name]) return \"error\";\n\n const index = fields.findIndex((field) => field.name === name);\n const present = value[name] !== undefined;\n\n if (!present) return \"pending\";\n if (explicit) return explicit.has(name) ? \"settled\" : \"streaming\";\n // No explicit signal. Once a later field has appeared this one cannot\n // still be growing, which holds for depth-first JSON streaming. The last\n // present field stays \"streaming\" until the caller says streaming ended.\n if (!streaming) return \"settled\";\n return index < lastPresentIndex ? \"settled\" : \"streaming\";\n };\n }, [fields, value, streaming, settled, errors]);\n\n const context = useMemo<StructuredOutputContextValue>(\n () => ({ fields, value, stateOf, formatValue }),\n [fields, value, stateOf, formatValue],\n );\n\n return (\n <StructuredOutputContext.Provider value={context}>\n <dl\n data-slot=\"structured-output\"\n data-streaming={streaming || undefined}\n // Fields arrive one at a time and each is a small, self-contained fact,\n // so the region is polite and additive rather than re-reading the whole\n // object on every token.\n aria-live=\"polite\"\n aria-busy={streaming}\n className={cn(\"flex flex-col gap-3\", className)}\n {...props}\n >\n {children ??\n fields.map((field) => <StructuredField key={field.name} name={field.name} />)}\n </dl>\n </StructuredOutputContext.Provider>\n );\n}\n\nexport interface StructuredFieldProps extends Omit<ComponentPropsWithRef<\"div\">, \"children\"> {\n name: string;\n children?: ReactNode;\n}\n\n/**\n * One field: its label, its value, and the space it will occupy before it has\n * one.\n *\n * The reserved height is what stops the layout jumping as the object fills in.\n */\nexport function StructuredField({ className, name, children, ...props }: StructuredFieldProps) {\n const { fields, value, stateOf, formatValue } = useStructuredOutputContext(\"StructuredField\");\n\n const field = fields.find((candidate) => candidate.name === name);\n if (!field) {\n throw new Error(`StructuredField \"${name}\" is not declared in the fields prop.`);\n }\n\n const state = stateOf(name);\n const raw = value[name];\n\n // \"A token arrived\" and \"this field is final\" are different facts, and in a\n // streamed object they look identical. The settle makes that distinction\n // visible — so it plays on the transition into settled, and never on a field\n // that was already final when the component mounted.\n //\n // State adjusted during render rather than tracked in a ref: reading a ref\n // while rendering is unsafe under concurrent rendering, and an effect would\n // paint the plain value first and animate a frame later. React re-renders\n // immediately here and discards the intermediate pass.\n const [seenState, setSeenState] = useState<FieldState>(state);\n const [hasSettled, setHasSettled] = useState(false);\n\n if (seenState !== state) {\n setSeenState(state);\n if (state === \"settled\" && seenState !== \"settled\") setHasSettled(true);\n }\n\n return (\n <div\n data-slot=\"structured-field\"\n data-state={state}\n data-settled={hasSettled || undefined}\n data-field={name}\n className={cn(\"flex flex-col gap-1\", className)}\n {...props}\n >\n <dt className=\"text-xs text-muted-foreground\">{field.label}</dt>\n <dd\n className={cn(\n \"m-0 text-sm\",\n state === \"pending\" && \"text-muted-foreground\",\n state === \"error\" && \"text-destructive\",\n )}\n // Height is reserved from the declared line count so the value lands in\n // space already set aside for it rather than pushing the page down.\n // Computed rather than left to calc() so the reserved height is a plain\n // value that can be asserted and reasoned about.\n style={{ minHeight: `${String((field.lines ?? 1) * 1.25)}rem` }}\n >\n {state === \"pending\" ? (\n <span\n data-slot=\"structured-field-placeholder\"\n aria-hidden=\"true\"\n className=\"block h-4 w-24 animate-pulse-soft rounded bg-muted\"\n />\n ) : (\n <span\n data-slot=\"structured-field-value\"\n className={cn(hasSettled && \"inline-block animate-settle\")}\n >\n {children ?? formatValue(raw, name)}\n </span>\n )}\n </dd>\n </div>\n );\n}\n\n/**\n * A confidence score for one field.\n *\n * Rendered as text with the number, never as a bare colour, because \"how much\n * should I trust this\" is exactly the thing a colour cannot answer.\n */\nexport function StructuredConfidence({\n className,\n value: confidence,\n lowBelow = 0.7,\n ...props\n}: ComponentPropsWithRef<\"p\"> & { value: number; lowBelow?: number }) {\n const low = confidence < lowBelow;\n const percent = new Intl.NumberFormat(undefined, {\n style: \"percent\",\n maximumFractionDigits: 0,\n }).format(confidence);\n\n return (\n <p\n data-slot=\"structured-confidence\"\n data-low={low || undefined}\n className={cn(\"text-xs\", low ? \"text-warning\" : \"text-muted-foreground\", className)}\n {...props}\n >\n {low ? `Low confidence, ${percent} — worth checking` : `${percent} confidence`}\n </p>\n );\n}\n\n/** The reason a field could not be extracted, shown where the value would be. */\nexport function StructuredFieldError({ className, ...props }: ComponentPropsWithRef<\"p\">) {\n return (\n <p\n data-slot=\"structured-field-error\"\n className={cn(\"text-xs text-destructive\", className)}\n {...props}\n />\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { FieldState, OutputField, StructuredConfidence, StructuredField, StructuredFieldError, StructuredFieldProps, StructuredOutput, StructuredOutputProps } from "./ai-structured-output.js";
|
|
2
|
+
export { type FieldState, type OutputField, StructuredConfidence, StructuredField, StructuredFieldError, type StructuredFieldProps, StructuredOutput, type StructuredOutputProps };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineMeta } from "../../registry/schema.js";
|
|
2
|
+
//#region src/components/ai-structured-output/meta.ts
|
|
3
|
+
const meta = defineMeta({
|
|
4
|
+
name: "ai-structured-output",
|
|
5
|
+
title: "AI Structured Output",
|
|
6
|
+
description: "An object arriving field by field from the model, without the layout jumping.",
|
|
7
|
+
category: "ai",
|
|
8
|
+
status: "stable",
|
|
9
|
+
dependencies: [],
|
|
10
|
+
registryDependencies: [],
|
|
11
|
+
files: ["ai-structured-output.tsx"],
|
|
12
|
+
a11y: "A description list, so each value is associated with its label rather than floating beside it. The region is aria-live=\"polite\" with aria-busy while streaming, so fields are announced as they settle instead of re-reading the whole object on every token. Height is reserved from the declared field list, which keeps focus and reading position stable as values arrive. Confidence is stated as a number in words, never as a colour alone."
|
|
13
|
+
});
|
|
14
|
+
//#endregion
|
|
15
|
+
export { meta };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta.js","names":[],"sources":["../../../src/components/ai-structured-output/meta.ts"],"sourcesContent":["import { defineMeta } from \"@/registry/schema\";\n\nexport const meta = defineMeta({\n name: \"ai-structured-output\",\n title: \"AI Structured Output\",\n description: \"An object arriving field by field from the model, without the layout jumping.\",\n category: \"ai\",\n status: \"stable\",\n dependencies: [],\n registryDependencies: [],\n files: [\"ai-structured-output.tsx\"],\n a11y:\n \"A description list, so each value is associated with its label rather than floating beside \" +\n 'it. The region is aria-live=\"polite\" with aria-busy while streaming, so fields are ' +\n \"announced as they settle instead of re-reading the whole object on every token. Height is \" +\n \"reserved from the declared field list, which keeps focus and reading position stable as \" +\n \"values arrive. Confidence is stated as a number in words, never as a colour alone.\",\n});\n"],"mappings":";;AAEA,MAAa,OAAO,WAAW;CAC7B,MAAM;CACN,OAAO;CACP,aAAa;CACb,UAAU;CACV,QAAQ;CACR,cAAc,CAAC;CACf,sBAAsB,CAAC;CACvB,OAAO,CAAC,0BAA0B;CAClC,MACE;AAKJ,CAAC"}
|
|
@@ -14,7 +14,7 @@ import { ComponentPropsWithRef, ReactNode } from "react";
|
|
|
14
14
|
*/
|
|
15
15
|
type ToolStatus = "pending" | "running" | "success" | "error";
|
|
16
16
|
declare const toolVariants: (props?: ({
|
|
17
|
-
status?: "
|
|
17
|
+
status?: "success" | "pending" | "running" | "error" | null | undefined;
|
|
18
18
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
19
19
|
interface ToolProps extends ComponentPropsWithRef<typeof Collapsible.Root>, VariantProps<typeof toolVariants> {
|
|
20
20
|
status?: ToolStatus;
|
|
@@ -2,7 +2,7 @@ import { VariantProps } from "class-variance-authority";
|
|
|
2
2
|
import { ComponentPropsWithRef } from "react";
|
|
3
3
|
//#region src/components/alert/alert.d.ts
|
|
4
4
|
declare const alertVariants: (props?: ({
|
|
5
|
-
variant?: "
|
|
5
|
+
variant?: "default" | "destructive" | "success" | "warning" | "info" | null | undefined;
|
|
6
6
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
7
7
|
/**
|
|
8
8
|
* How urgently the alert should interrupt a screen reader.
|
|
@@ -2,7 +2,7 @@ import { VariantProps } from "class-variance-authority";
|
|
|
2
2
|
import { ComponentPropsWithRef } from "react";
|
|
3
3
|
//#region src/components/badge/badge.d.ts
|
|
4
4
|
declare const badgeVariants: (props?: ({
|
|
5
|
-
variant?: "
|
|
5
|
+
variant?: "default" | "secondary" | "outline" | "destructive" | "success" | "warning" | "info" | null | undefined;
|
|
6
6
|
size?: "sm" | "md" | null | undefined;
|
|
7
7
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
8
8
|
interface BadgeProps extends ComponentPropsWithRef<"span">, VariantProps<typeof badgeVariants> {
|
|
@@ -2,7 +2,7 @@ import { VariantProps } from "class-variance-authority";
|
|
|
2
2
|
import { ComponentPropsWithRef } from "react";
|
|
3
3
|
//#region src/components/button/button.d.ts
|
|
4
4
|
declare const buttonVariants: (props?: ({
|
|
5
|
-
variant?: "link" | "
|
|
5
|
+
variant?: "link" | "secondary" | "outline" | "destructive" | "primary" | "ghost" | null | undefined;
|
|
6
6
|
size?: "sm" | "md" | "lg" | "icon" | "icon-sm" | null | undefined;
|
|
7
7
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
8
8
|
interface ButtonProps extends ComponentPropsWithRef<"button">, VariantProps<typeof buttonVariants> {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { QueuedFile } from "./upload-queue.js";
|
|
2
|
+
import { ComponentPropsWithRef, ReactNode } from "react";
|
|
3
|
+
//#region src/components/file-upload/file-upload.d.ts
|
|
4
|
+
interface FileUploadProps extends Omit<ComponentPropsWithRef<"div">, "onDrop"> {
|
|
5
|
+
/** Names the control. */
|
|
6
|
+
label: string;
|
|
7
|
+
onFiles: (files: File[]) => void;
|
|
8
|
+
accept?: string;
|
|
9
|
+
multiple?: boolean;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
/** Shown under the prompt: accepted types, size limit. */
|
|
12
|
+
hint?: ReactNode;
|
|
13
|
+
children?: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
declare function FileUpload({ className, label, onFiles, accept, multiple, disabled, hint, children, ...props }: FileUploadProps): import("react").JSX.Element;
|
|
16
|
+
interface FileUploadListProps extends ComponentPropsWithRef<"ul"> {
|
|
17
|
+
files: QueuedFile[];
|
|
18
|
+
onCancel?: (id: string) => void;
|
|
19
|
+
onRetry?: (id: string) => void;
|
|
20
|
+
onRemove?: (id: string) => void;
|
|
21
|
+
}
|
|
22
|
+
declare function FileUploadList({ className, files, onCancel, onRetry, onRemove, ...props }: FileUploadListProps): import("react").JSX.Element | null;
|
|
23
|
+
interface FileUploadItemProps extends Omit<ComponentPropsWithRef<"li">, "children"> {
|
|
24
|
+
entry: QueuedFile;
|
|
25
|
+
onCancel?: (id: string) => void;
|
|
26
|
+
onRetry?: (id: string) => void;
|
|
27
|
+
onRemove?: (id: string) => void;
|
|
28
|
+
}
|
|
29
|
+
declare function FileUploadItem({ className, entry, onCancel, onRetry, onRemove, ...props }: FileUploadItemProps): import("react").JSX.Element;
|
|
30
|
+
/**
|
|
31
|
+
* One sentence covering the whole queue, announced politely.
|
|
32
|
+
*
|
|
33
|
+
* Per-file live regions would talk over each other the moment two uploads run
|
|
34
|
+
* at once; one summary of the set is readable where six competing ones are not.
|
|
35
|
+
*/
|
|
36
|
+
declare function FileUploadStatus({ className, stats, ...props }: ComponentPropsWithRef<"p"> & {
|
|
37
|
+
stats: {
|
|
38
|
+
total: number;
|
|
39
|
+
active: number;
|
|
40
|
+
done: number;
|
|
41
|
+
failed: number;
|
|
42
|
+
};
|
|
43
|
+
}): import("react").JSX.Element;
|
|
44
|
+
//#endregion
|
|
45
|
+
export { FileUpload, FileUploadItem, FileUploadItemProps, FileUploadList, FileUploadListProps, FileUploadProps, FileUploadStatus };
|
|
46
|
+
//# sourceMappingURL=file-upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-upload.d.ts","names":[],"sources":["../../../src/components/file-upload/file-upload.tsx"],"mappings":";;;UAqCiB,wBAAwB,KAAK;;EAE5C;EACA,UAAU,OAAO;EACjB;EACA;EACA;;EAEA,OAAO;EACP,WAAW;;iBAGG,aACd,WACA,OACA,SACA,QACA,UACA,UACA,MACA,aACG,SACF,kCAAe,IAAA;UAsFD,4BAA4B;EAC3C,OAAO;EACP,YAAY;EACZ,WAAW;EACX,YAAY;;iBAGE,iBACd,WACA,OACA,UACA,SACA,aACG,SACF,sCAAmB,IAAA;UAsBL,4BAA4B,KAAK;EAChD,OAAO;EACP,YAAY;EACZ,WAAW;EACX,YAAY;;iBAGE,iBACd,WACA,OACA,UACA,SACA,aACG,SACF,sCAAmB,IAAA;;;;;;;iBA+FN,mBACd,WACA,UACG,SACF;EACD;IAAS;IAAe;IAAgB;IAAc;;oBACvD,IAAA"}
|