Skip to content

Commit ab76991

Browse files
authored
WIP: UIMix elements as React components (#194)
1 parent 4753ebb commit ab76991

File tree

20 files changed

+1223
-56
lines changed

20 files changed

+1223
-56
lines changed

packages/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"@types/shelljs": "^0.8.12",
5757
"@types/tmp": "^0.2.3",
5858
"@uimix/adapter-types": "workspace:*",
59+
"@uimix/elements-react": "workspace:*",
5960
"@uimix/foundation": "workspace:*",
6061
"@uimix/model": "workspace:*",
6162
"csstype": "^3.1.2",

packages/cli/src/compiler/CSSGenerator.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,9 @@ import { kebabCase } from "lodash-es";
22
import * as CSS from "csstype";
33
import { Page } from "@uimix/model/src/models/Page";
44
import * as CodeAsset from "@uimix/adapter-types";
5-
import {
6-
buildNodeCSS,
7-
Selectable,
8-
SelfAndChildrenCSS,
9-
Variant,
10-
} from "@uimix/model/src/models";
5+
import { Selectable, Variant } from "@uimix/model/src/models";
116
import { ClassNameGenerator } from "./ClassNameGenerator";
7+
import { SelfAndChildrenCSS } from "@uimix/elements-react/src/style";
128

139
function isDesignToken(
1410
value: CodeAsset.DesignToken | CodeAsset.DesignTokens
@@ -70,12 +66,9 @@ export class CSSGenerator {
7066
return css;
7167
}
7268

73-
css = buildNodeCSS(
74-
selectable.node.type,
75-
selectable.style,
69+
css = selectable.buildCSS(
7670
(tokenID) =>
77-
getColorToken(this.designTokens, tokenID.split("/"))?.$value ??
78-
selectable.project.colorTokens.resolve(tokenID)
71+
getColorToken(this.designTokens, tokenID.split("/"))?.$value
7972
);
8073
const isTopLevel =
8174
selectable.idPath.length === 1 &&

packages/editor/src/views/viewport/TextEditor.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { action, reaction } from "mobx";
22
import { observer } from "mobx-react-lite";
33
import React, { useEffect, useRef } from "react";
4-
import { Selectable, buildNodeCSS } from "@uimix/model/src/models";
4+
import { Selectable } from "@uimix/model/src/models";
55
import { viewportState } from "../../state/ViewportState";
66
import { projectState } from "../../state/ProjectState";
77

@@ -10,9 +10,7 @@ export const TextEditorBody: React.FC<{
1010
}> = observer(({ selectable }) => {
1111
const style = selectable.style;
1212

13-
const cssStyle = buildNodeCSS("text", style, (tokenID) =>
14-
selectable.project.colorTokens.resolve(tokenID)
15-
).self;
13+
const cssStyle = selectable.buildCSS().self;
1614
delete cssStyle.marginTop;
1715
delete cssStyle.marginRight;
1816
delete cssStyle.marginBottom;

packages/editor/src/views/viewport/renderer/NodeRenderer.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { createRef, useEffect, useRef } from "react";
22
import { observer } from "mobx-react-lite";
3-
import { Selectable, buildNodeCSS } from "@uimix/model/src/models";
3+
import { Selectable } from "@uimix/model/src/models";
44
import { viewportState } from "../../../state/ViewportState";
55
import { ComputedRectProvider } from "./ComputedRectProvider";
66
import { projectState } from "../../../state/ProjectState";
@@ -59,9 +59,7 @@ export const NodeRenderer: React.FC<{
5959
const style = selectable.style;
6060
const type = selectable.node.type;
6161

62-
const builtStyle = buildNodeCSS(type, style, (tokenID) =>
63-
selectable.project.colorTokens.resolve(tokenID)
64-
);
62+
const builtStyle = selectable.buildCSS();
6563

6664
const cssStyle: React.CSSProperties = {
6765
all: "revert",

packages/elements-react/.babelrc.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"sourceType": "unambiguous",
3+
"presets": [
4+
[
5+
"@babel/preset-env",
6+
{
7+
"targets": {
8+
"chrome": 100
9+
}
10+
}
11+
],
12+
"@babel/preset-typescript",
13+
"@babel/preset-react"
14+
],
15+
"plugins": []
16+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { StorybookConfig } from "@storybook/react-webpack5";
2+
const config: StorybookConfig = {
3+
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
4+
addons: [
5+
"@storybook/addon-links",
6+
"@storybook/addon-essentials",
7+
"@storybook/addon-interactions",
8+
],
9+
framework: {
10+
name: "@storybook/react-webpack5",
11+
options: {},
12+
},
13+
docs: {
14+
autodocs: "tag",
15+
},
16+
};
17+
export default config;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { Preview } from "@storybook/react";
2+
3+
const preview: Preview = {
4+
parameters: {
5+
actions: { argTypesRegex: "^on[A-Z].*" },
6+
controls: {
7+
matchers: {
8+
color: /(background|color)$/i,
9+
date: /Date$/,
10+
},
11+
},
12+
},
13+
};
14+
15+
export default preview;

packages/elements-react/package.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "@uimix/elements-react",
3+
"license": "MIT",
4+
"type": "module",
5+
"main": "src/index.ts",
6+
"scripts": {
7+
"lint": "eslint src",
8+
"storybook": "storybook dev -p 8008",
9+
"build-storybook": "storybook build"
10+
},
11+
"dependencies": {
12+
"csstype": "^3.1.2"
13+
},
14+
"devDependencies": {
15+
"@babel/preset-env": "^7.21.4",
16+
"@babel/preset-react": "^7.18.6",
17+
"@babel/preset-typescript": "^7.21.4",
18+
"@storybook/addon-essentials": "^7.0.7",
19+
"@storybook/addon-interactions": "^7.0.7",
20+
"@storybook/addon-links": "^7.0.7",
21+
"@storybook/blocks": "^7.0.7",
22+
"@storybook/react": "^7.0.7",
23+
"@storybook/react-webpack5": "^7.0.7",
24+
"@storybook/testing-library": "^0.0.14-next.2",
25+
"@types/react": "^18.0.38",
26+
"@types/react-dom": "^18.0.11",
27+
"prop-types": "^15.8.1",
28+
"react": "^18.2.0",
29+
"react-dom": "^18.2.0",
30+
"storybook": "^7.0.7",
31+
"typescript": "^5.0.4"
32+
}
33+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Box, Text } from "./Box";
2+
3+
export default {
4+
title: "Box",
5+
component: Box,
6+
};
7+
8+
export const Basic = () => {
9+
return <Box width={100} height={200} fills={[{ solid: "#c0ffee" }]} />;
10+
};
11+
12+
export const WithText = () => {
13+
return (
14+
<Box
15+
width={100}
16+
height={200}
17+
fills={[{ solid: "#c0ffee" }]}
18+
layout="flex"
19+
flexDirection="y"
20+
rowGap={20}
21+
paddingLeft={10}
22+
paddingRight={10}
23+
>
24+
<Box width={100} height={100} fills={[{ solid: "#f0f0f0" }]} />
25+
<Text fills={[{ solid: "#000" }]} textContent="Hello, world!" />
26+
<Text fills={[{ solid: "#666" }]} textContent="This is a text" />
27+
</Box>
28+
);
29+
};
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { ReactNode, useId } from "react";
2+
import {
3+
StyleProps,
4+
defaultStyle,
5+
SelfAndChildrenCSS,
6+
buildNodeCSS,
7+
} from "../style";
8+
9+
function kebabCase(str: string): string {
10+
return str.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
11+
}
12+
13+
export const Box: React.FC<
14+
Partial<StyleProps> & {
15+
children?: ReactNode;
16+
}
17+
> = (props) => {
18+
const styles = buildNodeCSS("frame", {
19+
...defaultStyle,
20+
...props,
21+
});
22+
23+
const id = useId().replaceAll(":", "_");
24+
const className = `box-${id}`;
25+
26+
return (
27+
<div className={className}>
28+
<Style targetClassName={className} styles={styles} />
29+
{props.children}
30+
</div>
31+
);
32+
};
33+
34+
export const Text: React.FC<
35+
Partial<StyleProps> & {
36+
children?: ReactNode;
37+
}
38+
> = (props) => {
39+
const styles = buildNodeCSS("text", {
40+
...defaultStyle,
41+
...props,
42+
});
43+
44+
const id = useId().replaceAll(":", "_");
45+
const className = `box-${id}`;
46+
47+
return (
48+
<div className={className}>
49+
<Style targetClassName={className} styles={styles} />
50+
{props.textContent}
51+
</div>
52+
);
53+
};
54+
55+
const Style: React.FC<{
56+
targetClassName: string;
57+
styles: SelfAndChildrenCSS;
58+
}> = ({ styles, targetClassName }) => {
59+
const cssBody = Object.entries(styles.self)
60+
.map(([key, value]) => {
61+
return `${kebabCase(key)}: ${String(value)};`;
62+
})
63+
.join(";");
64+
const childrenCSSBody = Object.entries(styles.children)
65+
.map(([key, value]) => {
66+
return `${kebabCase(key)}: ${String(value)};`;
67+
})
68+
.join(";");
69+
70+
const styleText = `.${targetClassName}{${cssBody}} .${targetClassName}>*{${childrenCSSBody}}`;
71+
72+
return <style>{styleText}</style>;
73+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./Box";

packages/elements-react/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./components";
2+
export * from "./style";

0 commit comments

Comments
 (0)