Skip to content

Commit f592616

Browse files
add react-native-svg support (incomplete)
1 parent 4a05757 commit f592616

9 files changed

Lines changed: 263 additions & 36 deletions

File tree

packages/builder-config/framework-reactnative/reactnative-config-svg.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ export type ReactNativeSvgConfig = RNSvgModuleConfig;
88
*/
99
interface RNSvgModuleConfig {
1010
module: "react-native-svg";
11+
prefer_mode: "svg-xml" | "svg-with-path";
1112
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,47 @@
11
# react-native-svg
2+
3+
## Mode - xml
4+
5+
**Example**:
6+
7+
```tsx
8+
import * as React from "react";
9+
import { SvgXml } from "react-native-svg";
10+
11+
const xml = `
12+
<svg width="32" height="32" viewBox="0 0 32 32">
13+
<path
14+
fill-rule="evenodd"
15+
clip-rule="evenodd"
16+
fill="url(#gradient)"
17+
d="M4 0C1.79086 0 0 1.79086 0 4V28C0 30.2091 1.79086 32 4 32H28C30.2091 32 32 30.2091 32 28V4C32 1.79086 30.2091 0 28 0H4ZM17 6C17 5.44772 17.4477 5 18 5H20C20.5523 5 21 5.44772 21 6V25C21 25.5523 20.5523 26 20 26H18C17.4477 26 17 25.5523 17 25V6ZM12 11C11.4477 11 11 11.4477 11 12V25C11 25.5523 11.4477 26 12 26H14C14.5523 26 15 25.5523 15 25V12C15 11.4477 14.5523 11 14 11H12ZM6 18C5.44772 18 5 18.4477 5 19V25C5 25.5523 5.44772 26 6 26H8C8.55228 26 9 25.5523 9 25V19C9 18.4477 8.55228 18 8 18H6ZM24 14C23.4477 14 23 14.4477 23 15V25C23 25.5523 23.4477 26 24 26H26C26.5523 26 27 25.5523 27 25V15C27 14.4477 26.5523 14 26 14H24Z"
18+
/>
19+
<defs>
20+
<linearGradient
21+
id="gradient"
22+
x1="0"
23+
y1="0"
24+
x2="8.46631"
25+
y2="37.3364"
26+
gradient-units="userSpaceOnUse">
27+
<stop offset="0" stop-color="#FEA267" />
28+
<stop offset="1" stop-color="#E75A4C" />
29+
</linearGradient>
30+
</defs>
31+
</svg>
32+
`;
33+
34+
export default () => <SvgXml xml={xml} width="100%" height="100%" />;
35+
```
36+
37+
### Mode - SvgWithPath `<Svg><Path d=""/></Svg>`
38+
39+
```tsx
40+
<Svg height="100" width="100">
41+
<Path
42+
d="M25 10 L98 65 L70 25 L16 77 L11 30 L0 4 L90 50 L50 10 L11 22 L77 95 L20 25"
43+
fill="none"
44+
stroke="red"
45+
/>
46+
</Svg>
47+
```
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./rn-svg-with-path";
2+
export * from "./rn-svg-xml";
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import {
2+
StylableJsxWidget,
3+
SvgElement,
4+
UnstylableJSXElementConfig,
5+
WidgetKey,
6+
} from "@web-builder/core";
7+
import { JSX, JSXAttribute, StringLiteral } from "coli";
8+
9+
export class SvgWithPathElement extends SvgElement {
10+
path({ fill }: { fill: string | false }) {
11+
return <StylableJsxWidget>{
12+
key: new WidgetKey(`${this.key.id}.svg-path`, "svg-path"),
13+
styleData: () => null,
14+
jsxConfig: () => {
15+
// from `import Svg, { Path } from "react-native-svg";`
16+
const _tag = JSX.identifier("Path");
17+
return {
18+
type: "static-tree",
19+
tree: JSX.tag("Path", {
20+
selfClosing: true,
21+
attributes: [
22+
fill &&
23+
new JSXAttribute("fill", new StringLiteral(fill || "current")),
24+
new JSXAttribute("d", new StringLiteral(this.data ?? "")),
25+
],
26+
}).make(),
27+
};
28+
},
29+
};
30+
}
31+
32+
private get childConfig() {
33+
// single "Path" element make by above `path` method
34+
return this.children[0].jsxConfig() as UnstylableJSXElementConfig;
35+
}
36+
37+
jsxConfig(): UnstylableJSXElementConfig {
38+
// from `import Svg from "react-native-svg";`
39+
const tree = JSX.tag("Svg", {
40+
children: [this.childConfig.tree],
41+
attributes: [
42+
new JSXAttribute("width", new StringLiteral("100%")),
43+
new JSXAttribute("height", new StringLiteral("100%")),
44+
],
45+
}).make();
46+
47+
return {
48+
type: "static-tree",
49+
tree: tree,
50+
};
51+
}
52+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import assert from "assert";
2+
import {
3+
JSX,
4+
JSXAttribute,
5+
JSXExpression,
6+
StringLiteral,
7+
TemplateLiteral,
8+
} from "coli";
9+
import {
10+
StylableJSXElementConfig,
11+
WidgetKey,
12+
k,
13+
UnstylableJSXElementConfig,
14+
} from "@web-builder/core";
15+
import * as css from "@web-builder/styles";
16+
import type { ViewStyle } from "react-native";
17+
import { SelfClosingContainer } from "../rn-widgets";
18+
19+
/**
20+
* Makes Svg element for react-native with `import { SvgXml } from 'react-native-svg';`
21+
*
22+
* dependency:
23+
* - [`react-native-svg`](https://github.com/react-native-svg/react-native-svg)
24+
*
25+
* ```tsx
26+
* // Example
27+
* import * as React from 'react';
28+
import { SvgXml } from 'react-native-svg';
29+
30+
const xml = `
31+
<svg width="32" height="32" viewBox="0 0 32 32">
32+
<path
33+
d="M4 0C1.79086 .... 26 14H24Z"
34+
/>
35+
</svg>
36+
`;
37+
38+
export default () => <SvgXml xml={xml} width="100%" height="100%" />;
39+
* ```
40+
*/
41+
export class SvgXmlElement extends SelfClosingContainer {
42+
_type = "rn-svg-xml";
43+
readonly xml: string;
44+
width: number;
45+
height: number;
46+
47+
constructor({
48+
key,
49+
xml,
50+
width,
51+
height,
52+
}: {
53+
key: WidgetKey;
54+
xml: string;
55+
width?: number;
56+
height?: number;
57+
}) {
58+
super({ key });
59+
assert(xml !== undefined, "SvgXmlElement requires xml data");
60+
this.xml = xml;
61+
this.width = width;
62+
this.height = height;
63+
}
64+
65+
styleData(): ViewStyle {
66+
return {
67+
...super.styleData(),
68+
width: css.px(this.width),
69+
height: css.px(this.height),
70+
};
71+
}
72+
73+
jsxConfig(): UnstylableJSXElementConfig {
74+
return <UnstylableJSXElementConfig>{
75+
type: "static-tree",
76+
// // `SvgXml` from `import { SvgXml } from 'react-native-svg';`
77+
tree: JSX.tag("SvgXml", {
78+
attributes: [
79+
new JSXAttribute("width", new StringLiteral("100%")),
80+
new JSXAttribute("height", new StringLiteral("100%")),
81+
new JSXAttribute(
82+
"xml",
83+
new JSXExpression(new TemplateLiteral(this.xml))
84+
),
85+
],
86+
}),
87+
};
88+
}
89+
}

packages/builder-react-native/rn-widgets/rn-container/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
StylableJSXElementConfig,
44
WidgetKey,
55
JsxWidget,
6+
UnstylableJSXElementConfig,
67
} from "@web-builder/core";
78
import {
89
Background,
@@ -92,9 +93,9 @@ export class Container
9293
};
9394
}
9495

