@tailorkit/react 0.1.0-beta.3 → 0.1.0-beta.33
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/LICENSE.md +74 -0
- package/README.md +3 -0
- package/dist/components/context.d.ts +7 -10
- package/dist/components/index.d.ts +1 -2
- package/dist/components/render-slot.d.ts +9 -0
- package/dist/hooks/use-apps.d.ts +6 -13
- package/dist/hooks/use-query-result.d.ts +9 -0
- package/dist/hooks/use-view-context.d.ts +6 -0
- package/dist/hooks/use-views.d.ts +7 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +362 -839
- package/dist/primitives.d.ts +11 -21
- package/dist/remote-view.d.ts +12 -6
- package/dist/tailorkit.d.ts +45 -0
- package/package.json +42 -24
- package/LICENSE +0 -674
- package/dist/components/context.d.ts.map +0 -1
- package/dist/components/index.d.ts.map +0 -1
- package/dist/components/render.d.ts +0 -20
- package/dist/components/render.d.ts.map +0 -1
- package/dist/components/root.d.ts +0 -11
- package/dist/components/root.d.ts.map +0 -1
- package/dist/hooks/use-apps.d.ts.map +0 -1
- package/dist/hooks/use-current-screen.d.ts +0 -24
- package/dist/hooks/use-current-screen.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/node-store.d.ts +0 -17
- package/dist/node-store.d.ts.map +0 -1
- package/dist/primitives.d.ts.map +0 -1
- package/dist/remote-context.d.ts +0 -9
- package/dist/remote-context.d.ts.map +0 -1
- package/dist/remote-view.d.ts.map +0 -1
- package/dist/tailor-kit.d.ts +0 -117
- package/dist/tailor-kit.d.ts.map +0 -1
- package/src/__tests__/node-store.test.ts +0 -206
- package/src/__tests__/primitives.test.tsx +0 -85
- package/src/__tests__/tailor-kit.test.tsx +0 -424
- package/src/__tests__/tailor-kit.types.tsx +0 -151
- package/src/components/context.ts +0 -21
- package/src/components/index.ts +0 -1
- package/src/components/render.tsx +0 -116
- package/src/components/root.tsx +0 -37
- package/src/hooks/use-apps.ts +0 -47
- package/src/hooks/use-current-screen.ts +0 -81
- package/src/index.ts +0 -8
- package/src/node-store.ts +0 -131
- package/src/primitives.tsx +0 -261
- package/src/remote-context.ts +0 -11
- package/src/remote-view.tsx +0 -232
- package/src/tailor-kit.tsx +0 -501
|
@@ -1,424 +0,0 @@
|
|
|
1
|
-
import { act, cleanup, render, screen as testingScreen, waitFor } from "@testing-library/react";
|
|
2
|
-
import { createElement } from "react";
|
|
3
|
-
import type { ReactNode } from "react";
|
|
4
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
|
-
import { createTailorKitServer } from "@tailorkit/core/server";
|
|
6
|
-
import type { IframeUiHost } from "@tailorkit/sandbox/host";
|
|
7
|
-
import type { HostToWorkerPayload, RemoteNode } from "@tailorkit/sandbox/protocol";
|
|
8
|
-
import { createTailorKitClient } from "../tailor-kit";
|
|
9
|
-
import type { TailorKitApp } from "../tailor-kit";
|
|
10
|
-
|
|
11
|
-
const hostRecords: { appUrl: string; props: Record<string, unknown> | undefined }[] = [];
|
|
12
|
-
|
|
13
|
-
vi.mock("@tailorkit/sandbox/host", () => ({
|
|
14
|
-
createIframeUiHost: (
|
|
15
|
-
appUrl: string | URL,
|
|
16
|
-
options: { props?: Record<string, unknown> } = {},
|
|
17
|
-
): IframeUiHost => {
|
|
18
|
-
hostRecords.push({ appUrl: appUrl.toString(), props: options.props });
|
|
19
|
-
|
|
20
|
-
const tree: RemoteNode = {
|
|
21
|
-
children: [{ id: `text-${hostRecords.length}`, kind: "text", text: appUrl.toString() }],
|
|
22
|
-
id: `root-${hostRecords.length}`,
|
|
23
|
-
kind: "element",
|
|
24
|
-
props: {},
|
|
25
|
-
type: appUrl.toString().includes("missing-component") ? "MissingComponent" : "Button",
|
|
26
|
-
};
|
|
27
|
-
let listener: (() => void) | null = null;
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
destroy: () => {},
|
|
31
|
-
dispatch: (_payload: HostToWorkerPayload) => {},
|
|
32
|
-
getSnapshot: () => tree,
|
|
33
|
-
iframe: document.createElement("iframe"),
|
|
34
|
-
mount: () => {
|
|
35
|
-
listener?.();
|
|
36
|
-
},
|
|
37
|
-
subscribe: (nextListener: () => void) => {
|
|
38
|
-
listener = nextListener;
|
|
39
|
-
return () => {
|
|
40
|
-
listener = null;
|
|
41
|
-
};
|
|
42
|
-
},
|
|
43
|
-
} as unknown as IframeUiHost;
|
|
44
|
-
},
|
|
45
|
-
}));
|
|
46
|
-
|
|
47
|
-
const emptySchema = {
|
|
48
|
-
"~standard": {
|
|
49
|
-
jsonSchema: {
|
|
50
|
-
input: () => ({}),
|
|
51
|
-
output: () => ({}),
|
|
52
|
-
},
|
|
53
|
-
validate: (value: unknown) => ({ value }),
|
|
54
|
-
vendor: "test",
|
|
55
|
-
version: 1,
|
|
56
|
-
},
|
|
57
|
-
} as const;
|
|
58
|
-
|
|
59
|
-
const server = createTailorKitServer({
|
|
60
|
-
components: {
|
|
61
|
-
Button: {},
|
|
62
|
-
},
|
|
63
|
-
screens: {
|
|
64
|
-
"/": { context: emptySchema },
|
|
65
|
-
"/home": { context: emptySchema },
|
|
66
|
-
"/home/detail": { context: emptySchema },
|
|
67
|
-
"/user": { context: emptySchema },
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
const components = {
|
|
72
|
-
Button: ({ slots }: { slots: { default?: ReactNode } }) =>
|
|
73
|
-
createElement("button", null, slots.default),
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const schema = server.$internal.schema;
|
|
77
|
-
|
|
78
|
-
function CurrentScreenRoute({
|
|
79
|
-
nested,
|
|
80
|
-
tailor,
|
|
81
|
-
}: {
|
|
82
|
-
nested: boolean;
|
|
83
|
-
tailor: ReturnType<typeof createTailorKitClient<typeof server>>;
|
|
84
|
-
}) {
|
|
85
|
-
tailor.useCurrentScreen(
|
|
86
|
-
nested
|
|
87
|
-
? {
|
|
88
|
-
context: { detail: "profile", page: "home" },
|
|
89
|
-
screen: "/home/detail",
|
|
90
|
-
}
|
|
91
|
-
: {
|
|
92
|
-
context: { page: "home" },
|
|
93
|
-
screen: "/home",
|
|
94
|
-
},
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
return <tailor.AppView app={{ clientPath: "/apps/todo.js", id: "todo" }} />;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function CurrentScreenHost({
|
|
101
|
-
nested,
|
|
102
|
-
tailor,
|
|
103
|
-
}: {
|
|
104
|
-
nested: boolean;
|
|
105
|
-
tailor: ReturnType<typeof createTailorKitClient<typeof server>>;
|
|
106
|
-
}) {
|
|
107
|
-
return (
|
|
108
|
-
<tailor.Root apps={[{ clientPath: "/apps/todo.js", id: "todo" }]}>
|
|
109
|
-
<CurrentScreenRoute nested={nested} tailor={tailor} />
|
|
110
|
-
</tailor.Root>
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function HomeAppView({
|
|
115
|
-
app,
|
|
116
|
-
tailor,
|
|
117
|
-
}: {
|
|
118
|
-
app: TailorKitApp;
|
|
119
|
-
tailor: ReturnType<typeof createTailorKitClient<typeof server>>;
|
|
120
|
-
}) {
|
|
121
|
-
tailor.useCurrentScreen({ context: { page: "home" }, screen: "/home" });
|
|
122
|
-
return <tailor.AppView app={app} />;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
describe("tailorKitClient React adapter", () => {
|
|
126
|
-
beforeEach(() => {
|
|
127
|
-
hostRecords.length = 0;
|
|
128
|
-
vi.restoreAllMocks();
|
|
129
|
-
vi.spyOn(globalThis, "fetch").mockResolvedValue(
|
|
130
|
-
Response.json({
|
|
131
|
-
assetsBaseUrl: "http://assets.test/",
|
|
132
|
-
schema: schema.serialize(),
|
|
133
|
-
}),
|
|
134
|
-
);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
afterEach(() => {
|
|
138
|
-
cleanup();
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it("fetches and caches apps", async () => {
|
|
142
|
-
vi.spyOn(globalThis, "fetch").mockResolvedValue(Response.json([{ id: "todo", name: "Todo" }]));
|
|
143
|
-
const tailor = createTailorKitClient<typeof server>({
|
|
144
|
-
baseUrl: "http://runtime.test/api/tailorkit",
|
|
145
|
-
components,
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
function AppList() {
|
|
149
|
-
const { data, status } = tailor.useApps();
|
|
150
|
-
return createElement("p", null, `${status}:${(data ?? []).map((app) => app.id).join(",")}`);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
render(createElement("div", null, createElement(AppList), createElement(AppList)));
|
|
154
|
-
|
|
155
|
-
await waitFor(() => {
|
|
156
|
-
expect(testingScreen.getAllByText("ready:todo")).toHaveLength(2);
|
|
157
|
-
});
|
|
158
|
-
expect(globalThis.fetch).toHaveBeenCalledTimes(1);
|
|
159
|
-
expect(globalThis.fetch).toHaveBeenCalledWith(
|
|
160
|
-
new URL("apps", "http://runtime.test/api/tailorkit/"),
|
|
161
|
-
);
|
|
162
|
-
expect(tailor.getApps()).toEqual([{ id: "todo", name: "Todo" }]);
|
|
163
|
-
expect(tailor.getApp("todo")).toEqual({ id: "todo", name: "Todo" });
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it("derives the hierarchy from the current screen and reuses its extended context", async () => {
|
|
167
|
-
const tailor = createTailorKitClient<typeof server>({
|
|
168
|
-
baseUrl: "http://runtime.test",
|
|
169
|
-
components,
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
const view = render(<CurrentScreenHost nested tailor={tailor} />);
|
|
173
|
-
|
|
174
|
-
await waitFor(() => {
|
|
175
|
-
expect(hostRecords.at(-1)?.props).toMatchObject({
|
|
176
|
-
screen: {
|
|
177
|
-
context: { detail: "profile", page: "home" },
|
|
178
|
-
path: "/home/detail",
|
|
179
|
-
status: "ready",
|
|
180
|
-
},
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
await act(() => {
|
|
185
|
-
view.rerender(<CurrentScreenHost nested={false} tailor={tailor} />);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
await waitFor(() => {
|
|
189
|
-
expect(hostRecords.at(-1)?.props).toMatchObject({
|
|
190
|
-
screen: {
|
|
191
|
-
context: { page: "home" },
|
|
192
|
-
path: "/home",
|
|
193
|
-
status: "ready",
|
|
194
|
-
},
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it("publishes loading and error states without stale context", async () => {
|
|
200
|
-
const tailor = createTailorKitClient<typeof server>({
|
|
201
|
-
baseUrl: "http://runtime.test",
|
|
202
|
-
components,
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
function Route({ status }: { status: "error" | "loading" }) {
|
|
206
|
-
tailor.useCurrentScreen({ screen: "/home/detail", status });
|
|
207
|
-
return <tailor.AppView app={{ clientPath: "/apps/todo.js", id: "todo" }} />;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const view = render(
|
|
211
|
-
<tailor.Root apps={[{ clientPath: "/apps/todo.js", id: "todo" }]}>
|
|
212
|
-
<Route status="loading" />
|
|
213
|
-
</tailor.Root>,
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
await waitFor(() => {
|
|
217
|
-
expect(hostRecords.at(-1)?.props).toEqual({
|
|
218
|
-
screen: { context: undefined, path: "/home/detail", status: "loading" },
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
view.rerender(
|
|
223
|
-
<tailor.Root apps={[{ clientPath: "/apps/todo.js", id: "todo" }]}>
|
|
224
|
-
<Route status="error" />
|
|
225
|
-
</tailor.Root>,
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
await waitFor(() => {
|
|
229
|
-
expect(hostRecords.at(-1)?.props).toEqual({
|
|
230
|
-
screen: { context: undefined, path: "/home/detail", status: "error" },
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
it("renders the current match for multiple direct app props", async () => {
|
|
236
|
-
hostRecords.length = 0;
|
|
237
|
-
const tailor = createTailorKitClient<typeof server>({
|
|
238
|
-
baseUrl: "http://runtime.test",
|
|
239
|
-
components,
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
function Route() {
|
|
243
|
-
tailor.useCurrentScreen({ context: { page: "home" }, screen: "/home" });
|
|
244
|
-
return (
|
|
245
|
-
<>
|
|
246
|
-
<tailor.AppView app={{ clientPath: "/apps/b.js", id: "b" }} />
|
|
247
|
-
<tailor.AppView
|
|
248
|
-
app={{
|
|
249
|
-
currentDeployment: { id: "deployment_1" },
|
|
250
|
-
id: "a",
|
|
251
|
-
projectId: "project_1",
|
|
252
|
-
}}
|
|
253
|
-
/>
|
|
254
|
-
</>
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
render(
|
|
259
|
-
<tailor.Root
|
|
260
|
-
apps={[
|
|
261
|
-
{ clientPath: "/apps/b.js", id: "b" },
|
|
262
|
-
{
|
|
263
|
-
currentDeployment: { id: "deployment_1" },
|
|
264
|
-
id: "a",
|
|
265
|
-
projectId: "project_1",
|
|
266
|
-
},
|
|
267
|
-
]}
|
|
268
|
-
>
|
|
269
|
-
<Route />
|
|
270
|
-
</tailor.Root>,
|
|
271
|
-
);
|
|
272
|
-
|
|
273
|
-
await waitFor(() => {
|
|
274
|
-
expect(hostRecords).toHaveLength(2);
|
|
275
|
-
});
|
|
276
|
-
expect(hostRecords.map((record) => record.appUrl)).toEqual([
|
|
277
|
-
"http://runtime.test/apps/b.js",
|
|
278
|
-
"http://assets.test/projects/project_1/apps/a/deployments/deployment_1/files/client.js",
|
|
279
|
-
]);
|
|
280
|
-
expect(hostRecords.map((record) => record.props?.screen)).toEqual([
|
|
281
|
-
{ context: { page: "home" }, path: "/home", status: "ready" },
|
|
282
|
-
{ context: { page: "home" }, path: "/home", status: "ready" },
|
|
283
|
-
]);
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
it("renders an explicit screen override without a registered current screen", async () => {
|
|
287
|
-
const tailor = createTailorKitClient<typeof server>({
|
|
288
|
-
baseUrl: "http://runtime.test",
|
|
289
|
-
components,
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
render(
|
|
293
|
-
<tailor.AppView
|
|
294
|
-
app={{ clientPath: "/apps/todo.js", id: "todo" }}
|
|
295
|
-
context={{ userId: "user_1" }}
|
|
296
|
-
screen="/user"
|
|
297
|
-
/>,
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
await waitFor(() => {
|
|
301
|
-
expect(hostRecords.at(-1)?.props).toMatchObject({
|
|
302
|
-
screen: {
|
|
303
|
-
context: { userId: "user_1" },
|
|
304
|
-
path: "/user",
|
|
305
|
-
status: "ready",
|
|
306
|
-
},
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
it("warns when multiple hooks register screens at the same hierarchy depth", async () => {
|
|
312
|
-
const consoleWarn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
313
|
-
const tailor = createTailorKitClient<typeof server>({
|
|
314
|
-
baseUrl: "http://runtime.test",
|
|
315
|
-
components,
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
function HomeRoute() {
|
|
319
|
-
tailor.useCurrentScreen({ context: { page: "home" }, screen: "/home" });
|
|
320
|
-
return null;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
function UserRoute() {
|
|
324
|
-
tailor.useCurrentScreen({ context: { userId: "user_1" }, screen: "/user" });
|
|
325
|
-
return <tailor.AppView app={{ clientPath: "/apps/todo.js", id: "todo" }} />;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
render(
|
|
329
|
-
<tailor.Root apps={[{ clientPath: "/apps/todo.js", id: "todo" }]}>
|
|
330
|
-
<HomeRoute />
|
|
331
|
-
<UserRoute />
|
|
332
|
-
</tailor.Root>,
|
|
333
|
-
);
|
|
334
|
-
|
|
335
|
-
await waitFor(() => {
|
|
336
|
-
expect(consoleWarn).toHaveBeenCalledWith(expect.stringContaining('"/home", "/user"'));
|
|
337
|
-
});
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
it("passes primitive theme tokens into mounted screens", async () => {
|
|
341
|
-
const tailor = createTailorKitClient<typeof server>({
|
|
342
|
-
baseUrl: "http://runtime.test",
|
|
343
|
-
components,
|
|
344
|
-
theme: {
|
|
345
|
-
tokens: {
|
|
346
|
-
background: {
|
|
347
|
-
surface: "var(--card)",
|
|
348
|
-
},
|
|
349
|
-
},
|
|
350
|
-
},
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
render(
|
|
354
|
-
<tailor.Root apps={[{ clientPath: "/apps/todo.js", id: "todo" }]}>
|
|
355
|
-
<HomeAppView app={{ clientPath: "/apps/todo.js", id: "todo" }} tailor={tailor} />
|
|
356
|
-
</tailor.Root>,
|
|
357
|
-
);
|
|
358
|
-
|
|
359
|
-
await waitFor(() => {
|
|
360
|
-
expect(hostRecords).toHaveLength(1);
|
|
361
|
-
});
|
|
362
|
-
expect(document.querySelector("[data-tailorkit-theme-style]")?.textContent).toContain(
|
|
363
|
-
"--tailorkit-background-surface: var(--card);",
|
|
364
|
-
);
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
it("renders missing component errors inside the app container", async () => {
|
|
368
|
-
const consoleError = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
369
|
-
const tailor = createTailorKitClient<typeof server>({
|
|
370
|
-
baseUrl: "http://runtime.test",
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
render(
|
|
374
|
-
<tailor.Root apps={[{ clientPath: "/apps/todo.js", id: "todo" }]}>
|
|
375
|
-
<HomeAppView app={{ clientPath: "/apps/todo.js", id: "todo" }} tailor={tailor} />
|
|
376
|
-
</tailor.Root>,
|
|
377
|
-
);
|
|
378
|
-
|
|
379
|
-
await waitFor(() => {
|
|
380
|
-
expect(
|
|
381
|
-
testingScreen.getByText('TailorKit component "Button" is not registered.'),
|
|
382
|
-
).toBeTruthy();
|
|
383
|
-
});
|
|
384
|
-
expect(consoleError).toHaveBeenCalled();
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
it("clears a missing component error when switching apps", async () => {
|
|
388
|
-
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
389
|
-
const tailor = createTailorKitClient<typeof server>({
|
|
390
|
-
baseUrl: "http://runtime.test",
|
|
391
|
-
components,
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
const view = render(
|
|
395
|
-
<tailor.Root apps={[{ clientPath: "/apps/missing-component.js", id: "bad" }]}>
|
|
396
|
-
<HomeAppView
|
|
397
|
-
app={{ clientPath: "/apps/missing-component.js", id: "bad" }}
|
|
398
|
-
tailor={tailor}
|
|
399
|
-
/>
|
|
400
|
-
</tailor.Root>,
|
|
401
|
-
);
|
|
402
|
-
|
|
403
|
-
await waitFor(() => {
|
|
404
|
-
expect(
|
|
405
|
-
testingScreen.getByText('TailorKit component "MissingComponent" is not registered.'),
|
|
406
|
-
).toBeTruthy();
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
await act(() => {
|
|
410
|
-
view.rerender(
|
|
411
|
-
<tailor.Root apps={[{ clientPath: "/apps/email.js", id: "email" }]}>
|
|
412
|
-
<HomeAppView app={{ clientPath: "/apps/email.js", id: "email" }} tailor={tailor} />
|
|
413
|
-
</tailor.Root>,
|
|
414
|
-
);
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
await waitFor(() => {
|
|
418
|
-
expect(
|
|
419
|
-
testingScreen.queryByText('TailorKit component "MissingComponent" is not registered.'),
|
|
420
|
-
).toBeNull();
|
|
421
|
-
expect(testingScreen.getByRole("button").textContent).toContain("/apps/email.js");
|
|
422
|
-
});
|
|
423
|
-
});
|
|
424
|
-
});
|
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
import { createTailorKitServer } from "@tailorkit/core/server";
|
|
2
|
-
import type { StandardJSONSchemaV1, StandardSchemaV1 } from "@standard-schema/spec";
|
|
3
|
-
import type { ReactNode } from "react";
|
|
4
|
-
import { components, createTailorKitClient } from "../tailor-kit";
|
|
5
|
-
|
|
6
|
-
const typedSchema = <TValue,>(): StandardSchemaV1<unknown, TValue> &
|
|
7
|
-
StandardJSONSchemaV1<unknown, TValue> =>
|
|
8
|
-
({
|
|
9
|
-
"~standard": {
|
|
10
|
-
jsonSchema: {
|
|
11
|
-
input: () => ({}),
|
|
12
|
-
output: () => ({}),
|
|
13
|
-
},
|
|
14
|
-
validate: (value: unknown) => ({ value: value as TValue }),
|
|
15
|
-
vendor: "test",
|
|
16
|
-
version: 1,
|
|
17
|
-
},
|
|
18
|
-
}) as const satisfies StandardSchemaV1<unknown, TValue> & StandardJSONSchemaV1<unknown, TValue>;
|
|
19
|
-
|
|
20
|
-
const server = createTailorKitServer({
|
|
21
|
-
components: {
|
|
22
|
-
Button: {},
|
|
23
|
-
},
|
|
24
|
-
screens: {
|
|
25
|
-
"/": { context: typedSchema<{ user: { id: string } }>() },
|
|
26
|
-
"/home": { context: typedSchema<{ page: { title: string }; user: { id: string } }>() },
|
|
27
|
-
"/home/detail": {
|
|
28
|
-
context: typedSchema<{
|
|
29
|
-
detail: { id: string };
|
|
30
|
-
page: { title: string };
|
|
31
|
-
user: { id: string };
|
|
32
|
-
}>(),
|
|
33
|
-
},
|
|
34
|
-
"/user": { context: typedSchema<{ user: { id: string }; userId: string }>() },
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const tailor = createTailorKitClient<typeof server>({ baseUrl: "http://runtime.test" });
|
|
39
|
-
const app = { clientPath: "/apps/todo.js", id: "todo" };
|
|
40
|
-
|
|
41
|
-
const slotsServer = createTailorKitServer({
|
|
42
|
-
components: {
|
|
43
|
-
Button: {
|
|
44
|
-
slots: ["default"] as const,
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
const slotsSchema = slotsServer.$internal.schema;
|
|
50
|
-
|
|
51
|
-
createTailorKitClient<typeof slotsServer>({
|
|
52
|
-
baseUrl: "http://runtime.test",
|
|
53
|
-
components: {
|
|
54
|
-
Button: ({ slots }) => {
|
|
55
|
-
const slot: ReactNode = slots.default;
|
|
56
|
-
return slot;
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
const requiredComponentsServer = createTailorKitServer({
|
|
62
|
-
components: {
|
|
63
|
-
Button: {},
|
|
64
|
-
Input: {},
|
|
65
|
-
},
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
createTailorKitClient<typeof requiredComponentsServer>({
|
|
69
|
-
baseUrl: "http://runtime.test",
|
|
70
|
-
// @ts-expect-error all server components must have client renderers when components are provided
|
|
71
|
-
components: {
|
|
72
|
-
Button: () => null,
|
|
73
|
-
},
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
components(slotsSchema, {
|
|
77
|
-
Button: ({ props, slots }) => {
|
|
78
|
-
const typedProps = props satisfies Record<string, never>;
|
|
79
|
-
const typedSlot: ReactNode = slots.default;
|
|
80
|
-
void typedProps;
|
|
81
|
-
void typedSlot;
|
|
82
|
-
return null;
|
|
83
|
-
},
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const callbackServer = createTailorKitServer({
|
|
87
|
-
components: {
|
|
88
|
-
Button: {
|
|
89
|
-
fields: typedSchema<{ variant?: "default" | "secondary" }>(),
|
|
90
|
-
callbacks: {
|
|
91
|
-
onClick: {},
|
|
92
|
-
},
|
|
93
|
-
slots: ["default"] as const,
|
|
94
|
-
},
|
|
95
|
-
},
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
components(callbackServer.$internal.schema, {
|
|
99
|
-
Button: ({ props }) => {
|
|
100
|
-
const variant: "default" | "secondary" | undefined = props.variant;
|
|
101
|
-
const onClick: (() => void) | undefined = props.onClick;
|
|
102
|
-
void variant;
|
|
103
|
-
void onClick;
|
|
104
|
-
return null;
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
tailor.useCurrentScreen({
|
|
109
|
-
screen: "/home",
|
|
110
|
-
context: { page: { title: "Home" }, user: { id: "user_1" } },
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
tailor.useCurrentScreen({ screen: "/user", status: "loading" });
|
|
114
|
-
|
|
115
|
-
tailor.useCurrentScreen({ screen: "/user", status: "error" });
|
|
116
|
-
|
|
117
|
-
// @ts-expect-error invalid screen name
|
|
118
|
-
tailor.useCurrentScreen({ screen: "missing", context: {} });
|
|
119
|
-
|
|
120
|
-
// @ts-expect-error invalid context shape for selected screen
|
|
121
|
-
tailor.useCurrentScreen({ screen: "/user", context: { page: { title: "Home" } } });
|
|
122
|
-
|
|
123
|
-
// @ts-expect-error ready matches require context
|
|
124
|
-
tailor.useCurrentScreen({ screen: "/home" });
|
|
125
|
-
|
|
126
|
-
// @ts-expect-error loading screens cannot expose partial context
|
|
127
|
-
tailor.useCurrentScreen({ screen: "/user", status: "loading", context: { userId: "user_1" } });
|
|
128
|
-
|
|
129
|
-
<tailor.AppView app={app} />;
|
|
130
|
-
|
|
131
|
-
<tailor.AppView
|
|
132
|
-
app={app}
|
|
133
|
-
screen="/home"
|
|
134
|
-
context={{ page: { title: "Home" }, user: { id: "user_1" } }}
|
|
135
|
-
/>;
|
|
136
|
-
|
|
137
|
-
<tailor.AppView app={app} screen="/user" status="loading" />;
|
|
138
|
-
|
|
139
|
-
<tailor.AppView app={app} screen="/user" status="error" />;
|
|
140
|
-
|
|
141
|
-
// @ts-expect-error invalid screen name
|
|
142
|
-
<tailor.AppView app={app} screen="missing" context={{}} />;
|
|
143
|
-
|
|
144
|
-
// @ts-expect-error invalid context shape for selected screen
|
|
145
|
-
<tailor.AppView app={app} screen="/user" context={{ page: { title: "Home" } }} />;
|
|
146
|
-
|
|
147
|
-
// @ts-expect-error ready app views require context when screen is provided
|
|
148
|
-
<tailor.AppView app={app} screen="/home" />;
|
|
149
|
-
|
|
150
|
-
// @ts-expect-error loading app views cannot expose context
|
|
151
|
-
<tailor.AppView app={app} screen="/user" status="loading" context={{ userId: "user_1" }} />;
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext } from "react";
|
|
2
|
-
import type { UseAppsResult } from "../hooks/use-apps";
|
|
3
|
-
import type { TailorKitApp } from "../tailor-kit";
|
|
4
|
-
|
|
5
|
-
export interface TailorRootContextValue {
|
|
6
|
-
apps: TailorKitApp[];
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface RootTailor {
|
|
10
|
-
useApps: () => UseAppsResult;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const TailorRootContext = createContext<TailorRootContextValue | null>(null);
|
|
14
|
-
|
|
15
|
-
export function useTailorRootContext(component: string): TailorRootContextValue {
|
|
16
|
-
const context = useContext(TailorRootContext);
|
|
17
|
-
if (!context) {
|
|
18
|
-
throw new Error(`${component} must be rendered inside tailor.Root.`);
|
|
19
|
-
}
|
|
20
|
-
return context;
|
|
21
|
-
}
|
package/src/components/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { Root } from "./root";
|