@tailorkit/react 0.1.0-beta.3 → 0.1.0-beta.5
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/dist/components/context.d.ts +0 -1
- package/dist/components/index.d.ts +0 -1
- package/dist/components/render.d.ts +0 -1
- package/dist/components/root.d.ts +0 -1
- package/dist/hooks/use-apps.d.ts +0 -1
- package/dist/hooks/use-current-screen.d.ts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/node-store.d.ts +0 -1
- package/dist/primitives.d.ts +0 -1
- package/dist/remote-context.d.ts +0 -1
- package/dist/remote-view.d.ts +0 -1
- package/dist/tailor-kit.d.ts +0 -1
- package/package.json +4 -15
- package/dist/components/context.d.ts.map +0 -1
- package/dist/components/index.d.ts.map +0 -1
- package/dist/components/render.d.ts.map +0 -1
- 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.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/node-store.d.ts.map +0 -1
- package/dist/primitives.d.ts.map +0 -1
- package/dist/remote-context.d.ts.map +0 -1
- package/dist/remote-view.d.ts.map +0 -1
- 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,116 +0,0 @@
|
|
|
1
|
-
import { cloneElement, createElement, isValidElement, useMemo } from "react";
|
|
2
|
-
import type {
|
|
3
|
-
CSSProperties,
|
|
4
|
-
ComponentPropsWithoutRef,
|
|
5
|
-
DOMAttributes,
|
|
6
|
-
ElementType,
|
|
7
|
-
HTMLAttributes,
|
|
8
|
-
ReactElement,
|
|
9
|
-
ReactNode,
|
|
10
|
-
Ref,
|
|
11
|
-
} from "react";
|
|
12
|
-
|
|
13
|
-
type ElementProps<TTagName extends ElementType> = ComponentPropsWithoutRef<TTagName> & {
|
|
14
|
-
ref?: Ref<unknown>;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export interface RenderProps {
|
|
18
|
-
render?: ReactElement<Record<string, unknown>>;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export type ComponentProps<TTagName extends ElementType> = ElementProps<TTagName> & RenderProps;
|
|
22
|
-
|
|
23
|
-
export function useRender<TTagName extends ElementType>({
|
|
24
|
-
defaultTagName,
|
|
25
|
-
props,
|
|
26
|
-
render,
|
|
27
|
-
}: {
|
|
28
|
-
defaultTagName: TTagName;
|
|
29
|
-
props: ElementProps<TTagName>;
|
|
30
|
-
render?: ReactElement<Record<string, unknown>>;
|
|
31
|
-
}): ReactNode {
|
|
32
|
-
return useMemo(() => {
|
|
33
|
-
if (render) {
|
|
34
|
-
if (!isValidElement(render)) {
|
|
35
|
-
throw new Error("The render prop must be a valid React element.");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return cloneElement(render, mergeProps(render.props, props));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return createElement(defaultTagName, props);
|
|
42
|
-
}, [defaultTagName, props, render]);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function mergeProps<TProps extends Record<string, unknown>>(
|
|
46
|
-
...propSets: (TProps | Record<string, unknown> | undefined)[]
|
|
47
|
-
): TProps {
|
|
48
|
-
const merged: Record<string, unknown> = {};
|
|
49
|
-
|
|
50
|
-
for (const props of propSets) {
|
|
51
|
-
if (!props) {
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
for (const [key, value] of Object.entries(props)) {
|
|
56
|
-
if (value === undefined) {
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const existing = merged[key];
|
|
61
|
-
|
|
62
|
-
if (key === "className" && existing && value) {
|
|
63
|
-
merged[key] = `${existing as string} ${value as string}`;
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (key === "style" && existing && value) {
|
|
68
|
-
merged[key] = {
|
|
69
|
-
...(existing as CSSProperties),
|
|
70
|
-
...(value as CSSProperties),
|
|
71
|
-
};
|
|
72
|
-
continue;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (key === "ref" && existing && value) {
|
|
76
|
-
merged[key] = composeRefs(existing as Ref<unknown>, value as Ref<unknown>);
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (isEventHandler(key) && typeof existing === "function" && typeof value === "function") {
|
|
81
|
-
merged[key] = (...args: unknown[]) => {
|
|
82
|
-
(existing as (...eventArgs: unknown[]) => void)(...args);
|
|
83
|
-
const event = args[0] as { defaultPrevented?: boolean } | undefined;
|
|
84
|
-
if (!event?.defaultPrevented) {
|
|
85
|
-
(value as (...eventArgs: unknown[]) => void)(...args);
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
merged[key] = value;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return merged as TProps;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function composeRefs<T>(...refs: (Ref<T> | undefined)[]): Ref<T> {
|
|
99
|
-
return (node: T) => {
|
|
100
|
-
for (const ref of refs) {
|
|
101
|
-
if (typeof ref === "function") {
|
|
102
|
-
ref(node);
|
|
103
|
-
} else if (ref) {
|
|
104
|
-
(ref as { current: T | null }).current = node;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function isEventHandler(key: string): key is keyof DOMAttributes<HTMLElement> {
|
|
111
|
-
return /^on[A-Z]/u.test(key);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export type HtmlProps<TElement extends HTMLElement> = HTMLAttributes<TElement> & {
|
|
115
|
-
ref?: Ref<TElement>;
|
|
116
|
-
};
|
package/src/components/root.tsx
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { useMemo } from "react";
|
|
2
|
-
import type { ReactNode } from "react";
|
|
3
|
-
import type { TailorKitApp } from "../tailor-kit";
|
|
4
|
-
import type { ComponentProps } from "./render";
|
|
5
|
-
import { mergeProps, useRender } from "./render";
|
|
6
|
-
import { TailorRootContext } from "./context";
|
|
7
|
-
import type { RootTailor, TailorRootContextValue } from "./context";
|
|
8
|
-
|
|
9
|
-
const EMPTY_APPS: TailorKitApp[] = [];
|
|
10
|
-
|
|
11
|
-
export interface RootProps extends ComponentProps<"div"> {
|
|
12
|
-
apps?: TailorKitApp[];
|
|
13
|
-
children?: ReactNode;
|
|
14
|
-
tailor: RootTailor;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function Root({ apps: appsProp, children, render, tailor, ...props }: RootProps): ReactNode {
|
|
18
|
-
const appsResult = tailor.useApps();
|
|
19
|
-
const apps = appsProp ?? appsResult.data ?? EMPTY_APPS;
|
|
20
|
-
|
|
21
|
-
const context = useMemo<TailorRootContextValue>(
|
|
22
|
-
() => ({
|
|
23
|
-
apps,
|
|
24
|
-
}),
|
|
25
|
-
[apps],
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
const element = render
|
|
29
|
-
? useRender({
|
|
30
|
-
defaultTagName: "div",
|
|
31
|
-
props: mergeProps({ children }, props),
|
|
32
|
-
render,
|
|
33
|
-
})
|
|
34
|
-
: children;
|
|
35
|
-
|
|
36
|
-
return <TailorRootContext.Provider value={context}>{element}</TailorRootContext.Provider>;
|
|
37
|
-
}
|
package/src/hooks/use-apps.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useSyncExternalStore } from "react";
|
|
2
|
-
import type { TailorKitApp, TailorKitAppsSnapshot, TailorKitStore } from "../tailor-kit";
|
|
3
|
-
|
|
4
|
-
export interface UseAppsResult {
|
|
5
|
-
data: TailorKitApp[] | undefined;
|
|
6
|
-
error: Error | null;
|
|
7
|
-
isError: boolean;
|
|
8
|
-
isLoading: boolean;
|
|
9
|
-
isPending: boolean;
|
|
10
|
-
isSuccess: boolean;
|
|
11
|
-
refetch: () => Promise<void>;
|
|
12
|
-
status: "error" | "idle" | "loading" | "ready";
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function createUseApps(store: TailorKitStore): () => UseAppsResult {
|
|
16
|
-
return function useApps(): UseAppsResult {
|
|
17
|
-
const snapshot = useSyncExternalStore(
|
|
18
|
-
store.subscribe,
|
|
19
|
-
store.getAppsSnapshot,
|
|
20
|
-
store.getAppsSnapshot,
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
void store.fetchApps();
|
|
25
|
-
}, []);
|
|
26
|
-
|
|
27
|
-
const refetch = useCallback(() => store.fetchApps({ force: true }), []);
|
|
28
|
-
|
|
29
|
-
return toUseAppsResult(snapshot, refetch);
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function toUseAppsResult(
|
|
34
|
-
snapshot: TailorKitAppsSnapshot,
|
|
35
|
-
refetch: () => Promise<void>,
|
|
36
|
-
): UseAppsResult {
|
|
37
|
-
return {
|
|
38
|
-
data: snapshot.status === "ready" ? snapshot.apps : undefined,
|
|
39
|
-
error: snapshot.error,
|
|
40
|
-
isError: snapshot.status === "error",
|
|
41
|
-
isLoading: snapshot.status === "loading",
|
|
42
|
-
isPending: snapshot.status === "idle" || snapshot.status === "loading",
|
|
43
|
-
isSuccess: snapshot.status === "ready",
|
|
44
|
-
refetch,
|
|
45
|
-
status: snapshot.status,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { useEffect, useMemo, useRef } from "react";
|
|
2
|
-
import type { StandardJSONSchemaV1 } from "@standard-schema/spec";
|
|
3
|
-
import type { ScreenDefinition } from "@tailorkit/core/schema";
|
|
4
|
-
import type { TailorKitStore } from "../tailor-kit";
|
|
5
|
-
import { useTailorRootContext } from "../components/context";
|
|
6
|
-
|
|
7
|
-
export type ScreenName<TScreens extends Record<string, ScreenDefinition>> = keyof TScreens & string;
|
|
8
|
-
|
|
9
|
-
export type ScreenContext<TScreen> =
|
|
10
|
-
TScreen extends ScreenDefinition<infer TContext>
|
|
11
|
-
? TContext extends StandardJSONSchemaV1
|
|
12
|
-
? StandardJSONSchemaV1.InferOutput<TContext>
|
|
13
|
-
: Record<string, never>
|
|
14
|
-
: never;
|
|
15
|
-
|
|
16
|
-
interface ReadyScreenOptions<
|
|
17
|
-
TScreens extends Record<string, ScreenDefinition>,
|
|
18
|
-
TScreen extends ScreenName<TScreens>,
|
|
19
|
-
> {
|
|
20
|
-
context: ScreenContext<TScreens[TScreen]>;
|
|
21
|
-
screen: TScreen;
|
|
22
|
-
status?: "ready";
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface LoadingScreenOptions<TScreen extends string> {
|
|
26
|
-
context?: never;
|
|
27
|
-
screen: TScreen;
|
|
28
|
-
status: "loading";
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface ErrorScreenOptions<TScreen extends string> {
|
|
32
|
-
context?: never;
|
|
33
|
-
screen: TScreen;
|
|
34
|
-
status: "error";
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export type CurrentScreenOptions<
|
|
38
|
-
TScreens extends Record<string, ScreenDefinition>,
|
|
39
|
-
TScreen extends ScreenName<TScreens> = ScreenName<TScreens>,
|
|
40
|
-
> =
|
|
41
|
-
TScreen extends ScreenName<TScreens>
|
|
42
|
-
?
|
|
43
|
-
| ReadyScreenOptions<TScreens, TScreen>
|
|
44
|
-
| LoadingScreenOptions<TScreen>
|
|
45
|
-
| ErrorScreenOptions<TScreen>
|
|
46
|
-
: never;
|
|
47
|
-
|
|
48
|
-
export function createUseCurrentScreen<TScreens extends Record<string, ScreenDefinition>>(
|
|
49
|
-
store: TailorKitStore,
|
|
50
|
-
) {
|
|
51
|
-
return function useCurrentScreen<TScreen extends ScreenName<TScreens>>(
|
|
52
|
-
options: CurrentScreenOptions<TScreens, TScreen>,
|
|
53
|
-
): void {
|
|
54
|
-
useTailorRootContext("tailor.useCurrentScreen");
|
|
55
|
-
const id = useMemo(() => Symbol("tailorkit-current-screen"), []);
|
|
56
|
-
const status = options.status ?? "ready";
|
|
57
|
-
const context = "context" in options ? options.context : undefined;
|
|
58
|
-
const contextKey = JSON.stringify(context);
|
|
59
|
-
const contextRef = useRef({ key: contextKey, value: context });
|
|
60
|
-
if (contextRef.current.key !== contextKey) {
|
|
61
|
-
contextRef.current = { key: contextKey, value: context };
|
|
62
|
-
}
|
|
63
|
-
const contextSnapshot = contextRef.current.value;
|
|
64
|
-
|
|
65
|
-
useEffect(
|
|
66
|
-
() => () => {
|
|
67
|
-
store.unregisterScreen(id);
|
|
68
|
-
},
|
|
69
|
-
[id],
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
useEffect(() => {
|
|
73
|
-
store.registerScreen({
|
|
74
|
-
context: contextSnapshot,
|
|
75
|
-
id,
|
|
76
|
-
screen: options.screen,
|
|
77
|
-
status,
|
|
78
|
-
});
|
|
79
|
-
}, [contextSnapshot, id, options.screen, status]);
|
|
80
|
-
};
|
|
81
|
-
}
|
package/src/index.ts
DELETED
package/src/node-store.ts
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import type { RemoteNode } from "@tailorkit/sandbox/protocol";
|
|
2
|
-
|
|
3
|
-
type Listener = () => void;
|
|
4
|
-
|
|
5
|
-
const childIds = (children: RemoteNode[]): string => children.map((c) => c.id).join(",");
|
|
6
|
-
|
|
7
|
-
const nodeSignature = (node: RemoteNode): string => {
|
|
8
|
-
if (node.kind === "text") {
|
|
9
|
-
return `text:${node.text}`;
|
|
10
|
-
}
|
|
11
|
-
if (node.kind === "fragment") {
|
|
12
|
-
return `frag:${childIds(node.children)}`;
|
|
13
|
-
}
|
|
14
|
-
// For elements: type + child IDs + serialized props + callback bindings
|
|
15
|
-
const propsStr = JSON.stringify(node.props);
|
|
16
|
-
const callbacksStr = (node.callbacks ?? [])
|
|
17
|
-
.map((binding) => `${binding.callback}:${binding.event}`)
|
|
18
|
-
.join(",");
|
|
19
|
-
return `elem:${node.type}|${childIds(node.children)}|${propsStr}|${callbacksStr}`;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const flattenTree = (node: RemoteNode, out: Map<string, RemoteNode>): void => {
|
|
23
|
-
out.set(node.id, node);
|
|
24
|
-
if (node.kind === "text") {
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
for (const child of node.children) {
|
|
28
|
-
flattenTree(child, out);
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export class NodeStore {
|
|
33
|
-
private nodes = new Map<string, RemoteNode>();
|
|
34
|
-
private signatures = new Map<string, string>();
|
|
35
|
-
private nodeListeners = new Map<string, Set<Listener>>();
|
|
36
|
-
private rootListeners = new Set<Listener>();
|
|
37
|
-
private rootId: string | null = null;
|
|
38
|
-
|
|
39
|
-
clear(): void {
|
|
40
|
-
const nodeIds = [...this.nodeListeners.keys()];
|
|
41
|
-
const hadRoot = this.rootId !== null;
|
|
42
|
-
|
|
43
|
-
this.nodes = new Map();
|
|
44
|
-
this.signatures = new Map();
|
|
45
|
-
this.rootId = null;
|
|
46
|
-
|
|
47
|
-
for (const id of nodeIds) {
|
|
48
|
-
const subs = this.nodeListeners.get(id);
|
|
49
|
-
if (subs) {
|
|
50
|
-
for (const l of subs) {
|
|
51
|
-
l();
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (hadRoot) {
|
|
57
|
-
for (const l of this.rootListeners) {
|
|
58
|
-
l();
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
setSnapshot(tree: RemoteNode): void {
|
|
64
|
-
const next = new Map<string, RemoteNode>();
|
|
65
|
-
flattenTree(tree, next);
|
|
66
|
-
|
|
67
|
-
const changed = new Set<string>();
|
|
68
|
-
|
|
69
|
-
for (const [id, node] of next) {
|
|
70
|
-
const sig = nodeSignature(node);
|
|
71
|
-
if (this.signatures.get(id) !== sig) {
|
|
72
|
-
changed.add(id);
|
|
73
|
-
this.signatures.set(id, sig);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
for (const id of this.signatures.keys()) {
|
|
78
|
-
if (!next.has(id)) {
|
|
79
|
-
this.signatures.delete(id);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
this.nodes = next;
|
|
84
|
-
|
|
85
|
-
const prevRootId = this.rootId;
|
|
86
|
-
this.rootId = tree.id;
|
|
87
|
-
|
|
88
|
-
for (const id of changed) {
|
|
89
|
-
const subs = this.nodeListeners.get(id);
|
|
90
|
-
if (subs) {
|
|
91
|
-
for (const l of subs) {
|
|
92
|
-
l();
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (prevRootId !== this.rootId) {
|
|
98
|
-
for (const l of this.rootListeners) {
|
|
99
|
-
l();
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
getRootId(): string | null {
|
|
105
|
-
return this.rootId;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
getNode(id: string): RemoteNode | null {
|
|
109
|
-
return this.nodes.get(id) ?? null;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
subscribe(id: string, listener: Listener): () => void {
|
|
113
|
-
let subs = this.nodeListeners.get(id);
|
|
114
|
-
if (!subs) {
|
|
115
|
-
subs = new Set();
|
|
116
|
-
this.nodeListeners.set(id, subs);
|
|
117
|
-
}
|
|
118
|
-
subs.add(listener);
|
|
119
|
-
return () => {
|
|
120
|
-
subs.delete(listener);
|
|
121
|
-
if (subs.size === 0) {
|
|
122
|
-
this.nodeListeners.delete(id);
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
subscribeRoot(listener: Listener): () => void {
|
|
128
|
-
this.rootListeners.add(listener);
|
|
129
|
-
return () => this.rootListeners.delete(listener);
|
|
130
|
-
}
|
|
131
|
-
}
|
package/src/primitives.tsx
DELETED
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext, useId, useMemo } from "react";
|
|
2
|
-
import type { CSSProperties, ReactNode } from "react";
|
|
3
|
-
import type { TailorKitTheme } from "@tailorkit/core/schema";
|
|
4
|
-
import { resolveTheme } from "@tailorkit/core/primitives/theme";
|
|
5
|
-
|
|
6
|
-
type Responsive<T> = T | Record<string, T | undefined>;
|
|
7
|
-
type PrimitiveValue = number | string;
|
|
8
|
-
type PrimitiveProps = Record<string, unknown>;
|
|
9
|
-
|
|
10
|
-
interface PrimitiveThemeContextValue {
|
|
11
|
-
screenId: string;
|
|
12
|
-
theme: TailorKitTheme;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const PrimitiveThemeContext = createContext<PrimitiveThemeContextValue | null>(null);
|
|
16
|
-
|
|
17
|
-
const tokenGroups = [
|
|
18
|
-
"space",
|
|
19
|
-
"radius",
|
|
20
|
-
"background",
|
|
21
|
-
"border",
|
|
22
|
-
"borderColor",
|
|
23
|
-
"textColor",
|
|
24
|
-
] as const;
|
|
25
|
-
|
|
26
|
-
const cssEscape = (value: string): string => {
|
|
27
|
-
const css = (globalThis as { CSS?: { escape?: (input: string) => string } }).CSS;
|
|
28
|
-
if (typeof css?.escape === "function") {
|
|
29
|
-
return css.escape(value);
|
|
30
|
-
}
|
|
31
|
-
return value.replaceAll(/[^A-Za-z0-9_-]/gu, "\\$&");
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const toCssValue = (
|
|
35
|
-
group: (typeof tokenGroups)[number] | "size" | "raw",
|
|
36
|
-
value: PrimitiveValue,
|
|
37
|
-
): string => {
|
|
38
|
-
if (typeof value === "number") {
|
|
39
|
-
return `${value}px`;
|
|
40
|
-
}
|
|
41
|
-
if (group === "size" || group === "raw") {
|
|
42
|
-
return value;
|
|
43
|
-
}
|
|
44
|
-
return `var(--tailorkit-${group}-${cssEscape(value)})`;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const toResponsiveEntries = <T,>(value: Responsive<T> | undefined): [string, T][] => {
|
|
48
|
-
if (value === undefined) {
|
|
49
|
-
return [];
|
|
50
|
-
}
|
|
51
|
-
if (!(value && typeof value === "object") || Array.isArray(value)) {
|
|
52
|
-
return [["base", value as T]];
|
|
53
|
-
}
|
|
54
|
-
return Object.entries(value as Record<string, T | undefined>).filter(
|
|
55
|
-
(entry): entry is [string, T] => entry[1] !== undefined,
|
|
56
|
-
);
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const declarationsForProp = (prop: string, value: PrimitiveValue): string[] => {
|
|
60
|
-
if (prop === "columns") {
|
|
61
|
-
return [`grid-template-columns: repeat(${String(value)}, minmax(0, 1fr));`];
|
|
62
|
-
}
|
|
63
|
-
const tokenDeclarations: Record<string, [(typeof tokenGroups)[number], string]> = {
|
|
64
|
-
background: ["background", "background"],
|
|
65
|
-
border: ["border", "border"],
|
|
66
|
-
borderColor: ["borderColor", "border-color"],
|
|
67
|
-
textColor: ["textColor", "color"],
|
|
68
|
-
};
|
|
69
|
-
const tokenDeclaration = tokenDeclarations[prop];
|
|
70
|
-
if (tokenDeclaration) {
|
|
71
|
-
const [group, property] = tokenDeclaration;
|
|
72
|
-
if (prop === "border") {
|
|
73
|
-
return [`border-style: ${toCssValue(group, value)};`, "border-width: 1px;"];
|
|
74
|
-
}
|
|
75
|
-
return [`${property}: ${toCssValue(group, value)};`];
|
|
76
|
-
}
|
|
77
|
-
if (prop === "gap" || prop === "margin" || prop === "padding") {
|
|
78
|
-
return [`${prop}: ${toCssValue("space", value)};`];
|
|
79
|
-
}
|
|
80
|
-
if (prop === "height" || prop === "width") {
|
|
81
|
-
return [`${prop}: ${toCssValue("size", value)};`];
|
|
82
|
-
}
|
|
83
|
-
if (prop === "radius") {
|
|
84
|
-
return [`border-radius: ${toCssValue("radius", value)};`];
|
|
85
|
-
}
|
|
86
|
-
if (prop === "align") {
|
|
87
|
-
return [`align-items: ${toFlexAlignment(value)};`];
|
|
88
|
-
}
|
|
89
|
-
if (prop === "justify") {
|
|
90
|
-
return [`justify-content: ${toFlexJustification(value)};`];
|
|
91
|
-
}
|
|
92
|
-
if (prop === "direction") {
|
|
93
|
-
return [`flex-direction: ${toCssValue("raw", value)};`];
|
|
94
|
-
}
|
|
95
|
-
if (prop === "overflow") {
|
|
96
|
-
return [`overflow: ${toCssValue("raw", value)};`];
|
|
97
|
-
}
|
|
98
|
-
if (prop === "grow") {
|
|
99
|
-
return [`flex-grow: ${String(value)};`];
|
|
100
|
-
}
|
|
101
|
-
if (prop === "shrink") {
|
|
102
|
-
return [`flex-shrink: ${String(value)};`];
|
|
103
|
-
}
|
|
104
|
-
if (prop === "minHeight") {
|
|
105
|
-
return [`min-height: ${toCssValue("size", value)};`];
|
|
106
|
-
}
|
|
107
|
-
if (prop === "minWidth") {
|
|
108
|
-
return [`min-width: ${toCssValue("size", value)};`];
|
|
109
|
-
}
|
|
110
|
-
if (prop === "basis") {
|
|
111
|
-
return [`flex-basis: ${toCssValue("size", value)};`];
|
|
112
|
-
}
|
|
113
|
-
if (prop === "wrap") {
|
|
114
|
-
return [`flex-wrap: ${toCssValue("raw", value)};`];
|
|
115
|
-
}
|
|
116
|
-
return [];
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const toFlexAlignment = (value: PrimitiveValue): string => {
|
|
120
|
-
if (value === "start") {
|
|
121
|
-
return "flex-start";
|
|
122
|
-
}
|
|
123
|
-
if (value === "end") {
|
|
124
|
-
return "flex-end";
|
|
125
|
-
}
|
|
126
|
-
return String(value);
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const toFlexJustification = (value: PrimitiveValue): string => {
|
|
130
|
-
if (value === "start") {
|
|
131
|
-
return "flex-start";
|
|
132
|
-
}
|
|
133
|
-
if (value === "end") {
|
|
134
|
-
return "flex-end";
|
|
135
|
-
}
|
|
136
|
-
if (value === "between") {
|
|
137
|
-
return "space-between";
|
|
138
|
-
}
|
|
139
|
-
return String(value);
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const buildPrimitiveCss = ({
|
|
143
|
-
display,
|
|
144
|
-
nodeId,
|
|
145
|
-
props,
|
|
146
|
-
screenId,
|
|
147
|
-
theme,
|
|
148
|
-
}: {
|
|
149
|
-
display: "block" | "flex" | "grid" | "inline";
|
|
150
|
-
nodeId: string;
|
|
151
|
-
props: PrimitiveProps;
|
|
152
|
-
screenId: string;
|
|
153
|
-
theme: TailorKitTheme;
|
|
154
|
-
}): string => {
|
|
155
|
-
const selector = `[data-tailorkit-screen="${cssEscape(screenId)}"] [data-tailorkit-node="${cssEscape(
|
|
156
|
-
nodeId,
|
|
157
|
-
)}"]`;
|
|
158
|
-
const base = [`display: ${display};`];
|
|
159
|
-
const byBreakpoint = new Map<string, string[]>();
|
|
160
|
-
|
|
161
|
-
for (const [prop, rawValue] of Object.entries(props)) {
|
|
162
|
-
for (const [breakpoint, value] of toResponsiveEntries(rawValue as Responsive<PrimitiveValue>)) {
|
|
163
|
-
const declarations = declarationsForProp(prop, value);
|
|
164
|
-
if (declarations.length === 0) {
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
const target = breakpoint === "base" ? base : (byBreakpoint.get(breakpoint) ?? []);
|
|
168
|
-
target.push(...declarations);
|
|
169
|
-
if (breakpoint !== "base") {
|
|
170
|
-
byBreakpoint.set(breakpoint, target);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const rules = [`${selector} { ${base.join(" ")} }`];
|
|
176
|
-
for (const [breakpoint, declarations] of byBreakpoint) {
|
|
177
|
-
const minWidth = theme.breakpoints?.[breakpoint];
|
|
178
|
-
if (!minWidth) {
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
rules.push(`@media (min-width: ${minWidth}) { ${selector} { ${declarations.join(" ")} } }`);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return rules.join("\n");
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
export const buildThemeCss = (screenId: string, theme: TailorKitTheme): string => {
|
|
188
|
-
const selector = `[data-tailorkit-screen="${cssEscape(screenId)}"]`;
|
|
189
|
-
const declarations: string[] = [];
|
|
190
|
-
const resolvedTheme = resolveTheme(theme);
|
|
191
|
-
|
|
192
|
-
for (const group of tokenGroups) {
|
|
193
|
-
for (const [name, value] of Object.entries(resolvedTheme.tokens?.[group] ?? {})) {
|
|
194
|
-
declarations.push(`--tailorkit-${group}-${cssEscape(name)}: ${value};`);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return `${selector} { ${declarations.join(" ")} }`;
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
const usePrimitiveNodeId = (): string => {
|
|
202
|
-
const id = useId();
|
|
203
|
-
return useMemo(() => `tailorkit-${id.replaceAll(":", "")}`, [id]);
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
const Primitive = ({
|
|
207
|
-
children,
|
|
208
|
-
display,
|
|
209
|
-
element,
|
|
210
|
-
props,
|
|
211
|
-
}: {
|
|
212
|
-
children?: ReactNode;
|
|
213
|
-
display: "block" | "flex" | "grid" | "inline";
|
|
214
|
-
element: "div" | "span";
|
|
215
|
-
props: PrimitiveProps;
|
|
216
|
-
}): ReactNode => {
|
|
217
|
-
const context = useContext(PrimitiveThemeContext);
|
|
218
|
-
const nodeId = usePrimitiveNodeId();
|
|
219
|
-
const Element = element;
|
|
220
|
-
const css = context
|
|
221
|
-
? buildPrimitiveCss({
|
|
222
|
-
display,
|
|
223
|
-
nodeId,
|
|
224
|
-
props,
|
|
225
|
-
screenId: context.screenId,
|
|
226
|
-
theme: context.theme,
|
|
227
|
-
})
|
|
228
|
-
: "";
|
|
229
|
-
|
|
230
|
-
return (
|
|
231
|
-
<Element data-tailorkit-node={nodeId}>
|
|
232
|
-
{css === "" ? null : <style data-tailorkit-node-style={nodeId}>{css}</style>}
|
|
233
|
-
{children}
|
|
234
|
-
</Element>
|
|
235
|
-
);
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
export const primitives = {
|
|
239
|
-
Box: ({ props, slots }: { props: PrimitiveProps; slots: { default?: ReactNode } }) => (
|
|
240
|
-
<Primitive display="block" element="div" props={props}>
|
|
241
|
-
{slots.default}
|
|
242
|
-
</Primitive>
|
|
243
|
-
),
|
|
244
|
-
Flex: ({ props, slots }: { props: PrimitiveProps; slots: { default?: ReactNode } }) => (
|
|
245
|
-
<Primitive display="flex" element="div" props={props}>
|
|
246
|
-
{slots.default}
|
|
247
|
-
</Primitive>
|
|
248
|
-
),
|
|
249
|
-
Grid: ({ props, slots }: { props: PrimitiveProps; slots: { default?: ReactNode } }) => (
|
|
250
|
-
<Primitive display="grid" element="div" props={props}>
|
|
251
|
-
{slots.default}
|
|
252
|
-
</Primitive>
|
|
253
|
-
),
|
|
254
|
-
Inline: ({ props, slots }: { props: PrimitiveProps; slots: { default?: ReactNode } }) => (
|
|
255
|
-
<Primitive display="inline" element="span" props={props}>
|
|
256
|
-
{slots.default}
|
|
257
|
-
</Primitive>
|
|
258
|
-
),
|
|
259
|
-
} as const;
|
|
260
|
-
|
|
261
|
-
export type PrimitiveStyle = CSSProperties;
|
package/src/remote-context.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { createContext } from "react";
|
|
2
|
-
import type { HostToWorkerPayload } from "@tailorkit/sandbox/protocol";
|
|
3
|
-
import type { NodeStore } from "./node-store";
|
|
4
|
-
|
|
5
|
-
export interface RemoteViewContext {
|
|
6
|
-
components: Record<string, unknown>;
|
|
7
|
-
dispatch: (payload: HostToWorkerPayload) => void;
|
|
8
|
-
store: NodeStore;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const RemoteUIContext = createContext<RemoteViewContext | null>(null);
|