95-
jsxConfig(): StylableJSXElementConfig {
96+
jsxConfig(): StylableJSXElementConfig | UnstylableJSXElementConfig {
9697
// TODO: add dependency loading
97-
return {
98+
return <StylableJSXElementConfig>{
9899
type: "tag-and-attr",
99100
tag: JSX.identifier("View"),
100101
};
Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,28 @@
1-
export class SvgElement {}
1+
import { reactnative } from "@designto/config";
2+
import { WidgetKey } from "@web-builder/core";
3+
import { SvgWithPathElement, SvgXmlElement } from "../../rn-svg";
4+
5+
export function SvgElement(
6+
p: {
7+
key: WidgetKey;
8+
width: number;
9+
height: number;
10+
data: string;
11+
fill;
12+
stroke?;
13+
},
14+
{
15+
config,
16+
}: {
17+
config: reactnative.ReactNativeSvgConfig;
18+
}
19+
) {
20+
switch (config.prefer_mode) {
21+
case "svg-with-path":
22+
return new SvgWithPathElement(p);
23+
case "svg-xml":
24+
return new SvgXmlElement({ ...p, xml: p.data });
25+
default:
26+
return new SvgWithPathElement(p);
27+
}
28+
}

packages/builder-web-core/widgets-native/html-svg/index.ts

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export class SvgElement extends StylableJsxWidget {
5353
*/
5454
readonly stroke?: Color;
5555

56-
readonly children;
56+
readonly children: StylableJsxWidget[];
5757

5858
constructor(p: {
5959
key: WidgetKey;
@@ -81,39 +81,40 @@ export class SvgElement extends StylableJsxWidget {
8181
this.children = this._init_children();
8282
}
8383

84-
private _init_children() {
85-
const path_with_fill = (fill: string | false) =>
86-
<StylableJsxWidget>{
87-
key: new WidgetKey(`${this.key.id}.svg-path`, "svg-path"),
88-
styleData: () => null,
89-
jsxConfig: () => {
90-
const _tag = JSX.identifier("path");
91-
return {
92-
tag: _tag,
93-
type: "tag-and-attr",
94-
attributes: [
95-
fill &&
96-
new JSXAttribute("fill", new StringLiteral(fill || "current")),
97-
new JSXAttribute("d", new StringLiteral(this.data ?? "")),
98-
],
99-
};
100-
},
101-
};
84+
path({ fill }: { fill: string | false }) {
85+
return <StylableJsxWidget>{
86+
key: new WidgetKey(`${this.key.id}.svg-path`, "svg-path"),
87+
styleData: () => null,
88+
jsxConfig: () => {
89+
const _tag = JSX.identifier("path");
90+
return {
91+
tag: _tag,
92+
type: "tag-and-attr",
93+
attributes: [
94+
fill &&
95+
new JSXAttribute("fill", new StringLiteral(fill || "current")),
96+
new JSXAttribute("d", new StringLiteral(this.data ?? "")),
97+
],
98+
};
99+
},
100+
};
101+
}
102102

103+
private _init_children() {
103104
if (!this.fill) {
104-
return [path_with_fill("transparent")];
105+
return [this.path({ fill: "transparent" })];
105106
}
106107

107108
if (Array.isArray(this.fill)) {
108109
console.error("multiple fills for svg path is not supported.");
109110
} else {
110111
switch (this.fill.type) {
111112
case "solid-color": {
112-
return [path_with_fill(css.color(this.fill as Color))];
113+
return [this.path({ fill: css.color(this.fill as Color) })];
113114
}
114115
case "graphics": {
115116
console.error("graphics fill for svg not supported.");
116-
return [path_with_fill("black")];
117+
return [this.path({ fill: "black" })];
117118
}
118119
case "gradient": {
119120
switch (this.fill._type) {
@@ -168,11 +169,11 @@ export class SvgElement extends StylableJsxWidget {
168169
},
169170
};
170171

171-
return [fill, path_with_fill(`url(#${fillid})`)];
172+
return [fill, this.path({ fill: `url(#${fillid})` })];
172173
}
173174
default: {
174175
console.error("unsupported gradient type for svg path.");
175-
return [path_with_fill("black")];
176+
return [this.path({ fill: "black" })];
176177
}
177178
}
178179
}
@@ -199,8 +200,8 @@ export class SvgElement extends StylableJsxWidget {
199200
};
200201
}
201202

202-
jsxConfig(): StylableJSXElementConfig {
203-
return {
203+
jsxConfig(): StylableJSXElementConfig | UnstylableJSXElementConfig {
204+
return <StylableJSXElementConfig>{
204205
type: "tag-and-attr",
205206
tag: JSX.identifier("svg"),
206207
attributes: [

packages/designto-react-native/tokens-to-rn-widget/index.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -161,13 +161,21 @@ function compose<T extends JsxWidget>(
161161
elementPreference: widget.element_preference_experimental,
162162
});
163163
} else if (widget instanceof core.VectorWidget) {
164-
// FIXME: add svg support
165-
// thisRnWidget = new rn.SvgElement({
166-
// ...widget,
167-
// data: widget.data,
168-
// fill: widget.fill,
169-
// key: _key,
170-
// });
164+
thisRnWidget = rn.SvgElement(
165+
{
166+
width: widget.width,
167+
height: widget.height,
168+
data: widget.data,
169+
fill: widget.fill,
170+
key: _key,
171+
},
172+
{
173+
config: {
174+
module: "react-native-svg",
175+
prefer_mode: "svg-with-path",
176+
},
177+
}
178+
);
171179
} else if (widget instanceof core.ImageWidget) {
172180
thisRnWidget = new rn.ImageElement({
173181
...widget,

0 commit comments

Comments
 (0)