@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
package/package.json ADDED
@@ -0,0 +1,138 @@
1
+ {
2
+ "name": "@superxd-studio/adptr",
3
+ "version": "0.5.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "description": "ADPTR by SuperXD: React components and finished product flows for conversation, review and AI-assisted work, with every state designed.",
7
+ "author": "SuperXD",
8
+ "homepage": "https://superxd.app",
9
+ "keywords": [
10
+ "react",
11
+ "components",
12
+ "design-system",
13
+ "react-aria",
14
+ "ai",
15
+ "chat",
16
+ "adptr"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "exports": {
22
+ "./ui": {
23
+ "types": "./types/reusable/ui/index.d.ts",
24
+ "import": "./ui/index.js"
25
+ },
26
+ "./interactions": {
27
+ "types": "./types/reusable/interactions/index.d.ts",
28
+ "import": "./interactions/index.js"
29
+ },
30
+ "./core": {
31
+ "types": "./types/reusable/core/index.d.ts",
32
+ "import": "./core/index.js"
33
+ },
34
+ "./custom": {
35
+ "types": "./types/reusable/custom/index.d.ts",
36
+ "import": "./custom/index.js"
37
+ },
38
+ "./web": {
39
+ "types": "./types/reusable/web/index.d.ts",
40
+ "import": "./web/index.js"
41
+ },
42
+ "./assistant-ui": {
43
+ "types": "./types/reusable/assistant-ui/index.d.ts",
44
+ "import": "./assistant-ui/index.js"
45
+ },
46
+ "./ai-sdk": {
47
+ "types": "./types/reusable/ai-sdk/index.d.ts",
48
+ "import": "./ai-sdk/index.js"
49
+ },
50
+ "./uploads": {
51
+ "types": "./types/reusable/uploads/index.d.ts",
52
+ "import": "./uploads/index.js"
53
+ },
54
+ "./review": {
55
+ "types": "./types/reusable/review/index.d.ts",
56
+ "import": "./review/index.js"
57
+ },
58
+ "./generative": {
59
+ "types": "./types/reusable/generative/index.d.ts",
60
+ "import": "./generative/index.js"
61
+ },
62
+ "./langgraph-review": {
63
+ "types": "./types/reusable/langgraph-review/index.d.ts",
64
+ "import": "./langgraph-review/index.js"
65
+ },
66
+ "./styles.css": {
67
+ "types": "./styles.d.ts",
68
+ "default": "./styles.css"
69
+ },
70
+ "./fonts.css": {
71
+ "types": "./styles.d.ts",
72
+ "default": "./fonts.css"
73
+ }
74
+ },
75
+ "files": [
76
+ "source",
77
+ "examples",
78
+ "ui",
79
+ "interactions",
80
+ "INTERACTIONS.md",
81
+ "core",
82
+ "custom",
83
+ "web",
84
+ "assistant-ui",
85
+ "ai-sdk",
86
+ "uploads",
87
+ "review",
88
+ "generative",
89
+ "langgraph-review",
90
+ "types",
91
+ "assets",
92
+ "chunks",
93
+ "styles.css",
94
+ "fonts.css",
95
+ "styles.d.ts",
96
+ "LICENSE",
97
+ "THIRD-PARTY-NOTICES.md",
98
+ "licenses",
99
+ "README.md",
100
+ "REUSE.md",
101
+ "REVIEW.md",
102
+ "GENERATIVE.md"
103
+ ],
104
+ "sideEffects": [
105
+ "*.css"
106
+ ],
107
+ "dependencies": {
108
+ "clsx": "^2.1.1",
109
+ "tailwind-merge": "^3.7.0",
110
+ "react-aria-components": "^1.21.1",
111
+ "react-markdown": ">=10.0.0 <=10.1.0",
112
+ "remark-gfm": ">=4.0.0 <=4.0.1",
113
+ "@internationalized/date": "^3.12.4",
114
+ "zod": ">=4.6.4 <=4.6.5"
115
+ },
116
+ "peerDependencies": {
117
+ "react": "^19.3.0",
118
+ "react-dom": "^19.3.0",
119
+ "@assistant-ui/react": ">=0.15.22 <=0.15.23",
120
+ "@assistant-ui/react-generative-ui": ">=0.0.21 <=0.0.22",
121
+ "ai": ">=7.0.126 <=7.0.127",
122
+ "@langchain/langgraph": ">=1.4.18 <=1.4.19"
123
+ },
124
+ "peerDependenciesMeta": {
125
+ "@assistant-ui/react": {
126
+ "optional": true
127
+ },
128
+ "@assistant-ui/react-generative-ui": {
129
+ "optional": true
130
+ },
131
+ "ai": {
132
+ "optional": true
133
+ },
134
+ "@langchain/langgraph": {
135
+ "optional": true
136
+ }
137
+ }
138
+ }
@@ -0,0 +1,31 @@
1
+ import { a as e, i as t, n, o as r, r as i, t as a } from "../chunks/types-BGF7r1my.js";
2
+ import { n as o, t as s } from "../chunks/controller-Cfpg0C7v.js";
3
+ //#region src/reusable/review/index.ts
4
+ function c(n) {
5
+ let i = n.endpoint.replace(/\/$/, "");
6
+ async function o(e, t, r) {
7
+ let o = AbortSignal.timeout(n.timeoutMs ?? 15e3), s = await (n.fetch ?? globalThis.fetch)(i + e, {
8
+ signal: AbortSignal.any([t, o]),
9
+ method: r === void 0 ? "GET" : "POST",
10
+ credentials: n.credentials ?? "same-origin",
11
+ headers: {
12
+ ...Object.fromEntries(new Headers(await n.headers?.()).entries()),
13
+ ...r === void 0 ? {} : { "content-type": "application/json" }
14
+ },
15
+ body: r === void 0 ? void 0 : JSON.stringify(r)
16
+ });
17
+ if (s.status === 409) throw new a("conflict");
18
+ if (s.status === 401 || s.status === 403) throw new a("blocked");
19
+ if (!s.ok || !s.headers.get("content-type")?.startsWith("application/json")) throw Error("Review unavailable");
20
+ let c = await s.text();
21
+ if (c.length > 22e4) throw Error("Review response too large");
22
+ return JSON.parse(c);
23
+ }
24
+ return {
25
+ load: async (t) => e(await o("", t)),
26
+ decide: async (e, n) => t(await o("/decisions", n, e)),
27
+ recover: async (e, t) => r(await o(`/receipts/${encodeURIComponent(e)}`, t))
28
+ };
29
+ }
30
+ //#endregion
31
+ export { s as ReviewController, a as ReviewFault, c as createHttpReviewAdapter, n as parseReviewDecision, i as parseReviewProposal, t as parseReviewReceipt, e as parseReviewRecord, r as parseReviewRecovery, o as reviewReducer };
@@ -0,0 +1,50 @@
1
+ import { useState } from "react";
2
+ import { cn } from "../lib/cn.js";
3
+
4
+ const sizes = {
5
+ sm: "size-avatar-sm text-avatar-sm",
6
+ md: "size-avatar-md text-xs",
7
+ lg: "size-avatar-lg text-sm",
8
+ } as const;
9
+
10
+ export interface AvatarProps {
11
+ name: string;
12
+ src?: string;
13
+ size?: keyof typeof sizes;
14
+ className?: string;
15
+ }
16
+
17
+ function initials(name: string): string {
18
+ const parts = name.trim().split(/\s+/);
19
+ const first = parts[0]?.[0] ?? "";
20
+ const last = parts.length > 1 ? (parts[parts.length - 1]?.[0] ?? "") : "";
21
+ return (first + last).toUpperCase();
22
+ }
23
+
24
+ /** A person's photo, or their initials when there's no photo or it fails to load. */
25
+ export function Avatar({ name, src, size = "md", className }: AvatarProps) {
26
+ const [failed, setFailed] = useState(false);
27
+ const showImage = src && !failed;
28
+ return (
29
+ <span
30
+ className={cn(
31
+ "inline-flex shrink-0 items-center justify-center overflow-hidden rounded-pill bg-surface-emphasis font-semibold text-fg-emphasis select-none",
32
+ sizes[size],
33
+ className,
34
+ )}
35
+ >
36
+ {showImage ? (
37
+ <img
38
+ src={src}
39
+ alt={name}
40
+ className="size-full object-cover"
41
+ onError={() => setFailed(true)}
42
+ />
43
+ ) : (
44
+ <span role="img" aria-label={name}>
45
+ {initials(name)}
46
+ </span>
47
+ )}
48
+ </span>
49
+ );
50
+ }
@@ -0,0 +1,155 @@
1
+ import type { ReactNode, Ref } from "react";
2
+ import {
3
+ Button as AriaButton,
4
+ Link as AriaLink,
5
+ composeRenderProps,
6
+ type ButtonProps as AriaButtonProps,
7
+ type LinkProps as AriaLinkProps,
8
+ } from "react-aria-components";
9
+ import { cn } from "../lib/cn.js";
10
+ import { IconCheck } from "./icons";
11
+ import { Spinner } from "./spinner";
12
+
13
+ export type ButtonVariant = "primary" | "secondary" | "ghost" | "destructive";
14
+ export type ButtonSize = "sm" | "md" | "lg" | "icon-sm" | "icon-md";
15
+
16
+ const base =
17
+ "relative inline-flex select-none items-center justify-center gap-2 whitespace-nowrap rounded-control type-label outline-none press data-focus-visible:focus-ring data-disabled:cursor-not-allowed data-disabled:opacity-50 data-done:border-done! data-done:bg-done! data-done:text-on-done! data-done:shadow-none!";
18
+
19
+ const variants: Record<ButtonVariant, string> = {
20
+ primary:
21
+ "bg-brand text-on-brand shadow-control data-hovered:bg-brand-emphasis data-pressed:bg-brand-emphasis",
22
+ secondary:
23
+ "border border-line bg-surface text-fg-emphasis shadow-control data-hovered:border-line-emphasis data-hovered:bg-surface-muted data-pressed:bg-surface-subtle",
24
+ ghost: "text-fg-emphasis data-hovered:bg-surface-subtle data-pressed:bg-surface-emphasis",
25
+ destructive:
26
+ "bg-destructive text-on-destructive shadow-control data-hovered:bg-destructive/90 data-pressed:bg-destructive/85",
27
+ };
28
+
29
+ const sizes: Record<ButtonSize, string> = {
30
+ sm: "h-control-sm px-control-sm-x",
31
+ md: "h-control-md px-control-md-x",
32
+ lg: "h-control-lg px-control-lg-x",
33
+ "icon-sm": "size-control-sm",
34
+ "icon-md": "size-control-md",
35
+ };
36
+
37
+ /** The button classes, for anything that should look like a button (links, triggers). */
38
+ export function buttonStyles({
39
+ variant = "secondary",
40
+ size = "md",
41
+ className,
42
+ }: {
43
+ variant?: ButtonVariant;
44
+ size?: ButtonSize;
45
+ className?: string;
46
+ } = {}): string {
47
+ return cn(base, variants[variant], sizes[size], className);
48
+ }
49
+
50
+ export interface ButtonProps extends AriaButtonProps {
51
+ ref?: Ref<HTMLButtonElement>;
52
+ variant?: ButtonVariant;
53
+ size?: ButtonSize;
54
+ /**
55
+ * Shown with a spinner while `isPending` is true, for example "Booking".
56
+ * Without it the label stays in place (so the width holds) under a spinner.
57
+ */
58
+ pendingLabel?: ReactNode;
59
+ /**
60
+ * The moment the action finished, for example "Saved". The button turns SuperXD's
61
+ * ultramarine with a tick, keeping its width. Announce the result nearby as well.
62
+ */
63
+ isDone?: boolean;
64
+ /** Shown with the tick while `isDone` is true. Without it the button's own label stays. */
65
+ doneLabel?: ReactNode;
66
+ }
67
+
68
+ export function Button({
69
+ variant = "secondary",
70
+ size = "md",
71
+ pendingLabel,
72
+ isDone = false,
73
+ doneLabel,
74
+ className,
75
+ children,
76
+ ...props
77
+ }: ButtonProps) {
78
+ return (
79
+ <AriaButton
80
+ {...props}
81
+ data-done={isDone || undefined}
82
+ className={composeRenderProps(className, (className) =>
83
+ buttonStyles({ variant, size, className }),
84
+ )}
85
+ >
86
+ {composeRenderProps(children, (children, { isPending }) => {
87
+ if (isDone && !isPending) {
88
+ return (
89
+ <>
90
+ <span className="invisible inline-flex items-center gap-2">{children}</span>
91
+ <span className="absolute inset-0 flex animate-reveal-in items-center justify-center gap-2">
92
+ <IconCheck />
93
+ {doneLabel ?? children}
94
+ </span>
95
+ </>
96
+ );
97
+ }
98
+ if (!isPending) return children;
99
+ if (pendingLabel) {
100
+ return (
101
+ <>
102
+ <Spinner />
103
+ {pendingLabel}
104
+ </>
105
+ );
106
+ }
107
+ return (
108
+ <>
109
+ <span className="invisible inline-flex items-center gap-2">{children}</span>
110
+ <span className="absolute inset-0 flex items-center justify-center">
111
+ <Spinner />
112
+ </span>
113
+ </>
114
+ );
115
+ })}
116
+ </AriaButton>
117
+ );
118
+ }
119
+
120
+ export interface ButtonLinkProps extends AriaLinkProps {
121
+ variant?: ButtonVariant;
122
+ size?: ButtonSize;
123
+ }
124
+
125
+ /** A link that looks like a button. Use it when the action goes somewhere. */
126
+ export function ButtonLink({
127
+ variant = "secondary",
128
+ size = "md",
129
+ className,
130
+ ...props
131
+ }: ButtonLinkProps) {
132
+ return (
133
+ <AriaLink
134
+ {...props}
135
+ className={composeRenderProps(className, (className) =>
136
+ buttonStyles({ variant, size, className }),
137
+ )}
138
+ />
139
+ );
140
+ }
141
+
142
+ /** An inline text link. */
143
+ export function TextLink({ className, ...props }: AriaLinkProps) {
144
+ return (
145
+ <AriaLink
146
+ {...props}
147
+ className={composeRenderProps(className, (className) =>
148
+ cn(
149
+ "cursor-pointer rounded-[2px] font-medium text-fg-emphasis underline decoration-line-emphasis underline-offset-[3px] transition-colors duration-(--duration-fast) outline-none data-focus-visible:focus-ring data-hovered:decoration-fg-emphasis",
150
+ className,
151
+ ),
152
+ )}
153
+ />
154
+ );
155
+ }
@@ -0,0 +1,65 @@
1
+ import type { ReactNode } from "react";
2
+ import { cn } from "../lib/cn.js";
3
+ import { IconAlert, IconCircleAlert, IconCircleCheck, IconInfo } from "./icons";
4
+
5
+ export type CalloutTone = "neutral" | "info" | "success" | "attention" | "error";
6
+
7
+ const tones: Record<CalloutTone, { box: string; icon: ReactNode }> = {
8
+ neutral: {
9
+ box: "border-line-subtle bg-surface-muted text-fg",
10
+ icon: <IconInfo className="text-fg-subtle" />,
11
+ },
12
+ info: { box: "border-info-line bg-info-subtle text-info", icon: <IconInfo /> },
13
+ success: { box: "border-success-line bg-success-subtle text-success", icon: <IconCircleCheck /> },
14
+ attention: {
15
+ box: "border-attention-line bg-attention-subtle text-attention",
16
+ icon: <IconAlert />,
17
+ },
18
+ error: { box: "border-error-line bg-error-subtle text-error", icon: <IconCircleAlert /> },
19
+ };
20
+
21
+ export interface CalloutProps {
22
+ tone?: CalloutTone;
23
+ title?: ReactNode;
24
+ children?: ReactNode;
25
+ /** Buttons or links shown under the message. */
26
+ actions?: ReactNode;
27
+ /** Replace or remove (null) the tone's icon. */
28
+ icon?: ReactNode | null;
29
+ /**
30
+ * How screen readers hear it when it appears: "polite" waits its turn,
31
+ * "assertive" interrupts. Leave unset for messages that are there on load.
32
+ */
33
+ live?: "polite" | "assertive";
34
+ className?: string;
35
+ }
36
+
37
+ /** A short message in the flow of the page: what happened and what to do next. */
38
+ export function Callout({
39
+ tone = "neutral",
40
+ title,
41
+ children,
42
+ actions,
43
+ icon,
44
+ live,
45
+ className,
46
+ }: CalloutProps) {
47
+ const style = tones[tone];
48
+ return (
49
+ <div
50
+ role={live === "assertive" ? "alert" : live === "polite" ? "status" : undefined}
51
+ className={cn(
52
+ "flex gap-2.5 rounded-control border px-control-md-x py-control-y type-body",
53
+ style.box,
54
+ className,
55
+ )}
56
+ >
57
+ {icon === null ? null : <span className="mt-0.5 flex">{icon ?? style.icon}</span>}
58
+ <div className="flex min-w-0 flex-1 flex-col gap-1">
59
+ {title ? <p className="font-medium">{title}</p> : null}
60
+ {children ? <div className="text-pretty">{children}</div> : null}
61
+ {actions ? <div className="mt-1.5 flex flex-wrap gap-2">{actions}</div> : null}
62
+ </div>
63
+ </div>
64
+ );
65
+ }
@@ -0,0 +1,14 @@
1
+ import { SxdArrow } from "../superxd-arrow";
2
+
3
+ /** SuperXD's arrow marks the transition, centred in the space between state frames. */
4
+ export function CanvasConnector() {
5
+ return (
6
+ <span
7
+ data-canvas-connector
8
+ aria-hidden="true"
9
+ className="pointer-events-none absolute -top-10 left-1/2 -translate-x-1/2 -translate-y-1/2 text-system-blue"
10
+ >
11
+ <SxdArrow className="size-6 rotate-90" />
12
+ </span>
13
+ );
14
+ }
@@ -0,0 +1,34 @@
1
+ export const canvasCopy = {
2
+ previous: "Previous state",
3
+ loading: "Loading",
4
+ ready: "Ready",
5
+ details: "Your details",
6
+ holding: "Holding the time",
7
+ submitting: "Submitting",
8
+ failed: "Try again",
9
+ checking: "Checking status",
10
+ confirm: "Confirm the change",
11
+ booked: "Booked",
12
+ requested: "Requested",
13
+ moved: "Booking moved",
14
+ manage: "Manage booking",
15
+ cancelling: "Cancelling",
16
+ cancelled: "Cancelled",
17
+ closed: "Closed",
18
+ calendar: "Calendar",
19
+ event: "Event details",
20
+ edit: "Edit event",
21
+ saving: "Saving changes",
22
+ saved: "Changes saved",
23
+ discard: "Unsaved changes",
24
+ review: "Choose changes",
25
+ applying: "Applying changes",
26
+ partial: "Review remaining changes",
27
+ complete: "Changes applied",
28
+ denied: "Changes declined",
29
+ conflict: "Draft changed",
30
+ blocked: "View only",
31
+ uncertain: "Check the outcome",
32
+ support: "Support request",
33
+ supportSaved: "Request saved",
34
+ };