@blinkorb/rcx 0.0.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.
Files changed (66) hide show
  1. package/.eslintignore +4 -0
  2. package/.eslintrc.json +286 -0
  3. package/.gitattributes +2 -0
  4. package/.github/CODEOWNERS +1 -0
  5. package/.github/workflows/ci.yml +19 -0
  6. package/.nvmrc +1 -0
  7. package/.prettierignore +28 -0
  8. package/.prettierrc.json +4 -0
  9. package/demo/index.html +29 -0
  10. package/demo/index.tsx +316 -0
  11. package/demo/tsconfig.json +12 -0
  12. package/jest.config.ts +21 -0
  13. package/package.json +80 -0
  14. package/scripts/prep-package.js +29 -0
  15. package/src/components/canvas/context.ts +6 -0
  16. package/src/components/canvas/index.ts +98 -0
  17. package/src/components/index.ts +5 -0
  18. package/src/components/paths/arc-to.ts +66 -0
  19. package/src/components/paths/clip.ts +32 -0
  20. package/src/components/paths/index.ts +5 -0
  21. package/src/components/paths/line.ts +53 -0
  22. package/src/components/paths/path.ts +59 -0
  23. package/src/components/paths/point.ts +24 -0
  24. package/src/components/shapes/circle.tsx +32 -0
  25. package/src/components/shapes/ellipse.ts +75 -0
  26. package/src/components/shapes/index.ts +3 -0
  27. package/src/components/shapes/rectangle.ts +45 -0
  28. package/src/components/text/index.ts +1 -0
  29. package/src/components/text/text.ts +137 -0
  30. package/src/components/transform/index.ts +3 -0
  31. package/src/components/transform/rotate.ts +26 -0
  32. package/src/components/transform/scale.ts +34 -0
  33. package/src/components/transform/translate.ts +27 -0
  34. package/src/context/create-context.ts +49 -0
  35. package/src/context/index.ts +1 -0
  36. package/src/hooks/index.ts +8 -0
  37. package/src/hooks/use-canvas-context.ts +11 -0
  38. package/src/hooks/use-linear-gradient.ts +39 -0
  39. package/src/hooks/use-loop.ts +11 -0
  40. package/src/hooks/use-on.ts +18 -0
  41. package/src/hooks/use-radial-gradient.ts +45 -0
  42. package/src/hooks/use-render.ts +14 -0
  43. package/src/hooks/use-state.ts +9 -0
  44. package/src/hooks/use-window-size.ts +24 -0
  45. package/src/index.ts +6 -0
  46. package/src/internal/emitter.ts +39 -0
  47. package/src/internal/global.ts +5 -0
  48. package/src/internal/hooks.ts +32 -0
  49. package/src/internal/reactive.test.ts +20 -0
  50. package/src/internal/reactive.ts +20 -0
  51. package/src/jsx-runtime.ts +21 -0
  52. package/src/render.ts +299 -0
  53. package/src/types.ts +151 -0
  54. package/src/utils/apply-fill-and-stroke-style.ts +33 -0
  55. package/src/utils/get-recommended-pixel-ratio.ts +2 -0
  56. package/src/utils/index.ts +8 -0
  57. package/src/utils/is-own-property-of.ts +6 -0
  58. package/src/utils/is-valid-fill-or-stroke-style.ts +5 -0
  59. package/src/utils/is-valid-stroke-cap.ts +10 -0
  60. package/src/utils/is-valid-stroke-join.ts +10 -0
  61. package/src/utils/resolve-styles.ts +21 -0
  62. package/src/utils/type-guards.ts +4 -0
  63. package/src/utils/with-px.ts +4 -0
  64. package/tsb.config.ts +11 -0
  65. package/tsconfig.dist.json +13 -0
  66. package/tsconfig.json +25 -0
