@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.
Files changed (221) hide show
  1. package/GENERATIVE.md +101 -0
  2. package/INTERACTIONS.md +51 -0
  3. package/LICENSE +21 -0
  4. package/README.md +57 -0
  5. package/REUSE.md +170 -0
  6. package/REVIEW.md +68 -0
  7. package/THIRD-PARTY-NOTICES.md +28 -0
  8. package/ai-sdk/index.js +96 -0
  9. package/assets/ibm-plex-mono-cyrillic-400-normal.woff2 +0 -0
  10. package/assets/ibm-plex-mono-cyrillic-500-normal.woff2 +0 -0
  11. package/assets/ibm-plex-mono-cyrillic-ext-400-normal.woff2 +0 -0
  12. package/assets/ibm-plex-mono-cyrillic-ext-500-normal.woff2 +0 -0
  13. package/assets/ibm-plex-mono-latin-400-normal.woff2 +0 -0
  14. package/assets/ibm-plex-mono-latin-500-normal.woff2 +0 -0
  15. package/assets/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
  16. package/assets/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
  17. package/assets/ibm-plex-mono-vietnamese-400-normal.woff2 +0 -0
  18. package/assets/ibm-plex-mono-vietnamese-500-normal.woff2 +0 -0
  19. package/assets/instrument-sans-latin-ext-wght-normal.woff2 +0 -0
  20. package/assets/instrument-sans-latin-wght-normal.woff2 +0 -0
  21. package/assistant-ui/index.js +139 -0
  22. package/chunks/callout-DKMHSnjc.js +342 -0
  23. package/chunks/canvas-BJ15-XxE.js +598 -0
  24. package/chunks/controller-Cfpg0C7v.js +180 -0
  25. package/chunks/core-BCqVRLUy.js +312 -0
  26. package/chunks/text-field-BLtoaZFn.js +41 -0
  27. package/chunks/types-BGF7r1my.js +115 -0
  28. package/chunks/ui-9DyNsYXY.js +967 -0
  29. package/core/index.js +4 -0
  30. package/custom/index.js +64 -0
  31. package/examples/ai-sdk-backend/package.json +11 -0
  32. package/examples/ai-sdk-backend/server.mjs +316 -0
  33. package/examples/custom-backend/copy.mjs +7 -0
  34. package/examples/custom-backend/server.mjs +118 -0
  35. package/examples/generative/specs.json +2094 -0
  36. package/examples/review-backend/copy.mjs +27 -0
  37. package/examples/review-backend/package.json +11 -0
  38. package/examples/review-backend/server.mjs +276 -0
  39. package/examples/web-ai-sdk/index.html +12 -0
  40. package/examples/web-ai-sdk/package.json +21 -0
  41. package/examples/web-ai-sdk/src/copy.ts +1 -0
  42. package/examples/web-ai-sdk/src/main.tsx +64 -0
  43. package/examples/web-ai-sdk/tsconfig.json +13 -0
  44. package/examples/web-generative/index.html +12 -0
  45. package/examples/web-generative/package.json +23 -0
  46. package/examples/web-generative/src/copy.ts +42 -0
  47. package/examples/web-generative/src/main.tsx +74 -0
  48. package/examples/web-generative/tsconfig.json +14 -0
  49. package/examples/web-independent/index.html +12 -0
  50. package/examples/web-independent/package.json +20 -0
  51. package/examples/web-independent/src/main.tsx +32 -0
  52. package/examples/web-independent/tsconfig.json +14 -0
  53. package/examples/web-interactions/index.html +12 -0
  54. package/examples/web-interactions/package.json +21 -0
  55. package/examples/web-interactions/src/copy.ts +215 -0
  56. package/examples/web-interactions/src/examples.tsx +628 -0
  57. package/examples/web-interactions/src/main.tsx +116 -0
  58. package/examples/web-interactions/tsconfig.json +14 -0
  59. package/examples/web-review/index.html +12 -0
  60. package/examples/web-review/package.json +20 -0
  61. package/examples/web-review/src/main.tsx +59 -0
  62. package/examples/web-review/tsconfig.json +14 -0
  63. package/fonts.css +117 -0
  64. package/generative/index.js +1560 -0
  65. package/interactions/index.js +1591 -0
  66. package/langgraph-review/index.js +36 -0
  67. package/licenses/ai-sdk.txt +13 -0
  68. package/licenses/assistant-ui-generative.txt +21 -0
  69. package/licenses/assistant-ui.txt +23 -0
  70. package/licenses/ibm-plex-mono.txt +93 -0
  71. package/licenses/instrument-sans.txt +93 -0
  72. package/licenses/internationalized-date.txt +201 -0
  73. package/licenses/langgraph.txt +21 -0
  74. package/licenses/lucide.txt +43 -0
  75. package/licenses/react-markdown.txt +21 -0
  76. package/licenses/remark-gfm.txt +22 -0
  77. package/licenses/zod.txt +21 -0
  78. package/package.json +138 -0
  79. package/review/index.js +31 -0
  80. package/source/components/avatar.tsx +50 -0
  81. package/source/components/button.tsx +155 -0
  82. package/source/components/callout.tsx +65 -0
  83. package/source/components/canvas/connector.tsx +14 -0
  84. package/source/components/canvas/copy.ts +34 -0
  85. package/source/components/canvas/index.tsx +257 -0
  86. package/source/components/canvas/recorder.ts +36 -0
  87. package/source/components/date-time-field.tsx +77 -0
  88. package/source/components/flow-motion.tsx +109 -0
  89. package/source/components/icons.tsx +275 -0
  90. package/source/components/responsive-overlay.tsx +91 -0
  91. package/source/components/scenario-picker/copy.ts +5 -0
  92. package/source/components/scenario-picker/index.tsx +142 -0
  93. package/source/components/scroll-area.tsx +59 -0
  94. package/source/components/search-picker.tsx +127 -0
  95. package/source/components/segmented-control.tsx +123 -0
  96. package/source/components/skeleton.tsx +11 -0
  97. package/source/components/spinner.tsx +19 -0
  98. package/source/components/superxd-arrow.tsx +22 -0
  99. package/source/components/switch.tsx +45 -0
  100. package/source/components/text-field.tsx +81 -0
  101. package/source/components/thinking.tsx +16 -0
  102. package/source/components/ui/copy.ts +12 -0
  103. package/source/components/ui/display.tsx +276 -0
  104. package/source/components/ui/navigation.tsx +346 -0
  105. package/source/components/ui/portal-theme.tsx +28 -0
  106. package/source/components/ui/selection.tsx +336 -0
  107. package/source/lib/cn.ts +23 -0
  108. package/source/reusable/ai-sdk/index.ts +114 -0
  109. package/source/reusable/assistant-ui/index.tsx +266 -0
  110. package/source/reusable/core/attachments.ts +163 -0
  111. package/source/reusable/core/index.ts +417 -0
  112. package/source/reusable/custom/index.ts +90 -0
  113. package/source/reusable/generative/actions.ts +188 -0
  114. package/source/reusable/generative/copy.ts +47 -0
  115. package/source/reusable/generative/data.tsx +126 -0
  116. package/source/reusable/generative/index.tsx +517 -0
  117. package/source/reusable/generative/schema.ts +327 -0
  118. package/source/reusable/generative/timeline.tsx +214 -0
  119. package/source/reusable/generative/vocabulary.tsx +847 -0
  120. package/source/reusable/interactions/agent.ts +259 -0
  121. package/source/reusable/interactions/artifact.ts +236 -0
  122. package/source/reusable/interactions/context.tsx +38 -0
  123. package/source/reusable/interactions/copy.ts +117 -0
  124. package/source/reusable/interactions/feedback.ts +73 -0
  125. package/source/reusable/interactions/index.ts +11 -0
  126. package/source/reusable/interactions/presentation.tsx +483 -0
  127. package/source/reusable/interactions/store.ts +74 -0
  128. package/source/reusable/interactions/threads.ts +126 -0
  129. package/source/reusable/interactions/web.tsx +574 -0
  130. package/source/reusable/langgraph-review/index.ts +73 -0
  131. package/source/reusable/review/controller.ts +263 -0
  132. package/source/reusable/review/index.ts +47 -0
  133. package/source/reusable/review/types.ts +189 -0
  134. package/source/reusable/ui/index.ts +25 -0
  135. package/source/reusable/uploads/index.ts +48 -0
  136. package/source/reusable/web/attachments/copy.ts +23 -0
  137. package/source/reusable/web/attachments/index.tsx +233 -0
  138. package/source/reusable/web/copy.ts +22 -0
  139. package/source/reusable/web/index.tsx +470 -0
  140. package/source/reusable/web/message/copy.ts +26 -0
  141. package/source/reusable/web/message/index.tsx +388 -0
  142. package/source/reusable/web/message/streaming.ts +64 -0
  143. package/source/reusable/web/message/styles.css +21 -0
  144. package/source/reusable/web/review/copy.ts +78 -0
  145. package/source/reusable/web/review/flow.ts +24 -0
  146. package/source/reusable/web/review/index.tsx +506 -0
  147. package/source/reusable/web/styles.ts +2 -0
  148. package/source/styles/library.css +612 -0
  149. package/source/styles/theme.css +228 -0
  150. package/styles.css +3 -0
  151. package/styles.d.ts +1 -0
  152. package/types/components/avatar.d.ts +14 -0
  153. package/types/components/button.d.ts +36 -0
  154. package/types/components/callout.d.ts +19 -0
  155. package/types/components/canvas/connector.d.ts +2 -0
  156. package/types/components/canvas/copy.d.ts +34 -0
  157. package/types/components/canvas/index.d.ts +33 -0
  158. package/types/components/canvas/recorder.d.ts +10 -0
  159. package/types/components/date-time-field.d.ts +11 -0
  160. package/types/components/flow-motion.d.ts +38 -0
  161. package/types/components/icons.d.ts +138 -0
  162. package/types/components/responsive-overlay.d.ts +15 -0
  163. package/types/components/scenario-picker/copy.d.ts +5 -0
  164. package/types/components/scenario-picker/index.d.ts +14 -0
  165. package/types/components/scroll-area.d.ts +14 -0
  166. package/types/components/search-picker.d.ts +27 -0
  167. package/types/components/segmented-control.d.ts +23 -0
  168. package/types/components/skeleton.d.ts +4 -0
  169. package/types/components/spinner.d.ts +4 -0
  170. package/types/components/superxd-arrow.d.ts +5 -0
  171. package/types/components/switch.d.ts +8 -0
  172. package/types/components/text-field.d.ts +19 -0
  173. package/types/components/thinking.d.ts +8 -0
  174. package/types/components/ui/copy.d.ts +12 -0
  175. package/types/components/ui/display.d.ts +50 -0
  176. package/types/components/ui/navigation.d.ts +63 -0
  177. package/types/components/ui/portal-theme.d.ts +8 -0
  178. package/types/components/ui/selection.d.ts +37 -0
  179. package/types/lib/cn.d.ts +3 -0
  180. package/types/reusable/ai-sdk/index.d.ts +15 -0
  181. package/types/reusable/assistant-ui/index.d.ts +12 -0
  182. package/types/reusable/core/attachments.d.ts +54 -0
  183. package/types/reusable/core/index.d.ts +107 -0
  184. package/types/reusable/custom/index.d.ts +14 -0
  185. package/types/reusable/generative/actions.d.ts +45 -0
  186. package/types/reusable/generative/copy.d.ts +48 -0
  187. package/types/reusable/generative/data.d.ts +4 -0
  188. package/types/reusable/generative/index.d.ts +30 -0
  189. package/types/reusable/generative/schema.d.ts +303 -0
  190. package/types/reusable/generative/timeline.d.ts +21 -0
  191. package/types/reusable/generative/vocabulary.d.ts +34 -0
  192. package/types/reusable/interactions/agent.d.ts +98 -0
  193. package/types/reusable/interactions/artifact.d.ts +118 -0
  194. package/types/reusable/interactions/context.d.ts +130 -0
  195. package/types/reusable/interactions/copy.d.ts +115 -0
  196. package/types/reusable/interactions/feedback.d.ts +34 -0
  197. package/types/reusable/interactions/index.d.ts +10 -0
  198. package/types/reusable/interactions/presentation.d.ts +97 -0
  199. package/types/reusable/interactions/store.d.ts +29 -0
  200. package/types/reusable/interactions/threads.d.ts +38 -0
  201. package/types/reusable/interactions/web.d.ts +22 -0
  202. package/types/reusable/langgraph-review/index.d.ts +5 -0
  203. package/types/reusable/review/controller.d.ts +52 -0
  204. package/types/reusable/review/index.d.ts +12 -0
  205. package/types/reusable/review/types.d.ts +61 -0
  206. package/types/reusable/ui/index.d.ts +19 -0
  207. package/types/reusable/uploads/index.d.ts +11 -0
  208. package/types/reusable/web/attachments/copy.d.ts +25 -0
  209. package/types/reusable/web/attachments/index.d.ts +24 -0
  210. package/types/reusable/web/copy.d.ts +24 -0
  211. package/types/reusable/web/index.d.ts +41 -0
  212. package/types/reusable/web/message/copy.d.ts +27 -0
  213. package/types/reusable/web/message/index.d.ts +56 -0
  214. package/types/reusable/web/message/streaming.d.ts +27 -0
  215. package/types/reusable/web/review/copy.d.ts +72 -0
  216. package/types/reusable/web/review/flow.d.ts +3 -0
  217. package/types/reusable/web/review/index.d.ts +38 -0
  218. package/types/reusable/web/styles.d.ts +2 -0
  219. package/ui/index.js +4 -0
  220. package/uploads/index.js +34 -0
  221. 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 };