@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,127 @@
1
+ import type { ReactNode } from "react";
2
+ import {
3
+ Autocomplete,
4
+ Button,
5
+ Dialog,
6
+ DialogTrigger,
7
+ Input,
8
+ ListBox,
9
+ ListBoxItem,
10
+ Popover,
11
+ SearchField,
12
+ useFilter,
13
+ } from "react-aria-components";
14
+ import { cn } from "../lib/cn.js";
15
+ import { IconCheck, IconChevronDown, IconSearch } from "./icons";
16
+
17
+ export interface PickerItem {
18
+ id: string;
19
+ /** What's shown and searched. */
20
+ label: string;
21
+ /** A quieter second line or right-hand detail, also searched. */
22
+ detail?: string;
23
+ /** Extra words to match when searching (not shown). */
24
+ keywords?: string;
25
+ }
26
+
27
+ export interface SearchPickerProps {
28
+ /** What's being chosen, for screen readers ("Time zone"). */
29
+ label: string;
30
+ items: PickerItem[];
31
+ value: string;
32
+ onChange: (id: string) => void;
33
+ /** The content of the trigger button. */
34
+ trigger: ReactNode;
35
+ searchPlaceholder?: string;
36
+ emptyMessage?: string;
37
+ triggerClassName?: string;
38
+ }
39
+
40
+ /**
41
+ * A button that opens a searchable list, for long lists like time zones.
42
+ * Typing filters the list; arrow keys and Enter choose; Escape closes.
43
+ */
44
+ export function SearchPicker({
45
+ label,
46
+ items,
47
+ value,
48
+ onChange,
49
+ trigger,
50
+ searchPlaceholder = "Search",
51
+ emptyMessage = "Nothing matches that search.",
52
+ triggerClassName,
53
+ }: SearchPickerProps) {
54
+ const { contains } = useFilter({ sensitivity: "base" });
55
+ return (
56
+ <DialogTrigger>
57
+ <Button
58
+ aria-label={label}
59
+ className={cn(
60
+ "inline-flex cursor-pointer items-center gap-1.5 rounded-cell text-left transition-colors duration-(--duration-fast) outline-none data-focus-visible:focus-ring data-hovered:text-fg-emphasis",
61
+ triggerClassName,
62
+ )}
63
+ >
64
+ {trigger}
65
+ <IconChevronDown className="size-3.5 text-fg-subtle" />
66
+ </Button>
67
+ <Popover
68
+ placement="bottom start"
69
+ offset={6}
70
+ className="w-72 origin-(--trigger-anchor-point) overflow-hidden rounded-surface border border-line-subtle bg-surface shadow-popover outline-none data-entering:animate-pop-in data-exiting:animate-pop-out"
71
+ >
72
+ <Dialog aria-label={label} className="outline-none">
73
+ {({ close }) => (
74
+ <Autocomplete filter={(text, input) => contains(text, input)}>
75
+ <SearchField
76
+ aria-label={`Search ${label.toLowerCase()}`}
77
+ autoFocus
78
+ className="border-b border-line-subtle"
79
+ >
80
+ <div className="flex items-center gap-2 px-3">
81
+ <IconSearch className="text-fg-subtle" />
82
+ <Input
83
+ placeholder={searchPlaceholder}
84
+ className="h-10 w-full bg-transparent text-sm text-fg-emphasis outline-none placeholder:text-fg-subtle [&::-webkit-search-cancel-button]:hidden"
85
+ />
86
+ </div>
87
+ </SearchField>
88
+ <ListBox
89
+ aria-label={label}
90
+ items={items}
91
+ selectionMode="single"
92
+ selectedKeys={[value]}
93
+ onSelectionChange={(keys) => {
94
+ const [next] = [...keys];
95
+ if (next !== undefined) {
96
+ onChange(String(next));
97
+ close();
98
+ }
99
+ }}
100
+ renderEmptyState={() => (
101
+ <p className="px-3 py-6 text-center type-body text-fg-subtle">{emptyMessage}</p>
102
+ )}
103
+ className="max-h-72 overflow-y-auto p-1 outline-none"
104
+ >
105
+ {(item) => (
106
+ <ListBoxItem
107
+ id={item.id}
108
+ textValue={[item.label, item.detail, item.keywords].filter(Boolean).join(" ")}
109
+ className="group flex cursor-pointer items-center gap-2 rounded-cell px-2 py-1.5 type-body text-fg outline-none data-focused:bg-surface-subtle data-focused:text-fg-emphasis data-selected:text-fg-emphasis"
110
+ >
111
+ <span className="min-w-0 flex-1 truncate">{item.label}</span>
112
+ {item.detail ? (
113
+ <span className="shrink-0 type-caption font-normal text-fg-subtle numeric">
114
+ {item.detail}
115
+ </span>
116
+ ) : null}
117
+ <IconCheck className="invisible size-3.5 text-fg-emphasis group-data-selected:visible" />
118
+ </ListBoxItem>
119
+ )}
120
+ </ListBox>
121
+ </Autocomplete>
122
+ )}
123
+ </Dialog>
124
+ </Popover>
125
+ </DialogTrigger>
126
+ );
127
+ }
@@ -0,0 +1,123 @@
1
+ import { useLayoutEffect, useRef, type ReactNode } from "react";
2
+ import { ToggleButton, ToggleButtonGroup } from "react-aria-components";
3
+ import { cn } from "../lib/cn.js";
4
+
5
+ export interface SegmentedOption<K extends string> {
6
+ id: K;
7
+ label: ReactNode;
8
+ icon?: ReactNode;
9
+ /** Required when the label is only an icon. */
10
+ "aria-label"?: string;
11
+ isDisabled?: boolean;
12
+ }
13
+
14
+ export interface SegmentedControlProps<K extends string> {
15
+ /** What the choice is about, for screen readers ("Meeting length"). */
16
+ label: string;
17
+ options: SegmentedOption<K>[];
18
+ value: K;
19
+ onChange: (value: K) => void;
20
+ size?: "sm" | "md";
21
+ className?: string;
22
+ }
23
+
24
+ /**
25
+ * The selected background slides from the last choice to the new one and settles. It sits
26
+ * inside the selected option, so it always fits it; only the slide itself is animated.
27
+ * (React Aria's SelectionIndicator does the same, but forces a synchronous render when it
28
+ * first appears, which cancels a flow's step transition.)
29
+ */
30
+ function useSlide(value: string, ids: readonly string[]) {
31
+ const group = useRef<HTMLDivElement>(null);
32
+ const mark = useRef<HTMLSpanElement>(null);
33
+ const last = useRef(value);
34
+ useLayoutEffect(() => {
35
+ const previous = last.current;
36
+ last.current = value;
37
+ const el = mark.current;
38
+ const old = group.current?.children[ids.indexOf(previous)];
39
+ if (previous === value || !el || !old || typeof el.animate !== "function") return;
40
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
41
+ const from = old.getBoundingClientRect();
42
+ const to = el.getBoundingClientRect();
43
+ const style = getComputedStyle(el);
44
+ el.animate(
45
+ [
46
+ {
47
+ translate: `${from.left - to.left}px ${from.top - to.top}px`,
48
+ width: `${from.width}px`,
49
+ height: `${from.height}px`,
50
+ },
51
+ { translate: "0 0", width: `${to.width}px`, height: `${to.height}px` },
52
+ ],
53
+ {
54
+ duration: parseFloat(style.getPropertyValue("--duration-settle")) || 0,
55
+ easing: style.getPropertyValue("--ease-settle").trim() || "ease-out",
56
+ },
57
+ );
58
+ }, [value, ids]);
59
+ return { group, mark };
60
+ }
61
+
62
+ /**
63
+ * One choice from a few options, shown side by side: 12h or 24h, 15, 30 or
64
+ * 60 minutes, month, week or columns. Arrow keys move between options.
65
+ */
66
+ export function SegmentedControl<K extends string>({
67
+ label,
68
+ options,
69
+ value,
70
+ onChange,
71
+ size = "sm",
72
+ className,
73
+ }: SegmentedControlProps<K>) {
74
+ const { group, mark } = useSlide(
75
+ value,
76
+ options.map((option) => option.id),
77
+ );
78
+ return (
79
+ <ToggleButtonGroup
80
+ ref={group}
81
+ aria-label={label}
82
+ selectionMode="single"
83
+ disallowEmptySelection
84
+ selectedKeys={[value]}
85
+ onSelectionChange={(keys) => {
86
+ const [next] = [...keys];
87
+ if (next !== undefined && next !== value) onChange(String(next) as K);
88
+ }}
89
+ className={cn(
90
+ "inline-flex max-w-full shrink-0 flex-wrap items-center gap-0.5 rounded-control bg-surface-subtle p-0.5",
91
+ className,
92
+ )}
93
+ >
94
+ {options.map((option) => (
95
+ <ToggleButton
96
+ key={option.id}
97
+ id={option.id}
98
+ aria-label={option["aria-label"]}
99
+ isDisabled={option.isDisabled}
100
+ className={cn(
101
+ "relative inline-flex cursor-pointer items-center justify-center gap-1.5 rounded-[calc(var(--radius-control)-2px)] text-fg transition-colors duration-(--duration-snap) ease-snap outline-none squircle",
102
+ "data-disabled:cursor-not-allowed data-disabled:text-fg-muted data-focus-visible:focus-ring data-hovered:text-fg-emphasis data-selected:text-fg-emphasis",
103
+ size === "sm"
104
+ ? "h-control-sm min-w-control-sm px-control-sm-x type-caption"
105
+ : "h-control-md min-w-control-md px-control-md-x type-label",
106
+ )}
107
+ >
108
+ {option.id === value ? (
109
+ <span
110
+ ref={mark}
111
+ aria-hidden="true"
112
+ className="absolute inset-0 rounded-[calc(var(--radius-control)-2px)] border border-line-subtle bg-surface shadow-control squircle"
113
+ />
114
+ ) : null}
115
+ <span className="relative inline-flex items-center gap-1.5">
116
+ {option.icon}
117
+ {option.label}
118
+ </span>
119
+ </ToggleButton>
120
+ ))}
121
+ </ToggleButtonGroup>
122
+ );
123
+ }
@@ -0,0 +1,11 @@
1
+ import { cn } from "../lib/cn.js";
2
+
3
+ /** A placeholder shape while content loads. Hidden from screen readers. */
4
+ export function Skeleton({ className }: { className?: string }) {
5
+ return (
6
+ <span
7
+ aria-hidden="true"
8
+ className={cn("block animate-pulse-soft rounded-cell bg-surface-subtle", className)}
9
+ />
10
+ );
11
+ }
@@ -0,0 +1,19 @@
1
+ import { cn } from "../lib/cn.js";
2
+
3
+ /** Three staggered dots. Decorative: announce progress with words nearby. */
4
+ export function Spinner({ className }: { className?: string }) {
5
+ return (
6
+ <svg
7
+ viewBox="0 0 24 24"
8
+ aria-hidden="true"
9
+ focusable="false"
10
+ className={cn("size-4 shrink-0", className)}
11
+ >
12
+ <g fill="currentColor" className="loader-dots">
13
+ <circle cx="4" cy="12" r="2.5" />
14
+ <circle cx="12" cy="12" r="2.5" />
15
+ <circle cx="20" cy="12" r="2.5" />
16
+ </g>
17
+ </svg>
18
+ );
19
+ }
@@ -0,0 +1,22 @@
1
+ import { cn } from "../lib/cn";
2
+
3
+ export const sxdArrowPath = "M5 12h14m-6-6 6 6-6 6";
4
+
5
+ /** The same SuperXD arrow used by the site chrome, without importing the rest of the chrome. */
6
+ export function SxdArrow({ className }: { className?: string }) {
7
+ return (
8
+ <svg
9
+ className={cn("sxd-icon", className)}
10
+ viewBox="0 0 24 24"
11
+ fill="none"
12
+ stroke="currentColor"
13
+ strokeWidth={1.7}
14
+ strokeLinecap="round"
15
+ strokeLinejoin="round"
16
+ aria-hidden="true"
17
+ focusable="false"
18
+ >
19
+ <path d={sxdArrowPath} />
20
+ </svg>
21
+ );
22
+ }
@@ -0,0 +1,45 @@
1
+ import { useId, type ReactNode } from "react";
2
+ import { Switch as AriaSwitch, type SwitchProps as AriaSwitchProps } from "react-aria-components";
3
+ import { cn } from "../lib/cn.js";
4
+
5
+ export interface SwitchProps extends Omit<AriaSwitchProps, "children"> {
6
+ label: ReactNode;
7
+ description?: ReactNode;
8
+ }
9
+
10
+ /** An immediately applied on/off setting, with a persistent label in both positions. */
11
+ export function Switch({ label, description, className, ...props }: SwitchProps) {
12
+ const id = useId();
13
+ return (
14
+ <AriaSwitch
15
+ {...props}
16
+ aria-labelledby={`${id}-label`}
17
+ aria-describedby={description ? `${id}-description` : props["aria-describedby"]}
18
+ className={cn(
19
+ "group inline-flex min-h-control-md cursor-pointer items-center gap-3 rounded-control outline-none data-disabled:cursor-default data-focus-visible:focus-ring",
20
+ typeof className === "string" ? className : undefined,
21
+ )}
22
+ >
23
+ <span
24
+ aria-hidden="true"
25
+ className="flex h-6 w-10 shrink-0 items-center rounded-pill border border-line-input bg-surface-subtle p-0.5 transition-colors duration-(--duration-snap) ease-snap [corner-shape:var(--corner-shape,round)] group-data-disabled:opacity-50 group-data-selected:border-brand group-data-selected:bg-brand"
26
+ >
27
+ {/* Settle: the thumb stretches while pressed, then springs across */}
28
+ <span className="size-4 origin-left rounded-pill bg-fg-subtle transition-[translate,scale] duration-(--duration-settle) ease-settle [corner-shape:var(--corner-shape,round)] group-data-pressed:scale-x-125 group-data-pressed:duration-(--duration-snap) group-data-pressed:ease-snap group-data-selected:origin-right group-data-selected:translate-x-4 group-data-selected:bg-on-brand motion-reduce:transition-none" />
29
+ </span>
30
+ <span className="flex min-w-0 flex-col gap-1">
31
+ <span
32
+ id={`${id}-label`}
33
+ className="type-label text-fg-emphasis group-data-disabled:text-fg-muted"
34
+ >
35
+ {label}
36
+ </span>
37
+ {description ? (
38
+ <span id={`${id}-description`} className="type-caption text-fg-subtle">
39
+ {description}
40
+ </span>
41
+ ) : null}
42
+ </span>
43
+ </AriaSwitch>
44
+ );
45
+ }
@@ -0,0 +1,81 @@
1
+ import type { ReactNode } from "react";
2
+ import {
3
+ FieldError,
4
+ Input,
5
+ Label,
6
+ Text,
7
+ TextArea,
8
+ TextField as AriaTextField,
9
+ type TextFieldProps as AriaTextFieldProps,
10
+ } from "react-aria-components";
11
+ import { cn } from "../lib/cn.js";
12
+ import { IconCircleAlert } from "./icons";
13
+
14
+ const control =
15
+ "w-full rounded-control border border-line-input bg-surface px-control-md-x text-sm text-fg-emphasis shadow-control outline-none transition-[border-color,box-shadow] duration-(--duration-snap) ease-snap placeholder:text-fg-subtle data-hovered:border-line-emphasis data-focused:border-focus data-focused:ring-2 data-focused:ring-focus/15 data-invalid:border-error data-invalid:data-focused:ring-error/15 data-disabled:cursor-not-allowed data-disabled:bg-surface-muted data-disabled:text-fg-muted";
16
+
17
+ export interface TextFieldProps extends Omit<AriaTextFieldProps, "children"> {
18
+ label: ReactNode;
19
+ description?: ReactNode;
20
+ /** The problem with the current value. Shows the field as invalid when set. */
21
+ error?: ReactNode;
22
+ placeholder?: string;
23
+ /** Adds "(optional)" after the label. Required fields stay unmarked. */
24
+ optional?: boolean;
25
+ multiline?: boolean;
26
+ rows?: number;
27
+ inputClassName?: string;
28
+ }
29
+
30
+ /**
31
+ * A labelled text box with an optional description and an error message.
32
+ * The error is linked to the input for screen readers.
33
+ */
34
+ export function TextField({
35
+ label,
36
+ description,
37
+ error,
38
+ placeholder,
39
+ optional,
40
+ multiline,
41
+ rows = 3,
42
+ className,
43
+ inputClassName,
44
+ ...props
45
+ }: TextFieldProps) {
46
+ return (
47
+ <AriaTextField
48
+ {...props}
49
+ isInvalid={Boolean(error) || props.isInvalid}
50
+ validationBehavior={props.validationBehavior ?? "aria"}
51
+ className={cn("flex flex-col gap-1.5", typeof className === "string" ? className : undefined)}
52
+ >
53
+ <Label className="type-label text-fg-emphasis">
54
+ {label}
55
+ {optional ? <span className="font-normal text-fg-subtle"> (optional)</span> : null}
56
+ </Label>
57
+ {multiline ? (
58
+ <TextArea
59
+ rows={rows}
60
+ placeholder={placeholder}
61
+ className={cn(control, "min-h-24 resize-y py-control-y leading-5", inputClassName)}
62
+ />
63
+ ) : (
64
+ <Input placeholder={placeholder} className={cn(control, "h-control-md", inputClassName)} />
65
+ )}
66
+ {description && !error ? (
67
+ <Text slot="description" className="type-caption font-normal text-fg-subtle">
68
+ {description}
69
+ </Text>
70
+ ) : null}
71
+ <FieldError className="flex animate-reveal-in items-start gap-1.5 type-caption text-error">
72
+ {({ validationErrors }) => (
73
+ <>
74
+ <IconCircleAlert className="mt-px size-3.5" />
75
+ <span>{error ?? validationErrors.join(" ")}</span>
76
+ </>
77
+ )}
78
+ </FieldError>
79
+ </AriaTextField>
80
+ );
81
+ }
@@ -0,0 +1,16 @@
1
+ import { cn } from "../lib/cn.js";
2
+
3
+ /**
4
+ * SuperXD's thinking mark: a 3×3 pixel matrix lighting in a spiral, for waits of unknown
5
+ * length (an AI preparing an answer). About the height of a line of text. Decorative: say
6
+ * what's happening in words beside it.
7
+ */
8
+ export function Thinking({ className }: { className?: string }) {
9
+ return (
10
+ <span aria-hidden="true" className={cn("thinking-matrix", className)}>
11
+ {Array.from({ length: 9 }, (_, i) => (
12
+ <i key={i} />
13
+ ))}
14
+ </span>
15
+ );
16
+ }
@@ -0,0 +1,12 @@
1
+ export const uiCopy = {
2
+ choose: "Choose an option",
3
+ empty: "No options available.",
4
+ close: "Close",
5
+ previous: "Previous page",
6
+ next: "Next page",
7
+ page: (page: number, total: number) => `Page ${page} of ${total}`,
8
+ sort: (label: string) => `Sort by ${label}`,
9
+ noRows: "No rows to show.",
10
+ dismiss: "Dismiss notification",
11
+ notifications: "Notifications",
12
+ } as const;