",
8 | "license": "MIT",
9 | "scripts": {
10 | "start": "react-unity-scripts start",
11 | "build": "react-unity-scripts build",
12 | "lint": "react-unity-scripts lint",
13 | "clean": "react-unity-scripts clean",
14 | "postyalc": "npm i",
15 | "test": "react-unity-scripts start --test"
16 | },
17 | "dependencies": {
18 | "@emotion/css": "^11.13.4",
19 | "@emotion/react": "^11.13.3",
20 | "@emotion/styled": "^11.13.0",
21 | "@reactunity/material": "^0.19.0",
22 | "@reactunity/renderer": "^0.19.1",
23 | "@reduxjs/toolkit": "^2.3.0",
24 | "@tanstack/react-query": "^5.60.5",
25 | "axios": "^1.7.7",
26 | "bootstrap": "^5.3.3",
27 | "clsx": "^2.1.1",
28 | "history": "^5.3.0",
29 | "react": "^18.3.1",
30 | "react-bootstrap": "^2.10.5",
31 | "react-icons": "^5.3.0",
32 | "react-jss": "^10.10.0",
33 | "react-redux": "^9.1.2",
34 | "react-router": "^6.28.0",
35 | "react-unity-webgl": "^9.6.0",
36 | "redux": "^5.0.1",
37 | "redux-persist": "^6.0.0",
38 | "styled-components": "^6.1.13",
39 | "stylis": "^4.3.4",
40 | "twin.macro": "^3.4.1"
41 | },
42 | "devDependencies": {
43 | "@reactunity/previewer": "^0.18.0",
44 | "@reactunity/scripts": "^0.19.2",
45 | "@types/react-redux": "^7.1.34",
46 | "babel-plugin-twin": "^1.1.0",
47 | "tailwindcss": "^3.4.15",
48 | "typescript": "^5.6.3"
49 | },
50 | "babelMacros": {
51 | "twin": {
52 | "preset": "styled-components",
53 | "styled": {
54 | "import": "default",
55 | "from": "styled-components"
56 | },
57 | "css": {
58 | "import": "css",
59 | "from": "styled-components"
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/react/public/html-test.html:
--------------------------------------------------------------------------------
1 |
25 |
26 |
32 |
33 |
34 |
35 | Hello
36 | Dear world
37 |
38 |
41 |
42 |
43 |
44 | more
45 |
46 |
--------------------------------------------------------------------------------
/react/src/app/index.module.scss:
--------------------------------------------------------------------------------
1 | .host {
2 | flex-direction: row;
3 | align-items: stretch;
4 | height: 100%;
5 | overflow: hidden;
6 | background-color: #f0f0f0;
7 | color: black;
8 | }
9 |
10 | .sidepanel {
11 | padding: 20px;
12 | width: 200px;
13 | flex: 0 0 auto;
14 | flex-direction: column;
15 | align-items: stretch;
16 | background-color: #dedede;
17 |
18 | >button {
19 | margin-bottom: 12px;
20 | background-color: white;
21 | color: black;
22 | padding: 6px 10px;
23 | }
24 | }
25 |
26 | .scroll {
27 | flex: 1 1 0;
28 | flex-direction: column;
29 | }
30 |
31 | .content {
32 | padding: 40px;
33 | flex-direction: column;
34 | align-items: stretch;
35 | flex-shrink: 0;
36 |
37 | max-width: 960px;
38 | margin: 0 auto;
39 | width: 100%;
40 | }
41 |
42 | // Global styles
43 |
44 | h1 {
45 | font-size: 36px;
46 | font-style: smallcaps, bold;
47 | color: #582a9c;
48 | margin-bottom: 20px;
49 | }
50 |
51 | *+h1,
52 | *+* h1 {
53 | margin-top: 24px;
54 | }
55 |
56 | h2 {
57 | font-size: 30px;
58 | font-style: smallcaps;
59 | color: #fb2f8e;
60 | margin-bottom: 20px;
61 | }
62 |
63 | *+h2,
64 | *+* h2 {
65 | margin-top: 16px;
66 | }
67 |
68 | section {
69 | margin-top: 10px;
70 | margin-bottom: 10px;
71 | }
72 |
73 | row {
74 | flex-direction: row;
75 | align-items: center;
76 | }
77 |
78 | column {
79 | flex-direction: column;
80 | align-items: center;
81 | flex-grow: 1;
82 | flex-shrink: 0;
83 | }
84 |
--------------------------------------------------------------------------------
/react/src/app/index.tsx:
--------------------------------------------------------------------------------
1 | import { render } from '@reactunity/renderer';
2 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
3 | import { Suspense } from 'react';
4 | import { MemoryRouter, useNavigate } from 'react-router';
5 | import styles from './index.module.scss';
6 | import { AppRoutes } from './routes';
7 |
8 | const queryClient = new QueryClient();
9 |
10 | function App() {
11 | const nav = useNavigate();
12 |
13 | return
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | ;
34 | }
35 |
36 | render(
37 | Loading}>
38 |
42 |
43 |
44 |
45 |
46 |
47 | );
48 |
--------------------------------------------------------------------------------
/react/src/app/routes.tsx:
--------------------------------------------------------------------------------
1 | import React, { Suspense } from 'react';
2 | import { Route, Routes } from 'react-router';
3 | import AnimationsPage from 'src/pages/animations';
4 | import BgPatternsPage from 'src/pages/bg-patterns';
5 | import HomePage from 'src/pages/home';
6 | import ImagesPage from 'src/pages/images';
7 | import InteropPage from 'src/pages/interop';
8 | import MaterialPage from 'src/pages/material';
9 | import { QueryPage } from 'src/pages/query/QueryPage';
10 | import { Redux } from 'src/pages/redux';
11 | import StyleFrameworksPage from 'src/pages/style-frameworks';
12 | import BootstrapPage from 'src/pages/style-frameworks/bootstrap';
13 | import EmotionPage from 'src/pages/style-frameworks/emotion';
14 | import JSSPage from 'src/pages/style-frameworks/jss';
15 | import StyledComponentsPage from 'src/pages/style-frameworks/styled-components';
16 | import SvgsPage from 'src/pages/svgs';
17 | import TodoPage from 'src/pages/todo';
18 |
19 | const TailwindPage = React.lazy(() => import('src/pages/style-frameworks/tailwind'));
20 |
21 | export function AppRoutes() {
22 | return
23 | } />
24 | } />
25 | } />
26 | } />
27 | } />
28 | } />
29 | } />
30 | } />
31 | } />
32 | } />
33 |
34 | }>
35 | } />
36 | } />
37 | } />
38 | } />
39 | Loading>}>} />
41 |
42 | ;
43 | }
44 |
--------------------------------------------------------------------------------
/react/src/assets/base64Image.txt:
--------------------------------------------------------------------------------
1 | data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAAcElEQVR4AezOsREAIAjFUAs3dgAGYkj8C3g2oBTJXfo3iD419dJ2eOn5EuM6LnsFynQkb4AKQP2zlBOLpAEBAgQIECBAgAA1Au29p6aDrlPBQd3UdJA7yIcUho4KA5UBBxDHk9GwzwdiGQY6gVEwCgC3bcAZ+oXojwAAAABJRU5ErkJggg==
2 |
--------------------------------------------------------------------------------
/react/src/assets/bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReactUnity/full-sample/20145583d09ab3c0d4e3feed506e87b63dd867f3/react/src/assets/bg.png
--------------------------------------------------------------------------------
/react/src/assets/check.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/react/src/assets/lorem.ts:
--------------------------------------------------------------------------------
1 | const lorem = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean aliquam placerat orci sit amet molestie. Mauris vitae vulputate enim. Nullam maximus maximus libero eu bibendum. Cras quis sapien nibh. Aenean eu sapien justo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Vivamus et sollicitudin massa. Pellentesque vulputate consequat leo, mattis facilisis leo convallis ac. Donec at malesuada nibh, nec elementum quam. Suspendisse leo nulla, congue sit amet lacus in, facilisis auctor odio. In placerat magna at eleifend luctus. Morbi est odio, finibus eget efficitur pharetra, maximus non urna.
2 |
3 | Maecenas et ex arcu. Donec maximus leo ac lacus ornare, quis efficitur dui bibendum. Suspendisse sit amet sodales enim, nec venenatis nisl. Vestibulum non iaculis tortor, et sodales ipsum. Sed tempus leo sit amet laoreet efficitur. Pellentesque eleifend volutpat turpis, eu facilisis sem ultrices eu. Proin nec orci tempor, luctus purus eget, sagittis enim. Integer massa magna, elementum id sapien vel, egestas rutrum elit. Nullam non pulvinar nulla. Donec dolor lacus, interdum id nunc nec, euismod pharetra sapien.
4 |
5 | Proin viverra libero odio, in ultrices magna tempus quis. In vestibulum lacus non varius tincidunt. Mauris fringilla eu massa ac dictum. Aliquam ex tellus, luctus congue lorem eget, interdum sagittis tellus. Ut sagittis, felis sit amet viverra eleifend, orci quam ornare dui, a condimentum odio nisi sed enim. Phasellus malesuada, arcu quis condimentum euismod, risus ligula vehicula felis, ac venenatis nunc ipsum vel leo. Sed nec ex quis est vestibulum dignissim in tincidunt lacus. Sed eu luctus mauris. Nunc rhoncus fermentum dapibus. Vivamus lacinia mollis orci sed placerat. Integer ante libero, fermentum at risus ut, pretium fermentum lacus. Ut tempor ex mauris, sit amet blandit nisi fringilla id. Sed quam tellus, lacinia a tellus ac, ultrices vestibulum elit.
6 |
7 | Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed venenatis pharetra dolor, vel dictum quam tristique non. Duis malesuada gravida urna vel ultrices. Integer fringilla arcu sit amet lacus hendrerit, quis lacinia quam rutrum. Donec rhoncus sagittis urna. Aenean consectetur pulvinar libero. Integer aliquam porta mi, at sodales metus cursus nec. Duis vel maximus erat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec sit amet fermentum nisl. In rutrum nibh a ultricies condimentum. Fusce finibus, mauris quis finibus viverra, felis ipsum euismod augue, vel malesuada urna ligula sit amet est.
8 |
9 | `;
10 |
11 | export default lorem;
12 |
--------------------------------------------------------------------------------
/react/src/assets/star.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReactUnity/full-sample/20145583d09ab3c0d4e3feed506e87b63dd867f3/react/src/assets/star.png
--------------------------------------------------------------------------------
/react/src/assets/trailer.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ReactUnity/full-sample/20145583d09ab3c0d4e3feed506e87b63dd867f3/react/src/assets/trailer.mp4
--------------------------------------------------------------------------------
/react/src/entry/uitoolkit.tsx:
--------------------------------------------------------------------------------
1 | import '../uitoolkit/home';
2 |
--------------------------------------------------------------------------------
/react/src/index.tsx:
--------------------------------------------------------------------------------
1 | import './app';
2 |
--------------------------------------------------------------------------------
/react/src/pages/animations/index.module.scss:
--------------------------------------------------------------------------------
1 | .host {
2 | align-items: center;
3 |
4 | button {
5 | &:hover {
6 | audio: url(res:click) 50%;
7 | audio-pitch: 1.5;
8 | }
9 | }
10 |
11 | catwalk {
12 | width: 399px;
13 | height: 200px;
14 | margin: auto;
15 | background: transparent url(res:catwalk);
16 | animation: sprite 800ms steps(11) infinite;
17 | }
18 |
19 | ryu {
20 | width: 435px;
21 | height: 267px;
22 | margin: auto;
23 | background: url(res:ryu);
24 | animation: sprite 3.5s steps(45) infinite;
25 | }
26 |
27 | pulsar {
28 | cursor: pointer;
29 | width: 300px;
30 | height: 300px;
31 | border-radius: 50%;
32 | animation: pulsate 6s linear infinite;
33 | box-shadow: 0 0 20px #fff, -20px 0 80px #f0f, 20px 0 80px #0ff, inset 0 0 50px #fff, inset 50px 0 80px #f0f,
34 | inset -50px 0 80px #0ff, inset 50px 0 300px #f0f, inset -50px 0 300px #0ff;
35 | }
36 |
37 | @keyframes sprite {
38 | from {
39 | background-position: 0 0%;
40 | }
41 |
42 | to {
43 | background-position: 0 100%;
44 | }
45 | }
46 |
47 | @keyframes pulsate {
48 | 50% {
49 | box-shadow: 0 0 20px #fff, 20px 0 80px #f0f, -20px 0 80px #0ff, inset 0 0 50px #fff, inset -50px 0 80px #f0f,
50 | inset 50px 0 80px #0ff, inset -50px 0 300px #f0f, inset 50px 0 300px #0ff;
51 | }
52 | }
53 | }
54 |
55 |
56 | .items {
57 | margin: 24px;
58 | }
59 |
60 | .item {
61 | width: 160px;
62 | margin-bottom: 10px;
63 | background-color: white;
64 | border: 1px solid black;
65 | padding: 6px;
66 | font-size: 20px;
67 | translate: 0 0;
68 | opacity: 1;
69 |
70 | transition: opacity 600ms ease-out;
71 | motion: 600ms;
72 |
73 | &:enter {
74 | translate: 100% 0;
75 | opacity: 0;
76 | }
77 |
78 | &:leave {
79 | opacity: 0;
80 | translate: -100% 0;
81 | state-duration: 600ms;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/react/src/pages/animations/index.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import { useState } from 'react';
3 | import styles from './index.module.scss';
4 |
5 | let lastId = 1;
6 |
7 | export const AnimationsPage = () => {
8 | const [items, setItems] = useState([]);
9 |
10 | return
11 |
12 |
13 |
14 |
15 |
18 |
19 |
22 |
23 |
26 |
27 |
28 | {items.map((x, i) =>
29 |
30 | Item {x}
31 | )}
32 |
33 | ;
34 | };
35 |
36 | export default AnimationsPage;
37 |
--------------------------------------------------------------------------------
/react/src/pages/bg-patterns/index.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import styles from './index.module.scss';
3 |
4 | export const BgPatternsPage = () => {
5 | return
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | ;
39 | };
40 |
41 | export default BgPatternsPage;
42 |
--------------------------------------------------------------------------------
/react/src/pages/home/index.module.scss:
--------------------------------------------------------------------------------
1 | .host {
2 | richtext:link-hover {
3 | cursor: pointer;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/react/src/pages/home/index.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { useNavigate } from 'react-router';
3 | import styles from './index.module.scss';
4 |
5 | export const HomePage = () => {
6 | const nav = useNavigate();
7 |
8 | return
9 | {
11 | const linkId = sender.GetLinkInfo(ev);
12 | if (linkId === 'svgs') nav('svgs');
13 | }}
14 | >
15 |
16 | Welcome to ReactUnity 😎
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Click an item in the
25 |
26 |
27 |
28 |
29 | left menu
30 |
31 |
32 |
33 |
34 | to see examples.
35 |
36 |
37 |
38 |
39 | Check out the new
40 |
41 |
42 |
43 | SVG
44 |
45 |
46 | feature for example.
47 |
48 | ;
49 | };
50 |
51 | export default HomePage;
52 |
--------------------------------------------------------------------------------
/react/src/pages/images/index.module.scss:
--------------------------------------------------------------------------------
1 | .host {
2 |
3 | image,
4 | svg,
5 | svgimage {
6 | flex-grow: 1;
7 | flex-shrink: 1;
8 | flex-basis: 0;
9 | object-fit: scale-down;
10 | object-position: 50%;
11 | transition: object-position 2s;
12 | align-self: stretch;
13 | }
14 |
15 | object {
16 | border-width: 1px;
17 | border-radius: 20px;
18 | border-color: black;
19 | margin: 5px;
20 | background-color: hsla(152deg, 37%, 59%, 0.745);
21 | object-fit: none;
22 | }
23 |
24 | render,
25 | video {
26 | object-fit: scale-down;
27 | object-position: left;
28 |
29 | &::image {
30 | background-color: yellowgreen;
31 | }
32 | }
33 |
34 | render {
35 | border: 2px solid rgb(188, 188, 188);
36 | border-radius: 6px;
37 | }
38 | }
39 |
40 | .borderImage {
41 | border: 1px solid transparent;
42 | // border-image-source: url('resource:border-image');
43 | border-image-source: url('pool:images/myBorder');
44 | border-image-width: calc(80 / 2) calc(72 / 2) calc(84 / 2);
45 | border-image-slice: 80 72 84 fill;
46 | border-image-repeat: round repeat;
47 |
48 | width: 400px;
49 | text-transform: uppercase;
50 | font-weight: bold;
51 | font-size: 50px;
52 | color: white;
53 | flex-direction: column;
54 | text-align: center;
55 | justify-content: center;
56 | white-space: normal;
57 | word-wrap: normal;
58 | padding: 40px;
59 |
60 | text-stroke: 0.3px black;
61 | }
62 |
--------------------------------------------------------------------------------
/react/src/pages/interop/index.module.scss:
--------------------------------------------------------------------------------
1 | .host {
2 | }
3 |
--------------------------------------------------------------------------------
/react/src/pages/interop/index.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { useGlobals, useReactiveValue } from '@reactunity/renderer';
3 | import { useEffect, useState } from 'react';
4 | import { useUnityContext } from 'react-unity-webgl';
5 | import styles from './index.module.scss';
6 |
7 | export const InteropPage = () => {
8 | const [shouldListen, setShouldListen] = useState(true);
9 | const [pressedKey, setPressedKey] = useState(null);
10 |
11 | const { addEventListener, removeEventListener } = useUnityContext({} as any);
12 |
13 |
14 | const { InteropTest }= useGlobals();
15 | const deltaTime = useReactiveValue(InteropTest.DeltaTime);
16 |
17 | useEffect(() => {
18 | if (shouldListen) {
19 | const remove = Globals.InteropTest.AddKeyPressListener((key) => {
20 | setPressedKey(key);
21 | });
22 |
23 | Interop.GetType('MyInterop.InteropTest').TestDebug();
24 |
25 | return () => remove();
26 | }
27 | }, [shouldListen]);
28 |
29 | useEffect(() => {
30 | const fn = (key) => {
31 | console.log('Pressed key is: ' + key);
32 | };
33 |
34 | addEventListener('OnKeyPress', fn);
35 |
36 | return () => removeEventListener('OnKeyPress', fn);
37 | }, [addEventListener, removeEventListener]);
38 |
39 | return
40 |
43 |
44 | {!!pressedKey &&
45 | Pressed key is: {pressedKey}
46 | }
47 |
48 | Delta Time: {deltaTime}
49 | ;
50 | };
51 |
52 | export default InteropPage;
53 |
--------------------------------------------------------------------------------
/react/src/pages/material/index.module.scss:
--------------------------------------------------------------------------------
1 | @use "@reactunity/material/styles";
2 |
3 | .app {
4 | > * {
5 | margin-bottom: 10px;
6 | }
7 |
8 | section > * {
9 | margin-bottom: 12px;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/react/src/pages/material/virtual-scrolls.tsx:
--------------------------------------------------------------------------------
1 | import { Paper } from '@reactunity/material/paper';
2 | import { FixedSizeGrid, FixedSizeList, VariableSizeList } from '@reactunity/material/virtual-scroll';
3 |
4 | const Row = ({ index, style }: any) => (
5 | Row {index}
6 | );
7 |
8 |
9 |
10 | const Cell = ({ columnIndex, rowIndex, style }) => (
11 | Item {rowIndex},{columnIndex}
12 | );
13 |
14 | const FixedGridExample = () => (
15 |
23 | {Cell as any}
24 |
25 | );
26 |
27 | // These row heights are arbitrary.
28 | // Yours should be based on the content of the row.
29 | const rowHeights = new Array(1000)
30 | .fill(true)
31 | .map(() => 25 + Math.round(Math.random() * 50));
32 |
33 | const getItemSize = index => rowHeights[index];
34 |
35 | const FixedSizeExample = () => (
36 |
43 | {Row}
44 |
45 | );
46 |
47 | const VariableSizeExample = () => (
48 |
54 | {Row}
55 |
56 | );
57 |
58 |
59 | export function VirtualScrolls() {
60 | return
61 | Virtual Scrolls
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | ;
71 | }
72 |
--------------------------------------------------------------------------------
/react/src/pages/query/QueryPage.tsx:
--------------------------------------------------------------------------------
1 | import { useQuery } from '@tanstack/react-query';
2 |
3 | export function QueryPage() {
4 | const { isPending, error, data, isFetching, refetch } = useQuery({
5 | queryKey: ['repoData'],
6 | queryFn: async () => {
7 | const response = await fetch(
8 | 'https://api.github.com/repos/TanStack/query'
9 | );
10 | return await response.json();
11 | },
12 | });
13 |
14 | if (isPending) return 'Loading...';
15 |
16 | if (error) return 'An error has occurred: ' + error.message;
17 |
18 | return (
19 |
20 |
{data.full_name}
21 | Subscribers: {data.subscribers_count}
22 | Stargazers: {data.stargazers_count}
23 | Forks: {data.forks_count}
24 |
25 |
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/react/src/pages/redux/index.tsx:
--------------------------------------------------------------------------------
1 | import { Provider, useDispatch, useSelector } from 'react-redux';
2 | import { PersistGate } from 'redux-persist/integration/react';
3 | import { decrement, increment, persistor, selectCount, store } from 'src/store';
4 |
5 | export function Redux() {
6 | return
7 |
8 | ;
9 | }
10 |
11 |
12 | function ReduxCore() {
13 | const dispatch = useDispatch();
14 | const count = useSelector(selectCount);
15 |
16 | return
17 | Count: {count?.toString()}
18 |
19 |
20 | ;
21 | }
22 |
--------------------------------------------------------------------------------
/react/src/pages/style-frameworks/bootstrap/index.scss:
--------------------------------------------------------------------------------
1 | @import "bootstrap/scss/_functions";
2 | @import "bootstrap/scss/_variables";
3 | @import "bootstrap/scss/_mixins";
4 |
5 | @import 'bootstrap/scss/_buttons';
6 | @import "bootstrap/scss/_card";
7 |
--------------------------------------------------------------------------------
/react/src/pages/style-frameworks/bootstrap/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import Button from 'react-bootstrap/Button';
3 | import './index.scss';
4 |
5 | export function BootstrapPage() {
6 | useEffect(() => {
7 | // This causes too much performance issues, so load individual scss files
8 | // @ts-ignore
9 | // import('bootstrap/dist/css/bootstrap.min.css');
10 | }, []);
11 |
12 | return <>
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Card title
21 |
Some quick example text to build on the card title and make up the bulk of the card's content.
22 |
Go somewhere
23 |
24 |
25 |
26 | >;
27 | }
28 |
29 | export default BootstrapPage;
30 |
--------------------------------------------------------------------------------
/react/src/pages/style-frameworks/emotion/index.tsx:
--------------------------------------------------------------------------------
1 | import { css } from '@emotion/css';
2 | import styled from '@emotion/styled';
3 |
4 | const Button = styled.button`
5 | margin: 20px;
6 | padding: 32px;
7 | background-color: crimson;
8 | font-size: 24px;
9 | border-radius: 4px;
10 | color: white;
11 | font-weight: bold;
12 | &:hover {
13 | color: yellow;
14 | }
15 | `;
16 | const color = 'white';
17 |
18 | export function EmotionPage() {
19 | // const [toggled, setToggled] = useState(false);
20 |
21 | return <>
22 |
33 | Hover to change color.
34 |
35 |
36 |
37 | >;
38 | }
39 |
40 | export default EmotionPage;
41 |
--------------------------------------------------------------------------------
/react/src/pages/style-frameworks/index.module.scss:
--------------------------------------------------------------------------------
1 | .host {}
2 |
3 | .tabs {
4 | margin-bottom: 24px;
5 | border-bottom: 1px solid rgb(201, 201, 201);
6 |
7 | >button {
8 | border-radius: 0;
9 | border-top-left-radius: 4px;
10 | border-top-right-radius: 4px;
11 | border: 1px solid rgb(201, 201, 201);
12 | font-size: 18px;
13 | padding: 14px 30px;
14 |
15 | &.active {
16 | background-color: rgb(227, 227, 227);
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/react/src/pages/style-frameworks/index.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import { ReactNode } from 'react';
3 | import { Outlet, To, useMatch, useNavigate, useResolvedPath } from 'react-router';
4 | import styles from './index.module.scss';
5 |
6 |
7 | const CustomNavLink = ({ to, children }: { to: To, children: ReactNode }) => {
8 | const nav = useNavigate();
9 | let resolved = useResolvedPath(to);
10 | let match = useMatch({ path: resolved.pathname, end: true });
11 |
12 | return (
13 |
16 | );
17 | };
18 |
19 | export function StyleFrameworksPage() {
20 | return
21 |
22 | JSS
23 | Styled Components
24 | Emotion
25 | Tailwind
26 | Bootstrap
27 |
28 |
29 |
30 | ;
31 | }
32 |
33 | export default StyleFrameworksPage;
34 |
--------------------------------------------------------------------------------
/react/src/pages/style-frameworks/jss/index.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { createUseStyles } from 'react-jss';
3 |
4 | export function JSSPage() {
5 | return <>
6 |
7 |
8 |
9 | >;
10 | }
11 |
12 | export default JSSPage;
13 |
14 |
15 | // Create your Styles. Remember, since React-JSS uses the default preset,
16 | // most plugins are available without further configuration needed.
17 | const useStyles = createUseStyles(({
18 | '@keyframes rotate': {
19 | from: {
20 | transform: 'scale(1)',
21 | },
22 | to: {
23 | transform: 'scale(1.5)',
24 | },
25 | },
26 | myButton: ({
27 | color: (prop: any) => prop.enabled ? 'green' : 'red',
28 | // color: 'green',
29 | margin: {
30 | // jss-plugin-expand gives more readable syntax
31 | top: 5, // jss-plugin-default-unit makes this 5px
32 | right: 0,
33 | bottom: 0,
34 | left: '1rem',
35 | },
36 | animation: '$rotate 1s infinite alternate',
37 | '& span': {
38 | // jss-plugin-nested applies this to a child span
39 | fontWeight: 'bold', // jss-plugin-camel-case turns this into 'font-weight'
40 | },
41 | }),
42 | myLabel: {
43 | fontStyle: 'italic',
44 | },
45 | '@media (max-width: 600px)': {
46 | myButton: {
47 | backgroundColor: 'yellow',
48 | },
49 | },
50 | }));
51 |
52 | // Define the component using these styles and pass it the 'classes' prop.
53 | // Use this to assign scoped class names.
54 | const Button = ({ children }) => {
55 | const [enabled, setEnabled] = useState(true);
56 | const classes = useStyles({ enabled } as any);
57 | return (
58 |
61 | );
62 | };
63 |
--------------------------------------------------------------------------------
/react/src/pages/style-frameworks/styled-components/index.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import styled, { css } from 'styled-components';
3 |
4 | const Button = styled.button<{ $toggled?: boolean }>`
5 | /* This renders the buttons above... Edit me! */
6 | display: inline-block;
7 | border-radius: 3px;
8 | padding: 0.5rem 0;
9 | margin: 0.5rem 1rem;
10 | width: 11rem;
11 | background: yellow;
12 | color: black;
13 | border: 2px solid black;
14 |
15 | ${props => props.$toggled && css`
16 | background: white;
17 | color: black;
18 | `}
19 | `;
20 |
21 |
22 | export function StyledComponentsPage() {
23 | const [toggled, setToggled] = useState(false);
24 |
25 | return <>
26 |
27 |
28 |
29 | >;
30 | }
31 |
32 | export default StyledComponentsPage;
33 |
--------------------------------------------------------------------------------
/react/src/pages/style-frameworks/tailwind/index.css:
--------------------------------------------------------------------------------
1 | /* @tailwind base; */
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/react/src/pages/style-frameworks/tailwind/index.tsx:
--------------------------------------------------------------------------------
1 | import './index.css';
2 |
3 | export function TailwindPage() {
4 | return
5 |
6 |
9 |
10 |
13 |
14 |
15 |
16 |
20 |
21 |
;
22 | }
23 |
24 | export default TailwindPage;
25 |
--------------------------------------------------------------------------------
/react/src/pages/svgs/graph.tsx:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 | import styles from './index.module.scss';
3 |
4 | function generatePolylineArray(arrayX: number[], arrayY: number[]) {
5 | let polyline = '';
6 | arrayX.map((coordX, i) => {
7 | return polyline += `${coordX}, ${arrayY[i]} `;
8 | });
9 | return polyline;
10 | }
11 |
12 | export const Graph = ({ arrayX, arrayY, lineWidth }: { arrayX: number[], arrayY: number[], lineWidth: number }) => {
13 | const polyline = useMemo(() => {
14 | return generatePolylineArray(arrayX, arrayY);
15 | }, [arrayX, arrayY]);
16 |
17 | return (
18 |
24 | );
25 | };
26 |
--------------------------------------------------------------------------------
/react/src/pages/svgs/index.module.scss:
--------------------------------------------------------------------------------
1 | .graph {
2 | margin: auto;
3 | height: 100px;
4 | }
5 |
--------------------------------------------------------------------------------
/react/src/pages/svgs/index.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import { useMemo, useState } from 'react';
3 | import { FaAlignCenter, FaBeer } from 'react-icons/fa';
4 | import { FcDoughnutChart } from 'react-icons/fc';
5 | import { IconContext } from 'react-icons/lib';
6 | import { ReactComponent as CheckSVG } from 'src/assets/check.svg';
7 | import { Graph } from './graph';
8 | import styles from './index.module.scss';
9 |
10 |
11 | const xPoints = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650, 700, 750, 800, 850, 900, 950, 1000];
12 | const yPoints = [5, 30, -5, -10, 15, -15, 20, 5, 8, -12, -20, 2, 3, -5, 8, -2, 22, -30, -15, -35, -20];
13 |
14 | export function SvgsPage() {
15 | const [points, setPoints] = useState(5);
16 | const [lineWidth, setLineWidth] = useState(3);
17 |
18 | const [arrayX, arrayY] = useMemo(() => {
19 | return [xPoints.slice(0, points), yPoints.slice(0, points)];
20 | }, [points]);
21 |
22 | return
23 |
24 |
25 | Inline SVGs
26 |
27 |
28 |
29 |
30 |
33 |
34 |
35 |
36 |
37 | React Icons
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | Dynamic SVG
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
;
60 | }
61 |
62 | export default SvgsPage;
63 |
--------------------------------------------------------------------------------
/react/src/pages/todo/index.css:
--------------------------------------------------------------------------------
1 | .todo-root {
2 | flex-direction: column;
3 | align-self: center;
4 | align-items: stretch;
5 | margin: 40px;
6 | padding: 40px;
7 |
8 | background-color: #dedede;
9 | border-radius: 8px;
10 | box-shadow: 1px 1px 6px -2px black;
11 |
12 | --item-animation-duration: 400ms;
13 | }
14 |
15 | .todo-header {
16 | color: cornflowerblue;
17 | font-size: 30px;
18 | font-weight: bold;
19 | margin-bottom: 18px;
20 | text-align: center;
21 | }
22 |
23 | .todo-input-section {
24 | flex-direction: row;
25 | align-items: stretch;
26 | width: 360px;
27 | margin-bottom: 18px;
28 | }
29 |
30 | .todo-input {
31 | flex-shrink: 1;
32 | flex-grow: 1;
33 | }
34 |
35 | .todo-item {
36 | flex-direction: row;
37 | align-items: center;
38 | transition: rotate var(--item-animation-duration), margin-bottom var(--item-animation-duration);
39 | rotate: 0 0 0;
40 | width: 360px;
41 | padding: 4px 0 4px 8px;
42 | margin-bottom: 0px;
43 | transform-origin: top;
44 |
45 | background-color: white;
46 | border: 1px solid #dedede;
47 | border-radius: 8px;
48 | }
49 |
50 | .todo-item:not(:first-child) {
51 | margin-top: 10px;
52 | }
53 |
54 | .todo-item:enter {
55 | rotate: 90deg 0 0;
56 | margin-bottom: -66px;
57 | }
58 |
59 | .todo-item:leave {
60 | rotate: 90deg 0 0;
61 | margin-bottom: -66px;
62 | pointer-events: none;
63 | state-duration: var(--item-animation-duration);
64 | }
65 |
66 | .todo-item-name {
67 | flex-shrink: 1;
68 | flex-grow: 1;
69 | }
70 |
71 | .todo-add-button,
72 | .todo-remove-button {
73 | width: 50px;
74 | height: 50px;
75 | margin-left: 8px;
76 | }
77 |
--------------------------------------------------------------------------------
/react/src/pages/todo/index.jsx:
--------------------------------------------------------------------------------
1 | import { useRef, useState } from 'react';
2 | import './index.css';
3 |
4 | export function TodoItem(props) {
5 | return
6 |
7 | {props.item.text}
8 |
9 |
10 |
14 | ;
15 | }
16 |
17 | export function TodoPage() {
18 | const lastId = useRef(3);
19 | const [items, setItems] = useState([
20 | { id: 0, text: 'Take a walk' },
21 | { id: 1, text: 'Buy groceries' },
22 | { id: 2, text: 'Prepare dinner' },
23 | ]);
24 |
25 | const inputRef = useRef();
26 |
27 | function addTodo(item) {
28 | inputRef.current.Value = '';
29 | setItems(oldItems => {
30 | const newItems = [...oldItems];
31 | newItems.push({ id: lastId.current++, text: item });
32 | return newItems;
33 | });
34 | }
35 |
36 | function removeTodo(id) {
37 | setItems(oldItems => oldItems.filter(x => x.id !== id));
38 | }
39 |
40 | return
41 |
42 | TODO app example
43 |
44 |
45 |
46 | addTodo(sender.Value)} />
49 |
50 |
54 |
55 |
56 |
57 | {items.map((item) =>
58 | )}
60 |
61 | ;
62 | }
63 |
64 | export default TodoPage;
65 |
--------------------------------------------------------------------------------
/react/src/store.ts:
--------------------------------------------------------------------------------
1 | import { Action, configureStore, createSlice, ThunkAction } from '@reduxjs/toolkit';
2 | import { FLUSH, PAUSE, PERSIST, PersistConfig, persistReducer, persistStore, PURGE, REGISTER, REHYDRATE } from 'redux-persist';
3 |
4 | const persistConfig: PersistConfig<{ count: number }> = {
5 | key: 'counter',
6 | storage: {
7 | getItem: x => {
8 | const item = localStorage.getItem(x);
9 | if (item) {
10 | try {
11 | return Promise.resolve(JSON.parse(item));
12 | } catch { }
13 | }
14 | return Promise.resolve(null);
15 | },
16 | setItem: (x, v) => { localStorage.setItem(x, JSON.stringify(v)); return Promise.resolve(); },
17 | removeItem: (x) => { localStorage.removeItem(x); return Promise.resolve(); },
18 | },
19 | };
20 |
21 | const counterSlice = createSlice({
22 | name: 'counter',
23 | initialState: {
24 | count: 0,
25 | },
26 | reducers: {
27 | increment: state => ({ count: (state.count || 0) + 1 }),
28 | decrement: state => ({ count: (state.count || 0) - 1 }),
29 | },
30 | });
31 |
32 | const counter = persistReducer(persistConfig, counterSlice.reducer);
33 |
34 | export const { increment, decrement } = counterSlice.actions;
35 |
36 | export const store = configureStore({
37 | reducer: {
38 | counter,
39 | },
40 | middleware: (getDefaultMiddleware) => getDefaultMiddleware({
41 |
42 | serializableCheck: {
43 | ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
44 | },
45 | }),
46 | devTools: false,
47 | });
48 | export const persistor = persistStore(store);
49 |
50 | export type RootState = ReturnType;
51 | export type AppThunk = ThunkAction<
52 | ReturnType,
53 | RootState,
54 | unknown,
55 | Action
56 | >;
57 | export type AppThunkCreator = (arg: PayloadType) => AppThunk;
58 |
59 | export const selectCount = (x: RootState) => x.counter.count;
60 |
--------------------------------------------------------------------------------
/react/src/tests/README.md:
--------------------------------------------------------------------------------
1 | ### What is this folder?
2 |
3 | This folder contains quick reproductions for testing some bug reports by users. You can delete this report safely.
4 |
--------------------------------------------------------------------------------
/react/src/tests/error-test/index.tsx:
--------------------------------------------------------------------------------
1 | import { render } from '@reactunity/renderer';
2 | import React, { useState } from 'react';
3 |
4 | function ErrorChild2() {
5 | const [state, setState] = useState(null);
6 |
7 | return
8 | {state.value}
9 |
;
10 | }
11 |
12 | function ErrorChild1() {
13 | return
14 |
15 |
;
16 | }
17 |
18 | export default function ErrorTest() {
19 |
20 | return (
21 |
22 | );
23 | }
24 |
25 |
26 | const createdRefs = new Set();
27 |
28 | function createRef(ref) {
29 | if (ref && !createdRefs.has(ref)) {
30 | createdRefs.add(ref);
31 | console.log('Created ref', ref);
32 | }
33 | }
34 |
35 | export class ErrorBoundary extends React.Component {
36 | constructor(props) {
37 | super(props);
38 | this.state = { hasError: false, error: null };
39 | }
40 |
41 | static getDerivedStateFromError(error) {
42 | // Update state so the next render will show the fallback UI.
43 | return { hasError: true, error };
44 | }
45 |
46 | render() {
47 | if (this.state.hasError) {
48 | return
51 |
52 | {this.state.error?.message || ''}
53 |
54 | {this.state.error?.stack || ''}
55 | ;
56 | }
57 |
58 | return this.props.children;
59 | }
60 | }
61 |
62 | render(
63 |
64 |
65 | , { disableHelpers: true });
66 |
--------------------------------------------------------------------------------
/react/src/tests/stress-test/index.css:
--------------------------------------------------------------------------------
1 | /* @tailwind base; */
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/react/src/tests/stress-test/index.tsx:
--------------------------------------------------------------------------------
1 | import { render } from '@reactunity/renderer';
2 | import { useMemo, useState } from 'react';
3 | import './index.css';
4 |
5 | const ROWS = 125;
6 | const COLUMNS = 15;
7 |
8 | function Row({ row, columns }: { row: number, columns: number }) {
9 | const children = useMemo(() => {
10 | return Array(columns)
11 | .fill(0)
12 | .map((_, col) => {
13 | return (
14 | console.log(`Clicked ${row} x ${col}`)}>
16 | {`${row} x ${col}`}
17 |
18 | );
19 | });
20 | }, [columns, row]);
21 |
22 | return {children}
;
23 | }
24 |
25 | function Rows({ columns, rows }: { columns: number, rows: number }) {
26 | const children = useMemo(() => {
27 | return Array(rows)
28 | .fill(0)
29 | .map((_, i) => {
30 | return
;
31 | });
32 | }, [columns, rows]);
33 |
34 | return {children}
;
35 | }
36 |
37 | export default function StressTest() {
38 | const [rows, setRows] = useState(ROWS);
39 | const [columns, setColumns] = useState(COLUMNS);
40 |
41 | return (
42 |
43 |
55 |
56 |
57 | );
58 | }
59 |
60 | render();
61 |
--------------------------------------------------------------------------------
/react/src/tests/width-test/index.scss:
--------------------------------------------------------------------------------
1 | .black-bar {
2 | position: fixed;
3 | bottom: 0;
4 | left: 0;
5 | right: 0;
6 | height: 250px;
7 | // background-color: red;
8 | // background: linear-gradient(90deg, rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.8) 20%, rgba(0, 0, 0, 0.8) 80%, rgba(0, 0, 0, 0.5));
9 | display: flex;
10 | align-items: center;
11 | justify-content: center;
12 | }
13 |
14 | .content {
15 | display: flex;
16 | flex-direction: column;
17 | align-items: flex-start;
18 | justify-content: center;
19 | text-align: left;
20 | }
21 |
22 | .title {
23 | margin: 0;
24 | color: white;
25 | font-size: 24px;
26 | }
27 |
28 | .gradient-rule {
29 | height: 4px;
30 | align-self: stretch;
31 | // width: 100%;
32 | background-color: green;
33 | background-image: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.8), transparent);
34 | margin: 16px 0;
35 | }
36 |
37 | .message {
38 | margin: 0;
39 | color: white;
40 | font-size: 18px;
41 | }
42 |
--------------------------------------------------------------------------------
/react/src/tests/width-test/index.tsx:
--------------------------------------------------------------------------------
1 | import { render } from '@reactunity/renderer';
2 | import './index.scss';
3 |
4 | function App() {
5 | return (
6 |
7 |
Title
8 |
9 |
Message text goes here
10 |
11 |
);
12 | }
13 |
14 | render();
15 |
--------------------------------------------------------------------------------
/react/src/twin.d.ts:
--------------------------------------------------------------------------------
1 | import { CSSProp } from 'styled-components';
2 | import 'twin.macro';
3 |
4 | declare global {
5 | interface ReactUnityCustomAttributes {
6 | css?: CSSProp;
7 | as?: string | Element;
8 | tw?: string;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/react/src/types.d.ts:
--------------------------------------------------------------------------------
1 | import { UnityEngine } from '@reactunity/renderer';
2 |
3 | declare global {
4 | interface DefaultGlobals {
5 | renderCamera: UnityEngine.GameObject;
6 | cameraRoot: UnityEngine.GameObject;
7 | }
8 | }
9 |
10 |
11 | declare global {
12 | interface ReactUnityCustomElements {
13 | // Add your custom native elements here
14 | // mycomp: { myprop?: number };
15 | }
16 |
17 | interface ReactUnityCustomAttributes {
18 | // Add your custom native elements here. May be required for some libraries like @emotion/react.
19 | // See twin.d.ts file for an example.
20 | // css?: CSSProp;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/react/src/uitoolkit/home/index.module.scss:
--------------------------------------------------------------------------------
1 | :root > * {
2 | background-color: #fafafa;
3 | }
4 |
5 | @font-face {
6 | font-family: Underdog;
7 | src: url(res:Underdog);
8 | }
9 |
10 | .app {
11 | padding: 20px;
12 | max-width: 960px;
13 | width: 100%;
14 | align-self: center;
15 | align-items: stretch;
16 |
17 | h1 {
18 | font-size: 36px;
19 | font-style: smallcaps, bold;
20 | color: #582a9c;
21 | margin-bottom: 12px;
22 | font-family: Underdog;
23 | }
24 |
25 | h2 {
26 | font-size: 30px;
27 | font-style: smallcaps;
28 | color: #fb2f8e;
29 | margin-bottom: 8px;
30 | }
31 |
32 | section {
33 | margin-top: 10px;
34 | margin-bottom: 10px;
35 | }
36 |
37 | row {
38 | flex-direction: row;
39 | align-items: center;
40 | }
41 |
42 | column {
43 | flex-direction: column;
44 | align-items: center;
45 | flex-grow: 1;
46 | flex-shrink: 0;
47 | }
48 |
49 | image {
50 | flex-grow: 1;
51 | flex-shrink: 1;
52 | flex-basis: 0;
53 | object-fit: scale-down;
54 | object-position: 50%;
55 | transition: object-position 2s;
56 | align-self: stretch;
57 | }
58 |
59 | input {
60 | border-width: 1px;
61 | border-color: black;
62 | }
63 |
64 | button {
65 | &:hover {
66 | audio: url(res:click);
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/react/src/uitoolkit/home/index.tsx:
--------------------------------------------------------------------------------
1 | /* @jsxImportSource @reactunity/renderer/uitoolkit */
2 |
3 | import { render } from '@reactunity/renderer';
4 | import base64Image from 'src/assets/base64Image.txt';
5 | import pngImage from 'src/assets/bg.png';
6 | import check, { ReactComponent as CheckSVG } from 'src/assets/check.svg';
7 | import style from './index.module.scss';
8 |
9 | const webImage = 'https://www.google.com.tr/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png';
10 |
11 | export function App() {
12 | return
13 |
14 | React Unity Showcase
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | Button
23 |
24 |
25 |
26 |
27 |
28 |
29 | Anchor
30 |
31 | Open Google
32 |
33 |
34 |
35 |
40 |
41 |
42 |
43 | Toggle
44 |
45 |
46 |
47 | Toggle
48 |
49 |
50 |
51 |
52 | Image
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | ;
62 | };
63 |
64 | render();
65 |
--------------------------------------------------------------------------------
/react/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: [
3 | "./src/**/*.{js,jsx,ts,tsx}",
4 | ],
5 | theme: {
6 | extend: {},
7 | },
8 | plugins: [],
9 | }
10 |
--------------------------------------------------------------------------------
/react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@reactunity/scripts",
3 | "compilerOptions": {
4 | "baseUrl": "./",
5 | "jsxImportSource": "@reactunity/renderer/ugui",
6 | "types": [
7 | "@reactunity/scripts/main"
8 | ]
9 | },
10 | "files": [
11 | "./src/index.tsx"
12 | ],
13 | "include": [
14 | "./src"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/react/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function (env, originalConfig) {
2 | // originalConfig.target = 'es5';
3 | // originalConfig.output.chunkFormat = 'commonjs';
4 | return originalConfig;
5 | }
6 |
--------------------------------------------------------------------------------