@superxd-studio/adptr 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/GENERATIVE.md +101 -0
- package/INTERACTIONS.md +51 -0
- package/LICENSE +21 -0
- package/README.md +57 -0
- package/REUSE.md +170 -0
- package/REVIEW.md +68 -0
- package/THIRD-PARTY-NOTICES.md +28 -0
- package/ai-sdk/index.js +96 -0
- package/assets/ibm-plex-mono-cyrillic-400-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-cyrillic-500-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-cyrillic-ext-400-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-cyrillic-ext-500-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-latin-400-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-latin-500-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-latin-ext-400-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-latin-ext-500-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-vietnamese-400-normal.woff2 +0 -0
- package/assets/ibm-plex-mono-vietnamese-500-normal.woff2 +0 -0
- package/assets/instrument-sans-latin-ext-wght-normal.woff2 +0 -0
- package/assets/instrument-sans-latin-wght-normal.woff2 +0 -0
- package/assistant-ui/index.js +139 -0
- package/chunks/callout-DKMHSnjc.js +342 -0
- package/chunks/canvas-BJ15-XxE.js +598 -0
- package/chunks/controller-Cfpg0C7v.js +180 -0
- package/chunks/core-BCqVRLUy.js +312 -0
- package/chunks/text-field-BLtoaZFn.js +41 -0
- package/chunks/types-BGF7r1my.js +115 -0
- package/chunks/ui-9DyNsYXY.js +967 -0
- package/core/index.js +4 -0
- package/custom/index.js +64 -0
- package/examples/ai-sdk-backend/package.json +11 -0
- package/examples/ai-sdk-backend/server.mjs +316 -0
- package/examples/custom-backend/copy.mjs +7 -0
- package/examples/custom-backend/server.mjs +118 -0
- package/examples/generative/specs.json +2094 -0
- package/examples/review-backend/copy.mjs +27 -0
- package/examples/review-backend/package.json +11 -0
- package/examples/review-backend/server.mjs +276 -0
- package/examples/web-ai-sdk/index.html +12 -0
- package/examples/web-ai-sdk/package.json +21 -0
- package/examples/web-ai-sdk/src/copy.ts +1 -0
- package/examples/web-ai-sdk/src/main.tsx +64 -0
- package/examples/web-ai-sdk/tsconfig.json +13 -0
- package/examples/web-generative/index.html +12 -0
- package/examples/web-generative/package.json +23 -0
- package/examples/web-generative/src/copy.ts +42 -0
- package/examples/web-generative/src/main.tsx +74 -0
- package/examples/web-generative/tsconfig.json +14 -0
- package/examples/web-independent/index.html +12 -0
- package/examples/web-independent/package.json +20 -0
- package/examples/web-independent/src/main.tsx +32 -0
- package/examples/web-independent/tsconfig.json +14 -0
- package/examples/web-interactions/index.html +12 -0
- package/examples/web-interactions/package.json +21 -0
- package/examples/web-interactions/src/copy.ts +215 -0
- package/examples/web-interactions/src/examples.tsx +628 -0
- package/examples/web-interactions/src/main.tsx +116 -0
- package/examples/web-interactions/tsconfig.json +14 -0
- package/examples/web-review/index.html +12 -0
- package/examples/web-review/package.json +20 -0
- package/examples/web-review/src/main.tsx +59 -0
- package/examples/web-review/tsconfig.json +14 -0
- package/fonts.css +117 -0
- package/generative/index.js +1560 -0
- package/interactions/index.js +1591 -0
- package/langgraph-review/index.js +36 -0
- package/licenses/ai-sdk.txt +13 -0
- package/licenses/assistant-ui-generative.txt +21 -0
- package/licenses/assistant-ui.txt +23 -0
- package/licenses/ibm-plex-mono.txt +93 -0
- package/licenses/instrument-sans.txt +93 -0
- package/licenses/internationalized-date.txt +201 -0
- package/licenses/langgraph.txt +21 -0
- package/licenses/lucide.txt +43 -0
- package/licenses/react-markdown.txt +21 -0
- package/licenses/remark-gfm.txt +22 -0
- package/licenses/zod.txt +21 -0
- package/package.json +138 -0
- package/review/index.js +31 -0
- package/source/components/avatar.tsx +50 -0
- package/source/components/button.tsx +155 -0
- package/source/components/callout.tsx +65 -0
- package/source/components/canvas/connector.tsx +14 -0
- package/source/components/canvas/copy.ts +34 -0
- package/source/components/canvas/index.tsx +257 -0
- package/source/components/canvas/recorder.ts +36 -0
- package/source/components/date-time-field.tsx +77 -0
- package/source/components/flow-motion.tsx +109 -0
- package/source/components/icons.tsx +275 -0
- package/source/components/responsive-overlay.tsx +91 -0
- package/source/components/scenario-picker/copy.ts +5 -0
- package/source/components/scenario-picker/index.tsx +142 -0
- package/source/components/scroll-area.tsx +59 -0
- package/source/components/search-picker.tsx +127 -0
- package/source/components/segmented-control.tsx +123 -0
- package/source/components/skeleton.tsx +11 -0
- package/source/components/spinner.tsx +19 -0
- package/source/components/superxd-arrow.tsx +22 -0
- package/source/components/switch.tsx +45 -0
- package/source/components/text-field.tsx +81 -0
- package/source/components/thinking.tsx +16 -0
- package/source/components/ui/copy.ts +12 -0
- package/source/components/ui/display.tsx +276 -0
- package/source/components/ui/navigation.tsx +346 -0
- package/source/components/ui/portal-theme.tsx +28 -0
- package/source/components/ui/selection.tsx +336 -0
- package/source/lib/cn.ts +23 -0
- package/source/reusable/ai-sdk/index.ts +114 -0
- package/source/reusable/assistant-ui/index.tsx +266 -0
- package/source/reusable/core/attachments.ts +163 -0
- package/source/reusable/core/index.ts +417 -0
- package/source/reusable/custom/index.ts +90 -0
- package/source/reusable/generative/actions.ts +188 -0
- package/source/reusable/generative/copy.ts +47 -0
- package/source/reusable/generative/data.tsx +126 -0
- package/source/reusable/generative/index.tsx +517 -0
- package/source/reusable/generative/schema.ts +327 -0
- package/source/reusable/generative/timeline.tsx +214 -0
- package/source/reusable/generative/vocabulary.tsx +847 -0
- package/source/reusable/interactions/agent.ts +259 -0
- package/source/reusable/interactions/artifact.ts +236 -0
- package/source/reusable/interactions/context.tsx +38 -0
- package/source/reusable/interactions/copy.ts +117 -0
- package/source/reusable/interactions/feedback.ts +73 -0
- package/source/reusable/interactions/index.ts +11 -0
- package/source/reusable/interactions/presentation.tsx +483 -0
- package/source/reusable/interactions/store.ts +74 -0
- package/source/reusable/interactions/threads.ts +126 -0
- package/source/reusable/interactions/web.tsx +574 -0
- package/source/reusable/langgraph-review/index.ts +73 -0
- package/source/reusable/review/controller.ts +263 -0
- package/source/reusable/review/index.ts +47 -0
- package/source/reusable/review/types.ts +189 -0
- package/source/reusable/ui/index.ts +25 -0
- package/source/reusable/uploads/index.ts +48 -0
- package/source/reusable/web/attachments/copy.ts +23 -0
- package/source/reusable/web/attachments/index.tsx +233 -0
- package/source/reusable/web/copy.ts +22 -0
- package/source/reusable/web/index.tsx +470 -0
- package/source/reusable/web/message/copy.ts +26 -0
- package/source/reusable/web/message/index.tsx +388 -0
- package/source/reusable/web/message/streaming.ts +64 -0
- package/source/reusable/web/message/styles.css +21 -0
- package/source/reusable/web/review/copy.ts +78 -0
- package/source/reusable/web/review/flow.ts +24 -0
- package/source/reusable/web/review/index.tsx +506 -0
- package/source/reusable/web/styles.ts +2 -0
- package/source/styles/library.css +612 -0
- package/source/styles/theme.css +228 -0
- package/styles.css +3 -0
- package/styles.d.ts +1 -0
- package/types/components/avatar.d.ts +14 -0
- package/types/components/button.d.ts +36 -0
- package/types/components/callout.d.ts +19 -0
- package/types/components/canvas/connector.d.ts +2 -0
- package/types/components/canvas/copy.d.ts +34 -0
- package/types/components/canvas/index.d.ts +33 -0
- package/types/components/canvas/recorder.d.ts +10 -0
- package/types/components/date-time-field.d.ts +11 -0
- package/types/components/flow-motion.d.ts +38 -0
- package/types/components/icons.d.ts +138 -0
- package/types/components/responsive-overlay.d.ts +15 -0
- package/types/components/scenario-picker/copy.d.ts +5 -0
- package/types/components/scenario-picker/index.d.ts +14 -0
- package/types/components/scroll-area.d.ts +14 -0
- package/types/components/search-picker.d.ts +27 -0
- package/types/components/segmented-control.d.ts +23 -0
- package/types/components/skeleton.d.ts +4 -0
- package/types/components/spinner.d.ts +4 -0
- package/types/components/superxd-arrow.d.ts +5 -0
- package/types/components/switch.d.ts +8 -0
- package/types/components/text-field.d.ts +19 -0
- package/types/components/thinking.d.ts +8 -0
- package/types/components/ui/copy.d.ts +12 -0
- package/types/components/ui/display.d.ts +50 -0
- package/types/components/ui/navigation.d.ts +63 -0
- package/types/components/ui/portal-theme.d.ts +8 -0
- package/types/components/ui/selection.d.ts +37 -0
- package/types/lib/cn.d.ts +3 -0
- package/types/reusable/ai-sdk/index.d.ts +15 -0
- package/types/reusable/assistant-ui/index.d.ts +12 -0
- package/types/reusable/core/attachments.d.ts +54 -0
- package/types/reusable/core/index.d.ts +107 -0
- package/types/reusable/custom/index.d.ts +14 -0
- package/types/reusable/generative/actions.d.ts +45 -0
- package/types/reusable/generative/copy.d.ts +48 -0
- package/types/reusable/generative/data.d.ts +4 -0
- package/types/reusable/generative/index.d.ts +30 -0
- package/types/reusable/generative/schema.d.ts +303 -0
- package/types/reusable/generative/timeline.d.ts +21 -0
- package/types/reusable/generative/vocabulary.d.ts +34 -0
- package/types/reusable/interactions/agent.d.ts +98 -0
- package/types/reusable/interactions/artifact.d.ts +118 -0
- package/types/reusable/interactions/context.d.ts +130 -0
- package/types/reusable/interactions/copy.d.ts +115 -0
- package/types/reusable/interactions/feedback.d.ts +34 -0
- package/types/reusable/interactions/index.d.ts +10 -0
- package/types/reusable/interactions/presentation.d.ts +97 -0
- package/types/reusable/interactions/store.d.ts +29 -0
- package/types/reusable/interactions/threads.d.ts +38 -0
- package/types/reusable/interactions/web.d.ts +22 -0
- package/types/reusable/langgraph-review/index.d.ts +5 -0
- package/types/reusable/review/controller.d.ts +52 -0
- package/types/reusable/review/index.d.ts +12 -0
- package/types/reusable/review/types.d.ts +61 -0
- package/types/reusable/ui/index.d.ts +19 -0
- package/types/reusable/uploads/index.d.ts +11 -0
- package/types/reusable/web/attachments/copy.d.ts +25 -0
- package/types/reusable/web/attachments/index.d.ts +24 -0
- package/types/reusable/web/copy.d.ts +24 -0
- package/types/reusable/web/index.d.ts +41 -0
- package/types/reusable/web/message/copy.d.ts +27 -0
- package/types/reusable/web/message/index.d.ts +56 -0
- package/types/reusable/web/message/streaming.d.ts +27 -0
- package/types/reusable/web/review/copy.d.ts +72 -0
- package/types/reusable/web/review/flow.d.ts +3 -0
- package/types/reusable/web/review/index.d.ts +38 -0
- package/types/reusable/web/styles.d.ts +2 -0
- package/ui/index.js +4 -0
- package/uploads/index.js +34 -0
- package/web/index.js +931 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
cloneElement,
|
|
4
|
+
isValidElement,
|
|
5
|
+
createContext,
|
|
6
|
+
useContext,
|
|
7
|
+
useId,
|
|
8
|
+
useEffect,
|
|
9
|
+
useLayoutEffect,
|
|
10
|
+
useRef,
|
|
11
|
+
useState,
|
|
12
|
+
type ReactNode,
|
|
13
|
+
} from "react";
|
|
14
|
+
import { canvasCopy as copy } from "./copy";
|
|
15
|
+
import { createCanvasRecorder, type RecordableFrame } from "./recorder";
|
|
16
|
+
import { cn } from "../../lib/cn";
|
|
17
|
+
import { CanvasConnector } from "./connector";
|
|
18
|
+
|
|
19
|
+
/** The visitor canvas opts in; exported product components keep their usual single screen. */
|
|
20
|
+
export const CanvasModeContext = createContext(false);
|
|
21
|
+
export const CanvasPastContext = createContext(false);
|
|
22
|
+
/** null disables observation; false records quietly; true also displays the recorded states. */
|
|
23
|
+
export const CanvasObservationContext = createContext<boolean | null>(null);
|
|
24
|
+
export type CanvasFrame = RecordableFrame & {
|
|
25
|
+
label: string;
|
|
26
|
+
node: ReactNode;
|
|
27
|
+
/** The same width constraint as the component; caption and content share this frame. */
|
|
28
|
+
className?: string;
|
|
29
|
+
};
|
|
30
|
+
const RunContext = createContext<((frame: CanvasFrame) => void) | null>(null);
|
|
31
|
+
const ObservationContext = createContext<((frame: CanvasFrame, source: string) => void) | null>(
|
|
32
|
+
null,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export function useCanvasObserver() {
|
|
36
|
+
const publish = useContext(ObservationContext);
|
|
37
|
+
const historical = useContext(CanvasPastContext);
|
|
38
|
+
return historical ? null : publish;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The live demo is mounted once. Recording never moves focus or executes historical actions. */
|
|
42
|
+
function ObservationRun({ children }: { children: ReactNode }) {
|
|
43
|
+
const shown = useContext(CanvasObservationContext);
|
|
44
|
+
const [frames, setFrames] = useState<CanvasFrame[]>([]);
|
|
45
|
+
const [recorder] = useState(() => createCanvasRecorder<CanvasFrame>());
|
|
46
|
+
const [publish] = useState(() => (frame: CanvasFrame, source: string) => {
|
|
47
|
+
const next = recorder({ ...frame, node: historicalNode(frame.node) }, source);
|
|
48
|
+
if (next) setFrames(next);
|
|
49
|
+
});
|
|
50
|
+
return (
|
|
51
|
+
<div className="flex min-w-0 flex-col gap-20" data-observed-canvas>
|
|
52
|
+
<div
|
|
53
|
+
data-live-demo
|
|
54
|
+
data-canvas-current="true"
|
|
55
|
+
data-current="true"
|
|
56
|
+
className="min-w-0"
|
|
57
|
+
onPointerDownCapture={recorder.start}
|
|
58
|
+
onKeyDownCapture={recorder.start}
|
|
59
|
+
onClickCapture={recorder.start}
|
|
60
|
+
>
|
|
61
|
+
<ObservationContext.Provider value={publish}>{children}</ObservationContext.Provider>
|
|
62
|
+
</div>
|
|
63
|
+
<div
|
|
64
|
+
data-state-history
|
|
65
|
+
hidden={!shown || !frames.length}
|
|
66
|
+
className="flex min-w-0 flex-col gap-20 empty:hidden"
|
|
67
|
+
>
|
|
68
|
+
{frames.map((frame, index) => (
|
|
69
|
+
<div
|
|
70
|
+
key={index}
|
|
71
|
+
data-canvas-phase={frame.phase}
|
|
72
|
+
data-canvas-snapshot
|
|
73
|
+
data-canvas-current="false"
|
|
74
|
+
data-current="false"
|
|
75
|
+
data-generative-step={
|
|
76
|
+
frame.phase.startsWith("generative-") ? frame.phase.split("-")[1] : undefined
|
|
77
|
+
}
|
|
78
|
+
className={cn("relative mx-auto w-full min-w-0", frame.className)}
|
|
79
|
+
>
|
|
80
|
+
{index > 0 ? <CanvasConnector /> : null}
|
|
81
|
+
<Caption index={index} label={frame.label} previous={index < frames.length - 1} />
|
|
82
|
+
<div data-canvas-content inert aria-hidden="true">
|
|
83
|
+
<CanvasPastContext.Provider value={true}>{frame.node}</CanvasPastContext.Provider>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
))}
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function CanvasSequence({ children }: { children: ReactNode }) {
|
|
93
|
+
const enabled = useContext(CanvasModeContext);
|
|
94
|
+
const parent = useContext(RunContext);
|
|
95
|
+
const observation = useContext(CanvasObservationContext);
|
|
96
|
+
const observing = useContext(ObservationContext);
|
|
97
|
+
const historical = useContext(CanvasPastContext);
|
|
98
|
+
if (observation !== null && !observing && !historical)
|
|
99
|
+
return <ObservationRun>{children}</ObservationRun>;
|
|
100
|
+
return enabled && !parent ? <Run>{children}</Run> : children;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function Run({ children }: { children: ReactNode }) {
|
|
104
|
+
const [past, setPast] = useState<CanvasFrame[]>([]);
|
|
105
|
+
const [current, setCurrent] = useState<Omit<CanvasFrame, "node"> | null>(null);
|
|
106
|
+
const last = useRef<CanvasFrame | null>(null);
|
|
107
|
+
const root = useRef<HTMLDivElement>(null);
|
|
108
|
+
const initiated = useRef(false);
|
|
109
|
+
const [publish] = useState(() => (frame: CanvasFrame) => {
|
|
110
|
+
const previous = last.current;
|
|
111
|
+
if (previous?.terminal) return;
|
|
112
|
+
if (previous && previous.phase !== frame.phase)
|
|
113
|
+
setPast((frames) => [...frames, { ...previous, node: historicalNode(previous.node) }]);
|
|
114
|
+
last.current = frame;
|
|
115
|
+
setCurrent((prior) =>
|
|
116
|
+
prior?.phase === frame.phase &&
|
|
117
|
+
prior.label === frame.label &&
|
|
118
|
+
prior.terminal === frame.terminal
|
|
119
|
+
? prior
|
|
120
|
+
: { phase: frame.phase, label: frame.label, terminal: frame.terminal },
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (!initiated.current || !current) return;
|
|
125
|
+
const active = root.current?.querySelector<HTMLElement>('[data-canvas-current="true"]');
|
|
126
|
+
if (!active) return;
|
|
127
|
+
// The live owner may already have focused its next field or confirmation heading.
|
|
128
|
+
if (!active.contains(document.activeElement)) active.focus({ preventScroll: true });
|
|
129
|
+
active.scrollIntoView({ block: "nearest", behavior: "instant" });
|
|
130
|
+
}, [current]);
|
|
131
|
+
return (
|
|
132
|
+
<div
|
|
133
|
+
ref={root}
|
|
134
|
+
data-canvas-sequence
|
|
135
|
+
className="flex w-full min-w-0 flex-col gap-20"
|
|
136
|
+
onPointerDownCapture={() => {
|
|
137
|
+
initiated.current = true;
|
|
138
|
+
}}
|
|
139
|
+
onKeyDownCapture={() => {
|
|
140
|
+
initiated.current = true;
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
{past.map((frame, index) => (
|
|
144
|
+
<div
|
|
145
|
+
key={index}
|
|
146
|
+
data-canvas-phase={frame.phase}
|
|
147
|
+
data-canvas-current="false"
|
|
148
|
+
className={cn("relative mx-auto w-full min-w-0", frame.className)}
|
|
149
|
+
>
|
|
150
|
+
{index > 0 ? <CanvasConnector /> : null}
|
|
151
|
+
<Caption index={index} label={frame.label} previous />
|
|
152
|
+
<div inert aria-hidden="true">
|
|
153
|
+
<CanvasPastContext.Provider value={true}>{frame.node}</CanvasPastContext.Provider>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
))}
|
|
157
|
+
<div
|
|
158
|
+
data-canvas-phase={current?.phase}
|
|
159
|
+
data-canvas-current="true"
|
|
160
|
+
data-canvas-terminal={current?.terminal || undefined}
|
|
161
|
+
tabIndex={-1}
|
|
162
|
+
className="relative min-w-0 scroll-mt-6 outline-none"
|
|
163
|
+
>
|
|
164
|
+
{past.length > 0 ? <CanvasConnector /> : null}
|
|
165
|
+
{current ? <Caption index={past.length} label={current.label} /> : null}
|
|
166
|
+
<RunContext.Provider value={publish}>{children}</RunContext.Provider>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function Caption({ index, label, previous }: { index: number; label: string; previous?: boolean }) {
|
|
173
|
+
return (
|
|
174
|
+
<div
|
|
175
|
+
data-canvas-caption
|
|
176
|
+
className="mb-3 flex flex-wrap justify-between gap-2 font-mono type-caption text-system-blue"
|
|
177
|
+
>
|
|
178
|
+
<span>
|
|
179
|
+
{String(index + 1).padStart(2, "0")} {label}
|
|
180
|
+
</span>
|
|
181
|
+
{previous ? <span>{copy.previous}</span> : null}
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Publish presentation only: historical frames never mount another controller or adapter. */
|
|
187
|
+
export function CanvasStage({
|
|
188
|
+
phase,
|
|
189
|
+
label,
|
|
190
|
+
terminal = false,
|
|
191
|
+
frameClassName,
|
|
192
|
+
snapshot,
|
|
193
|
+
children,
|
|
194
|
+
}: {
|
|
195
|
+
phase: string;
|
|
196
|
+
label: string;
|
|
197
|
+
terminal?: boolean;
|
|
198
|
+
frameClassName?: string;
|
|
199
|
+
/** Complete presentation for a snapshot when the live content inherits a parent surface. */
|
|
200
|
+
snapshot?: ReactNode;
|
|
201
|
+
children: ReactNode;
|
|
202
|
+
}) {
|
|
203
|
+
const publish = useContext(RunContext);
|
|
204
|
+
const observe = useCanvasObserver();
|
|
205
|
+
const source = useId();
|
|
206
|
+
const historical = useContext(CanvasPastContext);
|
|
207
|
+
useLayoutEffect(() => {
|
|
208
|
+
if (!historical) publish?.({ phase, label, terminal, node: children });
|
|
209
|
+
observe?.(
|
|
210
|
+
{ phase, label, terminal, className: frameClassName, node: snapshot ?? children },
|
|
211
|
+
source,
|
|
212
|
+
);
|
|
213
|
+
}, [
|
|
214
|
+
publish,
|
|
215
|
+
observe,
|
|
216
|
+
source,
|
|
217
|
+
historical,
|
|
218
|
+
phase,
|
|
219
|
+
label,
|
|
220
|
+
terminal,
|
|
221
|
+
frameClassName,
|
|
222
|
+
snapshot,
|
|
223
|
+
children,
|
|
224
|
+
]);
|
|
225
|
+
return children;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function historicalNode(node: ReactNode): ReactNode {
|
|
229
|
+
return Children.map(node, (child) => {
|
|
230
|
+
if (!isValidElement<Record<string, unknown>>(child)) return child;
|
|
231
|
+
// Shared DOM/measurement refs belong to the live view, never its retained presentation.
|
|
232
|
+
const props: Record<string, unknown> = {};
|
|
233
|
+
if ("ref" in child.props) props.ref = null;
|
|
234
|
+
for (const name of ["measureRef", "triggerRef", "eventTriggerRef"])
|
|
235
|
+
if (name in child.props) props[name] = undefined;
|
|
236
|
+
// Retained headings must not own IDs that the current view uses for its accessible name.
|
|
237
|
+
if (typeof child.type === "string") {
|
|
238
|
+
for (const name of ["id", "aria-labelledby", "aria-describedby", "htmlFor"])
|
|
239
|
+
if (name in child.props) props[name] = undefined;
|
|
240
|
+
}
|
|
241
|
+
if ("autoFocus" in child.props) props.autoFocus = false;
|
|
242
|
+
if ("children" in child.props)
|
|
243
|
+
props.children = historicalNode(child.props.children as ReactNode);
|
|
244
|
+
return cloneElement(child, props);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Let a host-owned destination add its local steps to the existing response canvas. */
|
|
249
|
+
export function CanvasStageBridge({
|
|
250
|
+
publish,
|
|
251
|
+
children,
|
|
252
|
+
}: {
|
|
253
|
+
publish: (frame: CanvasFrame) => void;
|
|
254
|
+
children: ReactNode;
|
|
255
|
+
}) {
|
|
256
|
+
return <RunContext.Provider value={publish}>{children}</RunContext.Provider>;
|
|
257
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface RecordableFrame {
|
|
2
|
+
phase: string;
|
|
3
|
+
terminal: boolean;
|
|
4
|
+
/** A result with its own stages replaces this enclosing result with its first child stage. */
|
|
5
|
+
yieldToChild?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Records transitions, never the initial demo or a second copy of a nested result. */
|
|
9
|
+
export function createCanvasRecorder<T extends RecordableFrame>() {
|
|
10
|
+
let active = false;
|
|
11
|
+
let frames: T[] = [];
|
|
12
|
+
let latestSource: string | undefined;
|
|
13
|
+
const seen = new Map<string, string>();
|
|
14
|
+
const record = (frame: T, source: string): T[] | undefined => {
|
|
15
|
+
const last = frames.at(-1);
|
|
16
|
+
if (last?.terminal) return;
|
|
17
|
+
const samePhase = seen.get(source) === frame.phase;
|
|
18
|
+
// Background parents (for example the calendar behind an event) are not new steps.
|
|
19
|
+
if (samePhase && source !== latestSource) return;
|
|
20
|
+
seen.set(source, frame.phase);
|
|
21
|
+
// Mounting, loading fixtures and typing in the entry screen are not new steps.
|
|
22
|
+
if (!active || (!frames.length && samePhase)) {
|
|
23
|
+
latestSource = source;
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const replace = samePhase || (last?.yieldToChild && source !== latestSource);
|
|
27
|
+
latestSource = source;
|
|
28
|
+
frames = replace ? [...frames.slice(0, -1), frame] : [...frames, frame];
|
|
29
|
+
return frames;
|
|
30
|
+
};
|
|
31
|
+
return Object.assign(record, {
|
|
32
|
+
start: () => {
|
|
33
|
+
active = true;
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { parseDate, parseTime } from "@internationalized/date";
|
|
2
|
+
import {
|
|
3
|
+
DateField,
|
|
4
|
+
DateInput,
|
|
5
|
+
DateSegment,
|
|
6
|
+
FieldError,
|
|
7
|
+
I18nProvider,
|
|
8
|
+
Label,
|
|
9
|
+
TimeField,
|
|
10
|
+
} from "react-aria-components";
|
|
11
|
+
import { cn } from "../lib/cn.js";
|
|
12
|
+
|
|
13
|
+
export interface DateTimeFieldProps {
|
|
14
|
+
label: string;
|
|
15
|
+
value: string;
|
|
16
|
+
onChange: (value: string) => void;
|
|
17
|
+
error?: string;
|
|
18
|
+
isDisabled?: boolean;
|
|
19
|
+
kind: "date" | "time";
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Segmented keyboard entry, using the same height, padding and focus treatment as text fields. */
|
|
24
|
+
export function DateTimeField({
|
|
25
|
+
label,
|
|
26
|
+
value,
|
|
27
|
+
onChange,
|
|
28
|
+
error,
|
|
29
|
+
isDisabled,
|
|
30
|
+
kind,
|
|
31
|
+
className,
|
|
32
|
+
}: DateTimeFieldProps) {
|
|
33
|
+
const children = (
|
|
34
|
+
<>
|
|
35
|
+
<Label className="type-label text-fg-emphasis">{label}</Label>
|
|
36
|
+
<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 shadow-control outline-none data-disabled:bg-surface-muted data-disabled:text-fg-muted data-focus-within:border-brand data-focus-within:ring-2 data-focus-within:ring-brand/15 data-invalid:border-error">
|
|
37
|
+
{(segment) => (
|
|
38
|
+
<DateSegment
|
|
39
|
+
segment={segment}
|
|
40
|
+
className="rounded-cell px-0.5 numeric outline-none data-focused:bg-brand data-focused:text-on-brand data-placeholder:text-fg-subtle"
|
|
41
|
+
/>
|
|
42
|
+
)}
|
|
43
|
+
</DateInput>
|
|
44
|
+
<FieldError className="type-caption text-error">{error}</FieldError>
|
|
45
|
+
</>
|
|
46
|
+
);
|
|
47
|
+
const props = {
|
|
48
|
+
isInvalid: Boolean(error),
|
|
49
|
+
isDisabled,
|
|
50
|
+
isRequired: true,
|
|
51
|
+
validationBehavior: "aria" as const,
|
|
52
|
+
className: cn("flex min-w-0 flex-col gap-1.5", className),
|
|
53
|
+
};
|
|
54
|
+
return (
|
|
55
|
+
<I18nProvider locale="en-AU">
|
|
56
|
+
{kind === "date" ? (
|
|
57
|
+
<DateField
|
|
58
|
+
{...props}
|
|
59
|
+
value={value ? parseDate(value) : null}
|
|
60
|
+
onChange={(date) => onChange(date?.toString() ?? "")}
|
|
61
|
+
>
|
|
62
|
+
{children}
|
|
63
|
+
</DateField>
|
|
64
|
+
) : (
|
|
65
|
+
<TimeField
|
|
66
|
+
{...props}
|
|
67
|
+
hourCycle={24}
|
|
68
|
+
granularity="minute"
|
|
69
|
+
value={value ? parseTime(value) : null}
|
|
70
|
+
onChange={(time) => onChange(time ? time.toString().slice(0, 5) : "")}
|
|
71
|
+
>
|
|
72
|
+
{children}
|
|
73
|
+
</TimeField>
|
|
74
|
+
)}
|
|
75
|
+
</I18nProvider>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addTransitionType,
|
|
3
|
+
createContext,
|
|
4
|
+
startTransition,
|
|
5
|
+
useContext,
|
|
6
|
+
useId,
|
|
7
|
+
ViewTransition,
|
|
8
|
+
type ReactNode,
|
|
9
|
+
} from "react";
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* How a flow moves between its steps (booking, reviewing a draft). Each step change runs as
|
|
13
|
+
* one view transition: the old step lifts away, the next one inks in from the top, the frame
|
|
14
|
+
* resizes to fit, and parts that carry over travel to their new place. The look lives in
|
|
15
|
+
* library.css ("Flow transitions"); this file says when a change is a step and which way it
|
|
16
|
+
* goes. Reduced motion gets the next step at once.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** The view transition types a step change is tagged with. */
|
|
20
|
+
export const FLOW = {
|
|
21
|
+
forward: "flow-forward",
|
|
22
|
+
back: "flow-back",
|
|
23
|
+
swap: "flow-swap",
|
|
24
|
+
} as const;
|
|
25
|
+
export type FlowType = (typeof FLOW)[keyof typeof FLOW];
|
|
26
|
+
|
|
27
|
+
/** Forward or back between two step positions, or null when the step stays the same. */
|
|
28
|
+
export function flowDirection(from: number, to: number): FlowType | null {
|
|
29
|
+
return to > from ? FLOW.forward : to < from ? FLOW.back : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Runs a state update as a step transition of the given type, or at once when there's none.
|
|
34
|
+
* Pass the start function from useTransition to know while the step is still on its way.
|
|
35
|
+
*/
|
|
36
|
+
export function runFlow(
|
|
37
|
+
type: FlowType | null,
|
|
38
|
+
update: () => void,
|
|
39
|
+
start: (callback: () => void) => void = startTransition,
|
|
40
|
+
) {
|
|
41
|
+
if (!type) return update();
|
|
42
|
+
start(() => {
|
|
43
|
+
addTransitionType(type);
|
|
44
|
+
update();
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Class names per type: a step change inks in, a swap (a different view of the same step) fades. */
|
|
49
|
+
const each = (step: string, swap: string) => ({
|
|
50
|
+
[FLOW.forward]: `flow-motion ${step}`,
|
|
51
|
+
[FLOW.back]: `flow-motion ${step}`,
|
|
52
|
+
[FLOW.swap]: `flow-motion ${swap}`,
|
|
53
|
+
default: "none",
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
/** The flow's frame: it resizes to the next step instead of jumping. Its child must be one element. */
|
|
57
|
+
export function FlowFrame({ children }: { children: ReactNode }) {
|
|
58
|
+
return (
|
|
59
|
+
<ViewTransition default="none" update={each("flow-card", "flow-card")}>
|
|
60
|
+
{children}
|
|
61
|
+
</ViewTransition>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** One step's content. Give it a key per step, so the new step enters and the old one leaves. */
|
|
66
|
+
export function FlowStep({ children }: { children: ReactNode }) {
|
|
67
|
+
return (
|
|
68
|
+
<ViewTransition
|
|
69
|
+
default="none"
|
|
70
|
+
enter={each("flow-in", "flow-in-swap")}
|
|
71
|
+
exit={each("flow-out", "flow-out-swap")}
|
|
72
|
+
>
|
|
73
|
+
{children}
|
|
74
|
+
</ViewTransition>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const NamesContext = createContext<string | null>(null);
|
|
79
|
+
|
|
80
|
+
/** Gives one flow its own names, so two flows on a page never claim the same moving part. */
|
|
81
|
+
export function FlowNames({ children }: { children: ReactNode }) {
|
|
82
|
+
const id = useId().replace(/[^\w-]/g, "");
|
|
83
|
+
return <NamesContext value={`flow-${id}`}>{children}</NamesContext>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* A part that carries over from one step to the next. A part that travels (the chosen time,
|
|
88
|
+
* from the list into the summary) moves to its new place; the others (the host, the event's
|
|
89
|
+
* name) stay put while the rest changes. Its child must be one element. Outside FlowNames, it
|
|
90
|
+
* renders the child as it is.
|
|
91
|
+
*/
|
|
92
|
+
export function FlowShared({
|
|
93
|
+
part,
|
|
94
|
+
travel = false,
|
|
95
|
+
children,
|
|
96
|
+
}: {
|
|
97
|
+
part: string;
|
|
98
|
+
travel?: boolean;
|
|
99
|
+
children: ReactNode;
|
|
100
|
+
}) {
|
|
101
|
+
const prefix = useContext(NamesContext);
|
|
102
|
+
if (!prefix) return children;
|
|
103
|
+
const share = travel ? "flow-motion flow-travel" : "flow-motion flow-keep";
|
|
104
|
+
return (
|
|
105
|
+
<ViewTransition name={`${prefix}-${part}`} default="none" share={each(share, share)}>
|
|
106
|
+
{children}
|
|
107
|
+
</ViewTransition>
|
|
108
|
+
);
|
|
109
|
+
}
|