package/.eslintignore ADDED
@@ -0,0 +1,4 @@
1
+ /node_modules/*
2
+ /coverage/*
3
+ /build/*
4
+ /dist/*
package/.eslintrc.json ADDED
@@ -0,0 +1,286 @@
1
+ {
2
+ "extends": [
3
+ "eslint:recommended",
4
+ "plugin:prettier/recommended",
5
+ "plugin:@typescript-eslint/eslint-recommended",
6
+ "plugin:@typescript-eslint/recommended"
7
+ ],
8
+ "plugins": ["prettier", "simple-import-sort"],
9
+ "env": {
10
+ "es6": true,
11
+ "commonjs": true
12
+ },
13
+ "parser": "@typescript-eslint/parser",
14
+ "parserOptions": {
15
+ "sourceType": "module",
16
+ "ecmaVersion": 9
17
+ },
18
+ "globals": {
19
+ "window": false,
20
+ "performance": false,
21
+ "document": false,
22
+ "navigator": false,
23
+ "location": false,
24
+ "console": false,
25
+ "setTimeout": false,
26
+ "clearTimeout": false,
27
+ "setInterval": false,
28
+ "clearInterval": false,
29
+ "alert": false,
30
+ "requestAnimationFrame": false,
31
+ "cancelAnimationFrame": false,
32
+ "localStorage": false,
33
+ "sessionStorage": false,
34
+ "FormData": false,
35
+ "Node": false,
36
+ "Image": false,
37
+ "CanvasRenderingContext2D": false
38
+ },
39
+ "rules": {
40
+ "strict": [2, "global"],
41
+ "no-duplicate-imports": [2, { "includeExports": true }],
42
+
43
+ "eqeqeq": 2,
44
+ "block-scoped-var": 2,
45
+ "no-constant-condition": 2,
46
+ "no-console": 2,
47
+ "no-debugger": 2,
48
+ "no-lonely-if": 2,
49
+ "no-lone-blocks": 2,
50
+ "no-nested-ternary": 2,
51
+ "no-dupe-keys": 2,
52
+ "no-extra-boolean-cast": 2,
53
+ "no-irregular-whitespace": 2,
54
+ "no-else-return": 2,
55
+ "no-eval": 2,
56
+ "no-multi-str": 2,
57
+ "no-self-compare": 2,
58
+ "no-useless-call": 2,
59
+ "no-shadow-restricted-names": 2,
60
+ "no-shadow": 0,
61
+ "no-undef": 2,
62
+ "no-undef-init": 2,
63
+ "no-unreachable": 2,
64
+ "no-unused-vars": [2, { "varsIgnorePattern": "^_\\w" }],
65
+ "no-use-before-define": 2,
66
+
67
+ "radix": 2,
68
+ "curly": 2,
69
+ "no-fallthrough": 2,
70
+ "default-case": 2,
71
+
72
+ "no-var": 2,
73
+ "no-unused-expressions": 2,
74
+ "camelcase": [
75
+ 2,
76
+ {
77
+ "properties": "always",
78
+ "allow": ["^UNSAFE_", "call_id"]
79
+ }
80
+ ],
81
+ "@typescript-eslint/no-explicit-any": 2,
82
+ "@typescript-eslint/explicit-function-return-type": 0,
83
+ "@typescript-eslint/member-delimiter-style": 0,
84
+ "@typescript-eslint/type-annotation-spacing": 0,
85
+ "@typescript-eslint/no-unused-vars": 2,
86
+ "@typescript-eslint/no-use-before-define": 2,
87
+ "@typescript-eslint/no-shadow": 2,
88
+ "no-restricted-globals": [
89
+ "error",
90
+ "URL",
91
+ "history",
92
+ "dispatchEvent",
93
+ "require",
94
+ "postMessage",
95
+ "blur",
96
+ "focus",
97
+ "close",
98
+ "frames",
99
+ "self",
100
+ "parent",
101
+ "opener",
102
+ "top",
103
+ "length",
104
+ "closed",
105
+ "location",
106
+ "origin",
107
+ "name",
108
+ "locationbar",
109
+ "menubar",
110
+ "personalbar",
111
+ "scrollbars",
112
+ "statusbar",
113
+ "toolbar",
114
+ "status",
115
+ "frameElement",
116
+ "navigator",
117
+ "customElements",
118
+ "external",
119
+ "screen",
120
+ "innerWidth",
121
+ "innerHeight",
122
+ "scrollX",
123
+ "pageXOffset",
124
+ "scrollY",
125
+ "pageYOffset",
126
+ "screenX",
127
+ "screenY",
128
+ "outerWidth",
129
+ "outerHeight",
130
+ "devicePixelRatio",
131
+ "clientInformation",
132
+ "screenLeft",
133
+ "screenTop",
134
+ "defaultStatus",
135
+ "defaultstatus",
136
+ "styleMedia",
137
+ "onanimationend",
138
+ "onanimationiteration",
139
+ "onanimationstart",
140
+ "onsearch",
141
+ "ontransitionend",
142
+ "onwebkitanimationend",
143
+ "onwebkitanimationiteration",
144
+ "onwebkitanimationstart",
145
+ "onwebkittransitionend",
146
+ "isSecureContext",
147
+ "onabort",
148
+ "onblur",
149
+ "oncancel",
150
+ "oncanplay",
151
+ "oncanplaythrough",
152
+ "onchange",
153
+ "onclick",
154
+ "onclose",
155
+ "oncontextmenu",
156
+ "oncuechange",
157
+ "ondblclick",
158
+ "ondrag",
159
+ "ondragend",
160
+ "ondragenter",
161
+ "ondragleave",
162
+ "ondragover",
163
+ "ondragstart",
164
+ "ondrop",
165
+ "ondurationchange",
166
+ "onemptied",
167
+ "onended",
168
+ "onerror",
169
+ "onfocus",
170
+ "oninput",
171
+ "oninvalid",
172
+ "onkeydown",
173
+ "onkeypress",
174
+ "onkeyup",
175
+ "onload",
176
+ "onloadeddata",
177
+ "onloadedmetadata",
178
+ "onloadstart",
179
+ "onmousedown",
180
+ "onmouseenter",
181
+ "onmouseleave",
182
+ "onmousemove",
183
+ "onmouseout",
184
+ "onmouseover",
185
+ "onmouseup",
186
+ "onmousewheel",
187
+ "onpause",
188
+ "onplay",
189
+ "onplaying",
190
+ "onprogress",
191
+ "onratechange",
192
+ "onreset",
193
+ "onresize",
194
+ "onscroll",
195
+ "onseeked",
196
+ "onseeking",
197
+ "onselect",
198
+ "onstalled",
199
+ "onsubmit",
200
+ "onsuspend",
201
+ "ontimeupdate",
202
+ "ontoggle",
203
+ "onvolumechange",
204
+ "onwaiting",
205
+ "onwheel",
206
+ "onauxclick",
207
+ "ongotpointercapture",
208
+ "onlostpointercapture",
209
+ "onpointerdown",
210
+ "onpointermove",
211
+ "onpointerup",
212
+ "onpointercancel",
213
+ "onpointerover",
214
+ "onpointerout",
215
+ "onpointerenter",
216
+ "onpointerleave",
217
+ "onafterprint",
218
+ "onbeforeprint",
219
+ "onbeforeunload",
220
+ "onhashchange",
221
+ "onlanguagechange",
222
+ "onmessage",
223
+ "onmessageerror",
224
+ "onoffline",
225
+ "ononline",
226
+ "onpagehide",
227
+ "onpageshow",
228
+ "onpopstate",
229
+ "onrejectionhandled",
230
+ "onstorage",
231
+ "onunhandledrejection",
232
+ "onunload",
233
+ "performance",
234
+ "stop",
235
+ "open",
236
+ "print",
237
+ "captureEvents",
238
+ "releaseEvents",
239
+ "getComputedStyle",
240
+ "matchMedia",
241
+ "moveTo",
242
+ "moveBy",
243
+ "resizeTo",
244
+ "resizeBy",
245
+ "getSelection",
246
+ "find",
247
+ "createImageBitmap",
248
+ "scroll",
249
+ "scrollTo",
250
+ "scrollBy",
251
+ "onappinstalled",
252
+ "onbeforeinstallprompt",
253
+ "crypto",
254
+ "ondevicemotion",
255
+ "ondeviceorientation",
256
+ "ondeviceorientationabsolute",
257
+ "indexedDB",
258
+ "webkitStorageInfo",
259
+ "chrome",
260
+ "visualViewport",
261
+ "speechSynthesis",
262
+ "webkitRequestFileSystem",
263
+ "webkitResolveLocalFileSystemURL",
264
+ "openDatabase",
265
+ "setTimeout",
266
+ "clearTimeout",
267
+ "setInterval",
268
+ "clearInterval",
269
+ "requestAnimationFrame",
270
+ "cancelAnimationFrame",
271
+ "addEventListener",
272
+ "removeEventListener"
273
+ ],
274
+ "simple-import-sort/imports": "error"
275
+ },
276
+ "overrides": [
277
+ {
278
+ "files": ["*.ts", "*.tsx"],
279
+ "rules": {
280
+ "no-undef": 0,
281
+ "no-unused-vars": 0,
282
+ "no-use-before-define": 0
283
+ }
284
+ }
285
+ ]
286
+ }
package/.gitattributes ADDED
@@ -0,0 +1,2 @@
1
+ # Auto detect text files and perform LF normalization
2
+ * text=auto
@@ -0,0 +1 @@
1
+ * @jakesidsmith
@@ -0,0 +1,19 @@
1
+ name: CI
2
+ on: [pull_request]
3
+
4
+ jobs:
5
+ test:
6
+ runs-on: ubuntu-latest
7
+ steps:
8
+ - uses: actions/checkout@v4
9
+ - uses: actions/cache@v4
10
+ with:
11
+ path: ~/.npm
12
+ key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
13
+ - uses: actions/setup-node@v4
14
+ with:
15
+ node-version-file: '.nvmrc'
16
+ - name: Install dependencies
17
+ run: npm ci
18
+ - name: Run tests
19
+ run: npm test
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 22.14.0
@@ -0,0 +1,28 @@
1
+ /coverage/*
2
+ /build/*
3
+ /dist/*
4
+ /packages/*/coverage/*
5
+ /packages/*/static/*
6
+ /packages/*/server/*
7
+ /packages/*/build/*
8
+ /packages/*/dist/*
9
+ /.vscode/*
10
+ **/*.txt
11
+ **/*.snap
12
+ *.toml
13
+ *.png
14
+ *.svg
15
+ *.jpg
16
+ *.jpeg
17
+ *.gif
18
+
19
+ .DS_Store
20
+ .eslintignore
21
+ .prettierignore
22
+ .gitignore
23
+ .gitattributes
24
+ .npmignore
25
+ .nvmrc
26
+ .browserslistrc
27
+ .env
28
+ LICENSE.txt
@@ -0,0 +1,4 @@
1
+ {
2
+ "singleQuote": true,
3
+ "trailingComma": "es5"
4
+ }
@@ -0,0 +1,29 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta
6
+ name="viewport"
7
+ content="width=device-width, initial-scale=1, maximum-scale=1"
8
+ />
9
+ <title>
10
+ RCX - Reactive JSX-based library for creating HTML5 canvas applications
11
+ </title>
12
+ <style type="text/css">
13
+ html,
14
+ body {
15
+ margin: 0;
16
+ padding: 0;
17
+ width: 100%;
18
+ height: 100%;
19
+ overflow: hidden;
20
+ }
21
+
22
+ canvas {
23
+ width: 100%;
24
+ height: 100%;
25
+ }
26
+ </style>
27
+ </head>
28
+ <body></body>
29
+ </html>
package/demo/index.tsx ADDED
@@ -0,0 +1,316 @@
1
+ import {
2
+ ArcTo,
3
+ Canvas,
4
+ Circle,
5
+ Clip,
6
+ Ellipse,
7
+ Line,
8
+ Path,
9
+ Point,
10
+ RCXComponent,
11
+ Rectangle,
12
+ render,
13
+ Rotate,
14
+ Scale,
15
+ Text,
16
+ Translate,
17
+ useCanvasContext,
18
+ useLinearGradient,
19
+ useLoop,
20
+ useOnMount,
21
+ useRadialGradient,
22
+ useReactive,
23
+ } from '@blinkorb/rcx';
24
+
25
+ const RendersChildren: RCXComponent = ({ children }) => children;
26
+
27
+ const Unmounts: RCXComponent = () => {
28
+ // eslint-disable-next-line no-console
29
+ console.log('rendered');
30
+
31
+ useOnMount(() => {
32
+ // eslint-disable-next-line no-console
33
+ console.log('mounted');
34
+
35
+ return () => {
36
+ // eslint-disable-next-line no-console
37
+ console.log('unmounted');
38
+ };
39
+ });
40
+
41
+ return (
42
+ <Rectangle x={0} y={0} width={10} height={10} style={{ fill: 'black' }} />
43
+ );
44
+ };
45
+
46
+ const Gradients: RCXComponent = () => {
47
+ const stroke = useLinearGradient({
48
+ startX: 10,
49
+ startY: 480,
50
+ endX: 10 + 50,
51
+ endY: 480 + 50,
52
+ stops: [
53
+ {
54
+ offset: 0,
55
+ color: '#f00',
56
+ },
57
+ {
58
+ offset: 1,
59
+ color: '#000',
60
+ },
61
+ ],
62
+ });
63
+
64
+ const fill = useRadialGradient({
65
+ startX: 10 + 25,
66
+ startY: 480 + 25,
67
+ startRadius: 0,
68
+ endX: 10 + 25,
69
+ endY: 480 + 25,
70
+ endRadius: 25,
71
+ stops: [
72
+ {
73
+ offset: 0,
74
+ color: '#000',
75
+ },
76
+ {
77
+ offset: 1,
78
+ color: 'cyan',
79
+ },
80
+ ],
81
+ });
82
+
83
+ return (
84
+ <Rectangle
85
+ x={10}
86
+ y={480}
87
+ width={50}
88
+ height={50}
89
+ style={{ fill, stroke, strokeWidth: 5, strokeCap: 'round' }}
90
+ />
91
+ );
92
+ };
93
+
94
+ const Page: RCXComponent = () => {
95
+ const canvasContext = useCanvasContext();
96
+ const getOffset = () => Math.cos(Date.now() * 0.001) * 10;
97
+ const reactive = useReactive({ isMounted: true, offset: getOffset() });
98
+
99
+ useLoop(() => {
100
+ reactive.offset = getOffset();
101
+ });
102
+
103
+ useOnMount(() => {
104
+ window.setTimeout(() => {
105
+ reactive.isMounted = false;
106
+ }, 1000);
107
+ });
108
+
109
+ return (
110
+ <>
111
+ <Rectangle
112
+ x={canvasContext.width * 0.25}
113
+ y={canvasContext.height * 0.25 + reactive.offset}
114
+ width={canvasContext.width * 0.5}
115
+ height={canvasContext.height * 0.5}
116
+ style={{ fill: 'red' }}
117
+ />
118
+ <Ellipse
119
+ x={canvasContext.width * 0.5}
120
+ y={canvasContext.height * 0.5}
121
+ radiusX={canvasContext.width * 0.2}
122
+ radiusY={canvasContext.width * 0.1}
123
+ style={{ fill: 'black' }}
124
+ rotation={((Date.now() % 5000) / 5000) * Math.PI * 2}
125
+ />
126
+ <Circle
127
+ x={canvasContext.width * 0.5}
128
+ y={canvasContext.height * 0.5}
129
+ radius={canvasContext.width * 0.05}
130
+ endAngle={
131
+ Math.PI +
132
+ Math.cos(((Date.now() % 5000) / 5000) * Math.PI * 2) * Math.PI
133
+ }
134
+ style={{ fill: 'white' }}
135
+ >
136
+ <Point x={canvasContext.width * 0.5} y={canvasContext.height * 0.5} />
137
+ </Circle>
138
+ <Line
139
+ startX={10}
140
+ startY={10}
141
+ endX={20}
142
+ endY={20}
143
+ style={{ stroke: 'green', strokeWidth: 5, strokeJoin: 'bevel' }}
144
+ >
145
+ <Point x={30} y={10} />
146
+ </Line>
147
+ <Path
148
+ points={[
149
+ [0, 20],
150
+ [5, 50],
151
+ [10, 0],
152
+ [15, 20],
153
+ [20, 5],
154
+ ]}
155
+ style={{ stroke: 'black' }}
156
+ />
157
+ <Path style={{ stroke: 'black' }} closePath>
158
+ <Point x={0} y={50} />
159
+ <Point x={5} y={55} />
160
+ <Point x={10} y={50} />
161
+ <Point x={15} y={55} />
162
+ <Point x={20} y={50} />
163
+ <Point x={20} y={75} />
164
+ <Point x={0} y={75} />
165
+ </Path>
166
+ <Path style={{ stroke: 'blue', strokeWidth: 5, strokeCap: 'round' }}>
167
+ <Point x={200} y={200} />
168
+ <ArcTo
169
+ startControlX={200}
170
+ startControlY={100}
171
+ endControlX={300}
172
+ endControlY={100}
173
+ radius={50}
174
+ />
175
+ <Point x={300} y={100} />
176
+ </Path>
177
+ <Text x={305} y={102} style={{ fill: 'red', stroke: 'black' }}>
178
+ The offset is {reactive.offset.toFixed(2)} {[1, 2, 3].map((n) => n)}{' '}
179
+ <RendersChildren>Children</RendersChildren>
180
+ </Text>
181
+ <Translate x={100} y={100}>
182
+ <Rotate rotation={Math.PI * 0.25}>
183
+ <Scale scale={0.5}>
184
+ <Rectangle
185
+ x={0}
186
+ y={0}
187
+ width={100}
188
+ height={50}
189
+ style={{ fill: 'cyan' }}
190
+ />
191
+ </Scale>
192
+ </Rotate>
193
+ </Translate>
194
+ <Rectangle x={0} y={100} width={50} height={50}>
195
+ <Clip>
196
+ <Circle x={25} y={125} radius={30} style={{ fill: '#d5d5d5' }} />
197
+ <Text x={10} y={125} style={{ fill: 'red' }}>
198
+ This text is clipped by a rectangle
199
+ </Text>
200
+ </Clip>
201
+ </Rectangle>
202
+
203
+ <Line
204
+ startX={100}
205
+ endX={100}
206
+ startY={190}
207
+ endY={290}
208
+ style={{ stroke: '#d5d5d5' }}
209
+ />
210
+ <Text x={100} y={200} style={{ fill: 'black' }}>
211
+ Left (default)
212
+ </Text>
213
+ <Text x={100} y={220} style={{ fill: 'black', align: 'right' }}>
214
+ Right
215
+ </Text>
216
+ <Text x={100} y={240} style={{ fill: 'black', align: 'center' }}>
217
+ Center
218
+ </Text>
219
+ <Text x={100} y={260} style={{ fill: 'black', align: 'start' }}>
220
+ Start
221
+ </Text>
222
+ <Text x={100} y={280} style={{ fill: 'black', align: 'end' }}>
223
+ End
224
+ </Text>
225
+
226
+ <Line
227
+ startX={50}
228
+ endX={150}
229
+ startY={300}
230
+ endY={300}
231
+ style={{ stroke: '#d5d5d5' }}
232
+ />
233
+ <Text x={100} y={300} style={{ fill: 'black', baseline: 'alphabetic' }}>
234
+ Alphabetic (default)
235
+ </Text>
236
+ <Line
237
+ startX={50}
238
+ endX={150}
239
+ startY={320}
240
+ endY={320}
241
+ style={{ stroke: '#d5d5d5' }}
242
+ />
243
+ <Text x={100} y={320} style={{ fill: 'black', baseline: 'bottom' }}>
244
+ Bottom
245
+ </Text>
246
+ <Line
247
+ startX={50}
248
+ endX={150}
249
+ startY={340}
250
+ endY={340}
251
+ style={{ stroke: '#d5d5d5' }}
252
+ />
253
+ <Text x={100} y={340} style={{ fill: 'black', baseline: 'hanging' }}>
254
+ Hanging
255
+ </Text>
256
+ <Line
257
+ startX={50}
258
+ endX={150}
259
+ startY={360}
260
+ endY={360}
261
+ style={{ stroke: '#d5d5d5' }}
262
+ />
263
+ <Text x={100} y={360} style={{ fill: 'black', baseline: 'ideographic' }}>
264
+ Ideographic
265
+ </Text>
266
+ <Line
267
+ startX={50}
268
+ endX={150}
269
+ startY={380}
270
+ endY={380}
271
+ style={{ stroke: '#d5d5d5' }}
272
+ />
273
+ <Text x={100} y={380} style={{ fill: 'black', baseline: 'middle' }}>
274
+ Middle
275
+ </Text>
276
+ <Line
277
+ startX={50}
278
+ endX={150}
279
+ startY={400}
280
+ endY={400}
281
+ style={{ stroke: '#d5d5d5' }}
282
+ />
283
+ <Text x={100} y={400} style={{ fill: 'black', baseline: 'top' }}>
284
+ Top
285
+ </Text>
286
+ <Text
287
+ x={10}
288
+ y={450}
289
+ style={{
290
+ fill: 'black',
291
+ fontSize: 20,
292
+ fontFamily: 'serif',
293
+ fontWeight: 'bold',
294
+ fontStyle: 'italic',
295
+ fontVariant: 'small-caps',
296
+ }}
297
+ >
298
+ Styled font
299
+ </Text>
300
+ <Gradients />
301
+ {reactive.isMounted && <Unmounts />}
302
+ </>
303
+ );
304
+ };
305
+
306
+ Page.displayName = 'Page';
307
+
308
+ const App = () => {
309
+ return (
310
+ <Canvas>
311
+ <Page />
312
+ </Canvas>
313
+ );
314
+ };
315
+
316
+ render(<App />, document.body);