@superxd-studio/adptr 0.5.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/GENERATIVE.md +101 -0
- package/INTERACTIONS.md +51 -0
- package/LICENSE +21 -0
- package/README.md +57 -0
- package/REUSE.md +170 -0
- package/REVIEW.md +68 -0
- package/THIRD-PARTY-NOTICES.md +28 -0
- package/ai-sdk/index.js +96 -0
- package/assets/ibm-plex-mono-cyrillic-400-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-cyrillic-500-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-cyrillic-ext-400-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-cyrillic-ext-500-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-vietnamese-400-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-vietnamese-500-normal.woff2 +0 -0
- package/assets/instrument-sans-latin-ext-wght-normal.woff2 +0 -0
- package/assets/instrument-sans-latin-wght-normal.woff2 +0 -0
- package/assistant-ui/index.js +139 -0
- package/chunks/callout-DKMHSnjc.js +342 -0
- package/chunks/canvas-BJ15-XxE.js +598 -0
- package/chunks/controller-Cfpg0C7v.js +180 -0
- package/chunks/core-BCqVRLUy.js +312 -0
- package/chunks/text-field-BLtoaZFn.js +41 -0
- package/chunks/types-BGF7r1my.js +115 -0
- package/chunks/ui-9DyNsYXY.js +967 -0
- package/core/index.js +4 -0
- package/custom/index.js +64 -0
- package/examples/ai-sdk-backend/package.json +11 -0
- package/examples/ai-sdk-backend/server.mjs +316 -0
- package/examples/custom-backend/copy.mjs +7 -0
- package/examples/custom-backend/server.mjs +118 -0
- package/examples/generative/specs.json +2094 -0
- package/examples/review-backend/copy.mjs +27 -0
- package/examples/review-backend/package.json +11 -0
- package/examples/review-backend/server.mjs +276 -0
- package/examples/web-ai-sdk/index.html +12 -0
- package/examples/web-ai-sdk/package.json +21 -0
- package/examples/web-ai-sdk/src/copy.ts +1 -0
- package/examples/web-ai-sdk/src/main.tsx +64 -0
- package/examples/web-ai-sdk/tsconfig.json +13 -0
- package/examples/web-generative/index.html +12 -0
- package/examples/web-generative/package.json +23 -0
- package/examples/web-generative/src/copy.ts +42 -0
- package/examples/web-generative/src/main.tsx +74 -0
- package/examples/web-generative/tsconfig.json +14 -0
- package/examples/web-independent/index.html +12 -0
- package/examples/web-independent/package.json +20 -0
- package/examples/web-independent/src/main.tsx +32 -0
- package/examples/web-independent/tsconfig.json +14 -0
- package/examples/web-interactions/index.html +12 -0
- package/examples/web-interactions/package.json +21 -0
- package/examples/web-interactions/src/copy.ts +215 -0
- package/examples/web-interactions/src/examples.tsx +628 -0
- package/examples/web-interactions/src/main.tsx +116 -0
- package/examples/web-interactions/tsconfig.json +14 -0
- package/examples/web-review/index.html +12 -0
- package/examples/web-review/package.json +20 -0
- package/examples/web-review/src/main.tsx +59 -0
- package/examples/web-review/tsconfig.json +14 -0
- package/fonts.css +117 -0
- package/generative/index.js +1560 -0
- package/interactions/index.js +1591 -0
- package/langgraph-review/index.js +36 -0
- package/licenses/ai-sdk.txt +13 -0
- package/licenses/assistant-ui-generative.txt +21 -0
- package/licenses/assistant-ui.txt +23 -0
- package/licenses/ibm-plex-mono.txt +93 -0
- package/licenses/instrument-sans.txt +93 -0
- package/licenses/internationalized-date.txt +201 -0
- package/licenses/langgraph.txt +21 -0
- package/licenses/lucide.txt +43 -0
- package/licenses/react-markdown.txt +21 -0
- package/licenses/remark-gfm.txt +22 -0
- package/licenses/zod.txt +21 -0
- package/package.json +138 -0
- package/review/index.js +31 -0
- package/source/components/avatar.tsx +50 -0
- package/source/components/button.tsx +155 -0
- package/source/components/callout.tsx +65 -0
- package/source/components/canvas/connector.tsx +14 -0
- package/source/components/canvas/copy.ts +34 -0
- package/source/components/canvas/index.tsx +257 -0
- package/source/components/canvas/recorder.ts +36 -0
- package/source/components/date-time-field.tsx +77 -0
- package/source/components/flow-motion.tsx +109 -0
- package/source/components/icons.tsx +275 -0
- package/source/components/responsive-overlay.tsx +91 -0
- package/source/components/scenario-picker/copy.ts +5 -0
- package/source/components/scenario-picker/index.tsx +142 -0
- package/source/components/scroll-area.tsx +59 -0
- package/source/components/search-picker.tsx +127 -0
- package/source/components/segmented-control.tsx +123 -0
- package/source/components/skeleton.tsx +11 -0
- package/source/components/spinner.tsx +19 -0
- package/source/components/superxd-arrow.tsx +22 -0
- package/source/components/switch.tsx +45 -0
- package/source/components/text-field.tsx +81 -0
- package/source/components/thinking.tsx +16 -0
- package/source/components/ui/copy.ts +12 -0
- package/source/components/ui/display.tsx +276 -0
- package/source/components/ui/navigation.tsx +346 -0
- package/source/components/ui/portal-theme.tsx +28 -0
- package/source/components/ui/selection.tsx +336 -0
- package/source/lib/cn.ts +23 -0
- package/source/reusable/ai-sdk/index.ts +114 -0
- package/source/reusable/assistant-ui/index.tsx +266 -0
- package/source/reusable/core/attachments.ts +163 -0
- package/source/reusable/core/index.ts +417 -0
- package/source/reusable/custom/index.ts +90 -0
- package/source/reusable/generative/actions.ts +188 -0
- package/source/reusable/generative/copy.ts +47 -0
- package/source/reusable/generative/data.tsx +126 -0
- package/source/reusable/generative/index.tsx +517 -0
- package/source/reusable/generative/schema.ts +327 -0
- package/source/reusable/generative/timeline.tsx +214 -0
- package/source/reusable/generative/vocabulary.tsx +847 -0
- package/source/reusable/interactions/agent.ts +259 -0
- package/source/reusable/interactions/artifact.ts +236 -0
- package/source/reusable/interactions/context.tsx +38 -0
- package/source/reusable/interactions/copy.ts +117 -0
- package/source/reusable/interactions/feedback.ts +73 -0
- package/source/reusable/interactions/index.ts +11 -0
- package/source/reusable/interactions/presentation.tsx +483 -0
- package/source/reusable/interactions/store.ts +74 -0
- package/source/reusable/interactions/threads.ts +126 -0
- package/source/reusable/interactions/web.tsx +574 -0
- package/source/reusable/langgraph-review/index.ts +73 -0
- package/source/reusable/review/controller.ts +263 -0
- package/source/reusable/review/index.ts +47 -0
- package/source/reusable/review/types.ts +189 -0
- package/source/reusable/ui/index.ts +25 -0
- package/source/reusable/uploads/index.ts +48 -0
- package/source/reusable/web/attachments/copy.ts +23 -0
- package/source/reusable/web/attachments/index.tsx +233 -0
- package/source/reusable/web/copy.ts +22 -0
- package/source/reusable/web/index.tsx +470 -0
- package/source/reusable/web/message/copy.ts +26 -0
- package/source/reusable/web/message/index.tsx +388 -0
- package/source/reusable/web/message/streaming.ts +64 -0
- package/source/reusable/web/message/styles.css +21 -0
- package/source/reusable/web/review/copy.ts +78 -0
- package/source/reusable/web/review/flow.ts +24 -0
- package/source/reusable/web/review/index.tsx +506 -0
- package/source/reusable/web/styles.ts +2 -0
- package/source/styles/library.css +612 -0
- package/source/styles/theme.css +228 -0
- package/styles.css +3 -0
- package/styles.d.ts +1 -0
- package/types/components/avatar.d.ts +14 -0
- package/types/components/button.d.ts +36 -0
- package/types/components/callout.d.ts +19 -0
- package/types/components/canvas/connector.d.ts +2 -0
- package/types/components/canvas/copy.d.ts +34 -0
- package/types/components/canvas/index.d.ts +33 -0
- package/types/components/canvas/recorder.d.ts +10 -0
- package/types/components/date-time-field.d.ts +11 -0
- package/types/components/flow-motion.d.ts +38 -0
- package/types/components/icons.d.ts +138 -0
- package/types/components/responsive-overlay.d.ts +15 -0
- package/types/components/scenario-picker/copy.d.ts +5 -0
- package/types/components/scenario-picker/index.d.ts +14 -0
- package/types/components/scroll-area.d.ts +14 -0
- package/types/components/search-picker.d.ts +27 -0
- package/types/components/segmented-control.d.ts +23 -0
- package/types/components/skeleton.d.ts +4 -0
- package/types/components/spinner.d.ts +4 -0
- package/types/components/superxd-arrow.d.ts +5 -0
- package/types/components/switch.d.ts +8 -0
- package/types/components/text-field.d.ts +19 -0
- package/types/components/thinking.d.ts +8 -0
- package/types/components/ui/copy.d.ts +12 -0
- package/types/components/ui/display.d.ts +50 -0
- package/types/components/ui/navigation.d.ts +63 -0
- package/types/components/ui/portal-theme.d.ts +8 -0
- package/types/components/ui/selection.d.ts +37 -0
- package/types/lib/cn.d.ts +3 -0
- package/types/reusable/ai-sdk/index.d.ts +15 -0
- package/types/reusable/assistant-ui/index.d.ts +12 -0
- package/types/reusable/core/attachments.d.ts +54 -0
- package/types/reusable/core/index.d.ts +107 -0
- package/types/reusable/custom/index.d.ts +14 -0
- package/types/reusable/generative/actions.d.ts +45 -0
- package/types/reusable/generative/copy.d.ts +48 -0
- package/types/reusable/generative/data.d.ts +4 -0
- package/types/reusable/generative/index.d.ts +30 -0
- package/types/reusable/generative/schema.d.ts +303 -0
- package/types/reusable/generative/timeline.d.ts +21 -0
- package/types/reusable/generative/vocabulary.d.ts +34 -0
- package/types/reusable/interactions/agent.d.ts +98 -0
- package/types/reusable/interactions/artifact.d.ts +118 -0
- package/types/reusable/interactions/context.d.ts +130 -0
- package/types/reusable/interactions/copy.d.ts +115 -0
- package/types/reusable/interactions/feedback.d.ts +34 -0
- package/types/reusable/interactions/index.d.ts +10 -0
- package/types/reusable/interactions/presentation.d.ts +97 -0
- package/types/reusable/interactions/store.d.ts +29 -0
- package/types/reusable/interactions/threads.d.ts +38 -0
- package/types/reusable/interactions/web.d.ts +22 -0
- package/types/reusable/langgraph-review/index.d.ts +5 -0
- package/types/reusable/review/controller.d.ts +52 -0
- package/types/reusable/review/index.d.ts +12 -0
- package/types/reusable/review/types.d.ts +61 -0
- package/types/reusable/ui/index.d.ts +19 -0
- package/types/reusable/uploads/index.d.ts +11 -0
- package/types/reusable/web/attachments/copy.d.ts +25 -0
- package/types/reusable/web/attachments/index.d.ts +24 -0
- package/types/reusable/web/copy.d.ts +24 -0
- package/types/reusable/web/index.d.ts +41 -0
- package/types/reusable/web/message/copy.d.ts +27 -0
- package/types/reusable/web/message/index.d.ts +56 -0
- package/types/reusable/web/message/streaming.d.ts +27 -0
- package/types/reusable/web/review/copy.d.ts +72 -0
- package/types/reusable/web/review/flow.d.ts +3 -0
- package/types/reusable/web/review/index.d.ts +38 -0
- package/types/reusable/web/styles.d.ts +2 -0
- package/ui/index.js +4 -0
- package/uploads/index.js +34 -0
- package/web/index.js +931 -0
|
@@ -0,0 +1,847 @@
|
|
|
1
|
+
import { createContext, useContext, useEffect, useState, type ReactNode } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Checkbox,
|
|
4
|
+
RadioGroup,
|
|
5
|
+
Radio,
|
|
6
|
+
Label,
|
|
7
|
+
Select,
|
|
8
|
+
SelectValue,
|
|
9
|
+
Popover,
|
|
10
|
+
ListBox,
|
|
11
|
+
ListBoxItem,
|
|
12
|
+
DateField,
|
|
13
|
+
DateInput,
|
|
14
|
+
DateSegment,
|
|
15
|
+
FieldError,
|
|
16
|
+
I18nProvider,
|
|
17
|
+
ProgressBar,
|
|
18
|
+
} from "react-aria-components";
|
|
19
|
+
import { parseDate } from "@internationalized/date";
|
|
20
|
+
import type { z } from "zod";
|
|
21
|
+
import type { GenerativeUILibrary, GenerativeUIDispatch } from "@assistant-ui/react-generative-ui";
|
|
22
|
+
import { Button, buttonStyles } from "../../components/button.js";
|
|
23
|
+
import { TextField } from "../../components/text-field.js";
|
|
24
|
+
import { Callout } from "../../components/callout.js";
|
|
25
|
+
import {
|
|
26
|
+
IconCheck,
|
|
27
|
+
IconChevronDown,
|
|
28
|
+
IconSun,
|
|
29
|
+
IconMoon,
|
|
30
|
+
IconClock,
|
|
31
|
+
IconCalendar,
|
|
32
|
+
IconMapPin,
|
|
33
|
+
IconChevronRight,
|
|
34
|
+
IconX,
|
|
35
|
+
IconUser,
|
|
36
|
+
IconSearch,
|
|
37
|
+
} from "../../components/icons.js";
|
|
38
|
+
import { MessageMarkdown } from "../web/message/index.js";
|
|
39
|
+
import { cn } from "../../lib/cn.js";
|
|
40
|
+
import { nodeSchemas, type StructuredAction } from "./schema.js";
|
|
41
|
+
import { generativeCopy as copy } from "./copy.js";
|
|
42
|
+
import { GenerativeChart, GenerativeTable } from "./data.js";
|
|
43
|
+
|
|
44
|
+
export const GenerativeRuntimeContext = createContext({
|
|
45
|
+
blocked: false,
|
|
46
|
+
available: undefined as ReadonlySet<string> | undefined,
|
|
47
|
+
captureInput: undefined as ((input: unknown) => void) | undefined,
|
|
48
|
+
initialValues: undefined as Readonly<Record<string, string | boolean>> | undefined,
|
|
49
|
+
});
|
|
50
|
+
interface Field {
|
|
51
|
+
value: string | boolean;
|
|
52
|
+
required?: boolean;
|
|
53
|
+
inputType?: string;
|
|
54
|
+
date?: boolean;
|
|
55
|
+
}
|
|
56
|
+
interface FormState {
|
|
57
|
+
fields: Map<string, Field>;
|
|
58
|
+
bindings: Set<string>;
|
|
59
|
+
canSubmit: boolean;
|
|
60
|
+
errors: Record<string, string>;
|
|
61
|
+
setError: (name: string) => void;
|
|
62
|
+
}
|
|
63
|
+
const FieldContext = createContext<FormState | undefined>(undefined);
|
|
64
|
+
function useField(
|
|
65
|
+
name: string,
|
|
66
|
+
initial: string | boolean,
|
|
67
|
+
required?: boolean,
|
|
68
|
+
inputType?: string,
|
|
69
|
+
date?: boolean,
|
|
70
|
+
) {
|
|
71
|
+
const form = useContext(FieldContext);
|
|
72
|
+
const { initialValues } = useContext(GenerativeRuntimeContext);
|
|
73
|
+
const [value, setValue] = useState(initialValues?.[name] ?? initial);
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
form?.fields.set(name, { value, required, inputType, date });
|
|
76
|
+
return () => {
|
|
77
|
+
form?.fields.delete(name);
|
|
78
|
+
};
|
|
79
|
+
}, [form?.fields, name, value, required, inputType, date]);
|
|
80
|
+
return {
|
|
81
|
+
value,
|
|
82
|
+
error: form?.errors[name],
|
|
83
|
+
onChange: (next: string | boolean) => {
|
|
84
|
+
form?.fields.set(name, { value: next, required, inputType, date });
|
|
85
|
+
setValue(next);
|
|
86
|
+
form?.setError(name);
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function formValues(fields: Map<string, Field>) {
|
|
91
|
+
const errors: Record<string, string> = {},
|
|
92
|
+
values: Record<string, string | boolean> = Object.create(null);
|
|
93
|
+
fields.forEach((field, name) => {
|
|
94
|
+
values[name] = field.value;
|
|
95
|
+
if (field.required && !String(field.value).trim()) errors[name] = copy.fieldRequired;
|
|
96
|
+
else if (
|
|
97
|
+
field.inputType === "email" &&
|
|
98
|
+
field.value &&
|
|
99
|
+
!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(field.value))
|
|
100
|
+
)
|
|
101
|
+
errors[name] = copy.email;
|
|
102
|
+
else if (field.date && field.value) {
|
|
103
|
+
try {
|
|
104
|
+
parseDate(String(field.value));
|
|
105
|
+
} catch {
|
|
106
|
+
errors[name] = copy.date;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
if (values.startDate && values.endDate && String(values.endDate) <= String(values.startDate))
|
|
111
|
+
errors.endDate = copy.date;
|
|
112
|
+
return { errors, values };
|
|
113
|
+
}
|
|
114
|
+
export type GenerativeFactResolver = (
|
|
115
|
+
binding: string,
|
|
116
|
+
values: Readonly<Record<string, string | boolean>>,
|
|
117
|
+
) => string | undefined;
|
|
118
|
+
export interface GenerativeFormProps {
|
|
119
|
+
children?: ReactNode;
|
|
120
|
+
action?: StructuredAction;
|
|
121
|
+
dispatch?: GenerativeUIDispatch;
|
|
122
|
+
title?: string;
|
|
123
|
+
confirm?: { label: string; $action?: StructuredAction };
|
|
124
|
+
cancel?: { label: string; $action?: StructuredAction };
|
|
125
|
+
resolveFact?: GenerativeFactResolver;
|
|
126
|
+
}
|
|
127
|
+
export function GenerativeForm({
|
|
128
|
+
children,
|
|
129
|
+
action,
|
|
130
|
+
dispatch,
|
|
131
|
+
title,
|
|
132
|
+
confirm,
|
|
133
|
+
cancel,
|
|
134
|
+
resolveFact,
|
|
135
|
+
}: GenerativeFormProps) {
|
|
136
|
+
const [fields] = useState(() => new Map<string, Field>());
|
|
137
|
+
const [bindings] = useState(() => new Set<string>());
|
|
138
|
+
const [, change] = useState(0);
|
|
139
|
+
const [quoteError, setQuoteError] = useState(false);
|
|
140
|
+
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
141
|
+
const { blocked, available, captureInput } = useContext(GenerativeRuntimeContext);
|
|
142
|
+
const intent = confirm?.$action ?? action;
|
|
143
|
+
const canSubmit = Boolean(dispatch && intent && (!available || available.has(intent.type)));
|
|
144
|
+
return (
|
|
145
|
+
<FieldContext.Provider
|
|
146
|
+
value={{
|
|
147
|
+
fields,
|
|
148
|
+
bindings,
|
|
149
|
+
canSubmit,
|
|
150
|
+
errors,
|
|
151
|
+
setError: (name) => {
|
|
152
|
+
change((v) => v + 1);
|
|
153
|
+
setQuoteError(false);
|
|
154
|
+
setErrors((prior) => {
|
|
155
|
+
if (!prior[name]) return prior;
|
|
156
|
+
return Object.fromEntries(Object.entries(prior).filter(([key]) => key !== name));
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
}}
|
|
160
|
+
>
|
|
161
|
+
<form
|
|
162
|
+
className="flex min-w-0 flex-col gap-5"
|
|
163
|
+
noValidate
|
|
164
|
+
onSubmit={(e) => {
|
|
165
|
+
e.preventDefault();
|
|
166
|
+
if (blocked || !canSubmit) return;
|
|
167
|
+
const { errors, values } = formValues(fields);
|
|
168
|
+
setErrors(errors);
|
|
169
|
+
if (Object.keys(errors).length) {
|
|
170
|
+
const form = e.currentTarget;
|
|
171
|
+
requestAnimationFrame(() =>
|
|
172
|
+
form.querySelector<HTMLElement>('[aria-invalid="true"]')?.focus(),
|
|
173
|
+
);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const quoted: Record<string, string> = {};
|
|
177
|
+
for (const binding of bindings) {
|
|
178
|
+
let value: string | undefined;
|
|
179
|
+
try {
|
|
180
|
+
value = resolveFact?.(binding, values);
|
|
181
|
+
} catch {
|
|
182
|
+
/* Host quote unavailable. */
|
|
183
|
+
}
|
|
184
|
+
if (value === undefined) {
|
|
185
|
+
setQuoteError(true);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
quoted[binding] = value;
|
|
189
|
+
}
|
|
190
|
+
if (intent && dispatch)
|
|
191
|
+
void dispatch({
|
|
192
|
+
...intent,
|
|
193
|
+
$input: values,
|
|
194
|
+
...(bindings.size ? { $bindings: quoted } : {}),
|
|
195
|
+
});
|
|
196
|
+
}}
|
|
197
|
+
>
|
|
198
|
+
{title ? <h3 className="type-title text-fg-emphasis">{title}</h3> : null}
|
|
199
|
+
{children}
|
|
200
|
+
{Object.keys(errors).length ? (
|
|
201
|
+
<p role="alert" className="type-body text-error">
|
|
202
|
+
{copy.validation}
|
|
203
|
+
</p>
|
|
204
|
+
) : null}
|
|
205
|
+
{quoteError ? (
|
|
206
|
+
<p role="alert" className="type-body text-error">
|
|
207
|
+
{copy.quoteUnavailable}
|
|
208
|
+
</p>
|
|
209
|
+
) : null}
|
|
210
|
+
{confirm || cancel ? (
|
|
211
|
+
<footer className="flex flex-wrap items-center gap-3 border-t border-line-subtle pt-5">
|
|
212
|
+
{confirm ? (
|
|
213
|
+
<Button
|
|
214
|
+
className="h-auto min-h-control-md max-w-full py-control-y wrap-anywhere whitespace-normal"
|
|
215
|
+
type="submit"
|
|
216
|
+
variant="primary"
|
|
217
|
+
isDisabled={blocked || !canSubmit}
|
|
218
|
+
>
|
|
219
|
+
{confirm.label}
|
|
220
|
+
</Button>
|
|
221
|
+
) : null}
|
|
222
|
+
{cancel ? (
|
|
223
|
+
<Button
|
|
224
|
+
variant="ghost"
|
|
225
|
+
className="h-auto min-h-control-md max-w-full py-control-y wrap-anywhere whitespace-normal"
|
|
226
|
+
isDisabled={
|
|
227
|
+
blocked ||
|
|
228
|
+
!dispatch ||
|
|
229
|
+
!cancel.$action ||
|
|
230
|
+
Boolean(available && !available.has(cancel.$action.type))
|
|
231
|
+
}
|
|
232
|
+
onPress={() => {
|
|
233
|
+
if (cancel.$action) {
|
|
234
|
+
captureInput?.({ ...cancel.$action, $input: formValues(fields).values });
|
|
235
|
+
void dispatch?.(cancel.$action);
|
|
236
|
+
}
|
|
237
|
+
}}
|
|
238
|
+
>
|
|
239
|
+
{cancel.label}
|
|
240
|
+
</Button>
|
|
241
|
+
) : null}
|
|
242
|
+
</footer>
|
|
243
|
+
) : null}
|
|
244
|
+
</form>
|
|
245
|
+
</FieldContext.Provider>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
function InputField(props: z.infer<typeof nodeSchemas.Input>) {
|
|
249
|
+
const field = useField(props.name, props.defaultValue ?? "", props.required, props.inputType);
|
|
250
|
+
const { blocked } = useContext(GenerativeRuntimeContext);
|
|
251
|
+
return (
|
|
252
|
+
<TextField
|
|
253
|
+
className="min-w-0 flex-1"
|
|
254
|
+
label={props.label}
|
|
255
|
+
name={props.name}
|
|
256
|
+
value={String(field.value)}
|
|
257
|
+
onChange={field.onChange}
|
|
258
|
+
multiline={props.multiline}
|
|
259
|
+
placeholder={props.placeholder}
|
|
260
|
+
error={field.error}
|
|
261
|
+
isRequired={props.required}
|
|
262
|
+
isDisabled={blocked}
|
|
263
|
+
type={props.inputType ?? "text"}
|
|
264
|
+
/>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
function SelectField(props: z.infer<typeof nodeSchemas.Select>) {
|
|
268
|
+
const field = useField(props.name, props.defaultValue ?? "", props.required);
|
|
269
|
+
const { blocked } = useContext(GenerativeRuntimeContext);
|
|
270
|
+
return (
|
|
271
|
+
<Select
|
|
272
|
+
placeholder={props.placeholder}
|
|
273
|
+
name={props.name}
|
|
274
|
+
value={String(field.value) || null}
|
|
275
|
+
onChange={(v) => field.onChange(String(v ?? ""))}
|
|
276
|
+
isRequired={props.required}
|
|
277
|
+
isInvalid={Boolean(field.error)}
|
|
278
|
+
isDisabled={blocked}
|
|
279
|
+
validationBehavior="aria"
|
|
280
|
+
className="flex min-w-0 flex-1 flex-col gap-1.5"
|
|
281
|
+
>
|
|
282
|
+
<Label className="type-label text-fg-emphasis">{props.label}</Label>
|
|
283
|
+
<Button className="group/trigger justify-between" isDisabled={blocked}>
|
|
284
|
+
<SelectValue className="min-w-0 truncate" />
|
|
285
|
+
{/* Turns with the settle spring as the list opens, like every SuperXD dropdown */}
|
|
286
|
+
<IconChevronDown className="transition-[rotate] duration-(--duration-settle) ease-settle group-aria-expanded/trigger:rotate-180 motion-reduce:transition-none" />
|
|
287
|
+
</Button>
|
|
288
|
+
<Popover className="min-w-(--trigger-width) origin-(--trigger-anchor-point) rounded-control border border-line-subtle bg-surface p-1 shadow-popover outline-none data-entering:animate-pop-in data-exiting:animate-pop-out">
|
|
289
|
+
<ListBox className="outline-none" items={props.options}>
|
|
290
|
+
{(option) => (
|
|
291
|
+
<ListBoxItem
|
|
292
|
+
id={option.value}
|
|
293
|
+
textValue={option.label}
|
|
294
|
+
className="cursor-pointer rounded-cell px-control-md-x py-control-y type-body text-fg-emphasis outline-none data-focus-visible:focus-ring data-focused:bg-surface-subtle data-selected:font-medium"
|
|
295
|
+
>
|
|
296
|
+
{option.label}
|
|
297
|
+
</ListBoxItem>
|
|
298
|
+
)}
|
|
299
|
+
</ListBox>
|
|
300
|
+
</Popover>
|
|
301
|
+
<FieldError className="type-caption text-error">{field.error}</FieldError>
|
|
302
|
+
</Select>
|
|
303
|
+
);
|
|
304
|
+
}
|
|
305
|
+
function GeneratedDate(props: z.infer<typeof nodeSchemas.DatePicker>) {
|
|
306
|
+
const field = useField(props.name, props.defaultValue ?? "", props.required, undefined, true);
|
|
307
|
+
const { blocked } = useContext(GenerativeRuntimeContext);
|
|
308
|
+
return (
|
|
309
|
+
<I18nProvider locale="en-AU">
|
|
310
|
+
<DateField
|
|
311
|
+
name={props.name}
|
|
312
|
+
value={field.value ? parseDate(String(field.value)) : null}
|
|
313
|
+
onChange={(date) => field.onChange(date?.toString() ?? "")}
|
|
314
|
+
isRequired={props.required}
|
|
315
|
+
isInvalid={Boolean(field.error)}
|
|
316
|
+
isDisabled={blocked}
|
|
317
|
+
validationBehavior="aria"
|
|
318
|
+
className="flex min-w-0 flex-1 flex-col gap-1.5"
|
|
319
|
+
>
|
|
320
|
+
<Label className="type-label text-fg-emphasis">{props.label}</Label>
|
|
321
|
+
<DateInput className="flex h-control-md items-center rounded-control border border-line-input bg-surface px-control-md-x type-body text-fg-emphasis data-disabled:opacity-50 data-focus-within:border-brand data-focus-within:focus-ring">
|
|
322
|
+
{(segment) => (
|
|
323
|
+
<DateSegment
|
|
324
|
+
segment={segment}
|
|
325
|
+
className="rounded-cell px-0.5 numeric outline-none data-focused:bg-brand data-focused:text-on-brand data-placeholder:text-fg-subtle"
|
|
326
|
+
/>
|
|
327
|
+
)}
|
|
328
|
+
</DateInput>
|
|
329
|
+
<FieldError className="type-caption text-error">{field.error}</FieldError>
|
|
330
|
+
</DateField>
|
|
331
|
+
</I18nProvider>
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
function GeneratedRadio(props: z.infer<typeof nodeSchemas.RadioGroup>) {
|
|
335
|
+
const field = useField(props.name, props.defaultValue ?? "", props.required);
|
|
336
|
+
const { blocked } = useContext(GenerativeRuntimeContext);
|
|
337
|
+
return (
|
|
338
|
+
<RadioGroup
|
|
339
|
+
name={props.name}
|
|
340
|
+
value={String(field.value)}
|
|
341
|
+
onChange={field.onChange}
|
|
342
|
+
isRequired={props.required}
|
|
343
|
+
isInvalid={Boolean(field.error)}
|
|
344
|
+
isDisabled={blocked}
|
|
345
|
+
validationBehavior="aria"
|
|
346
|
+
className="flex flex-col gap-2"
|
|
347
|
+
>
|
|
348
|
+
<Label className="type-label text-fg-emphasis">{props.label}</Label>
|
|
349
|
+
<div className="flex flex-wrap gap-3">
|
|
350
|
+
{props.options.map((o) => (
|
|
351
|
+
<Radio
|
|
352
|
+
key={o.value}
|
|
353
|
+
value={o.value}
|
|
354
|
+
className="group flex cursor-pointer items-center gap-2 rounded-control border border-line px-control-md-x py-control-y type-body text-fg data-disabled:opacity-50 data-focus-visible:focus-ring data-selected:border-brand data-selected:bg-surface-muted"
|
|
355
|
+
>
|
|
356
|
+
<span className="grid size-4 place-items-center rounded-full border border-line-input">
|
|
357
|
+
<span className="size-2 rounded-full group-data-selected:bg-brand" />
|
|
358
|
+
</span>
|
|
359
|
+
{o.label}
|
|
360
|
+
</Radio>
|
|
361
|
+
))}
|
|
362
|
+
</div>
|
|
363
|
+
<FieldError className="type-caption text-error">{field.error}</FieldError>
|
|
364
|
+
</RadioGroup>
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
function GeneratedCheckbox(props: z.infer<typeof nodeSchemas.Checkbox>) {
|
|
368
|
+
const field = useField(props.name, props.defaultChecked ?? false);
|
|
369
|
+
const { blocked } = useContext(GenerativeRuntimeContext);
|
|
370
|
+
return (
|
|
371
|
+
<Checkbox
|
|
372
|
+
name={props.name}
|
|
373
|
+
isSelected={Boolean(field.value)}
|
|
374
|
+
onChange={field.onChange}
|
|
375
|
+
isDisabled={blocked}
|
|
376
|
+
className="group flex cursor-pointer items-center gap-2 rounded-cell type-body text-fg data-disabled:opacity-50 data-focus-visible:focus-ring"
|
|
377
|
+
>
|
|
378
|
+
<span className="grid size-4 shrink-0 place-items-center rounded-cell border border-line-input group-data-selected:border-brand group-data-selected:bg-brand">
|
|
379
|
+
<IconCheck className="invisible size-3 text-on-brand group-data-selected:visible" />
|
|
380
|
+
</span>
|
|
381
|
+
{props.label}
|
|
382
|
+
</Checkbox>
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
const sizes = {
|
|
386
|
+
sm: "type-body",
|
|
387
|
+
md: "type-heading",
|
|
388
|
+
lg: "type-title",
|
|
389
|
+
xl: "type-title",
|
|
390
|
+
"2xl": "text-2xl leading-tight",
|
|
391
|
+
"3xl": "text-3xl leading-tight",
|
|
392
|
+
};
|
|
393
|
+
const gaps = ["gap-0", "gap-1", "gap-2", "gap-3", "gap-4", "gap-5", "gap-6", "gap-7", "gap-8"];
|
|
394
|
+
const icons: Record<string, ReactNode> = {
|
|
395
|
+
sun: <IconSun />,
|
|
396
|
+
moon: <IconMoon />,
|
|
397
|
+
clock: <IconClock />,
|
|
398
|
+
calendar: <IconCalendar />,
|
|
399
|
+
"map-pin": <IconMapPin />,
|
|
400
|
+
check: <IconCheck />,
|
|
401
|
+
x: <IconX />,
|
|
402
|
+
user: <IconUser />,
|
|
403
|
+
search: <IconSearch />,
|
|
404
|
+
"chevron-right": <IconChevronRight />,
|
|
405
|
+
};
|
|
406
|
+
function GenerativeIcon({ name, size }: z.infer<typeof nodeSchemas.Icon>) {
|
|
407
|
+
const paths: Record<string, ReactNode> = {
|
|
408
|
+
plane: <path d="m3 16 7-4-1-9 3 1 3 7 6 1v2l-6 1-3 7-3 1 1-9-7-2Z" />,
|
|
409
|
+
play: <path d="m8 5 11 7-11 7Z" />,
|
|
410
|
+
pause: (
|
|
411
|
+
<>
|
|
412
|
+
<path d="M8 5v14M16 5v14" />
|
|
413
|
+
</>
|
|
414
|
+
),
|
|
415
|
+
cloud: <path d="M6 18h12a4 4 0 0 0 0-8 6 6 0 0 0-11-2 5 5 0 0 0-1 10Z" />,
|
|
416
|
+
star: <path d="m12 3 3 6 7 1-5 5 1 7-6-3-6 3 1-7-5-5 7-1Z" />,
|
|
417
|
+
heart: <path d="M20 5a5 5 0 0 0-8 1 5 5 0 0 0-8-1C-2 11 12 21 12 21S26 11 20 5Z" />,
|
|
418
|
+
"arrow-right": <path d="M4 12h16m-6-6 6 6-6 6" />,
|
|
419
|
+
"arrow-up-right": <path d="M6 18 18 6M6 6h12v12" />,
|
|
420
|
+
"credit-card": (
|
|
421
|
+
<>
|
|
422
|
+
<rect x="3" y="5" width="18" height="14" rx="2" />
|
|
423
|
+
<path d="M3 10h18" />
|
|
424
|
+
</>
|
|
425
|
+
),
|
|
426
|
+
truck: (
|
|
427
|
+
<>
|
|
428
|
+
<path d="M3 4h12v13H3Zm12 5h3l3 4v4h-6" />
|
|
429
|
+
<circle cx="7" cy="18" r="2" />
|
|
430
|
+
<circle cx="18" cy="18" r="2" />
|
|
431
|
+
</>
|
|
432
|
+
),
|
|
433
|
+
bell: <path d="M4 17h16l-2-3V9a6 6 0 0 0-12 0v5ZM10 21h4" />,
|
|
434
|
+
wind: <path d="M3 8h12a3 3 0 1 0-3-3M3 12h16a3 3 0 1 1-3 3M3 16h5a3 3 0 1 1-3 3" />,
|
|
435
|
+
rain: (
|
|
436
|
+
<>
|
|
437
|
+
<path d="M6 14h12a3 3 0 0 0 0-6 6 6 0 0 0-11-1 4 4 0 0 0-1 7Z" />
|
|
438
|
+
<path d="m8 18-1 3m6-3-1 3m6-3-1 3" />
|
|
439
|
+
</>
|
|
440
|
+
),
|
|
441
|
+
snow: <path d="M12 2v20M3 7l18 10M3 17 21 7M9 3l3 3 3-3M9 21l3-3 3 3" />,
|
|
442
|
+
};
|
|
443
|
+
return (
|
|
444
|
+
<span
|
|
445
|
+
aria-hidden
|
|
446
|
+
className={cn(
|
|
447
|
+
"inline-flex shrink-0 items-center justify-center text-fg-subtle [&>svg]:size-full",
|
|
448
|
+
size === "lg" ? "size-10" : "size-5",
|
|
449
|
+
)}
|
|
450
|
+
>
|
|
451
|
+
{icons[name] ?? (
|
|
452
|
+
<svg
|
|
453
|
+
viewBox="0 0 24 24"
|
|
454
|
+
fill="none"
|
|
455
|
+
stroke="currentColor"
|
|
456
|
+
strokeWidth="1.5"
|
|
457
|
+
strokeLinecap="round"
|
|
458
|
+
strokeLinejoin="round"
|
|
459
|
+
>
|
|
460
|
+
{paths[name]}
|
|
461
|
+
</svg>
|
|
462
|
+
)}
|
|
463
|
+
</span>
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
export interface GenerativeLibraryOptions {
|
|
467
|
+
/** Only host-approved images may load. No arbitrary model-supplied network requests. */ resolveImage?: (
|
|
468
|
+
src: string,
|
|
469
|
+
) => string | undefined;
|
|
470
|
+
/** Host-derived values, such as a quote. The destination must revalidate them. */ resolveFact?: GenerativeFactResolver;
|
|
471
|
+
}
|
|
472
|
+
function GeneratedFact({
|
|
473
|
+
label,
|
|
474
|
+
value,
|
|
475
|
+
binding,
|
|
476
|
+
resolveFact,
|
|
477
|
+
}: z.infer<typeof nodeSchemas.Fact> & GenerativeLibraryOptions) {
|
|
478
|
+
const form = useContext(FieldContext);
|
|
479
|
+
useEffect(() => {
|
|
480
|
+
if (binding) form?.bindings.add(binding);
|
|
481
|
+
return () => {
|
|
482
|
+
if (binding) form?.bindings.delete(binding);
|
|
483
|
+
};
|
|
484
|
+
}, [binding, form?.bindings]);
|
|
485
|
+
let displayed = value;
|
|
486
|
+
if (binding) {
|
|
487
|
+
try {
|
|
488
|
+
displayed =
|
|
489
|
+
resolveFact?.(
|
|
490
|
+
binding,
|
|
491
|
+
Object.fromEntries([...(form?.fields ?? [])].map(([name, field]) => [name, field.value])),
|
|
492
|
+
) ?? copy.quoteUnavailable;
|
|
493
|
+
} catch {
|
|
494
|
+
displayed = copy.quoteUnavailable;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return (
|
|
498
|
+
<dl className="flex min-w-0 flex-col gap-1">
|
|
499
|
+
<dt className="type-caption font-normal text-fg-subtle">{label}</dt>
|
|
500
|
+
<dd
|
|
501
|
+
className="type-heading wrap-anywhere text-fg-emphasis numeric"
|
|
502
|
+
aria-live={binding ? "polite" : undefined}
|
|
503
|
+
>
|
|
504
|
+
{displayed}
|
|
505
|
+
</dd>
|
|
506
|
+
</dl>
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
function ApprovedImage({
|
|
510
|
+
src,
|
|
511
|
+
alt,
|
|
512
|
+
size,
|
|
513
|
+
round,
|
|
514
|
+
resolveImage,
|
|
515
|
+
}: z.infer<typeof nodeSchemas.Image> & GenerativeLibraryOptions) {
|
|
516
|
+
const [failed, setFailed] = useState(false);
|
|
517
|
+
let approved: string | undefined;
|
|
518
|
+
try {
|
|
519
|
+
approved = resolveImage?.(src);
|
|
520
|
+
} catch {
|
|
521
|
+
/* An unavailable host resolver uses the image fallback. */
|
|
522
|
+
}
|
|
523
|
+
const safe = approved && (/^https:\/\//.test(approved) || /^\/(?!\/)/.test(approved));
|
|
524
|
+
return safe && !failed ? (
|
|
525
|
+
<img
|
|
526
|
+
key={approved}
|
|
527
|
+
src={approved}
|
|
528
|
+
alt={alt}
|
|
529
|
+
loading="lazy"
|
|
530
|
+
referrerPolicy="no-referrer"
|
|
531
|
+
onError={() => setFailed(true)}
|
|
532
|
+
className={cn(
|
|
533
|
+
"max-w-full object-cover",
|
|
534
|
+
size === "sm" ? "size-avatar-lg shrink-0" : "aspect-video w-full rounded-control",
|
|
535
|
+
round && "rounded-full",
|
|
536
|
+
)}
|
|
537
|
+
/>
|
|
538
|
+
) : (
|
|
539
|
+
<div
|
|
540
|
+
role={alt ? "img" : undefined}
|
|
541
|
+
aria-label={alt ? `${alt} · ${copy.imageMissing}` : undefined}
|
|
542
|
+
className={cn(
|
|
543
|
+
"flex items-center justify-center bg-surface-subtle text-fg-subtle",
|
|
544
|
+
size === "sm" ? "size-avatar-lg shrink-0" : "aspect-video rounded-control",
|
|
545
|
+
round && "rounded-full",
|
|
546
|
+
)}
|
|
547
|
+
>
|
|
548
|
+
<span aria-hidden>▧</span>
|
|
549
|
+
</div>
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
/** Same wire vocabulary as upstream, with SuperXD controls and a closed token-only presentation. */
|
|
553
|
+
export function createSuperxdGenerativeLibrary(
|
|
554
|
+
options: GenerativeLibraryOptions = {},
|
|
555
|
+
): GenerativeUILibrary {
|
|
556
|
+
const component = <T extends z.ZodType>(
|
|
557
|
+
schema: T,
|
|
558
|
+
description: string,
|
|
559
|
+
render: (
|
|
560
|
+
props: z.infer<T> & {
|
|
561
|
+
children?: ReactNode;
|
|
562
|
+
$dispatch?: GenerativeUIDispatch;
|
|
563
|
+
$action?: StructuredAction;
|
|
564
|
+
},
|
|
565
|
+
) => ReactNode,
|
|
566
|
+
) => ({ properties: schema, description, render });
|
|
567
|
+
return {
|
|
568
|
+
Card: component(
|
|
569
|
+
nodeSchemas.Card,
|
|
570
|
+
"A related set of content, optionally with a form and actions.",
|
|
571
|
+
({ title, asForm, confirm, cancel, children, $dispatch }) => (
|
|
572
|
+
<section className="flex min-w-0 flex-col gap-5 rounded-surface border border-line-subtle bg-surface p-5 text-fg shadow-control sm:p-6">
|
|
573
|
+
{asForm ? (
|
|
574
|
+
<GenerativeForm
|
|
575
|
+
title={title}
|
|
576
|
+
confirm={confirm}
|
|
577
|
+
cancel={cancel}
|
|
578
|
+
dispatch={$dispatch}
|
|
579
|
+
resolveFact={options.resolveFact}
|
|
580
|
+
>
|
|
581
|
+
{children}
|
|
582
|
+
</GenerativeForm>
|
|
583
|
+
) : (
|
|
584
|
+
<>
|
|
585
|
+
{title ? <h3 className="type-title text-fg-emphasis">{title}</h3> : null}
|
|
586
|
+
{children}
|
|
587
|
+
{confirm || cancel ? (
|
|
588
|
+
<div className="flex flex-wrap gap-3 border-t border-line-subtle pt-5">
|
|
589
|
+
{confirm ? (
|
|
590
|
+
<GeneratedButton
|
|
591
|
+
label={confirm.label}
|
|
592
|
+
action={confirm.$action}
|
|
593
|
+
dispatch={$dispatch}
|
|
594
|
+
buttonStyle="primary"
|
|
595
|
+
/>
|
|
596
|
+
) : null}
|
|
597
|
+
{cancel ? (
|
|
598
|
+
<GeneratedButton
|
|
599
|
+
label={cancel.label}
|
|
600
|
+
action={cancel.$action}
|
|
601
|
+
dispatch={$dispatch}
|
|
602
|
+
buttonStyle="ghost"
|
|
603
|
+
/>
|
|
604
|
+
) : null}
|
|
605
|
+
</div>
|
|
606
|
+
) : null}
|
|
607
|
+
</>
|
|
608
|
+
)}
|
|
609
|
+
</section>
|
|
610
|
+
),
|
|
611
|
+
),
|
|
612
|
+
Form: component(
|
|
613
|
+
nodeSchemas.Form,
|
|
614
|
+
"A form collecting named, labelled controls.",
|
|
615
|
+
({ children, $action, $dispatch }) => (
|
|
616
|
+
<GenerativeForm action={$action} dispatch={$dispatch} resolveFact={options.resolveFact}>
|
|
617
|
+
{children}
|
|
618
|
+
</GenerativeForm>
|
|
619
|
+
),
|
|
620
|
+
),
|
|
621
|
+
Row: component(nodeSchemas.Row, "A wrapping row.", ({ children, gap = 3, align, justify }) => (
|
|
622
|
+
<div
|
|
623
|
+
className={cn(
|
|
624
|
+
"flex min-w-0 flex-wrap",
|
|
625
|
+
gaps[gap],
|
|
626
|
+
align === "center" ? "items-center" : align === "end" ? "items-end" : "items-start",
|
|
627
|
+
justify === "between"
|
|
628
|
+
? "justify-between"
|
|
629
|
+
: justify === "end"
|
|
630
|
+
? "justify-end"
|
|
631
|
+
: justify === "center"
|
|
632
|
+
? "justify-center"
|
|
633
|
+
: "justify-start",
|
|
634
|
+
)}
|
|
635
|
+
>
|
|
636
|
+
{children}
|
|
637
|
+
</div>
|
|
638
|
+
)),
|
|
639
|
+
Col: component(nodeSchemas.Col, "A vertical stack.", ({ children, gap = 3, align }) => (
|
|
640
|
+
<div
|
|
641
|
+
className={cn(
|
|
642
|
+
"flex min-w-0 flex-col",
|
|
643
|
+
gaps[gap],
|
|
644
|
+
align === "center" ? "items-center" : align === "end" ? "items-end" : "items-stretch",
|
|
645
|
+
)}
|
|
646
|
+
>
|
|
647
|
+
{children}
|
|
648
|
+
</div>
|
|
649
|
+
)),
|
|
650
|
+
Text: component(
|
|
651
|
+
nodeSchemas.Text,
|
|
652
|
+
"Plain text.",
|
|
653
|
+
({ value, size = "sm", weight, color, children }) => (
|
|
654
|
+
<p
|
|
655
|
+
className={cn(
|
|
656
|
+
"min-w-0 wrap-anywhere",
|
|
657
|
+
sizes[size],
|
|
658
|
+
weight && weight !== "normal" && "font-medium",
|
|
659
|
+
color === "secondary" ? "text-fg-subtle" : "text-fg-emphasis",
|
|
660
|
+
)}
|
|
661
|
+
>
|
|
662
|
+
{value}
|
|
663
|
+
{children}
|
|
664
|
+
</p>
|
|
665
|
+
),
|
|
666
|
+
),
|
|
667
|
+
Header: component(nodeSchemas.Header, "A section heading.", ({ text, size = "md" }) => (
|
|
668
|
+
<h3 className={cn("text-fg-emphasis", sizes[size])}>{text}</h3>
|
|
669
|
+
)),
|
|
670
|
+
Caption: component(nodeSchemas.Caption, "Quiet supporting text.", ({ value }) => (
|
|
671
|
+
<p className="type-caption font-normal wrap-anywhere text-fg-subtle">{value}</p>
|
|
672
|
+
)),
|
|
673
|
+
Markdown: component(nodeSchemas.Markdown, "Safe formatted text.", ({ value }) => (
|
|
674
|
+
<MessageMarkdown text={value} />
|
|
675
|
+
)),
|
|
676
|
+
Fact: component(nodeSchemas.Fact, "A labelled value.", (props) => (
|
|
677
|
+
<GeneratedFact {...props} resolveFact={options.resolveFact} />
|
|
678
|
+
)),
|
|
679
|
+
Badge: component(
|
|
680
|
+
nodeSchemas.Badge,
|
|
681
|
+
"A status with text as well as colour.",
|
|
682
|
+
({ value, variant, children }) => (
|
|
683
|
+
<span
|
|
684
|
+
className={cn(
|
|
685
|
+
"inline-flex items-center gap-1.5 self-start rounded-cell border px-2 py-1 font-mono type-caption",
|
|
686
|
+
variant === "success"
|
|
687
|
+
? "border-success-line bg-success-subtle text-success"
|
|
688
|
+
: variant === "warning"
|
|
689
|
+
? "border-attention-line bg-attention-subtle text-attention"
|
|
690
|
+
: variant === "info"
|
|
691
|
+
? "border-info-line bg-info-subtle text-info"
|
|
692
|
+
: "border-line-subtle bg-surface-muted text-fg",
|
|
693
|
+
)}
|
|
694
|
+
>
|
|
695
|
+
{children}
|
|
696
|
+
{value}
|
|
697
|
+
</span>
|
|
698
|
+
),
|
|
699
|
+
),
|
|
700
|
+
Divider: component(nodeSchemas.Divider, "A quiet separator.", () => (
|
|
701
|
+
<hr className="border-line-subtle" />
|
|
702
|
+
)),
|
|
703
|
+
Spacer: component(nodeSchemas.Spacer, "Flexible space.", () => <span className="flex-1" />),
|
|
704
|
+
Accent: component(nodeSchemas.Accent, "A neutral event marker.", () => (
|
|
705
|
+
<span aria-hidden className="h-control-sm w-1 rounded-full bg-line-emphasis" />
|
|
706
|
+
)),
|
|
707
|
+
Icon: component(nodeSchemas.Icon, "A supporting decorative symbol.", (props) => (
|
|
708
|
+
<GenerativeIcon {...props} />
|
|
709
|
+
)),
|
|
710
|
+
Image: component(nodeSchemas.Image, "A host-approved image with an alternative.", (props) => (
|
|
711
|
+
<ApprovedImage key={props.src} {...props} {...options} />
|
|
712
|
+
)),
|
|
713
|
+
Avatar: component(nodeSchemas.Avatar, "Initials for a person.", ({ name }) => (
|
|
714
|
+
<span
|
|
715
|
+
role="img"
|
|
716
|
+
aria-label={name}
|
|
717
|
+
className="grid size-avatar-lg shrink-0 place-items-center rounded-full bg-surface-emphasis type-label text-fg-emphasis"
|
|
718
|
+
>
|
|
719
|
+
{name
|
|
720
|
+
.split(/\s+/)
|
|
721
|
+
.slice(0, 2)
|
|
722
|
+
.map((n) => n[0])
|
|
723
|
+
.join("")}
|
|
724
|
+
</span>
|
|
725
|
+
)),
|
|
726
|
+
Alert: component(nodeSchemas.Alert, "A contextual message.", ({ title, description, tone }) => (
|
|
727
|
+
<Callout
|
|
728
|
+
title={title}
|
|
729
|
+
tone={tone === "warning" ? "attention" : tone === "danger" ? "error" : tone}
|
|
730
|
+
>
|
|
731
|
+
{description}
|
|
732
|
+
</Callout>
|
|
733
|
+
)),
|
|
734
|
+
ListView: component(nodeSchemas.ListView, "A set of related rows.", ({ children }) => (
|
|
735
|
+
<ul className="flex list-none flex-col divide-y divide-line-subtle p-0">{children}</ul>
|
|
736
|
+
)),
|
|
737
|
+
ListViewItem: component(
|
|
738
|
+
nodeSchemas.ListViewItem,
|
|
739
|
+
"A passive list row. Put explicit buttons within it.",
|
|
740
|
+
({ children }) => <li className="min-w-0 py-3 first:pt-0 last:pb-0">{children}</li>,
|
|
741
|
+
),
|
|
742
|
+
Input: component(nodeSchemas.Input, "A labelled text field.", (props) => (
|
|
743
|
+
<InputField {...props} />
|
|
744
|
+
)),
|
|
745
|
+
Select: component(nodeSchemas.Select, "A labelled choice.", (props) => (
|
|
746
|
+
<SelectField {...props} />
|
|
747
|
+
)),
|
|
748
|
+
DatePicker: component(
|
|
749
|
+
nodeSchemas.DatePicker,
|
|
750
|
+
"A calendar date, with segmented keyboard input.",
|
|
751
|
+
(props) => <GeneratedDate {...props} />,
|
|
752
|
+
),
|
|
753
|
+
RadioGroup: component(nodeSchemas.RadioGroup, "A small set of choices.", (props) => (
|
|
754
|
+
<GeneratedRadio {...props} />
|
|
755
|
+
)),
|
|
756
|
+
Checkbox: component(nodeSchemas.Checkbox, "An optional choice.", (props) => (
|
|
757
|
+
<GeneratedCheckbox {...props} />
|
|
758
|
+
)),
|
|
759
|
+
Button: component(
|
|
760
|
+
nodeSchemas.Button,
|
|
761
|
+
"An explicit action routed to the host.",
|
|
762
|
+
({ $action, $dispatch, children, ...props }) => (
|
|
763
|
+
<GeneratedButton {...props} action={$action} dispatch={$dispatch}>
|
|
764
|
+
{children}
|
|
765
|
+
</GeneratedButton>
|
|
766
|
+
),
|
|
767
|
+
),
|
|
768
|
+
Progress: component(
|
|
769
|
+
nodeSchemas.Progress,
|
|
770
|
+
"A labelled measure of progress.",
|
|
771
|
+
({ label, value, max }) => (
|
|
772
|
+
<ProgressBar
|
|
773
|
+
aria-label={label}
|
|
774
|
+
value={value}
|
|
775
|
+
maxValue={max}
|
|
776
|
+
className="h-1.5 overflow-hidden rounded-full bg-surface-emphasis"
|
|
777
|
+
>
|
|
778
|
+
<div
|
|
779
|
+
className="h-full rounded-full bg-brand"
|
|
780
|
+
style={{ width: `${(value / max) * 100}%` }}
|
|
781
|
+
/>
|
|
782
|
+
</ProgressBar>
|
|
783
|
+
),
|
|
784
|
+
),
|
|
785
|
+
Chart: component(
|
|
786
|
+
nodeSchemas.Chart,
|
|
787
|
+
"A bounded chart with a data-table alternative.",
|
|
788
|
+
(props) => <GenerativeChart {...props} />,
|
|
789
|
+
),
|
|
790
|
+
Table: component(nodeSchemas.Table, "A bounded data table, optionally sortable.", (props) => (
|
|
791
|
+
<GenerativeTable {...props} />
|
|
792
|
+
)),
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
function GeneratedButton({
|
|
796
|
+
label,
|
|
797
|
+
buttonStyle,
|
|
798
|
+
block,
|
|
799
|
+
submit,
|
|
800
|
+
children,
|
|
801
|
+
action,
|
|
802
|
+
dispatch,
|
|
803
|
+
}: z.infer<typeof nodeSchemas.Button> & {
|
|
804
|
+
children?: ReactNode;
|
|
805
|
+
action?: StructuredAction;
|
|
806
|
+
dispatch?: GenerativeUIDispatch;
|
|
807
|
+
}) {
|
|
808
|
+
const { blocked, available, captureInput } = useContext(GenerativeRuntimeContext);
|
|
809
|
+
const form = useContext(FieldContext);
|
|
810
|
+
return (
|
|
811
|
+
<Button
|
|
812
|
+
type={submit ? "submit" : "button"}
|
|
813
|
+
variant={
|
|
814
|
+
buttonStyle === "danger"
|
|
815
|
+
? "destructive"
|
|
816
|
+
: buttonStyle === "primary"
|
|
817
|
+
? "primary"
|
|
818
|
+
: buttonStyle === "ghost"
|
|
819
|
+
? "ghost"
|
|
820
|
+
: "secondary"
|
|
821
|
+
}
|
|
822
|
+
className={cn(
|
|
823
|
+
"h-auto min-h-control-md max-w-full py-control-y wrap-anywhere whitespace-normal",
|
|
824
|
+
block && "w-full",
|
|
825
|
+
)}
|
|
826
|
+
isDisabled={
|
|
827
|
+
blocked ||
|
|
828
|
+
!dispatch ||
|
|
829
|
+
(submit ? !form?.canSubmit : !action || Boolean(available && !available.has(action.type)))
|
|
830
|
+
}
|
|
831
|
+
onPress={
|
|
832
|
+
submit
|
|
833
|
+
? undefined
|
|
834
|
+
: () => {
|
|
835
|
+
if (action) {
|
|
836
|
+
if (form) captureInput?.({ ...action, $input: formValues(form.fields).values });
|
|
837
|
+
void dispatch?.(action);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
>
|
|
842
|
+
{children}
|
|
843
|
+
{label}
|
|
844
|
+
</Button>
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
export { buttonStyles };
|