From 6548bbb755526fb7968bbda0926af88e01566ae0 Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Wed, 23 Apr 2025 19:45:32 +0300 Subject: [PATCH 01/28] rewrite: started porting from Typescript to Luau - Frame - Builders (more efficient, than `ExpandableComponent`) But no Typescript (yet) --- .gitignore | 12 +- .neoconf.json | 7 + .npmrc | 1 - Makefile | 24 + README.md | 106 ++- aftman.toml | 9 +- default.project.json | 6 + package.json | 45 -- pnpm-lock.yaml | 710 -------------------- src/Builder.luau | 52 ++ src/Builders/FrameBuilder.luau | 10 + src/Builders/GuiObjectBuilder.luau | 56 ++ src/Builders/TextBuilder.luau | 39 ++ src/Components/Frame.luau | 3 + src/Utils.luau | 65 ++ src/components/Button.ts | 14 - src/components/CanvasGroup.ts | 30 - src/components/Frame.ts | 3 - src/components/GridLayout.tsx | 40 -- src/components/Image.ts | 28 - src/components/ListLayout.tsx | 38 -- src/components/ScrollingFrame.ts | 76 --- src/components/Text.ts | 5 - src/components/TextBox.ts | 24 - src/helpers/BaseComponent.tsx | 152 ----- src/helpers/CalculateAspectRatio.ts | 26 - src/helpers/ExpandableComponent.ts | 109 --- src/helpers/Gradient.tsx | 64 -- src/helpers/TextComponent.ts | 62 -- src/helpers/index.ts | 10 - src/index.ts | 13 - src/init.luau | 5 + src/stories/App.ts | 14 - src/stories/Frame.story.luau | 39 ++ src/stories/basics/1_helloworld.story.tsx | 26 - src/stories/basics/2_padding.story.tsx | 47 -- src/stories/basics/3_cornerRadius.story.tsx | 20 - src/stories/basics/5_flexItem.story.tsx | 41 -- src/stories/examples/sidebar.story.tsx | 67 -- src/stories/examples/textBox.story.tsx | 36 - src/stories/tests/bindings.story.tsx | 62 -- src/stories/tests/context.story.tsx | 57 -- src/stories/tests/gradients.story.tsx | 116 ---- src/stories/tests/refs.story.tsx | 42 -- src/stories/utils.ts | 66 -- src/utils.ts | 131 ---- story.project.json | 39 +- stylua.toml | 3 + tsconfig.json | 27 - wally.lock | 98 +++ wally.toml | 12 + 51 files changed, 486 insertions(+), 2301 deletions(-) create mode 100644 .neoconf.json delete mode 100644 .npmrc create mode 100644 Makefile create mode 100644 default.project.json delete mode 100644 package.json delete mode 100644 pnpm-lock.yaml create mode 100644 src/Builder.luau create mode 100644 src/Builders/FrameBuilder.luau create mode 100644 src/Builders/GuiObjectBuilder.luau create mode 100644 src/Builders/TextBuilder.luau create mode 100644 src/Components/Frame.luau create mode 100644 src/Utils.luau delete mode 100644 src/components/Button.ts delete mode 100644 src/components/CanvasGroup.ts delete mode 100644 src/components/Frame.ts delete mode 100644 src/components/GridLayout.tsx delete mode 100644 src/components/Image.ts delete mode 100644 src/components/ListLayout.tsx delete mode 100644 src/components/ScrollingFrame.ts delete mode 100644 src/components/Text.ts delete mode 100644 src/components/TextBox.ts delete mode 100644 src/helpers/BaseComponent.tsx delete mode 100644 src/helpers/CalculateAspectRatio.ts delete mode 100644 src/helpers/ExpandableComponent.ts delete mode 100644 src/helpers/Gradient.tsx delete mode 100644 src/helpers/TextComponent.ts delete mode 100644 src/helpers/index.ts delete mode 100644 src/index.ts create mode 100644 src/init.luau delete mode 100644 src/stories/App.ts create mode 100644 src/stories/Frame.story.luau delete mode 100644 src/stories/basics/1_helloworld.story.tsx delete mode 100644 src/stories/basics/2_padding.story.tsx delete mode 100644 src/stories/basics/3_cornerRadius.story.tsx delete mode 100644 src/stories/basics/5_flexItem.story.tsx delete mode 100644 src/stories/examples/sidebar.story.tsx delete mode 100644 src/stories/examples/textBox.story.tsx delete mode 100644 src/stories/tests/bindings.story.tsx delete mode 100644 src/stories/tests/context.story.tsx delete mode 100644 src/stories/tests/gradients.story.tsx delete mode 100644 src/stories/tests/refs.story.tsx delete mode 100644 src/stories/utils.ts delete mode 100644 src/utils.ts create mode 100644 stylua.toml delete mode 100644 tsconfig.json create mode 100644 wally.lock create mode 100644 wally.toml diff --git a/.gitignore b/.gitignore index f484755..356fdf8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,7 @@ -/node_modules -/out -/include +Packages/ +DevPackages/ +sourcemap.json .DS_Store -*.tsbuildinfo *.log - -# development tarballs -rbxts-better-react-components-*.tgz -package.tgz \ No newline at end of file +*.old diff --git a/.neoconf.json b/.neoconf.json new file mode 100644 index 0000000..fa51a35 --- /dev/null +++ b/.neoconf.json @@ -0,0 +1,7 @@ +{ + "luau-lsp": { + "sourcemap": { + "autogenerate": false + } + } +} diff --git a/.npmrc b/.npmrc deleted file mode 100644 index d67f374..0000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -node-linker=hoisted diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..00e1ccd --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +install: install-dependencies patch-sourcemap +serve: + rojo serve story.project.json + +#build: install-dependencies +# rojo build + +# intermediate scripts + +install-dependencies: aftman.toml wally.toml wally.lock + aftman install + wally install + +patch-sourcemap: sourcemap.json Packages/* DevPackages/ + wally-package-types --sourcemap dev-sourcemap.json Packages/ + wally-package-types --sourcemap dev-sourcemap.json DevPackages/ + +# target files/dirs: + +Packages: install-dependencies + +sourcemap.json: src/* story.project.json + rojo sourcemap story.project.json --output sourcemap.json + diff --git a/README.md b/README.md index 0164bd5..20b326a 100644 --- a/README.md +++ b/README.md @@ -1,83 +1,61 @@ -# Better React Components +# better-react-components -![NPM Downloads](https://img.shields.io/npm/dm/%40rbxts%2Fbetter-react-components?style=for-the-badge) -![NPM Version](https://img.shields.io/npm/v/%40rbxts%2Fbetter-react-components?style=for-the-badge) +Roblox's UI elements, but with builtin modifiers -Roblox's ui elements, with builtin modifiers +> ![NOTE] +> +> Currently, I am rewriting this library from Typescript to Lua. +> +> I'll still publish this library to NPM. -Also see **[introduction](docs/1_Introduction.md)** +## Setup guide -## Example +``` +Dear Future Me. -![Comperance](docs/better-react-components-is-based.png) +Please, publish and write here, how to install this library from wally or npm -
- better-react-components code +Respectfully, + Past Me +``` - ``` - - +1. Install Aftman and Make +2. Run: + ``` + make install # installs all toolchain, dependencies and patch sourcemap.json + make serve # starts rojo server + make build # builds .rbxmx (NOT IMPLEMENTED) - -
+ make patch-sourcemaps # patches sourcemaps. needed after installing wally dependencies + ``` - -## Support - -### TODO - -- [ ] Upgrade ESLint to `9.*.*` version -- [ ] Better documentation +## Supported Components/Modifiers ### Components -- [X] Frame -- [X] ScrollableFrame -- [X] Button +- [ ] Frame +- [ ] ScrollableFrame +- [ ] Button - [ ] ImageButton -- [X] Image -- [X] Text -- [X] TextBox -- [X] CanvasGroup +- [ ] Image +- [ ] Text +- [ ] TextBox +- [ ] CanvasGroup ### Modifiers -- [X] UIAspectRatioConstraint -- [X] UICorner -- [X] UIGradient -- [X] UIGridLayout [(see GridLayout)](src/components/GridLayout.tsx) -- [X] UIListLayout [(see ListLayout)](src/components/ListLayout.tsx) -- [X] UIFlexLayout [(see ListLayout)](src/components/ListLayout.tsx) -- [X] UIPadding +- [ ] UIAspectRatioConstraint +- [ ] UICorner +- [ ] UIGradient +- [ ] UIGridLayout +- [ ] UIListLayout +- [ ] UIFlexLayout +- [ ] UIPadding - [ ] UIPageLayout -- [X] UIScale -- [X] UISizeConstraint -- [X] UIStroke +- [ ] UIScale +- [ ] UISizeConstraint +- [ ] UIStroke - [ ] UITableLayout -- [X] UITextSizeConstraint - -### Custom Modifiers - -- ~~[ ] FlowLayout [(view)](https://devforum.roblox.com/t/flow-flexbox-layout-for-lua/2614394)~~ (Removed due release of flex features to ListLayout. [Devforum](https://devforum.roblox.com/t/flex-features-for-uilistlayout-client-release/3096190) ) +- [ ] UITextSizeConstraint diff --git a/aftman.toml b/aftman.toml index e989d3b..74c521c 100644 --- a/aftman.toml +++ b/aftman.toml @@ -1,7 +1,4 @@ -# This file lists tools managed by Aftman, a cross-platform toolchain manager. -# For more information, see https://github.com/LPGhatguy/aftman - -# To add a new tool, add an entry to this table. [tools] -rojo = "rojo-rbx/rojo@7.4.1" -# rojo = "rojo-rbx/rojo@6.2.0" \ No newline at end of file +rojo = "rojo-rbx/rojo@7.4.4" +wally = "UpliftGames/wally@0.3.2" +wally-package-types = "JohnnyMorganz/wally-package-types@1.4.2" diff --git a/default.project.json b/default.project.json new file mode 100644 index 0000000..8a94842 --- /dev/null +++ b/default.project.json @@ -0,0 +1,6 @@ +{ + "name": "better-react-components", + "tree": { + "$path": "src" + } +} diff --git a/package.json b/package.json deleted file mode 100644 index ef9f84a..0000000 --- a/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "@rbxts/better-react-components", - "version": "2.9.1", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/idkncc/better-react-components.git" - }, - "bugs": { - "url": "https://github.com/idkncc/better-react-components/issues" - }, - "homepage": "https://github.com/idkncc/better-react-components#readme", - "main": "out/init.lua", - "typings": "out/index.d.ts", - "files": [ - "out", - "!out/*.tsbuildinfo", - "!out/stories/**/*" - ], - "scripts": { - "build": "pnpm clean && rbxtsc", - "dev": "rbxtsc -w --type game --rojo story.project.json", - "dev-serve": "rojo serve story.project.json", - "lint": "eslint src", - "clean": "rimraf out/" - }, - "devDependencies": { - "@rbxts/compiler-types": "3.0.0-types.0", - "@rbxts/types": "^1.0.813", - "rimraf": "^6.0.1", - "roblox-ts": "3.0.0", - "typescript": "^5.6.3" - }, - "dependencies": { - "@rbxts/object-utils": "^1.0.4", - "@rbxts/pretty-react-hooks": "^0.6.0", - "@rbxts/react": "^17.2.1", - "@rbxts/react-roblox": "^17.2.1", - "@rbxts/ripple": "^0.9.1", - "@rbxts/services": "^1.5.5" - }, - "publishConfig": { - "access": "public" - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index 73e6307..0000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,710 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@rbxts/object-utils': - specifier: ^1.0.4 - version: 1.0.4 - '@rbxts/pretty-react-hooks': - specifier: ^0.6.0 - version: 0.6.0 - '@rbxts/react': - specifier: ^17.2.1 - version: 17.2.1 - '@rbxts/react-roblox': - specifier: ^17.2.1 - version: 17.2.1 - '@rbxts/ripple': - specifier: ^0.9.1 - version: 0.9.1 - '@rbxts/services': - specifier: ^1.5.5 - version: 1.5.5 - devDependencies: - '@rbxts/compiler-types': - specifier: 3.0.0-types.0 - version: 3.0.0-types.0 - '@rbxts/types': - specifier: ^1.0.813 - version: 1.0.813 - rimraf: - specifier: ^6.0.1 - version: 6.0.1 - roblox-ts: - specifier: 3.0.0 - version: 3.0.0 - typescript: - specifier: ^5.6.3 - version: 5.6.3 - -packages: - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@rbxts/compiler-types@3.0.0-types.0': - resolution: {integrity: sha512-VGOHJPoL7+56NTatMGqQj3K7xWuzEV+aP4QD5vZiHu+bcff3kiTmtoadaF6NkJrmwfFAvbsd4Dg764ZjWNceag==} - - '@rbxts/object-utils@1.0.4': - resolution: {integrity: sha512-dLLhf022ipV+9i910sOE7kl9losKHoon0WgeerHqVMQA5EYsLUsVT2AxhJuhk8MiDn5oJ2GiFofE/LadY9TpJQ==} - - '@rbxts/pretty-react-hooks@0.6.0': - resolution: {integrity: sha512-KPaJ9xvbE6UJ1ECe4bwHBM3iqQ+IYcn0QfHjyRNZgADrz6/An+2zpxydY4ACjawAqRYybV7jwd6MqCJJiXHjOA==} - - '@rbxts/react-roblox@17.2.1': - resolution: {integrity: sha512-qKCz63iMl6fFq2SUXoMG6paAW8TGxsEyUwoRk6bojg7b3NzBqYMz5/eRcJsbPqMjwR56PWBhCOH3fdf4sOcFbw==} - - '@rbxts/react-vendor@17.2.1': - resolution: {integrity: sha512-7DxFweCbhhso4M4tl1pO7Kil9SZrvOl1SoZ4lnwUfoxdHvSmWgeKgaUqd+ZS3/u1E8kRraqGQ56Q8zjUEEUzlA==} - - '@rbxts/react@17.2.1': - resolution: {integrity: sha512-iu0RsVvfHroI7l+QXjwE0FiWV2rXoKMxuznDZ8vnx2BsFqrPnCqFjXuNeGYZwibibZC6jbyq7uKl+/8cz7XlsA==} - - '@rbxts/ripple@0.9.1': - resolution: {integrity: sha512-gLjKYy+mJO67NoN/9d7/8kBy6+SNG1/09GNNnh89DYWT5iHYdxC0/XKbKrKZpx7Fcsz5iY9Sl6vOvSnFwFbQuw==} - - '@rbxts/services@1.5.5': - resolution: {integrity: sha512-Hu7QH2ecefS60zpakKG3T2vgABydoOJqpSG7nXr59wFAbPk/cA+OcsntYPHUWyHAat0VFGZ5jNZLTLdiqfssvg==} - - '@rbxts/set-timeout@1.1.2': - resolution: {integrity: sha512-P/A0IiH9wuZdSJYr4Us0MDFm61nvIFR0acfKFHLkcOsgvIgELC90Up9ugiSsaMEHRIcIcO5UjE39LuS3xTzQHw==} - - '@rbxts/types@1.0.813': - resolution: {integrity: sha512-Qpsmqnl+TNdl5AYqKFkYHyUrv652r30qK6A3GObOyDz+cvn8i+HE61mO021oq0nu1FcA4pMHbYqLSbv2CMF9Ig==} - - '@roblox-ts/luau-ast@2.0.0': - resolution: {integrity: sha512-cmMi093IdwBOLVxwuordhM8AmtbyTIyRpsTbB0D/JauidW4SXsQRQowSwWjHo4QP0DRJBXvOIlxtqEQi50uNzQ==} - - '@roblox-ts/path-translator@1.1.0': - resolution: {integrity: sha512-D0akTmnNYqBw+ZIek5JxocT3BjmbgGOuOy0x1nIIxHBPNLGCpzseToY8jyYs/0mlvnN2xnSP/k8Tv+jvGOQSwQ==} - - '@roblox-ts/rojo-resolver@1.1.0': - resolution: {integrity: sha512-QmvVryu1EeME+3QUoG5j/gHGJoJUaffCgZ92mhlG7cJSd1uyhgpY4CNWriZAwZJYkTlzd5Htkpn+18yDFbOFXA==} - - ajv@8.17.1: - resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.1.0: - resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-uri@3.0.3: - resolution: {integrity: sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - foreground-child@3.3.0: - resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} - engines: {node: '>=14'} - - fs-extra@11.2.0: - resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} - engines: {node: '>=14.14'} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob@11.0.0: - resolution: {integrity: sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==} - engines: {node: 20 || >=22} - hasBin: true - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-core-module@2.15.1: - resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} - engines: {node: '>= 0.4'} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - jackspeak@4.0.2: - resolution: {integrity: sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==} - engines: {node: 20 || >=22} - - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - - jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - - kleur@4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} - - lru-cache@11.0.1: - resolution: {integrity: sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==} - engines: {node: 20 || >=22} - - minimatch@10.0.1: - resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} - engines: {node: 20 || >=22} - - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - path-scurry@2.0.0: - resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} - engines: {node: 20 || >=22} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - - resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} - hasBin: true - - rimraf@6.0.1: - resolution: {integrity: sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==} - engines: {node: 20 || >=22} - hasBin: true - - roblox-ts@3.0.0: - resolution: {integrity: sha512-hwAC2frIFlLJOtHd6F+5opMEhBgfAMK9z5l1mP+bykLBbMO5cn1q5lIwhhXbeh9Pq07rlhF8uGHlmeRLPd/3AA==} - hasBin: true - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - typescript@5.5.3: - resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} - engines: {node: '>=14.17'} - hasBin: true - - typescript@5.6.3: - resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} - engines: {node: '>=14.17'} - hasBin: true - - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - -snapshots: - - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - - '@rbxts/compiler-types@3.0.0-types.0': {} - - '@rbxts/object-utils@1.0.4': {} - - '@rbxts/pretty-react-hooks@0.6.0': - dependencies: - '@rbxts/react': 17.2.1 - '@rbxts/react-roblox': 17.2.1 - '@rbxts/ripple': 0.9.1 - '@rbxts/services': 1.5.5 - '@rbxts/set-timeout': 1.1.2 - - '@rbxts/react-roblox@17.2.1': - dependencies: - '@rbxts/react': 17.2.1 - '@rbxts/react-vendor': 17.2.1 - - '@rbxts/react-vendor@17.2.1': {} - - '@rbxts/react@17.2.1': - dependencies: - '@rbxts/react-vendor': 17.2.1 - - '@rbxts/ripple@0.9.1': {} - - '@rbxts/services@1.5.5': {} - - '@rbxts/set-timeout@1.1.2': - dependencies: - '@rbxts/services': 1.5.5 - - '@rbxts/types@1.0.813': {} - - '@roblox-ts/luau-ast@2.0.0': {} - - '@roblox-ts/path-translator@1.1.0': - dependencies: - ajv: 8.17.1 - fs-extra: 11.2.0 - - '@roblox-ts/rojo-resolver@1.1.0': - dependencies: - ajv: 8.17.1 - fs-extra: 11.2.0 - - ajv@8.17.1: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.0.3 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - - ansi-regex@5.0.1: {} - - ansi-regex@6.1.0: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@6.2.1: {} - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - - balanced-match@1.0.2: {} - - binary-extensions@2.3.0: {} - - brace-expansion@2.0.1: - dependencies: - balanced-match: 1.0.2 - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - - cliui@8.0.1: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - cross-spawn@7.0.3: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - eastasianwidth@0.2.0: {} - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - - escalade@3.2.0: {} - - fast-deep-equal@3.1.3: {} - - fast-uri@3.0.3: {} - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - foreground-child@3.3.0: - dependencies: - cross-spawn: 7.0.3 - signal-exit: 4.1.0 - - fs-extra@11.2.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.1 - - fsevents@2.3.3: - optional: true - - function-bind@1.1.2: {} - - get-caller-file@2.0.5: {} - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - glob@11.0.0: - dependencies: - foreground-child: 3.3.0 - jackspeak: 4.0.2 - minimatch: 10.0.1 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 2.0.0 - - graceful-fs@4.2.11: {} - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - - is-core-module@2.15.1: - dependencies: - hasown: 2.0.2 - - is-extglob@2.1.1: {} - - is-fullwidth-code-point@3.0.0: {} - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-number@7.0.0: {} - - isexe@2.0.0: {} - - jackspeak@4.0.2: - dependencies: - '@isaacs/cliui': 8.0.2 - - json-schema-traverse@1.0.0: {} - - jsonfile@6.1.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - - kleur@4.1.5: {} - - lru-cache@11.0.1: {} - - minimatch@10.0.1: - dependencies: - brace-expansion: 2.0.1 - - minipass@7.1.2: {} - - normalize-path@3.0.0: {} - - package-json-from-dist@1.0.1: {} - - path-key@3.1.1: {} - - path-parse@1.0.7: {} - - path-scurry@2.0.0: - dependencies: - lru-cache: 11.0.1 - minipass: 7.1.2 - - picomatch@2.3.1: {} - - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 - - require-directory@2.1.1: {} - - require-from-string@2.0.2: {} - - resolve@1.22.8: - dependencies: - is-core-module: 2.15.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - rimraf@6.0.1: - dependencies: - glob: 11.0.0 - package-json-from-dist: 1.0.1 - - roblox-ts@3.0.0: - dependencies: - '@roblox-ts/luau-ast': 2.0.0 - '@roblox-ts/path-translator': 1.1.0 - '@roblox-ts/rojo-resolver': 1.1.0 - chokidar: 3.6.0 - fs-extra: 11.2.0 - kleur: 4.1.5 - resolve: 1.22.8 - typescript: 5.5.3 - yargs: 17.7.2 - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - signal-exit@4.1.0: {} - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.0 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.1.0: - dependencies: - ansi-regex: 6.1.0 - - supports-preserve-symlinks-flag@1.0.0: {} - - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - typescript@5.5.3: {} - - typescript@5.6.3: {} - - universalify@2.0.1: {} - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.1 - string-width: 5.1.2 - strip-ansi: 7.1.0 - - y18n@5.0.8: {} - - yargs-parser@21.1.1: {} - - yargs@17.7.2: - dependencies: - cliui: 8.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 diff --git a/src/Builder.luau b/src/Builder.luau new file mode 100644 index 0000000..4a2735b --- /dev/null +++ b/src/Builder.luau @@ -0,0 +1,52 @@ +local Packages = script.Parent.Parent + +local React = require(Packages.react) +local Utils = require(script.Parent.Utils) + +local ComponentBuilder = {} +ComponentBuilder.__index = ComponentBuilder + +--- Transformer is a function, that transforms user-friendly props to component props and children nodes +export type Transformer = (props: P) -> (OP, { React.ReactNode }) + +export type ComponentBuilderData = { + transformer: Transformer, +} + +export type ComponentBuilder = typeof(setmetatable( + {} :: ComponentBuilderData, + ComponentBuilder +)) + +function ComponentBuilder.new(transformer: Transformer): ComponentBuilder + local self = { + transformer = transformer, + } + + return setmetatable(self, ComponentBuilder) +end + +--- Expand +function ComponentBuilder.expand( + self: ComponentBuilder, + transformer: Transformer +): ComponentBuilder

+ return ComponentBuilder.new(function(props: P & NP) + local rbxProps, rbxChildren = self.transformer(props) + local rbxProps2, rbxChildren2 = transformer(props) + + return Utils.Assign(rbxProps, rbxProps2), Utils.Assign(rbxChildren, rbxChildren2) + end) +end + +function ComponentBuilder.build( + self: ComponentBuilder, + robloxComponent: string +): (props: P) -> any + return React.forwardRef(function(props: React.ElementProps & P) + local outputProps, outputChildren = self.transformer(props) + + return React.createElement(robloxComponent, outputProps, outputChildren, props.children) + end) +end +return ComponentBuilder diff --git a/src/Builders/FrameBuilder.luau b/src/Builders/FrameBuilder.luau new file mode 100644 index 0000000..32a6920 --- /dev/null +++ b/src/Builders/FrameBuilder.luau @@ -0,0 +1,10 @@ +local Packages = script.Parent.Parent.Parent + +local React = require(Packages.react) +local ComponentBuilder = require(script.Parent.Parent.Builder) +local Utils = require(script.Parent.Parent.Utils) +local GuiObjectBuilder = require(script.Parent.GuiObjectBuilder) + +export type FrameProps = {} + +return GuiObjectBuilder -- frame doesn't add any new properties, so passthrough diff --git a/src/Builders/GuiObjectBuilder.luau b/src/Builders/GuiObjectBuilder.luau new file mode 100644 index 0000000..1919a9c --- /dev/null +++ b/src/Builders/GuiObjectBuilder.luau @@ -0,0 +1,56 @@ +local Packages = script.Parent.Parent.Parent + +local React = require(Packages.react) +local ComponentBuilder = require(script.Parent.Parent.Builder) +local Utils = require(script.Parent.Parent.Utils) + +--- Border settings +--- +--- NOTE: when building, it uses `UIStroke`, instead of `GuiObject`'s border properties +export type Border = { + BorderColor: Utils.Color?, +} + +export type GuiObjectProps = { + AnchorPoint: Utils.AnchorPoint?, + AutomaticSize: Enum.AutomaticSize?, + BackgroundColor: Utils.Color?, + BackgroundTransparency: number?, + + --- Alias for `BackgroundTransparency = 1` + NoBackground: boolean?, + + Position: UDim2?, + Size: UDim2?, + + SizeConstraint: Enum.SizeConstraint?, + Visible: boolean?, + + LayoutOrder: number?, + ZIndex: number?, + + Border: Border?, +} + +return ComponentBuilder.new(function(props: GuiObjectProps) + return { + AnchorPoint = props.AnchorPoint and Utils.ConvertAnchorPoint(props.AnchorPoint), + AutomaticSize = props.AutomaticSize, + + BackgroundColor3 = props.BackgroundColor and Utils.ConvertColor(props.BackgroundColor), + BackgroundTransparency = props.NoBackground and 1 or props.BackgroundTransparency, + + Position = props.Position, + Size = props.Size, + + SizeConstraint = props.SizeConstraint, + LayoutOrder = props.LayoutOrder, + ZIndex = props.ZIndex, + }, { + Border = props.border + and React.createElement("UIBorder", { + Color = props.Border.BorderColor and Utils.ConvertColor(props.Border.BorderColor), + Thickness = props.Border.Thickness, + }), + } +end) diff --git a/src/Builders/TextBuilder.luau b/src/Builders/TextBuilder.luau new file mode 100644 index 0000000..46c178d --- /dev/null +++ b/src/Builders/TextBuilder.luau @@ -0,0 +1,39 @@ +local Packages = script.Parent.Parent.Parent + +local React = require(Packages.react) +local ComponentBuilder = require(script.Parent.Parent.Builder) +local Utils = require(script.Parent.Parent.Utils) +local GuiObjectBuilder = require(script.Parent.GuiObjectBuilder) + +export type TextProps = { + Text: string?, + + Font: Enum.Font?, + LineHeight: number?, + RichText: boolean?, + TextColor: Utils.Color?, + TextSize: Utils.TextSize?, + TextWrapped: boolean?, + + TextXAlignment: Enum.TextXAlignment?, + TextYAlignment: Enum.TextYAlignment?, +} + +--- Builder for text-based objects (`TextLabel`, `TextButton`, etc.) +return GuiObjectBuilder:expand(function(props: TextProps) + return { + Text = props.Text, + + FontFace = props.Font, + LineHeight = props.LineHeight, + RichText = props.RichText, + TextColor3 = Utils.ConvertColor(props.TextColor), + TextWrapped = props.TextWrapped, + + TextSize = props.TextSize == "auto" and 1 or props.TextSize, + TextScaled = props.TextSize == "auto", + + TextXAlignment = props.TextXAlignment, + TextYAlignment = props.TextYAlignment, + }, {} +end) diff --git a/src/Components/Frame.luau b/src/Components/Frame.luau new file mode 100644 index 0000000..91cdbef --- /dev/null +++ b/src/Components/Frame.luau @@ -0,0 +1,3 @@ +local FrameBuilder = require(script.Parent.Parent.Builders.FrameBuilder) + +return FrameBuilder:build("Frame") diff --git a/src/Utils.luau b/src/Utils.luau new file mode 100644 index 0000000..08ce8b8 --- /dev/null +++ b/src/Utils.luau @@ -0,0 +1,65 @@ +local Utils = {} + +--- User-friendly anchor points +--- +--- center +--- left | right +--- | | | +--- `tl`, `t`, `tr` – top +--- `ml`, `m`, `mr` – middle +--- `bl`, `b`, `br` – bottom +export type AnchorPoint = "tl" | "t" | "tr" | "ml" | "m" | "mr" | "bl" | "b" | "br" | Vector2 + +--- Color +--- +--- It's either `Color3` or hexcode of color (e.g. `#FF0000`) +export type Color = Color3 | string + +--- Text Size +--- +--- `"auto"` enables `TextScaled` property. Otherwise sets TextSize +export type TextSize = number | "auto" + +local AnchorPointMapping = setmetatable({ + tl = Vector2.new(0, 0), + t = Vector2.new(0.5, 0), + tr = Vector2.new(1, 0), + + ml = Vector2.new(0, 0.5), + m = Vector2.new(0.5, 0.5), + mr = Vector2.new(1, 0.5), + + bl = Vector2.new(0, 1), + b = Vector2.new(0.5, 1), + br = Vector2.new(1, 1), +}, { + __index = function() + error("Invalid anchor point.") + end, +}) + +function Utils.Assign(table1: T1, table2: T2): T1 & T2 + for k, v in pairs(table2) do + table1[k] = v + end + + return table1 +end + +function Utils.ConvertColor(value: Color3 | string): Color3 + if typeof(value) == "string" then + return Color3.fromHex(value) + else + return value + end +end + +function Utils.ConvertAnchorPoint(value: AnchorPoint): Vector2 + if typeof(value) == "Vector2" then + return value + end + + return AnchorPointMapping[value] +end + +return Utils diff --git a/src/components/Button.ts b/src/components/Button.ts deleted file mode 100644 index 0d7ca33..0000000 --- a/src/components/Button.ts +++ /dev/null @@ -1,14 +0,0 @@ -import TextComponent, { TextComponentProps } from "../helpers/TextComponent"; - -type ButtonProps = TextComponentProps & { - autoButtonColor: boolean -} - -export const ButtonComponent = TextComponent - .expand( - (props) => ({ - AutoButtonColor: props.autoButtonColor, - }), - ); - -export const Button = ButtonComponent.build("textbutton"); diff --git a/src/components/CanvasGroup.ts b/src/components/CanvasGroup.ts deleted file mode 100644 index 56146cc..0000000 --- a/src/components/CanvasGroup.ts +++ /dev/null @@ -1,30 +0,0 @@ -import BaseComponent from "../helpers/BaseComponent"; -import { ColorOrHex, resolveColor3 } from "../utils"; - -export interface CanvasGroupProps { - /** - * Color tint that applies to all descendants. - * - * `GroupColor3` property in Roblox - */ - tintColor?: ColorOrHex, - - /** - * Transparency that applies to all descendants. - * - * `GroupTransparency` property in Roblox - */ - opacity?: number -} - -/** - * @see https://create.roblox.com/docs/reference/engine/classes/CanvasGroup - */ -export const CanvasGroup = BaseComponent - .expand( - (props) => ({ - GroupColor3: resolveColor3(props.tintColor), - GroupTransparency: props.opacity - }), - ) - .build("canvasgroup"); diff --git a/src/components/Frame.ts b/src/components/Frame.ts deleted file mode 100644 index 9b8f740..0000000 --- a/src/components/Frame.ts +++ /dev/null @@ -1,3 +0,0 @@ -import BaseComponent from "../helpers/BaseComponent"; - -export const Frame = BaseComponent.build("frame"); diff --git a/src/components/GridLayout.tsx b/src/components/GridLayout.tsx deleted file mode 100644 index ee3eda6..0000000 --- a/src/components/GridLayout.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from "@rbxts/react"; - -import type { BindingVariants } from "../utils"; - -export type GridLayoutProps = BindingVariants<{ - cellPadding?: UDim2 - cellSize?: UDim2 - cellAspectRatio?: number - cellAspectType?: Enum.AspectType - cellAspectAxis?: Enum.DominantAxis - startCorner?: Enum.StartCorner - direction?: Enum.FillDirection - horizontalAlign?: Enum.HorizontalAlignment - verticalAlign?: Enum.VerticalAlignment, - sortOrder?: Enum.SortOrder; -}> - -export function GridLayout(props: GridLayoutProps) { - return ( - - {props.cellAspectRatio !== undefined - ? - : undefined} - - ); -} diff --git a/src/components/Image.ts b/src/components/Image.ts deleted file mode 100644 index 82e20e0..0000000 --- a/src/components/Image.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { BaseComponent } from "../helpers"; - -import { ColorOrHex, resolveColor3 } from "../utils"; -import { mapBinding } from "@rbxts/pretty-react-hooks"; - -export interface ImageRect { - offset: Vector2; - size: Vector2; -} - -export interface ImageProps { - image?: string, - imageColor?: ColorOrHex, - imageTransparency?: number, - imageRect?: ImageRect -} - -export const ImageComponent = BaseComponent - .expand((props) => ({ - Image: props.image, - ImageColor3: resolveColor3(props.imageColor), - ImageTransparency: props.imageTransparency, - - ImageRectOffset: mapBinding(props.imageRect, (rect) => rect?.offset ?? new Vector2()), - ImageRectSize: mapBinding(props.imageRect, (rect) => rect?.size ?? new Vector2()), - })); - -export const Image = ImageComponent.build("imagelabel"); diff --git a/src/components/ListLayout.tsx b/src/components/ListLayout.tsx deleted file mode 100644 index 77eeb82..0000000 --- a/src/components/ListLayout.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from "@rbxts/react"; - -import { BindingVariants, resolveUDim } from "../utils"; - -export type ListLayoutProps = BindingVariants<{ - padding?: number | UDim - direction?: Enum.FillDirection - order?: Enum.SortOrder - horizontalAlign?: Enum.HorizontalAlignment - verticalAlign?: Enum.VerticalAlignment - - - flexWrap?: boolean - flexAlignX?: Enum.UIFlexAlignment - flexAlignY?: Enum.UIFlexAlignment - flexAlignItems?: Enum.ItemLineAlignment -}> - -export function ListLayout(props: ListLayoutProps) { - return ( - - ); -} diff --git a/src/components/ScrollingFrame.ts b/src/components/ScrollingFrame.ts deleted file mode 100644 index e84d9b0..0000000 --- a/src/components/ScrollingFrame.ts +++ /dev/null @@ -1,76 +0,0 @@ -import Object from "@rbxts/object-utils"; -import BaseComponent from "../helpers/BaseComponent"; - -import { resolveColor3 } from "../utils"; -import { getBindingValue, mapBinding } from "@rbxts/pretty-react-hooks"; - -import type { InstanceAttributes } from "@rbxts/react"; - -export type ScrollingFrameProps = { - automaticCanvasSize?: Enum.AutomaticSize - - canvasPosition?: Vector2 - canvasSize?: UDim2 - - direction?: Enum.ScrollingDirection - - /* Horizontal Scrollbar inset */ - scrollbarInsetH?: Enum.ScrollBarInset | boolean - - /* Vertical Scrollbar inset */ - scrollbarInsetV?: Enum.ScrollBarInset | boolean - - scrollbar?: Scrollbar | false -} - -export type Scrollbar = { - topImage?: string - midImage?: string - bottomImage?: string - - imageColor?: Color3 | string - imageTransparency?: number -} - -export const ScrollingFrameComponent = BaseComponent - .expand( - (props) => Object.assign( - { - AutomaticCanvasSize: props.automaticCanvasSize, - - CanvasPosition: props.canvasPosition, - CanvasSize: props.canvasSize, - - HorizontalScrollBarInset: mapBinding( - props.scrollbarInsetH, - (scrollbarInsetH) => - typeIs(scrollbarInsetH, "boolean") && scrollbarInsetH - ? Enum.ScrollBarInset.Always - : (scrollbarInsetH as Enum.ScrollBarInset) || Enum.ScrollBarInset.None, - ), - VerticalScrollBarInset: mapBinding( - props.scrollbarInsetV, - (scrollbarInsetV) => - typeIs(scrollbarInsetV, "boolean") && scrollbarInsetV - ? Enum.ScrollBarInset.Always - : (scrollbarInsetV as Enum.ScrollBarInset) || Enum.ScrollBarInset.None, - ), - - ScrollingDirection: props.direction, - ScrollingEnabled: mapBinding(props.scrollbar, (scrollbar) => !typeIs(scrollbar, "boolean")), - } as InstanceAttributes, - - // scrollbar settings - { - TopImage: mapBinding(props.scrollbar, (scrollbar) => scrollbar && scrollbar.topImage), - MidImage: mapBinding(props.scrollbar, (scrollbar) => scrollbar && scrollbar.midImage), - BottomImage: mapBinding(props.scrollbar, (scrollbar) => scrollbar && scrollbar.bottomImage), - - ScrollBarImageColor3: mapBinding(props.scrollbar, (scrollbar) => scrollbar && getBindingValue(resolveColor3(scrollbar.imageColor))), - ScrollBarImageTransparency: mapBinding(props.scrollbar, (scrollbar) => scrollbar && scrollbar.imageTransparency), - - } as InstanceAttributes, - ), - ); - -export const ScrollingFrame = ScrollingFrameComponent.build("scrollingframe"); diff --git a/src/components/Text.ts b/src/components/Text.ts deleted file mode 100644 index 77a345d..0000000 --- a/src/components/Text.ts +++ /dev/null @@ -1,5 +0,0 @@ -import TextComponent, { TextComponentProps } from "../helpers/TextComponent"; - -export const Text = TextComponent - .expand>() - .build("textlabel"); diff --git a/src/components/TextBox.ts b/src/components/TextBox.ts deleted file mode 100644 index d54784c..0000000 --- a/src/components/TextBox.ts +++ /dev/null @@ -1,24 +0,0 @@ -import TextComponent, { TextComponentProps } from "../helpers/TextComponent"; - -import { resolveColor3 } from "../utils"; - -type TextBoxProps = { - placeholder?: string - placeholderColor?: Color3 | string, - - textEditable?: boolean, - clearTextOnFocus?: boolean -} - -export const TextBoxComponent = TextComponent - .expand & TextBoxProps>( - (props) => ({ - PlaceholderText: props.placeholder, - PlaceholderColor3: resolveColor3(props.placeholderColor), - - TextEditable: props.textEditable, - ClearTextOnFocus: props.clearTextOnFocus, - }), - ); - -export const TextBox = TextBoxComponent.build("textbox"); \ No newline at end of file diff --git a/src/helpers/BaseComponent.tsx b/src/helpers/BaseComponent.tsx deleted file mode 100644 index 818fb50..0000000 --- a/src/helpers/BaseComponent.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import React from "@rbxts/react"; -import ExpandableComponent from "./ExpandableComponent"; - -import { ColorOrHex, ResolvableAnchorPoint, resolveAnchorPoint, resolveUDim } from "../utils"; -import { getBaseColor, Gradient, GradientElement } from "./Gradient"; - -import type { InstanceProps } from "@rbxts/react"; - -export type BaseProps = { - visible?: boolean, - - noBackground?: boolean - background?: Gradient | ColorOrHex - backgroundTransparency?: number - gradientRotation: number - - border?: ColorOrHex | Gradient - borderGradientRotation?: number - borderMode?: Enum.ApplyStrokeMode - borderSize?: number - borderLineJoinMode?: Enum.LineJoinMode - stroke?: InstanceProps - - position?: UDim2 - size?: UDim2, - anchorPoint?: ResolvableAnchorPoint - automaticSize?: Enum.AutomaticSize - - cornerRadius?: number | UDim - - aspectRatio?: number - aspectType?: Enum.AspectType - aspectAxis?: Enum.DominantAxis - - padding?: number | UDim - paddingLeft?: number | UDim - paddingRight?: number | UDim - paddingTop?: number | UDim - paddingBottom?: number | UDim - - minSize?: Vector2 - maxSize?: Vector2 - - minTextSize?: number - maxTextSize?: number - - scale?: number, - - layoutOrder?: number - zIndex?: number - sizeConstraint?: Enum.SizeConstraint - - // Flex - flex?: InstanceProps - flexMode?: Enum.UIFlexMode -} - -export default new ExpandableComponent() - .expand( - // most basic props: - (userProps) => ({ - Visible: userProps.visible, - - BackgroundColor3: getBaseColor(userProps.background), - BackgroundTransparency: userProps.noBackground ? 1 : (userProps.backgroundTransparency ?? 0), - - AutomaticSize: userProps.automaticSize, - - Position: userProps.position, - Size: userProps.size, - AnchorPoint: resolveAnchorPoint(userProps.anchorPoint ?? new Vector2(0, 0)), - - BorderSizePixel: 0, // we use UIStroke instead - - LayoutOrder: userProps.layoutOrder, - ZIndex: userProps.zIndex, - SizeConstraint: userProps.sizeConstraint, - }), - - // most basic modifiers: - (userProps) => [ - userProps.cornerRadius !== undefined - ? - : undefined, - - // Aspect Ratio - userProps.aspectRatio !== undefined - ? - : undefined, - - // Stroke (border) - userProps.stroke !== undefined - || userProps.border !== undefined || userProps.borderSize !== undefined || userProps.borderLineJoinMode !== undefined - ? ( - - - - ) - : undefined, - - // Padding - userProps.padding !== undefined - || userProps.paddingLeft !== undefined || userProps.paddingRight !== undefined - || userProps.paddingTop !== undefined || userProps.paddingBottom !== undefined - ? - : undefined, - - // Size constraint - userProps.minSize !== undefined || userProps.maxSize !== undefined - ? - : undefined, - - // Text Size constraint - userProps.minTextSize !== undefined || userProps.maxTextSize !== undefined - ? - : undefined, - - // Scale - userProps.scale !== undefined - ? - : undefined, - - // Flex item - userProps.flex !== undefined || userProps.flexMode !== undefined - ? - : undefined, - - , - ], - ); diff --git a/src/helpers/CalculateAspectRatio.ts b/src/helpers/CalculateAspectRatio.ts deleted file mode 100644 index 59f05d0..0000000 --- a/src/helpers/CalculateAspectRatio.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Pass this function to GuiObject as ref to calculate aspect ratio - * - * @example - * ```tsx - * - * ... - * - * - * // In Output (some fraction): - * // [CALC_ASPECT_RATIO](1): .875 - * ``` - */ -export function calculateAspectRatio(element: unknown) { - if (typeIs(element, "Instance") && element.IsA("GuiObject")) { - const sizes = (element as GuiObject).AbsoluteSize; - const name = (element as GuiObject).Name; - - print(`[CALC_ASPECT_RATIO](${name}): ${sizes.X / sizes.Y}`); - } else { - error(`[CALC_ASPECT_RATIO]: Passed invalid argument. GuiObject expected, got ${typeOf(element)}`) - } -} diff --git a/src/helpers/ExpandableComponent.ts b/src/helpers/ExpandableComponent.ts deleted file mode 100644 index 0700bac..0000000 --- a/src/helpers/ExpandableComponent.ts +++ /dev/null @@ -1,109 +0,0 @@ -import React, { ForwardedRef, InstanceProps, ReactChild, ReactNode } from "@rbxts/react"; -import Object from "@rbxts/object-utils"; - -import { BindingVariants as BindingVariantsUtils, flat, ReactProps } from "../utils"; - -/** @deprecated import from `../utils`, not from here */ -export type BindingVariants = BindingVariantsUtils - -type PropBuilder

= (userProps: BindingVariantsUtils

, context: C) => InstanceProps -type ChildrenBuilder

= (userProps: BindingVariantsUtils

, context: C) => ReactNode[] -type ContextBuilder

= (userProps: BindingVariantsUtils

) => C - -/** - * Expandable component - * - * Generics: - * - I: instance - * - P: user props - * - C: context (passed in builders) - */ -export default class ExpandableComponent { - private propsBuilders: PropBuilder[]; - private childrenBuilders: ChildrenBuilder[]; - private contextBuilders: ContextBuilder[]; - - constructor( - propsBuilders: PropBuilder[] = [], - childrenBuilders: ChildrenBuilder[] = [], - contextBuilders: ContextBuilder[] = [], - ) { - this.propsBuilders = propsBuilders; - this.childrenBuilders = childrenBuilders; - this.contextBuilders = contextBuilders; - } - - expandContext( - builder: ContextBuilder

, - ): ExpandableComponent { - return new ExpandableComponent( - this.propsBuilders, - this.childrenBuilders, - [...this.contextBuilders, builder] as ContextBuilder

[], - ); - } - - expand( - propBuilder?: PropBuilder

, - childrenBuilder?: ChildrenBuilder

, - ): ExpandableComponent { - return new ExpandableComponent( - [...this.propsBuilders, propBuilder] as PropBuilder

[], - [...this.childrenBuilders, childrenBuilder] as ChildrenBuilder

[], - this.contextBuilders, - ); - } - - build(elementType: string) { - return React.forwardRef((userProps: BindingVariantsUtils

>, ref) => { - const context = this.buildContext(userProps); - const props = this.buildProps(userProps, context, ref); - const children = this.buildChildren(userProps, context); - - return React.createElement(elementType, props, ...children); - }); - } - - private buildContext(userProps: BindingVariantsUtils

>): C { - return Object.assign( - {}, - ...this.contextBuilders - .map((build) => build(userProps)), - ); - } - - private buildProps(userProps: BindingVariantsUtils

>, context: C, ref: ForwardedRef): InstanceProps { - const builtProps: InstanceProps[] = []; - - for (const build of this.propsBuilders) { - const props = build(userProps, context); - if (props !== undefined) { - builtProps.push(props); - } - } - - return Object.assign( - Object.assign( - {}, - ...builtProps, - ), - { - Event: userProps.event, - Change: userProps.change, - Tag: userProps.tag, - ref: ref, - }, - userProps.overrideRoblox as object, - ); - } - - private buildChildren(userProps: BindingVariantsUtils

>, context: C): React.ReactNode[] { - const children = flat( - this.childrenBuilders - .map((build) => build(userProps, context).filterUndefined()), - ); - if (userProps.children !== undefined) children.push(userProps.children as ReactChild); - - return children; - } -} diff --git a/src/helpers/Gradient.tsx b/src/helpers/Gradient.tsx deleted file mode 100644 index 6dd1d7a..0000000 --- a/src/helpers/Gradient.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React, { Binding } from "@rbxts/react"; - -import { ColorOrHex, resolveColor3 } from "../utils"; - -import { BindingOrValue, getBindingValue, mapBinding } from "@rbxts/pretty-react-hooks"; - -export type Gradient = Array | Array | ColorSequence - -export function createColorSequence(value: Gradient): ColorSequence { - if (typeIs(value, "ColorSequence")) { - return value as ColorSequence; - } else if (typeIs(value[0], "ColorSequenceKeypoint")) { - return new ColorSequence(value as Array); - } else { - if (value.size() === 1) value = [value[0], value[0]]; - - const keypointsCount = value.size(); - const step = 1 / (keypointsCount - 1); - - const seq: Array = []; - let j = 0; - for (let i = 0; i <= 1; i += step) { - seq.push(new ColorSequenceKeypoint( - i, - getBindingValue(resolveColor3(value[j] as ColorOrHex)) ?? new Color3(), - )); - j++; - } - - return new ColorSequence(seq); - } -} - -export type GradientElementProps = { - color?: BindingOrValue - rotation?: BindingOrValue -} - -export function getBaseColor(color: BindingOrValue): Binding | undefined { - if (typeIs(color, "nil")) return; - - return mapBinding( - color, - (value) => - typeIs(value, "string") || typeIs(value, "Color3") || typeIs(value, "nil") - ? getBindingValue(resolveColor3(value))! // regular color - : Color3.fromHex("#FFFFFF"), // gradient - ); -} - -export function GradientElement(props: GradientElementProps) { - const color = getBindingValue(props.color); - - return ( - !typeIs(color, "string") && !typeIs(color, "Color3") && !typeIs(color, "nil") - // if not single color, make gradient - ? , createColorSequence)} - Rotation={props.rotation} - /> - : <> - ); -} diff --git a/src/helpers/TextComponent.ts b/src/helpers/TextComponent.ts deleted file mode 100644 index 9dfefb0..0000000 --- a/src/helpers/TextComponent.ts +++ /dev/null @@ -1,62 +0,0 @@ -import BaseComponent from "./BaseComponent"; - -import { ColorOrHex, resolveColor3 } from "../utils"; -import { mapBinding } from "@rbxts/pretty-react-hooks"; - -import type { InferEnumNames, InstanceEvent } from "@rbxts/react"; - -export type TextComponentInstance = TextLabel | TextButton | TextBox -export type TextComponentProps = { - text?: string, - textColor?: ColorOrHex - - textSize?: number | "AUTO" - - font?: Enum.Font | Font - richText?: boolean - - textAlign?: Enum.TextXAlignment - verticalTextAlign?: Enum.TextYAlignment - - /** @deprecated use `textAlign` (WILL BE REMOVED IN NEXT MAJOR VERSION!) */ - align?: Enum.TextXAlignment - /** @deprecated use `verticalTextAlign` (WILL BE REMOVED IN NEXT MAJOR VERSION!) */ - verticalAlign?: Enum.TextYAlignment - - event?: InstanceEvent -} - -export default BaseComponent - .expand( - (props) => ({ - Text: props.text, - TextSize: mapBinding( - props.textSize, - (size) => - typeIs(size, "number") - ? size - : 0, - ), - TextScaled: mapBinding( - props.textSize, - (size) => size === "AUTO", - ), - TextColor3: resolveColor3(props.textColor), - - FontFace: mapBinding( - props.font, - (font) => - typeIs(font, "nil") - ? Font.fromEnum(Enum.Font.Legacy) - : typeIs(font, "Font") - ? font // font - : typeIs(font, "string") - ? Font.fromEnum(Enum.Font[font as InferEnumNames]) // font enum key - : Font.fromEnum(font), // font enum - ), - RichText: props.richText, - - TextXAlignment: props.textAlign ?? props.align, - TextYAlignment: props.verticalTextAlign ?? props.verticalAlign, - }), - ); diff --git a/src/helpers/index.ts b/src/helpers/index.ts deleted file mode 100644 index 2ef6369..0000000 --- a/src/helpers/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { calculateAspectRatio } from "./CalculateAspectRatio"; - -import ExpandableComponent from "./ExpandableComponent"; - -import BaseComponent from "./BaseComponent"; -import TextComponent from "./TextComponent"; - -export { calculateAspectRatio } -export { ExpandableComponent, BaseComponent, TextComponent }; -export * as Gradient from "./Gradient"; diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 5aa37d8..0000000 --- a/src/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -export { Button, ButtonComponent } from "./components/Button"; -export { CanvasGroup } from "./components/CanvasGroup"; -export { Frame } from "./components/Frame"; -export { ScrollingFrame } from "./components/ScrollingFrame"; -export { Image, ImageRect, ImageProps, ImageComponent } from "./components/Image"; -export { Text } from "./components/Text"; -export { TextBox, TextBoxComponent } from "./components/TextBox"; - -export { GridLayout } from "./components/GridLayout"; -export { ListLayout } from "./components/ListLayout"; - -export * from "./utils"; -export * as Helpers from "./helpers"; diff --git a/src/init.luau b/src/init.luau new file mode 100644 index 0000000..6cf4348 --- /dev/null +++ b/src/init.luau @@ -0,0 +1,5 @@ +return { + hello = function() + print("Hello world, from better-react-components!") + end, +} diff --git a/src/stories/App.ts b/src/stories/App.ts deleted file mode 100644 index 5cdd42c..0000000 --- a/src/stories/App.ts +++ /dev/null @@ -1,14 +0,0 @@ -import React from "@rbxts/react"; - -import { Frame } from ".."; // @rbxts/better-react-components - -declare const _G: Record; -_G.__DEV__ = true; - -export function App(props: { children: React.ReactNode }) { - return React.createElement( - Frame, - { size: UDim2.fromScale(1, 1), noBackground: true }, - props.children, - ); -} diff --git a/src/stories/Frame.story.luau b/src/stories/Frame.story.luau new file mode 100644 index 0000000..d97e228 --- /dev/null +++ b/src/stories/Frame.story.luau @@ -0,0 +1,39 @@ +local Packages = game:GetService("ReplicatedStorage").Packages +local DevPackages = game:GetService("ReplicatedStorage").DevPackages + +local React = require(Packages.react) +local ReactRoblox = require(Packages["react-roblox"]) +local UILabs = require(DevPackages["ui-labs"]) + +local Frame = require(script.Parent.Parent.Components.Frame) + +local story = { + react = React, + reactRoblox = ReactRoblox, + controls = { + AnchorPoint = UILabs.EnumList({ + TopLeft = "tl", + TopCenter = "t", + TopRight = "tr", + MiddleLeft = "ml", + MiddleCenter = "m", + MiddleRight = "mr", + BottomLeft = "bl", + BottomCenter = "b", + BottomRight = "br", + }, "MiddleCenter"), + BackgroundColor = Color3.fromHex("#FF0000"), + CornerRadius = 0 + }, + story = function(props) + return React.createElement(Frame, { + AnchorPoint = props.controls.AnchorPoint, + Position = UDim2.new(0.5, 0, 0.5, 0), + Size = UDim2.new(0.5, 0, 0.5, 0), + BackgroundColor = props.controls.BackgroundColor, + -- CornerRadius = props.controls.CornerRadius + }) + end, +} + +return story diff --git a/src/stories/basics/1_helloworld.story.tsx b/src/stories/basics/1_helloworld.story.tsx deleted file mode 100644 index 0da1176..0000000 --- a/src/stories/basics/1_helloworld.story.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from "@rbxts/react"; - -import { Text } from "../.."; // @rbxts/better-react-components -import { App } from "../App"; - -import { hoarcekat } from "@rbxts/pretty-react-hooks"; -import { CatppuccinLatte } from "../utils"; - -export = hoarcekat(() => { - return ( - - - - ); -}); diff --git a/src/stories/basics/2_padding.story.tsx b/src/stories/basics/2_padding.story.tsx deleted file mode 100644 index 7e209ae..0000000 --- a/src/stories/basics/2_padding.story.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from "@rbxts/react"; - -import { AnchorPoints, Frame, Text } from "../.."; // @rbxts/better-react-components - -import { hoarcekat } from "@rbxts/pretty-react-hooks"; -import { App } from "../App"; - -export = hoarcekat(() => { - return ( - - - - - - - - ); -}); diff --git a/src/stories/basics/3_cornerRadius.story.tsx b/src/stories/basics/3_cornerRadius.story.tsx deleted file mode 100644 index 25ff5f1..0000000 --- a/src/stories/basics/3_cornerRadius.story.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from "@rbxts/react"; - -import { AnchorPoints, Frame } from "../.."; // @rbxts/better-react-components -import { hoarcekat } from "@rbxts/pretty-react-hooks"; -import { App } from "../App"; - -export = hoarcekat(() => { - return ( - - - - ); -}); diff --git a/src/stories/basics/5_flexItem.story.tsx b/src/stories/basics/5_flexItem.story.tsx deleted file mode 100644 index b371551..0000000 --- a/src/stories/basics/5_flexItem.story.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React, { useBinding } from "@rbxts/react"; - -import { Frame, Helpers, ListLayout } from "../../index"; // @rbxts/better-react-components -import { hoarcekat } from "@rbxts/pretty-react-hooks"; -import { App } from "../App"; - -// const { calculateAspectRatio } = Helpers; - -export = hoarcekat(() => { - return ( - - - - - - - - - ); -}) diff --git a/src/stories/examples/sidebar.story.tsx b/src/stories/examples/sidebar.story.tsx deleted file mode 100644 index d749c09..0000000 --- a/src/stories/examples/sidebar.story.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import React from "@rbxts/react"; - -import { AnchorPoints, Button, Frame, ListLayout } from "../.."; // @rbxts/better-react-components -import { App } from "../App"; - -import { hoarcekat } from "@rbxts/pretty-react-hooks"; -import { CatppuccinLatte } from "../utils"; - -function CoolButton(props: { text: string, onClick: () => void }) { - return ( - - ); -} - -export = hoarcekat(() => { - return ( - - - - {/* Array of colors (timed automatically) */} - - - - - - - - {/* Array of keypoints */} - - - {/* Sequence */} - - - - - - - ); -}) \ No newline at end of file diff --git a/src/stories/tests/refs.story.tsx b/src/stories/tests/refs.story.tsx deleted file mode 100644 index a9c04a7..0000000 --- a/src/stories/tests/refs.story.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React, { useEffect, useRef, useState } from "@rbxts/react"; - -import { AnchorPoints, Text } from "../.."; // @rbxts/better-react-components -import { App } from "../App"; - -import { hoarcekat } from "@rbxts/pretty-react-hooks"; - -export = hoarcekat(() => { - const [textSizes, setTextSizes] = useState(new Vector2()); - const textRef = useRef(); - - useEffect(() => { - if (!textRef.current) return; - - const connection = textRef.current.GetPropertyChangedSignal("AbsoluteSize") - .Connect(updateSizes); - - updateSizes(); - return () => connection.Disconnect(); - }, [textRef]); - - function updateSizes() { - if (!textRef.current) return; - - setTextSizes(textRef.current!.AbsoluteSize.Floor()); - } - - return ( - - - - ); -}) diff --git a/src/stories/utils.ts b/src/stories/utils.ts deleted file mode 100644 index 67cfdb8..0000000 --- a/src/stories/utils.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { useEventListener } from "@rbxts/pretty-react-hooks"; -import { createMotion, Motion, MotionGoal } from "@rbxts/ripple"; -import { Binding, useBinding, useMemo } from "@rbxts/react"; -import { RunService } from "@rbxts/services"; - -export function useMotion(initialValue: number): LuaTuple<[Binding, Motion]>; - -export function useMotion(initialValue: T): LuaTuple<[Binding, Motion]>; - -export function useMotion(initialValue: T) { - const motion = useMemo(() => { - return createMotion(initialValue); - }, []); - - const [binding, setValue] = useBinding(initialValue); - - useEventListener(RunService.Heartbeat, (delta) => { - const value = motion.step(delta); - - if (value !== binding.getValue()) { - setValue(value); - } - }); - - return $tuple(binding, motion); -} - - -/** - * @param color The color to brighten or darken - * @param brightness The amount to brighten or darken the color - * @param vibrancy How much saturation changes with brightness - */ -export function brighten(color: Color3, brightness: number, vibrancy = 0.5) { - const [h, s, v] = color.ToHSV(); - return Color3.fromHSV(h, math.clamp(s - brightness * vibrancy, 0, 1), math.clamp(v + brightness, 0, 1)); -} - -export const CatppuccinLatte = { - Rosewater: new Color3(0.862745, 0.541176, 0.470588), - Flamingo: new Color3(0.866667, 0.470588, 0.470588), - Pink: new Color3(0.917647, 0.462745, 0.796078), - Mauve: new Color3(0.533333, 0.223529, 0.937255), - Red: new Color3(0.823529, 0.0588235, 0.223529), - Maroon: new Color3(0.901961, 0.270588, 0.32549), - Peach: new Color3(0.996078, 0.392157, 0.0431373), - Yellow: new Color3(0.87451, 0.556863, 0.113725), - Green: new Color3(0.25098, 0.627451, 0.168627), - Teal: new Color3(0.0901961, 0.572549, 0.6), - Sky: new Color3(0.0156863, 0.647059, 0.898039), - Sapphire: new Color3(0.12549, 0.623529, 0.709804), - Blue: new Color3(0.117647, 0.4, 0.960784), - Lavender: new Color3(0.447059, 0.529412, 0.992157), - Text: new Color3(0.298039, 0.309804, 0.411765), - Subtext1: new Color3(0.360784, 0.372549, 0.466667), - Subtext0: new Color3(0.423529, 0.435294, 0.521569), - Overlay2: new Color3(0.486275, 0.498039, 0.576471), - Overlay1: new Color3(0.54902, 0.560784, 0.631373), - Overlay0: new Color3(0.611765, 0.627451, 0.690196), - Surface2: new Color3(0.67451, 0.690196, 0.745098), - Surface1: new Color3(0.737255, 0.752941, 0.8), - Surface0: new Color3(0.8, 0.815686, 0.854902), - Base: new Color3(0.937255, 0.945098, 0.960784), - Mantle: new Color3(0.901961, 0.913725, 0.937255), - Crust: new Color3(0.862745, 0.878431, 0.909804), -}; diff --git a/src/utils.ts b/src/utils.ts deleted file mode 100644 index 3586649..0000000 --- a/src/utils.ts +++ /dev/null @@ -1,131 +0,0 @@ -// tl t tr -// cl c cr -// bl b br -import { - Binding, - type InferEnumNames, - InstanceChangeEvent, - InstanceEvent, - InstanceProps, - Key, - ReactNode, - RefObject, -} from "@rbxts/react"; - -import { BindingOrValue, isBinding, mapBinding } from "@rbxts/pretty-react-hooks"; - -export type BindingVariants = { - [P in keyof T]?: - | T[P] - | InferEnumNames - | Binding>; -}; - -export type ReactProps = { - key?: Key; - ref?: RefObject; - children?: ReactNode; - event?: InstanceEvent; - change?: InstanceChangeEvent; - tag?: string; - - overrideRoblox?: InstanceProps -} - -export type AnchorPointsVariant = - "tl" | "t" | "tr" | - "ml" | "m" | "mr" | - "bl" | "b" | "br" - -export type ColorOrHex = Color3 | string - -export enum AnchorPoints { - TopLeft = "tl", - Top = "t", - TopRight = "tr", - MiddleLeft = "ml", - Middle = "m", - MiddleRight = "mr", - BottomLeft = "bl", - Bottom = "b", - BottomRight = "br", -} - -export type ResolvableAnchorPoint = - Vector2 | AnchorPointsVariant | AnchorPoints - -export function resolveAnchorPoint(value: BindingOrValue): BindingOrValue { - return mapBinding(value, (value) => { - if (typeIs(value, "Vector2")) return value; - - switch (value) { - case AnchorPoints.TopLeft: - return new Vector2(0, 0); - case AnchorPoints.Top: - return new Vector2(.5, 0); - case AnchorPoints.TopRight: - return new Vector2(1, 0); - case AnchorPoints.MiddleLeft: - return new Vector2(0, .5); - case AnchorPoints.Middle: - return new Vector2(.5, .5); - case AnchorPoints.MiddleRight: - return new Vector2(1, .5); - case AnchorPoints.BottomLeft: - return new Vector2(0, 1); - case AnchorPoints.Bottom: - return new Vector2(.5, 1); - case AnchorPoints.BottomRight: - return new Vector2(1, 1); - - default: - return new Vector2(0, 0); - } - }); -} - -export function resolveUDim(value: BindingOrValue): BindingOrValue { - return mapBinding(value, (value) => { - if (typeIs(value, "UDim")) return value; - return new UDim(0, value); - }); -} - -export function resolveColor3Value(value: ColorOrHex): Color3 { - if (typeIs(value, "Color3")) return value; - return Color3.fromHex(value); -} - -export function resolveColor3(value: BindingOrValue | undefined): Binding | undefined { - if (value === undefined) return undefined; - - return mapBinding(value, (value) => { - if (typeIs(value, "Color3")) return value; - return Color3.fromHex(value); - }); -} - -/** - * @deprecated Use `mapBinding` from `@rbxts/pretty-react-hooks`. Probably will be removed later. - */ -export function resolveBinding(bindingOrValue: BindingOrValue | undefined, callback: (value: T) => R): BindingOrValue | undefined { - if (bindingOrValue === undefined) return; - - if (isBinding(bindingOrValue)) { - return bindingOrValue.map(callback); - } else { - return callback(bindingOrValue); - } -} - -export function flat(arr: T[][]): T[] { - const newArr: defined[] = []; - - for (const i of arr) { - for (const j of arr) { - newArr.push(j as unknown as defined); - } - } - - return newArr as T[]; -} diff --git a/story.project.json b/story.project.json index a96604f..f6d6991 100644 --- a/story.project.json +++ b/story.project.json @@ -1,25 +1,22 @@ { - "name": "better-react-components-stories", - "globIgnorePaths": [ - "**/package.json", - "**/tsconfig.json" - ], - "tree": { - "$className": "DataModel", - "ReplicatedStorage": { - "$className": "ReplicatedStorage", - "rbxts_include": { - "$path": "include", - "node_modules": { - "$className": "Folder", - "@rbxts": { - "$path": "node_modules/@rbxts" - } + "name": "better-react-components-stories", + "globIgnorePaths": [ + "**/package.json", + "**/tsconfig.json" + ], + "tree": { + "$className": "DataModel", + "ReplicatedStorage": { + "$className": "ReplicatedStorage", + "DevPackages": { + "$path": "DevPackages" + }, + "Packages": { + "$path": "Packages", + "better-react-components": { + "$path": "src" + } + } } - }, - "better-react-components": { - "$path": "out" - } } - } } diff --git a/stylua.toml b/stylua.toml new file mode 100644 index 0000000..3e254d0 --- /dev/null +++ b/stylua.toml @@ -0,0 +1,3 @@ +column_width = 100 +indent_type = "Spaces" +indent_width = 4 diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index 390056b..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - // required - "allowSyntheticDefaultImports": true, - "downlevelIteration": true, - "jsx": "react", - "jsxFactory": "React.createElement", - "jsxFragmentFactory": "React.Fragment", - "module": "commonjs", - "moduleResolution": "Node", - "noLib": true, - "resolveJsonModule": true, - "experimentalDecorators": true, - "forceConsistentCasingInFileNames": true, - "moduleDetection": "force", - "strict": true, - "target": "ESNext", - "typeRoots": ["node_modules/@rbxts"], - - // configurable - "rootDir": "src", - "outDir": "out", - "incremental": true, - "tsBuildInfoFile": "out/tsconfig.tsbuildinfo", - "declaration": true - } -} diff --git a/wally.lock b/wally.lock new file mode 100644 index 0000000..f4a703d --- /dev/null +++ b/wally.lock @@ -0,0 +1,98 @@ +# This file is automatically @generated by Wally. +# It is not intended for manual editing. +registry = "test" + +[[package]] +name = "idkncc/better-react-components" +version = "3.0.0" +dependencies = [["react", "jsdotlua/react@17.2.1"], ["react-roblox", "jsdotlua/react-roblox@17.2.1"], ["ui-labs", "pepeeltoro41/ui-labs@2.3.8"]] + +[[package]] +name = "jsdotlua/boolean" +version = "1.2.7" +dependencies = [["number", "jsdotlua/number@1.2.7"]] + +[[package]] +name = "jsdotlua/collections" +version = "1.2.7" +dependencies = [["es7-types", "jsdotlua/es7-types@1.2.7"], ["instance-of", "jsdotlua/instance-of@1.2.7"]] + +[[package]] +name = "jsdotlua/console" +version = "1.2.7" +dependencies = [["collections", "jsdotlua/collections@1.2.7"]] + +[[package]] +name = "jsdotlua/es7-types" +version = "1.2.7" +dependencies = [] + +[[package]] +name = "jsdotlua/instance-of" +version = "1.2.7" +dependencies = [] + +[[package]] +name = "jsdotlua/luau-polyfill" +version = "1.2.7" +dependencies = [["boolean", "jsdotlua/boolean@1.2.7"], ["collections", "jsdotlua/collections@1.2.7"], ["console", "jsdotlua/console@1.2.7"], ["es7-types", "jsdotlua/es7-types@1.2.7"], ["instance-of", "jsdotlua/instance-of@1.2.7"], ["math", "jsdotlua/math@1.2.7"], ["number", "jsdotlua/number@1.2.7"], ["string", "jsdotlua/string@1.2.7"], ["symbol-luau", "jsdotlua/symbol-luau@1.0.1"], ["timers", "jsdotlua/timers@1.2.7"]] + +[[package]] +name = "jsdotlua/math" +version = "1.2.7" +dependencies = [] + +[[package]] +name = "jsdotlua/number" +version = "1.2.7" +dependencies = [] + +[[package]] +name = "jsdotlua/promise" +version = "3.5.2" +dependencies = [] + +[[package]] +name = "jsdotlua/react" +version = "17.2.1" +dependencies = [["luau-polyfill", "jsdotlua/luau-polyfill@1.2.7"], ["shared", "jsdotlua/shared@17.2.1"]] + +[[package]] +name = "jsdotlua/react-reconciler" +version = "17.2.1" +dependencies = [["luau-polyfill", "jsdotlua/luau-polyfill@1.2.7"], ["promise", "jsdotlua/promise@3.5.2"], ["react", "jsdotlua/react@17.2.1"], ["scheduler", "jsdotlua/scheduler@17.2.1"], ["shared", "jsdotlua/shared@17.2.1"]] + +[[package]] +name = "jsdotlua/react-roblox" +version = "17.2.1" +dependencies = [["luau-polyfill", "jsdotlua/luau-polyfill@1.2.7"], ["react", "jsdotlua/react@17.2.1"], ["react-reconciler", "jsdotlua/react-reconciler@17.2.1"], ["scheduler", "jsdotlua/scheduler@17.2.1"], ["shared", "jsdotlua/shared@17.2.1"]] + +[[package]] +name = "jsdotlua/scheduler" +version = "17.2.1" +dependencies = [["luau-polyfill", "jsdotlua/luau-polyfill@1.2.7"], ["shared", "jsdotlua/shared@17.2.1"]] + +[[package]] +name = "jsdotlua/shared" +version = "17.2.1" +dependencies = [["luau-polyfill", "jsdotlua/luau-polyfill@1.2.7"]] + +[[package]] +name = "jsdotlua/string" +version = "1.2.7" +dependencies = [["es7-types", "jsdotlua/es7-types@1.2.7"], ["number", "jsdotlua/number@1.2.7"]] + +[[package]] +name = "jsdotlua/symbol-luau" +version = "1.0.1" +dependencies = [] + +[[package]] +name = "jsdotlua/timers" +version = "1.2.7" +dependencies = [["collections", "jsdotlua/collections@1.2.7"]] + +[[package]] +name = "pepeeltoro41/ui-labs" +version = "2.3.8" +dependencies = [] diff --git a/wally.toml b/wally.toml new file mode 100644 index 0000000..32d433d --- /dev/null +++ b/wally.toml @@ -0,0 +1,12 @@ +[package] +name = "idkncc/better-react-components" +version = "3.0.0" +registry = "https://github.com/UpliftGames/wally-index" +realm = "shared" + +[dependencies] +react = "jsdotlua/react@17.2.1" +react-roblox = "jsdotlua/react-roblox@17.2.1" + +[dev-dependencies] +ui-labs = "pepeeltoro41/ui-labs@2.3.8" From 8ff76b1ed8c860269e56ccb51816c79f3f9b9a68 Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Wed, 23 Apr 2025 19:52:08 +0300 Subject: [PATCH 02/28] fix: removed .github, .neoconf.json, added check to README.md --- .github/dependabot.yml | 19 ------------------- .github/workflows/build.yml | 28 ---------------------------- .github/workflows/release.yml | 34 ---------------------------------- .neoconf.json | 7 ------- README.md | 2 +- 5 files changed, 1 insertion(+), 89 deletions(-) delete mode 100644 .github/dependabot.yml delete mode 100644 .github/workflows/build.yml delete mode 100644 .github/workflows/release.yml delete mode 100644 .neoconf.json diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 619d664..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,19 +0,0 @@ -# credits: https://github.com/roblox-ts/types/blob/e3a610314ae49c945b917df654e1094f7f1987f4/.github/dependabot.yml - -version: 2 -updates: - - package-ecosystem: "npm" - versioning-strategy: increase - open-pull-requests-limit: 99 - directory: "/" - schedule: - interval: weekly - commit-message: - prefix: "build(deps)" - ignore: - - dependency-name: "typescript" - - dependency-name: "roblox-ts" - groups: - packages: - patterns: - - "*" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 4d4a0f1..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Build package -on: - push: - pull_request: - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - uses: pnpm/action-setup@v2 - with: - version: 9 - - - name: Use Node.js 20 - uses: actions/setup-node@v3 - with: - node-version: 20 - registry-url: 'https://registry.npmjs.org' - cache: 'pnpm' - - - name: Install dependencies - run: pnpm install - - - name: Build package - run: pnpm build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 45011c5..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: Release - -on: - push: - tags: - - 'v*' - -jobs: - release: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - uses: pnpm/action-setup@v2 - with: - version: 9 - - - name: Use Node.js 20 - uses: actions/setup-node@v3 - with: - node-version: 20 - registry-url: 'https://registry.npmjs.org' - cache: 'pnpm' - - - name: Install dependencies - run: pnpm install - - - name: Build package - run: pnpm build - - - name: Publish to NPM - run: pnpm publish - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.neoconf.json b/.neoconf.json deleted file mode 100644 index fa51a35..0000000 --- a/.neoconf.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "luau-lsp": { - "sourcemap": { - "autogenerate": false - } - } -} diff --git a/README.md b/README.md index 20b326a..7a56368 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Respectfully, ### Components -- [ ] Frame +- [x] Frame - [ ] ScrollableFrame - [ ] Button - [ ] ImageButton From 23b88bfe74113cbada3f7b8ef9dd440d819cdc6b Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Thu, 24 Apr 2025 18:58:52 +0300 Subject: [PATCH 03/28] feat: `TextLabel` and `TextButton` --- README.md | 4 ++-- src/Builders/TextButtonBuilder.luau | 15 +++++++++++++++ src/Builders/TextLabelBuilder.luau | 3 +++ src/Components/TextButton.luau | 3 +++ src/Components/TextLabel.luau | 3 +++ src/Components/init.luau | 5 +++++ src/init.luau | 13 ++++++++----- 7 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 src/Builders/TextButtonBuilder.luau create mode 100644 src/Builders/TextLabelBuilder.luau create mode 100644 src/Components/TextButton.luau create mode 100644 src/Components/TextLabel.luau create mode 100644 src/Components/init.luau diff --git a/README.md b/README.md index 7a56368..fc93f4e 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,10 @@ Respectfully, - [x] Frame - [ ] ScrollableFrame -- [ ] Button +- [x] TextButton - [ ] ImageButton - [ ] Image -- [ ] Text +- [x] TextLabel - [ ] TextBox - [ ] CanvasGroup diff --git a/src/Builders/TextButtonBuilder.luau b/src/Builders/TextButtonBuilder.luau new file mode 100644 index 0000000..f83e602 --- /dev/null +++ b/src/Builders/TextButtonBuilder.luau @@ -0,0 +1,15 @@ +local TextLabelBuilder = require(script.Parent.TextBuilder) + +export type TextButtonProps = { + Style: Enum.ButtonStyle?, + AutoButtonColor: boolean?, + Modal: boolean? +} + +return TextLabelBuilder:expand(function (props: TextButtonProps) + return { + Style = props.Style, + AutoButtonColor = props.AutoButtonColor, + Modal = props.Modal + }, {} +end) diff --git a/src/Builders/TextLabelBuilder.luau b/src/Builders/TextLabelBuilder.luau new file mode 100644 index 0000000..91db6fd --- /dev/null +++ b/src/Builders/TextLabelBuilder.luau @@ -0,0 +1,3 @@ +local TextLabelBuilder = require(script.Parent.TextBuilder) + +return TextLabelBuilder diff --git a/src/Components/TextButton.luau b/src/Components/TextButton.luau new file mode 100644 index 0000000..af03eeb --- /dev/null +++ b/src/Components/TextButton.luau @@ -0,0 +1,3 @@ +local TextButtonBuilder = require(script.Parent.Parent.Builders.TextButtonBuilder) + +return TextButtonBuilder:build("TextButton") diff --git a/src/Components/TextLabel.luau b/src/Components/TextLabel.luau new file mode 100644 index 0000000..3ea3afc --- /dev/null +++ b/src/Components/TextLabel.luau @@ -0,0 +1,3 @@ +local TextLabelBuilder = require(script.Parent.Parent.Builders.TextLabelBuilder) + +return TextLabelBuilder:build("TextLabel") diff --git a/src/Components/init.luau b/src/Components/init.luau new file mode 100644 index 0000000..7b9e734 --- /dev/null +++ b/src/Components/init.luau @@ -0,0 +1,5 @@ +return { + Frame = require(script.Frame), + TextButton = require(script.TextButton), + TextLabel = require(script.TextLabel), +} diff --git a/src/init.luau b/src/init.luau index 6cf4348..fab9466 100644 --- a/src/init.luau +++ b/src/init.luau @@ -1,5 +1,8 @@ -return { - hello = function() - print("Hello world, from better-react-components!") - end, -} +local BetterReactComponents = {} + +BetterReactComponents.Utils = require(script.Utils) +BetterReactComponents.Builder = require(script.Builder) + +BetterReactComponents.Utils.Assign(BetterReactComponents, require(script.Components)) + +return BetterReactComponents From aad02d079e7143db649d78b270b5f67f2218d7fc Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Fri, 25 Apr 2025 15:55:22 +0300 Subject: [PATCH 04/28] feat: darklua, scripts/, script to build wally package --- .darklua-dev.json | 18 +++++++++++++ .darklua-wally.json | 17 ++++++++++++ .gitignore | 2 ++ Makefile | 36 ++++++++++++++------------ aftman.toml | 1 + build/wally/.gitignore | 10 +++++++ build/wally/wally.project.json | 9 +++++++ story.project.json => dev.project.json | 5 ++-- scripts/wally.sh | 19 ++++++++++++++ src/Builder.luau | 6 ++--- src/Components/Frame.luau | 2 +- src/Components/TextButton.luau | 2 +- src/Components/TextLabel.luau | 2 +- src/Components/init.luau | 6 ++--- src/init.luau | 6 ++--- src/stories/Frame.story.luau | 11 +++----- wally.toml | 2 ++ 17 files changed, 116 insertions(+), 38 deletions(-) create mode 100644 .darklua-dev.json create mode 100644 .darklua-wally.json create mode 100644 build/wally/.gitignore create mode 100644 build/wally/wally.project.json rename story.project.json => dev.project.json (90%) create mode 100755 scripts/wally.sh diff --git a/.darklua-dev.json b/.darklua-dev.json new file mode 100644 index 0000000..d87fd68 --- /dev/null +++ b/.darklua-dev.json @@ -0,0 +1,18 @@ +{ + "rules": [ + { + "rule": "convert_require", + "current": { + "name": "path", + "sources": { + "@pkg": "Packages", + "@dev-pkg": "DevPackages" + } + }, + "target": { + "name": "roblox", + "rojo_sourcemap": "./sourcemap.json" + } + } + ] +} diff --git a/.darklua-wally.json b/.darklua-wally.json new file mode 100644 index 0000000..c763410 --- /dev/null +++ b/.darklua-wally.json @@ -0,0 +1,17 @@ +{ + "rules": [ + { + "rule": "convert_require", + "current": { + "name": "path", + "sources": { + "@pkg": "Packages" + } + }, + "target": { + "name": "roblox", + "rojo_sourcemap": "./sourcemap.json" + } + } + ] +} diff --git a/.gitignore b/.gitignore index 356fdf8..a5fea83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ Packages/ DevPackages/ +out/ + sourcemap.json .DS_Store diff --git a/Makefile b/Makefile index 00e1ccd..265f3ab 100644 --- a/Makefile +++ b/Makefile @@ -1,24 +1,28 @@ -install: install-dependencies patch-sourcemap -serve: - rojo serve story.project.json +install: install-toolchain +cleanup: cleanup-build -#build: install-dependencies -# rojo build +wally-package: cleanup-build + ./scripts/wally.sh -# intermediate scripts +# development +serve: + rojo serve dev.project.json -install-dependencies: aftman.toml wally.toml wally.lock - aftman install - wally install +watch: Packages/ DevPackages/ sourcemap.json .darklua-dev.json + darklua process -w -c .darklua-dev.json src out -patch-sourcemap: sourcemap.json Packages/* DevPackages/ - wally-package-types --sourcemap dev-sourcemap.json Packages/ - wally-package-types --sourcemap dev-sourcemap.json DevPackages/ +Packages DevPackages: wally.toml wally.lock + wally install + wally-package-types --sourcemap sourcemap.json Packages/ + wally-package-types --sourcemap sourcemap.json DevPackages/ -# target files/dirs: +sourcemap.json: src/* dev.project.json + rojo sourcemap dev.project.json --output sourcemap.json -Packages: install-dependencies +# intermediate steps -sourcemap.json: src/* story.project.json - rojo sourcemap story.project.json --output sourcemap.json +install-toolchain: + aftman install +cleanup-build: + git clean -Xf build # remove ignored files in build/ diff --git a/aftman.toml b/aftman.toml index 74c521c..368754c 100644 --- a/aftman.toml +++ b/aftman.toml @@ -2,3 +2,4 @@ rojo = "rojo-rbx/rojo@7.4.4" wally = "UpliftGames/wally@0.3.2" wally-package-types = "JohnnyMorganz/wally-package-types@1.4.2" +darklua = "seaofvoices/darklua@0.16.0" diff --git a/build/wally/.gitignore b/build/wally/.gitignore new file mode 100644 index 0000000..2af38b2 --- /dev/null +++ b/build/wally/.gitignore @@ -0,0 +1,10 @@ +# This folder used for storing Wally package files +# +# `wally.project.json` is used for generating sourcemap.json +# +# To build wally package run: +# $ make wally-package + +* +!.gitignore +!wally.project.json diff --git a/build/wally/wally.project.json b/build/wally/wally.project.json new file mode 100644 index 0000000..095ea86 --- /dev/null +++ b/build/wally/wally.project.json @@ -0,0 +1,9 @@ +{ + "name": "better-react-components", + "tree": { + "$path": "Packages", + "better-react-components": { + "$path": "src" + } + } +} diff --git a/story.project.json b/dev.project.json similarity index 90% rename from story.project.json rename to dev.project.json index f6d6991..706b131 100644 --- a/story.project.json +++ b/dev.project.json @@ -12,11 +12,12 @@ "$path": "DevPackages" }, "Packages": { - "$path": "Packages", + "$path": "Packages" + }, + "better-react-components": { "$path": "src" } - } } } } diff --git a/scripts/wally.sh b/scripts/wally.sh new file mode 100755 index 0000000..0e04a19 --- /dev/null +++ b/scripts/wally.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -o pipefail +set -e + +mkdir -p build/wally +cp README.md default.project.json .darklua-wally.json wally.toml wally.lock build/wally/ +cp -r src build/wally/ +rm -rf build/wally/src/Stories +cd build/wally + +wally install + +rojo sourcemap wally.project.json --output sourcemap.json +darklua process -c .darklua-wally.json src out + +rm -rf src +mv out src + diff --git a/src/Builder.luau b/src/Builder.luau index 4a2735b..665aa09 100644 --- a/src/Builder.luau +++ b/src/Builder.luau @@ -1,7 +1,5 @@ -local Packages = script.Parent.Parent - -local React = require(Packages.react) -local Utils = require(script.Parent.Utils) +local React = require("@pkg/react") +local Utils = require("./Utils") local ComponentBuilder = {} ComponentBuilder.__index = ComponentBuilder diff --git a/src/Components/Frame.luau b/src/Components/Frame.luau index 91cdbef..3094e7d 100644 --- a/src/Components/Frame.luau +++ b/src/Components/Frame.luau @@ -1,3 +1,3 @@ -local FrameBuilder = require(script.Parent.Parent.Builders.FrameBuilder) +local FrameBuilder = require("../Builders/FrameBuilder") return FrameBuilder:build("Frame") diff --git a/src/Components/TextButton.luau b/src/Components/TextButton.luau index af03eeb..bbb7839 100644 --- a/src/Components/TextButton.luau +++ b/src/Components/TextButton.luau @@ -1,3 +1,3 @@ -local TextButtonBuilder = require(script.Parent.Parent.Builders.TextButtonBuilder) +local TextButtonBuilder = require("../Builders/TextButtonBuilder") return TextButtonBuilder:build("TextButton") diff --git a/src/Components/TextLabel.luau b/src/Components/TextLabel.luau index 3ea3afc..b85fab6 100644 --- a/src/Components/TextLabel.luau +++ b/src/Components/TextLabel.luau @@ -1,3 +1,3 @@ -local TextLabelBuilder = require(script.Parent.Parent.Builders.TextLabelBuilder) +local TextLabelBuilder = require("../Builders/TextLabelBuilder") return TextLabelBuilder:build("TextLabel") diff --git a/src/Components/init.luau b/src/Components/init.luau index 7b9e734..5be84a6 100644 --- a/src/Components/init.luau +++ b/src/Components/init.luau @@ -1,5 +1,5 @@ return { - Frame = require(script.Frame), - TextButton = require(script.TextButton), - TextLabel = require(script.TextLabel), + Frame = require("./Frame"), + TextButton = require("./TextButton"), + TextLabel = require("./TextLabel"), } diff --git a/src/init.luau b/src/init.luau index fab9466..27cccf7 100644 --- a/src/init.luau +++ b/src/init.luau @@ -1,8 +1,8 @@ local BetterReactComponents = {} -BetterReactComponents.Utils = require(script.Utils) -BetterReactComponents.Builder = require(script.Builder) +BetterReactComponents.Utils = require("./Utils") +BetterReactComponents.Builder = require("./Builder") -BetterReactComponents.Utils.Assign(BetterReactComponents, require(script.Components)) +BetterReactComponents.Utils.Assign(BetterReactComponents, require("./Components")) return BetterReactComponents diff --git a/src/stories/Frame.story.luau b/src/stories/Frame.story.luau index d97e228..85eb2cc 100644 --- a/src/stories/Frame.story.luau +++ b/src/stories/Frame.story.luau @@ -1,9 +1,6 @@ -local Packages = game:GetService("ReplicatedStorage").Packages -local DevPackages = game:GetService("ReplicatedStorage").DevPackages - -local React = require(Packages.react) -local ReactRoblox = require(Packages["react-roblox"]) -local UILabs = require(DevPackages["ui-labs"]) +local React = require("@pkg/react") +local ReactRoblox = require("@pkg/react-roblox") +local UILabs = require("@dev-pkg/ui-labs") local Frame = require(script.Parent.Parent.Components.Frame) @@ -23,7 +20,7 @@ local story = { BottomRight = "br", }, "MiddleCenter"), BackgroundColor = Color3.fromHex("#FF0000"), - CornerRadius = 0 + CornerRadius = 0, }, story = function(props) return React.createElement(Frame, { diff --git a/wally.toml b/wally.toml index 32d433d..281b9d6 100644 --- a/wally.toml +++ b/wally.toml @@ -4,6 +4,8 @@ version = "3.0.0" registry = "https://github.com/UpliftGames/wally-index" realm = "shared" +include = [ "src", "default.project.json", "README.md" ] + [dependencies] react = "jsdotlua/react@17.2.1" react-roblox = "jsdotlua/react-roblox@17.2.1" From 7873c132310cff6140989f2870d0144ee56c838d Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Fri, 25 Apr 2025 16:12:33 +0300 Subject: [PATCH 05/28] fix: fixed development environment, fixed requires --- Makefile | 2 +- dev-sourcemap.project.json | 22 ++++++++++++++++++++++ dev.project.json | 7 +++---- src/Builders/FrameBuilder.luau | 5 ----- src/Builders/GuiObjectBuilder.luau | 8 +++----- src/Builders/TextBuilder.luau | 8 ++------ src/Builders/TextButtonBuilder.luau | 2 +- src/Builders/TextLabelBuilder.luau | 2 +- 8 files changed, 33 insertions(+), 23 deletions(-) create mode 100644 dev-sourcemap.project.json diff --git a/Makefile b/Makefile index 265f3ab..6cee1e3 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ Packages DevPackages: wally.toml wally.lock wally-package-types --sourcemap sourcemap.json DevPackages/ sourcemap.json: src/* dev.project.json - rojo sourcemap dev.project.json --output sourcemap.json + rojo sourcemap dev-sourcemap.project.json --output sourcemap.json # intermediate steps diff --git a/dev-sourcemap.project.json b/dev-sourcemap.project.json new file mode 100644 index 0000000..97cc14c --- /dev/null +++ b/dev-sourcemap.project.json @@ -0,0 +1,22 @@ +{ + "name": "better-react-components-stories", + "globIgnorePaths": [ + "**/package.json", + "**/tsconfig.json" + ], + "tree": { + "$className": "DataModel", + "ReplicatedStorage": { + "$className": "ReplicatedStorage", + "DevPackages": { + "$path": "DevPackages" + }, + "Packages": { + "$path": "Packages" + }, + "better-react-components": { + "$path": "src" + } + } + } +} diff --git a/dev.project.json b/dev.project.json index 706b131..ac81620 100644 --- a/dev.project.json +++ b/dev.project.json @@ -14,10 +14,9 @@ "Packages": { "$path": "Packages" }, - - "better-react-components": { - "$path": "src" - } + "better-react-components": { + "$path": "out" + } } } } diff --git a/src/Builders/FrameBuilder.luau b/src/Builders/FrameBuilder.luau index 32a6920..7a5ee74 100644 --- a/src/Builders/FrameBuilder.luau +++ b/src/Builders/FrameBuilder.luau @@ -1,8 +1,3 @@ -local Packages = script.Parent.Parent.Parent - -local React = require(Packages.react) -local ComponentBuilder = require(script.Parent.Parent.Builder) -local Utils = require(script.Parent.Parent.Utils) local GuiObjectBuilder = require(script.Parent.GuiObjectBuilder) export type FrameProps = {} diff --git a/src/Builders/GuiObjectBuilder.luau b/src/Builders/GuiObjectBuilder.luau index 1919a9c..578a2ab 100644 --- a/src/Builders/GuiObjectBuilder.luau +++ b/src/Builders/GuiObjectBuilder.luau @@ -1,8 +1,6 @@ -local Packages = script.Parent.Parent.Parent - -local React = require(Packages.react) -local ComponentBuilder = require(script.Parent.Parent.Builder) -local Utils = require(script.Parent.Parent.Utils) +local React = require("@pkg/react") +local ComponentBuilder = require("../Builder") +local Utils = require("../Utils") --- Border settings --- diff --git a/src/Builders/TextBuilder.luau b/src/Builders/TextBuilder.luau index 46c178d..6f1e2ce 100644 --- a/src/Builders/TextBuilder.luau +++ b/src/Builders/TextBuilder.luau @@ -1,9 +1,5 @@ -local Packages = script.Parent.Parent.Parent - -local React = require(Packages.react) -local ComponentBuilder = require(script.Parent.Parent.Builder) -local Utils = require(script.Parent.Parent.Utils) -local GuiObjectBuilder = require(script.Parent.GuiObjectBuilder) +local Utils = require("../Utils") +local GuiObjectBuilder = require("./GuiObjectBuilder") export type TextProps = { Text: string?, diff --git a/src/Builders/TextButtonBuilder.luau b/src/Builders/TextButtonBuilder.luau index f83e602..6fae50a 100644 --- a/src/Builders/TextButtonBuilder.luau +++ b/src/Builders/TextButtonBuilder.luau @@ -1,4 +1,4 @@ -local TextLabelBuilder = require(script.Parent.TextBuilder) +local TextLabelBuilder = require("./TextBuilder") export type TextButtonProps = { Style: Enum.ButtonStyle?, diff --git a/src/Builders/TextLabelBuilder.luau b/src/Builders/TextLabelBuilder.luau index 91db6fd..a318cee 100644 --- a/src/Builders/TextLabelBuilder.luau +++ b/src/Builders/TextLabelBuilder.luau @@ -1,3 +1,3 @@ -local TextLabelBuilder = require(script.Parent.TextBuilder) +local TextLabelBuilder = require("./TextBuilder") return TextLabelBuilder From 3b7a1b85dc6ea0c08063937bb3e86ed05409dcfd Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Sun, 27 Apr 2025 09:46:02 +0300 Subject: [PATCH 06/28] fix: refactored code, switched to old type-solver*, added UIStroke(Border) * - New type solver doesn't work with React component props. --- src/Builder.luau | 4 +- src/Builders/FrameBuilder.luau | 6 +-- src/Builders/GuiObjectBuilder.luau | 20 ++++--- src/Builders/TextBuilder.luau | 6 +-- src/Builders/TextButtonBuilder.luau | 6 +-- src/Builders/TextLabelBuilder.luau | 6 ++- src/Utils.luau | 81 ++++++++++++++++++++++++----- src/stories/Frame.story.luau | 1 + src/stories/TextLabel.story.luau | 37 +++++++++++++ 9 files changed, 133 insertions(+), 34 deletions(-) create mode 100644 src/stories/TextLabel.story.luau diff --git a/src/Builder.luau b/src/Builder.luau index 665aa09..38c1317 100644 --- a/src/Builder.luau +++ b/src/Builder.luau @@ -40,11 +40,11 @@ end function ComponentBuilder.build( self: ComponentBuilder, robloxComponent: string -): (props: P) -> any +): (P, any) -> React.ReactNode return React.forwardRef(function(props: React.ElementProps & P) local outputProps, outputChildren = self.transformer(props) return React.createElement(robloxComponent, outputProps, outputChildren, props.children) - end) + end) :: any end return ComponentBuilder diff --git a/src/Builders/FrameBuilder.luau b/src/Builders/FrameBuilder.luau index 7a5ee74..d091c35 100644 --- a/src/Builders/FrameBuilder.luau +++ b/src/Builders/FrameBuilder.luau @@ -1,5 +1,5 @@ local GuiObjectBuilder = require(script.Parent.GuiObjectBuilder) -export type FrameProps = {} - -return GuiObjectBuilder -- frame doesn't add any new properties, so passthrough +return GuiObjectBuilder:expand(function(props) + return {}, {} +end) diff --git a/src/Builders/GuiObjectBuilder.luau b/src/Builders/GuiObjectBuilder.luau index 578a2ab..13bdb39 100644 --- a/src/Builders/GuiObjectBuilder.luau +++ b/src/Builders/GuiObjectBuilder.luau @@ -1,12 +1,15 @@ local React = require("@pkg/react") -local ComponentBuilder = require("../Builder") +local Builder = require("../Builder") local Utils = require("../Utils") --- Border settings --- --- NOTE: when building, it uses `UIStroke`, instead of `GuiObject`'s border properties export type Border = { - BorderColor: Utils.Color?, + Color: Utils.Color?, + Thickness: number?, + LineJoinMode: Enum.LineJoinMode, + Transparency: number?, } export type GuiObjectProps = { @@ -30,7 +33,7 @@ export type GuiObjectProps = { Border: Border?, } -return ComponentBuilder.new(function(props: GuiObjectProps) +return Builder.new(function(props: GuiObjectProps) return { AnchorPoint = props.AnchorPoint and Utils.ConvertAnchorPoint(props.AnchorPoint), AutomaticSize = props.AutomaticSize, @@ -45,10 +48,11 @@ return ComponentBuilder.new(function(props: GuiObjectProps) LayoutOrder = props.LayoutOrder, ZIndex = props.ZIndex, }, { - Border = props.border - and React.createElement("UIBorder", { - Color = props.Border.BorderColor and Utils.ConvertColor(props.Border.BorderColor), - Thickness = props.Border.Thickness, - }), + Border = props.Border and React.createElement("UIStroke", { + Color = props.Border.Color and Utils.ConvertColor(props.Border.Color), + Thickness = props.Border.Thickness, + LineJoinMode = props.Border.LineJoinMode, + Transparency = props.Border.Transparency, + }), } end) diff --git a/src/Builders/TextBuilder.luau b/src/Builders/TextBuilder.luau index 6f1e2ce..e566029 100644 --- a/src/Builders/TextBuilder.luau +++ b/src/Builders/TextBuilder.luau @@ -4,7 +4,7 @@ local GuiObjectBuilder = require("./GuiObjectBuilder") export type TextProps = { Text: string?, - Font: Enum.Font?, + Font: Utils.FontValue?, LineHeight: number?, RichText: boolean?, TextColor: Utils.Color?, @@ -20,10 +20,10 @@ return GuiObjectBuilder:expand(function(props: TextProps) return { Text = props.Text, - FontFace = props.Font, + FontFace = props.Font and Utils.ConvertFont(props.Font), LineHeight = props.LineHeight, RichText = props.RichText, - TextColor3 = Utils.ConvertColor(props.TextColor), + TextColor3 = props.TextColor and Utils.ConvertColor(props.TextColor), TextWrapped = props.TextWrapped, TextSize = props.TextSize == "auto" and 1 or props.TextSize, diff --git a/src/Builders/TextButtonBuilder.luau b/src/Builders/TextButtonBuilder.luau index 6fae50a..a4c420f 100644 --- a/src/Builders/TextButtonBuilder.luau +++ b/src/Builders/TextButtonBuilder.luau @@ -3,13 +3,13 @@ local TextLabelBuilder = require("./TextBuilder") export type TextButtonProps = { Style: Enum.ButtonStyle?, AutoButtonColor: boolean?, - Modal: boolean? + Modal: boolean?, } -return TextLabelBuilder:expand(function (props: TextButtonProps) +return TextLabelBuilder:expand(function(props: TextButtonProps) return { Style = props.Style, AutoButtonColor = props.AutoButtonColor, - Modal = props.Modal + Modal = props.Modal, }, {} end) diff --git a/src/Builders/TextLabelBuilder.luau b/src/Builders/TextLabelBuilder.luau index a318cee..20ac4ba 100644 --- a/src/Builders/TextLabelBuilder.luau +++ b/src/Builders/TextLabelBuilder.luau @@ -1,3 +1,5 @@ -local TextLabelBuilder = require("./TextBuilder") +local TextBuilder = require("./TextBuilder") -return TextLabelBuilder +return TextBuilder:expand(function(props) + return {}, {} +end) diff --git a/src/Utils.luau b/src/Utils.luau index 08ce8b8..b6c1841 100644 --- a/src/Utils.luau +++ b/src/Utils.luau @@ -1,3 +1,5 @@ +local React = require("@pkg/react") + local Utils = {} --- User-friendly anchor points @@ -20,6 +22,11 @@ export type Color = Color3 | string --- `"auto"` enables `TextScaled` property. Otherwise sets TextSize export type TextSize = number | "auto" +--- Font +export type FontValue = Font | EnumItem + +export type BindingOrValue = React.Binding | T + local AnchorPointMapping = setmetatable({ tl = Vector2.new(0, 0), t = Vector2.new(0.5, 0), @@ -38,28 +45,76 @@ local AnchorPointMapping = setmetatable({ end, }) -function Utils.Assign(table1: T1, table2: T2): T1 & T2 +local function isBinding(value: any): boolean + return typeof(value) == "table" and value["getValue"] ~= nil and value["map"] ~= nil +end + +-- TODO: create separate package "better-react-hooks" +function Utils.mapBinding(bindingOrValue: BindingOrValue, map: (value: T) -> R): React.Binding + local binding = if isBinding(bindingOrValue) + then bindingOrValue :: React.Binding + else React.createBinding(bindingOrValue :: T) + + return binding:map(map) +end + +function Utils.Assign(table1: { T1 }, table2: { T2 }): { T1 & T2 } for k, v in pairs(table2) do table1[k] = v end - return table1 + return table1 :: { T1 & T2 } end -function Utils.ConvertColor(value: Color3 | string): Color3 - if typeof(value) == "string" then - return Color3.fromHex(value) - else - return value - end +function Utils.ConvertColor(value: BindingOrValue): React.Binding + return Utils.mapBinding(value, function(value) + if typeof(value) == "Color3" then + return value + elseif typeof(value) == "string" then + return Color3.fromHex(value) + else + error( + string.format( + 'Invalid value in ConvertColor. Expected Color3 or string, found "%s"', + value + ) + ) + end + end) end -function Utils.ConvertAnchorPoint(value: AnchorPoint): Vector2 - if typeof(value) == "Vector2" then - return value - end +function Utils.ConvertAnchorPoint(value: BindingOrValue): React.Binding + return Utils.mapBinding(value, function(value) + if typeof(value) == "Vector2" then + return value + elseif typeof(value) == "string" then + return AnchorPointMapping[value] + else + error( + string.format( + 'Invalid value in ConvertAnchorPoint. Expected Vector2 or string, found "%s"', + value + ) + ) + end + end) +end - return AnchorPointMapping[value] +function Utils.ConvertFont(value: BindingOrValue): React.Binding + return Utils.mapBinding(value, function(value) + if typeof(value) == "EnumItem" then + return Font.fromEnum(value :: Enum.Font) + elseif typeof(value) == "Font" then + return value + else + error( + string.format( + 'Invalid value in ConvertFont. Expected Font or Enum.Font, found "%s"', + value + ) + ) + end + end) end return Utils diff --git a/src/stories/Frame.story.luau b/src/stories/Frame.story.luau index 85eb2cc..b4b39c4 100644 --- a/src/stories/Frame.story.luau +++ b/src/stories/Frame.story.luau @@ -28,6 +28,7 @@ local story = { Position = UDim2.new(0.5, 0, 0.5, 0), Size = UDim2.new(0.5, 0, 0.5, 0), BackgroundColor = props.controls.BackgroundColor, + -- CornerRadius = props.controls.CornerRadius }) end, diff --git a/src/stories/TextLabel.story.luau b/src/stories/TextLabel.story.luau new file mode 100644 index 0000000..62ccef2 --- /dev/null +++ b/src/stories/TextLabel.story.luau @@ -0,0 +1,37 @@ +local React = require("@pkg/react") +local ReactRoblox = require("@pkg/react-roblox") +local UILabs = require("@dev-pkg/ui-labs") + +local TextLabel = require("../Components/TextLabel") + +local Fonts = {} +for _, font in pairs(Enum.Font:GetEnumItems()) do + Fonts[tostring(font):gsub("Enum.Font.", "")] = font +end + +print(Fonts) + +return { + react = React, + reactRoblox = ReactRoblox, + controls = { + Text = "Hello, world!", + Font = UILabs.EnumList(Fonts, "Arimo"), + TextColor = Color3.fromHex("#FFFFFF"), + BackgroundColor = Color3.fromHex("#CC0000"), + CornerRadius = 0, + }, + story = function(props) + return React.createElement(TextLabel, { + AnchorPoint = "m", + Position = UDim2.new(0.5, 0, 0.5, 0), + Size = UDim2.new(0.5, 0, 0.5, 0), + BackgroundColor = props.controls.BackgroundColor, + + Font = Font.fromEnum(props.controls.Font), + TextColor = props.controls.TextColor, + TextSize = 24, + Text = props.controls.Text, + }) + end, +} From 4a352f0289898e15ddfd400e4c5177ac34126eb2 Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Sun, 27 Apr 2025 10:20:12 +0300 Subject: [PATCH 07/28] feat: `UICorner` and `UIStroke` --- README.md | 4 +-- src/Builders/GuiObjectBuilder.luau | 6 +++- src/Utils.luau | 38 ++++++++++++++++++----- src/init.luau | 12 +++---- src/stories/Frame.story.luau | 3 -- src/stories/Modifiers/UICorner.story.luau | 24 ++++++++++++++ src/stories/Modifiers/UIStroke.story.luau | 30 ++++++++++++++++++ 7 files changed, 98 insertions(+), 19 deletions(-) create mode 100644 src/stories/Modifiers/UICorner.story.luau create mode 100644 src/stories/Modifiers/UIStroke.story.luau diff --git a/README.md b/README.md index fc93f4e..9511cdf 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Respectfully, ### Modifiers - [ ] UIAspectRatioConstraint -- [ ] UICorner +- [x] UICorner (`CornerRadius` property) - [ ] UIGradient - [ ] UIGridLayout - [ ] UIListLayout @@ -56,6 +56,6 @@ Respectfully, - [ ] UIPageLayout - [ ] UIScale - [ ] UISizeConstraint -- [ ] UIStroke +- [x] UIStroke (`Border` property) - [ ] UITableLayout - [ ] UITextSizeConstraint diff --git a/src/Builders/GuiObjectBuilder.luau b/src/Builders/GuiObjectBuilder.luau index 13bdb39..adf7060 100644 --- a/src/Builders/GuiObjectBuilder.luau +++ b/src/Builders/GuiObjectBuilder.luau @@ -31,6 +31,7 @@ export type GuiObjectProps = { ZIndex: number?, Border: Border?, + CornerRadius: Utils.UDimValue, } return Builder.new(function(props: GuiObjectProps) @@ -48,11 +49,14 @@ return Builder.new(function(props: GuiObjectProps) LayoutOrder = props.LayoutOrder, ZIndex = props.ZIndex, }, { - Border = props.Border and React.createElement("UIStroke", { + BRCBorder = props.Border and React.createElement("UIStroke", { Color = props.Border.Color and Utils.ConvertColor(props.Border.Color), Thickness = props.Border.Thickness, LineJoinMode = props.Border.LineJoinMode, Transparency = props.Border.Transparency, }), + BRCCorner = props.CornerRadius and React.createElement("UICorner", { + CornerRadius = Utils.ConvertUDim(props.CornerRadius), + }), } end) diff --git a/src/Utils.luau b/src/Utils.luau index b6c1841..f244f1b 100644 --- a/src/Utils.luau +++ b/src/Utils.luau @@ -25,6 +25,11 @@ export type TextSize = number | "auto" --- Font export type FontValue = Font | EnumItem +--- UDim +--- +--- Takes `UDim` or `number` as offset +export type UDimValue = UDim | number + export type BindingOrValue = React.Binding | T local AnchorPointMapping = setmetatable({ @@ -58,12 +63,14 @@ function Utils.mapBinding(bindingOrValue: BindingOrValue, map: (value: return binding:map(map) end -function Utils.Assign(table1: { T1 }, table2: { T2 }): { T1 & T2 } - for k, v in pairs(table2) do - table1[k] = v +function Utils.Assign(table1: T1, table2: T2): T1 & T2 + local result = table1 :: any + + for k, v in pairs(table2 :: any) do + result[k] = v end - return table1 :: { T1 & T2 } + return result :: T1 & T2 end function Utils.ConvertColor(value: BindingOrValue): React.Binding @@ -76,7 +83,7 @@ function Utils.ConvertColor(value: BindingOrValue): React.Bindi error( string.format( 'Invalid value in ConvertColor. Expected Color3 or string, found "%s"', - value + typeof(value) ) ) end @@ -93,7 +100,7 @@ function Utils.ConvertAnchorPoint(value: BindingOrValue): React.Bin error( string.format( 'Invalid value in ConvertAnchorPoint. Expected Vector2 or string, found "%s"', - value + typeof(value) ) ) end @@ -110,7 +117,24 @@ function Utils.ConvertFont(value: BindingOrValue): React.Binding): React.Binding + return Utils.mapBinding(value, function(value) + if typeof(value) == "UDim" then + return value + elseif typeof(value) == "number" then + return UDim.new(0, value) + else + error( + string.format( + 'Invalid value in ConvertUDim. Expected UDim or number, found "%s"', + typeof(value) ) ) end diff --git a/src/init.luau b/src/init.luau index 27cccf7..6c17f5b 100644 --- a/src/init.luau +++ b/src/init.luau @@ -1,8 +1,8 @@ -local BetterReactComponents = {} +local BetterReactComponents = { + Utils = require("./Utils"), + Builder = require("./Builder"), +} -BetterReactComponents.Utils = require("./Utils") -BetterReactComponents.Builder = require("./Builder") +local Components = require("./Components") -BetterReactComponents.Utils.Assign(BetterReactComponents, require("./Components")) - -return BetterReactComponents +return BetterReactComponents.Utils.Assign(BetterReactComponents, Components) diff --git a/src/stories/Frame.story.luau b/src/stories/Frame.story.luau index b4b39c4..bc78354 100644 --- a/src/stories/Frame.story.luau +++ b/src/stories/Frame.story.luau @@ -20,7 +20,6 @@ local story = { BottomRight = "br", }, "MiddleCenter"), BackgroundColor = Color3.fromHex("#FF0000"), - CornerRadius = 0, }, story = function(props) return React.createElement(Frame, { @@ -28,8 +27,6 @@ local story = { Position = UDim2.new(0.5, 0, 0.5, 0), Size = UDim2.new(0.5, 0, 0.5, 0), BackgroundColor = props.controls.BackgroundColor, - - -- CornerRadius = props.controls.CornerRadius }) end, } diff --git a/src/stories/Modifiers/UICorner.story.luau b/src/stories/Modifiers/UICorner.story.luau new file mode 100644 index 0000000..8fb9f21 --- /dev/null +++ b/src/stories/Modifiers/UICorner.story.luau @@ -0,0 +1,24 @@ +local React = require("@pkg/react") +local ReactRoblox = require("@pkg/react-roblox") +local UILabs = require("@dev-pkg/ui-labs") + +local Frame = require("../..").Frame + +local story = { + react = React, + reactRoblox = ReactRoblox, + controls = { + CornerRadius = UILabs.Slider(10, 0, 150, 1), + }, + story = function(props) + return React.createElement(Frame, { + AnchorPoint = "m", + Position = UDim2.new(0.5, 0, 0.5, 0), + Size = UDim2.new(0.5, 0, 0.5, 0), + BackgroundColor = "#FFF", + CornerRadius = props.controls.CornerRadius, -- number + }) + end, +} + +return story diff --git a/src/stories/Modifiers/UIStroke.story.luau b/src/stories/Modifiers/UIStroke.story.luau new file mode 100644 index 0000000..8bb309c --- /dev/null +++ b/src/stories/Modifiers/UIStroke.story.luau @@ -0,0 +1,30 @@ +local React = require("@pkg/react") +local ReactRoblox = require("@pkg/react-roblox") +local UILabs = require("@dev-pkg/ui-labs") + +local Frame = require("../..").Frame + +local story = { + react = React, + reactRoblox = ReactRoblox, + controls = { + Color = Color3.fromHex("#FF0000"), + Thickness = UILabs.Slider(5, 0, 50), + }, + story = function(props) + return React.createElement(Frame, { + AnchorPoint = "m", + Position = UDim2.new(0.5, 0, 0.5, 0), + Size = UDim2.new(0.5, 0, 0.5, 0), + BackgroundColor = "#FFF", + CornerRadius = 15, + + Border = { + Color = props.controls.Color, -- Color3 | number + Thickness = props.controls.Thickness, -- number + }, + }) + end, +} + +return story From c01c5ee905d9c67a9ad45402754ba136751c7f6d Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Tue, 29 Apr 2025 15:42:36 +0300 Subject: [PATCH 08/28] feat: Padding and aspect ratio --- README.md | 4 +- src/Builders/GuiObjectBuilder.luau | 19 +++- src/Utils.luau | 94 ++++++++++++++++++-- src/stories/Modifiers/AspectRatio.story.luau | 29 ++++++ src/stories/Modifiers/Padding.story.luau | 49 ++++++++++ 5 files changed, 186 insertions(+), 9 deletions(-) create mode 100644 src/stories/Modifiers/AspectRatio.story.luau create mode 100644 src/stories/Modifiers/Padding.story.luau diff --git a/README.md b/README.md index 9511cdf..d715c05 100644 --- a/README.md +++ b/README.md @@ -46,13 +46,13 @@ Respectfully, ### Modifiers -- [ ] UIAspectRatioConstraint +- [x] UIAspectRatioConstraint - [x] UICorner (`CornerRadius` property) - [ ] UIGradient - [ ] UIGridLayout - [ ] UIListLayout - [ ] UIFlexLayout -- [ ] UIPadding +- [x] UIPadding - [ ] UIPageLayout - [ ] UIScale - [ ] UISizeConstraint diff --git a/src/Builders/GuiObjectBuilder.luau b/src/Builders/GuiObjectBuilder.luau index adf7060..d4f357b 100644 --- a/src/Builders/GuiObjectBuilder.luau +++ b/src/Builders/GuiObjectBuilder.luau @@ -12,6 +12,12 @@ export type Border = { Transparency: number?, } +export type Aspect = { + Ratio: number, + Type: Enum.AspectType?, + DominantAxis: Enum.DominantAxis?, +} + export type GuiObjectProps = { AnchorPoint: Utils.AnchorPoint?, AutomaticSize: Enum.AutomaticSize?, @@ -31,7 +37,11 @@ export type GuiObjectProps = { ZIndex: number?, Border: Border?, - CornerRadius: Utils.UDimValue, + CornerRadius: Utils.UDimValue?, + + Aspect: Aspect?, + + Padding: Utils.PaddingValue?, } return Builder.new(function(props: GuiObjectProps) @@ -58,5 +68,12 @@ return Builder.new(function(props: GuiObjectProps) BRCCorner = props.CornerRadius and React.createElement("UICorner", { CornerRadius = Utils.ConvertUDim(props.CornerRadius), }), + BRCAspectRatio = props.Aspect and React.createElement("UIAspectRatioConstraint", { + AspectRatio = props.Aspect.Ratio, + AspectType = props.Aspect.Type, + DominantAxis = props.Aspect.DominantAxis, + }), + BRCPadding = props.Padding + and React.createElement("UIPadding", Utils.ConvertPadding(props.Padding):getValue()), } end) diff --git a/src/Utils.luau b/src/Utils.luau index f244f1b..db4225f 100644 --- a/src/Utils.luau +++ b/src/Utils.luau @@ -30,6 +30,15 @@ export type FontValue = Font | EnumItem --- Takes `UDim` or `number` as offset export type UDimValue = UDim | number +--- Padding (like in CSS) +--- +--- There's 3 scenarios +--- 1. Value is `UDimValue` or `{UDimValue}`: padding `UDimValue` is applied to all sides +--- 2. Value is `{UDimValue, UDimValue}`: first value is vertical padding, second - horizontal padding +--- 3. Value is `{UDimValue, UDimValue, UDimValue}`: where each value is {Top, Horizontal, Bottom} +--- 4. Value is `{UDimValue, UDimValue, UDimValue, UDimValue}`: where each value is {Top, Right, Bottom, Left} +export type PaddingValue = UDimValue | { UDimValue } + export type BindingOrValue = React.Binding | T local AnchorPointMapping = setmetatable({ @@ -124,21 +133,94 @@ function Utils.ConvertFont(value: BindingOrValue): React.Binding): React.Binding + return Utils.mapBinding(value, ConvertUDimInternal) +end + +export type ConvertPaddingResult = { + PaddingBottom: UDim, + PaddingLeft: UDim, + PaddingRight: UDim, + PaddingTop: UDim, +} + +local function ConvertPaddingInternal(value: { UDim }): ConvertPaddingResult + if #value == 0 then + return { PaddingTop = 0, PaddingRight = 0, PaddingBottom = 0, PaddingLeft = 0 } + elseif #value == 1 then + return { + PaddingTop = value[1], + PaddingRight = value[1], + PaddingBottom = value[1], + PaddingLeft = value[1], + } + elseif #value == 2 then + return { + PaddingTop = value[1], + PaddingRight = value[2], + PaddingBottom = value[1], + PaddingLeft = value[2], + } + elseif #value == 3 then + return { + PaddingTop = value[1], + PaddingRight = value[2], + PaddingBottom = value[3], + PaddingLeft = value[2], + } + elseif #value == 4 then + return { + PaddingTop = value[1], + PaddingRight = value[2], + PaddingBottom = value[3], + PaddingLeft = value[4], + } + else + error( + string.format( + "Invalid value in ConvertUDim. Expected up to 4 padding values, found %d.", + #value + ) + ) + end +end + +function Utils.ConvertPadding( + value: BindingOrValue +): React.Binding return Utils.mapBinding(value, function(value) - if typeof(value) == "UDim" then - return value - elseif typeof(value) == "number" then - return UDim.new(0, value) + if typeof(value) == "table" then + local result: { UDim } = {} + for _, v in value do + result[#result + 1] = ConvertUDimInternal(v) + end + + return ConvertPaddingInternal(result) + elseif typeof(value) == "UDim" or typeof(value) == "number" then + return ConvertPaddingInternal({ ConvertUDimInternal(value) }) else error( string.format( - 'Invalid value in ConvertUDim. Expected UDim or number, found "%s"', + 'Invalid value in ConvertPadding. Expected UDimValue or {UDimValue}, found "%s"', typeof(value) ) ) end end) end - return Utils diff --git a/src/stories/Modifiers/AspectRatio.story.luau b/src/stories/Modifiers/AspectRatio.story.luau new file mode 100644 index 0000000..ede032a --- /dev/null +++ b/src/stories/Modifiers/AspectRatio.story.luau @@ -0,0 +1,29 @@ +local React = require("@pkg/react") +local ReactRoblox = require("@pkg/react-roblox") +local UILabs = require("@dev-pkg/ui-labs") + +local Frame = require("../..").Frame + +local story = { + react = React, + reactRoblox = ReactRoblox, + controls = { + AspectRatio = UILabs.Slider(1, 0.5, 5, 0.1), + }, + story = function(props) + return React.createElement(Frame, { + AnchorPoint = "m", + Position = UDim2.new(0.5, 0, 0.5, 0), + Size = UDim2.new(0.5, 0, 0.5, 0), + BackgroundColor = "#FFF", + + Aspect = { + Ratio = props.controls.AspectRatio, -- number + Type = Enum.AspectType.FitWithinMaxSize, + DominantAxis = Enum.DominantAxis.Width, + }, + }) + end, +} + +return story diff --git a/src/stories/Modifiers/Padding.story.luau b/src/stories/Modifiers/Padding.story.luau new file mode 100644 index 0000000..632c65c --- /dev/null +++ b/src/stories/Modifiers/Padding.story.luau @@ -0,0 +1,49 @@ +local React = require("@pkg/react") +local ReactRoblox = require("@pkg/react-roblox") +local UILabs = require("@dev-pkg/ui-labs") +local BetterReactComponents = require("../..") + +local Frame = BetterReactComponents.Frame +local TextLabel = BetterReactComponents.TextLabel + +local function PaddingStory(props) + local Padding = React.useMemo(function() + local Padding = {} + for v in string.gmatch(props.controls.Padding, "%d+") do + Padding[#Padding + 1] = tonumber(v) + end + return Padding + end, { props.controls.Padding }) + + return React.createElement( + Frame, + { + AnchorPoint = "m", + Position = UDim2.new(0.5, 0, 0.5, 0), + Size = UDim2.new(0.5, 0, 0.5, 0), + BackgroundColor = "#F00", + + Padding = Padding, + }, + React.createElement(TextLabel, { + Size = UDim2.new(1, 0, 1, 0), + BackgroundColor = "#FFF", + Text = ("Padding = {%s}"):format(table.concat(Padding, ", ")), + Font = Enum.Font.Roboto, + TextSize = 30 + }) + ) +end + +local story = { + react = React, + reactRoblox = ReactRoblox, + controls = { + Padding = "15", + }, + story = function(props) + return React.createElement(PaddingStory, props) + end, +} + +return story From 0cb48bada5b3dff1b7ca787fd791869481559ec8 Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Thu, 1 May 2025 09:26:06 +0300 Subject: [PATCH 09/28] feat: `UISizeConstraint` and `UITextSizeConstraint` --- README.md | 4 +-- src/Builders/GuiObjectBuilder.luau | 9 ++++++ src/Builders/TextBuilder.luau | 13 ++++++++- .../Modifiers/SizeConstraint.story.luau | 26 +++++++++++++++++ .../Modifiers/TextSizeConstraint.story.luau | 29 +++++++++++++++++++ 5 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 src/stories/Modifiers/SizeConstraint.story.luau create mode 100644 src/stories/Modifiers/TextSizeConstraint.story.luau diff --git a/README.md b/README.md index d715c05..feebab2 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Respectfully, - [x] UIPadding - [ ] UIPageLayout - [ ] UIScale -- [ ] UISizeConstraint +- [x] UISizeConstraint - [x] UIStroke (`Border` property) - [ ] UITableLayout -- [ ] UITextSizeConstraint +- [x] UITextSizeConstraint diff --git a/src/Builders/GuiObjectBuilder.luau b/src/Builders/GuiObjectBuilder.luau index d4f357b..0cf1df5 100644 --- a/src/Builders/GuiObjectBuilder.luau +++ b/src/Builders/GuiObjectBuilder.luau @@ -42,6 +42,9 @@ export type GuiObjectProps = { Aspect: Aspect?, Padding: Utils.PaddingValue?, + + MinSize: Vector2, + MaxSize: Vector2, } return Builder.new(function(props: GuiObjectProps) @@ -75,5 +78,11 @@ return Builder.new(function(props: GuiObjectProps) }), BRCPadding = props.Padding and React.createElement("UIPadding", Utils.ConvertPadding(props.Padding):getValue()), + + BRCSizeConstraint = (props.MinSize ~= nil or props.MaxSize ~= nil) + and React.createElement("UISizeConstraint", { + MinSize = props.MinSize, + MaxSize = props.MaxSize, + }), } end) diff --git a/src/Builders/TextBuilder.luau b/src/Builders/TextBuilder.luau index e566029..8ffa7f9 100644 --- a/src/Builders/TextBuilder.luau +++ b/src/Builders/TextBuilder.luau @@ -1,3 +1,5 @@ +local React = require("@pkg/react") + local Utils = require("../Utils") local GuiObjectBuilder = require("./GuiObjectBuilder") @@ -13,6 +15,9 @@ export type TextProps = { TextXAlignment: Enum.TextXAlignment?, TextYAlignment: Enum.TextYAlignment?, + + MinTextSize: number, + MaxTextSize: number, } --- Builder for text-based objects (`TextLabel`, `TextButton`, etc.) @@ -31,5 +36,11 @@ return GuiObjectBuilder:expand(function(props: TextProps) TextXAlignment = props.TextXAlignment, TextYAlignment = props.TextYAlignment, - }, {} + }, { + BRCTextSizeConstraint = (props.MinTextSize ~= nil or props.MaxTextSize ~= nil) + and React.createElement("UITextSizeConstraint", { + MinTextSize = props.MinTextSize, + MaxTextSize = props.MaxTextSize, + }), + } end) diff --git a/src/stories/Modifiers/SizeConstraint.story.luau b/src/stories/Modifiers/SizeConstraint.story.luau new file mode 100644 index 0000000..15e530e --- /dev/null +++ b/src/stories/Modifiers/SizeConstraint.story.luau @@ -0,0 +1,26 @@ + +local React = require("@pkg/react") +local ReactRoblox = require("@pkg/react-roblox") +local UILabs = require("@dev-pkg/ui-labs") + +local Frame = require("../..").Frame + +return { + react = React, + reactRoblox = ReactRoblox, + controls = { + MinSizeX = UILabs.Slider(100, 0, 255, 1), + MaxSizeX = UILabs.Slider(200, 0, 500, 1), + }, + story = function(props) + return React.createElement(Frame, { + AnchorPoint = "m", + Position = UDim2.new(0.5, 0, 0.5, 0), + Size = UDim2.new(0.5, 0, 0.5, 0), + BackgroundColor = "#FFF", + + MinSize = Vector2.new(props.controls.MinSizeX, 0), + MaxSize = Vector2.new(props.controls.MaxSizeX, math.huge), + }) + end, +} diff --git a/src/stories/Modifiers/TextSizeConstraint.story.luau b/src/stories/Modifiers/TextSizeConstraint.story.luau new file mode 100644 index 0000000..087cc18 --- /dev/null +++ b/src/stories/Modifiers/TextSizeConstraint.story.luau @@ -0,0 +1,29 @@ +local React = require("@pkg/react") +local ReactRoblox = require("@pkg/react-roblox") +local UILabs = require("@dev-pkg/ui-labs") + +local TextLabel = require("../..").TextLabel + +return { + react = React, + reactRoblox = ReactRoblox, + controls = { + SizeY = UILabs.Slider(100, 0, 500), + MaxTextSize = UILabs.Slider(20, 1, 100) + }, + story = function(props) + return React.createElement(TextLabel, { + AnchorPoint = "m", + Position = UDim2.new(0.5, 0, 0.5, 0), + Size = UDim2.new(0.5, 0, 0, props.controls.SizeY), + BackgroundColor = "#FFF", + TextColor = "#F00", + + Text = "creepy ahh text", + TextSize = "auto", + Font = Enum.Font.Creepster, + + MaxTextSize = props.controls.MaxTextSize + }) + end, +} From ccfa9862c732aee71a25615d166bc30be3e4a88b Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Sat, 3 May 2025 10:15:33 +0300 Subject: [PATCH 10/28] docs: added and sorted support --- README.md | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index feebab2..3d0f984 100644 --- a/README.md +++ b/README.md @@ -36,26 +36,31 @@ Respectfully, ### Components - [x] Frame -- [ ] ScrollableFrame +- [ ] ScrollingFrame +- [ ] CanvasGroup +- [ ] ViewportFrame - [x] TextButton -- [ ] ImageButton -- [ ] Image - [x] TextLabel - [ ] TextBox -- [ ] CanvasGroup +- [ ] ImageButton +- [ ] ImageLabel +- [ ] Path2D ### Modifiers - [x] UIAspectRatioConstraint - [x] UICorner (`CornerRadius` property) +- [x] UIStroke (`Border` property) +- [x] UIPadding +- [x] UISizeConstraint +- [x] UITextSizeConstraint +- [ ] UIScale - [ ] UIGradient + +### Layouts + - [ ] UIGridLayout - [ ] UIListLayout -- [ ] UIFlexLayout -- [x] UIPadding -- [ ] UIPageLayout -- [ ] UIScale -- [x] UISizeConstraint -- [x] UIStroke (`Border` property) +- [ ] UIFlexItem - [ ] UITableLayout -- [x] UITextSizeConstraint +- [ ] UIPageLayout From 67ccbb625ccbefdaf55947fcbb88442e305b11ea Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Sat, 3 May 2025 14:21:09 +0300 Subject: [PATCH 11/28] feat: `UIListLayout` --- README.md | 2 +- src/Builders/GuiObjectBuilder.luau | 5 + src/Helpers/ListLayout.luau | 42 ++++++++ src/Utils.luau | 143 ++++++++++++++++++++++++++++ src/stories/Layouts/List.story.luau | 56 +++++++++++ 5 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 src/Helpers/ListLayout.luau create mode 100644 src/stories/Layouts/List.story.luau diff --git a/README.md b/README.md index 3d0f984..3c7131f 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Respectfully, ### Layouts - [ ] UIGridLayout -- [ ] UIListLayout +- [x] UIListLayout - [ ] UIFlexItem - [ ] UITableLayout - [ ] UIPageLayout diff --git a/src/Builders/GuiObjectBuilder.luau b/src/Builders/GuiObjectBuilder.luau index 0cf1df5..5d86ce4 100644 --- a/src/Builders/GuiObjectBuilder.luau +++ b/src/Builders/GuiObjectBuilder.luau @@ -2,6 +2,8 @@ local React = require("@pkg/react") local Builder = require("../Builder") local Utils = require("../Utils") +local ListLayout = require("../Helpers/ListLayout") + --- Border settings --- --- NOTE: when building, it uses `UIStroke`, instead of `GuiObject`'s border properties @@ -45,6 +47,8 @@ export type GuiObjectProps = { MinSize: Vector2, MaxSize: Vector2, + + ListLayout: ListLayout.ListLayoutProps, } return Builder.new(function(props: GuiObjectProps) @@ -84,5 +88,6 @@ return Builder.new(function(props: GuiObjectProps) MinSize = props.MinSize, MaxSize = props.MaxSize, }), + BRCListLayout = props.ListLayout and React.createElement(ListLayout, props.ListLayout), } end) diff --git a/src/Helpers/ListLayout.luau b/src/Helpers/ListLayout.luau new file mode 100644 index 0000000..a223e0b --- /dev/null +++ b/src/Helpers/ListLayout.luau @@ -0,0 +1,42 @@ +local React = require("@pkg/react") +local Utils = require("../Utils") + +export type ListLayoutProps = { + Gap: Utils.UDimValue, + + Direction: Enum.FillDirection?, + Order: Enum.SortOrder?, + Wraps: boolean?, + + JustifyContent: Utils.FlexAlignment?, + AlignItems: Utils.FlexAlignment?, + -- TODO: ItemLineAlignment +} + +return function(props: ListLayoutProps) + local rbxProps = React.useMemo(function() + local MainAxis = props.Direction or Enum.FillDirection.Vertical + local CrossAxis = if MainAxis == Enum.FillDirection.Horizontal + then Enum.FillDirection.Vertical + else Enum.FillDirection.Horizontal + + local MainAxisResult = if props.JustifyContent ~= nil + then Utils.ConvertFlexValue(MainAxis, props.JustifyContent) + else {} + local CrossAxisResult = if props.AlignItems ~= nil + then Utils.ConvertFlexValue(CrossAxis, props.AlignItems) + else {} + + local result = { + Padding = props.Gap and Utils.ConvertUDim(props.Gap), + + FillDirection = MainAxis, + SortOrder = props.Order, + Wraps = props.Wraps, + } + + return Utils.MergeMulti(result, MainAxisResult, CrossAxisResult) + end, {props}) + + return React.createElement("UIListLayout", rbxProps) +end diff --git a/src/Utils.luau b/src/Utils.luau index db4225f..19361b4 100644 --- a/src/Utils.luau +++ b/src/Utils.luau @@ -41,6 +41,15 @@ export type PaddingValue = UDimValue | { UDimValue } export type BindingOrValue = React.Binding | T +export type FlexAlignment = + "start" + | "end" + | "center" + | "space-between" + | "space-around" + | "space-evenly" + | "stretch" + local AnchorPointMapping = setmetatable({ tl = Vector2.new(0, 0), t = Vector2.new(0.5, 0), @@ -82,6 +91,33 @@ function Utils.Assign(table1: T1, table2: T2): T1 & T2 return result :: T1 & T2 end +-- thanks to https://gist.github.com/qizhihere/cb2a14432d9bf65693ad?permalink_comment_id=3389084#gistcomment-3389084 +function Utils.MergeMulti(...): any + local tables_to_merge = { ... } + assert(#tables_to_merge > 1, "There should be at least two tables to merge them") + + for k, t in ipairs(tables_to_merge) do + assert(typeof(t) == "table", string.format("Expected a table as function parameter %d", k)) + end + + local result = table.clone(tables_to_merge[1]) + + for i = 2, #tables_to_merge do + local from = tables_to_merge[i] + for k, v in pairs(from) do + if type(v) == "table" then + result[k] = result[k] or {} + assert(type(result[k]) == "table", string.format("Expected a table: '%s'", k)) + result[k] = Utils.MergeMulti(result[k], v) + else + result[k] = v + end + end + end + + return result +end + function Utils.ConvertColor(value: BindingOrValue): React.Binding return Utils.mapBinding(value, function(value) if typeof(value) == "Color3" then @@ -223,4 +259,111 @@ function Utils.ConvertPadding( end end) end + +export type ConvertFlexValueResult = { + HorizontalAlignment: Enum.HorizontalAlignment?, + HorizontalFlex: Enum.UIFlexAlignment?, + VerticalAlignment: Enum.VerticalAlignment?, + VerticalFlex: Enum.UIFlexAlignment?, +} + +--- Converts CSS like JustifyContent and AlignItems values to Roblox's ListLayout props +function Utils.ConvertFlexValue( + direction: Enum.FillDirection, + value: FlexAlignment +): ConvertFlexValueResult + if value == "start" then + if direction == Enum.FillDirection.Horizontal then + return { + HorizontalAlignment = Enum.HorizontalAlignment.Left, + HorizontalFlex = Enum.UIFlexAlignment.None, + } + else + return { + VerticalAlignment = Enum.VerticalAlignment.Top, + VerticalFlex = Enum.UIFlexAlignment.None, + } + end + elseif value == "end" then + if direction == Enum.FillDirection.Horizontal then + return { + HorizontalAlignment = Enum.HorizontalAlignment.Right, + HorizontalFlex = Enum.UIFlexAlignment.None, + } + else + return { + VerticalAlignment = Enum.VerticalAlignment.Bottom, + VerticalFlex = Enum.UIFlexAlignment.None, + } + end + elseif value == "center" then + if direction == Enum.FillDirection.Horizontal then + return { + HorizontalAlignment = Enum.HorizontalAlignment.Center, + HorizontalFlex = Enum.UIFlexAlignment.None, + } + else + return { + VerticalAlignment = Enum.VerticalAlignment.Center, + VerticalFlex = Enum.UIFlexAlignment.None, + } + end + elseif value == "space-between" then + if direction == Enum.FillDirection.Horizontal then + return { + HorizontalAlignment = Enum.HorizontalAlignment.Center, + HorizontalFlex = Enum.UIFlexAlignment.SpaceBetween, + } + else + return { + VerticalAlignment = Enum.VerticalAlignment.Center, + VerticalFlex = Enum.UIFlexAlignment.SpaceBetween, + } + end + elseif value == "space-around" then + if direction == Enum.FillDirection.Horizontal then + return { + HorizontalAlignment = Enum.HorizontalAlignment.Center, + HorizontalFlex = Enum.UIFlexAlignment.SpaceAround, + } + else + return { + VerticalAlignment = Enum.VerticalAlignment.Center, + VerticalFlex = Enum.UIFlexAlignment.SpaceAround, + } + end + elseif value == "space-evenly" then + if direction == Enum.FillDirection.Horizontal then + return { + HorizontalAlignment = Enum.HorizontalAlignment.Center, + HorizontalFlex = Enum.UIFlexAlignment.SpaceEvenly, + } + else + return { + VerticalAlignment = Enum.VerticalAlignment.Center, + VerticalFlex = Enum.UIFlexAlignment.SpaceEvenly, + } + end + elseif value == "stretch" then + if direction == Enum.FillDirection.Horizontal then + return { + HorizontalAlignment = Enum.HorizontalAlignment.Center, + HorizontalFlex = Enum.UIFlexAlignment.Fill, + } + else + return { + VerticalAlignment = Enum.VerticalAlignment.Center, + VerticalFlex = Enum.UIFlexAlignment.Fill, + } + end + else + error( + string.format( + 'Invalid value in ConvertFlexValue. Expected "start", "end", "center", "space-between", "space-around", "space-evenly" or "stretch", found "%s"', + value + ) + ) + end +end + return Utils diff --git a/src/stories/Layouts/List.story.luau b/src/stories/Layouts/List.story.luau new file mode 100644 index 0000000..86d202c --- /dev/null +++ b/src/stories/Layouts/List.story.luau @@ -0,0 +1,56 @@ +local React = require("@pkg/react") +local ReactRoblox = require("@pkg/react-roblox") +local UILabs = require("@dev-pkg/ui-labs") + +local Frame = require("../..").Frame + +local FlexAlignmentValues = { + Start = "start", + End = "end", + Center = "center", + SpaceBetween = "space-between", + SpaceAround = "space-around", + SpaceEvenly = "space-evenly", + Stretch = "stretch", +} + +return { + react = React, + reactRoblox = ReactRoblox, + controls = { + Gap = UILabs.Slider(0, 0, 24, 1), + JustifyContent = UILabs.EnumList(FlexAlignmentValues, "Center"), + AlignItems = UILabs.EnumList(FlexAlignmentValues, "Center"), + }, + story = function(props) + return React.createElement(Frame, { + AnchorPoint = "m", + Position = UDim2.new(0.5, 0, 0.5, 0), + Size = UDim2.new(0.5, 0, 0.75, 0), + BackgroundColor = "#FFF", + + Padding = props.controls.Gap, + ListLayout = { + -- JustifyContent is alignment at main axis + -- AlignItems is alignment at cross axis + + Gap = props.controls.Gap, + JustifyContent = props.controls.JustifyContent, -- CSS like values: start, end, center, space-between, space-around, space-evenly and stretch + AlignItems = props.controls.AlignItems, -- same as above + }, + }, { + React.createElement( + Frame, + { Size = UDim2.new(0.5, 0, 0.25, 0), BackgroundColor = "#E80000" } + ), + React.createElement( + Frame, + { Size = UDim2.new(0.5, 0, 0.25, 0), BackgroundColor = "#00E800" } + ), + React.createElement( + Frame, + { Size = UDim2.new(0.5, 0, 0.25, 0), BackgroundColor = "#0000E8" } + ), + }) + end, +} From 2c1990cb512719afa8deaed615ed465e3c9c3be5 Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Sun, 4 May 2025 09:22:30 +0300 Subject: [PATCH 12/28] feat: `UIFlexItem` --- README.md | 2 +- src/Builders/GuiObjectBuilder.luau | 9 ++-- src/stories/Layouts/FlexItem.story.luau | 57 +++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 src/stories/Layouts/FlexItem.story.luau diff --git a/README.md b/README.md index 3c7131f..aeb197c 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,6 @@ Respectfully, - [ ] UIGridLayout - [x] UIListLayout -- [ ] UIFlexItem +- [x] UIFlexItem - [ ] UITableLayout - [ ] UIPageLayout diff --git a/src/Builders/GuiObjectBuilder.luau b/src/Builders/GuiObjectBuilder.luau index 5d86ce4..2bdd9ce 100644 --- a/src/Builders/GuiObjectBuilder.luau +++ b/src/Builders/GuiObjectBuilder.luau @@ -45,10 +45,11 @@ export type GuiObjectProps = { Padding: Utils.PaddingValue?, - MinSize: Vector2, - MaxSize: Vector2, + MinSize: Vector2?, + MaxSize: Vector2?, - ListLayout: ListLayout.ListLayoutProps, + ListLayout: ListLayout.ListLayoutProps?, + FlexMode: Enum.UIFlexMode?, } return Builder.new(function(props: GuiObjectProps) @@ -89,5 +90,7 @@ return Builder.new(function(props: GuiObjectProps) MaxSize = props.MaxSize, }), BRCListLayout = props.ListLayout and React.createElement(ListLayout, props.ListLayout), + BRCFlexItem = props.FlexMode + and React.createElement("UIFlexItem", { FlexMode = props.FlexMode }), } end) diff --git a/src/stories/Layouts/FlexItem.story.luau b/src/stories/Layouts/FlexItem.story.luau new file mode 100644 index 0000000..b75291c --- /dev/null +++ b/src/stories/Layouts/FlexItem.story.luau @@ -0,0 +1,57 @@ +local React = require("@pkg/react") +local ReactRoblox = require("@pkg/react-roblox") +local UILabs = require("@dev-pkg/ui-labs") + +local BRC = require("../..") + +local FlexAlignmentValues = { + Start = "start", + End = "end", + Center = "center", + SpaceBetween = "space-between", + SpaceAround = "space-around", + SpaceEvenly = "space-evenly", + Stretch = "stretch", +} + +return { + react = React, + reactRoblox = ReactRoblox, + controls = { + ParentSizePercent = UILabs.Slider(70, 0, 100, 1), + }, + story = function(props) + return React.createElement(BRC.Frame, { + AnchorPoint = "m", + Position = UDim2.new(0.5, 0, 0.5, 0), + Size = UDim2.new(props.controls.ParentSizePercent / 100, 0, 0.25, 0), + BackgroundColor = "#FFF", + + Padding = props.controls.Gap, + ListLayout = { + Gap = props.controls.Gap, + Direction = Enum.FillDirection.Horizontal, + }, + }, { + React.createElement(BRC.TextLabel, { + Size = UDim2.new(0.5, 0, 1, 0), + BackgroundColor = "#E80000", + Text = "This component has SizeX = {0.5, 0} (NOTE: Scale units), FlexMode = Fill", + TextWrapped = true, + + TextSize = 10, + + FlexMode = Enum.UIFlexMode.Fill, + }), + React.createElement(BRC.TextLabel, { + Size = UDim2.new(0, 300, 1, 0), + BackgroundColor = "#080808", + TextColor = "#FFFFFF", + + Text = "But this component has SizeX = {0, 300} (NOTE: Offset units). Without FlexMode, this would be painful to recreate", + TextWrapped = true, + TextSize = 10, + }), + }) + end, +} From efe51f78da9e427b65e1077d5102890f0eb80082 Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Sun, 4 May 2025 13:08:43 +0300 Subject: [PATCH 13/28] refactor: refactored building scripts/files, added roblox model building --- .darklua-dev.json | 3 ++- .darklua-roblox.json | 18 +++++++++++++++ .darklua-wally.json | 3 ++- Makefile | 9 +++++--- README.md | 20 ++++++++++------- build/roblox/.gitignore | 10 +++++++++ build/roblox/default.project.json | 9 ++++++++ build/wally/.gitignore | 5 +++-- build/wally/default.project.json | 6 +++++ ...ly.project.json => sourcemap.project.json} | 2 +- default.project.json | 16 ++++++++++++-- dev-sourcemap.project.json | 22 ------------------- dev.project.json => out.project.json | 6 +---- scripts/roblox.sh | 22 +++++++++++++++++++ scripts/wally.sh | 4 ++-- wally.toml | 4 ++-- 16 files changed, 110 insertions(+), 49 deletions(-) create mode 100644 .darklua-roblox.json create mode 100644 build/roblox/.gitignore create mode 100644 build/roblox/default.project.json create mode 100644 build/wally/default.project.json rename build/wally/{wally.project.json => sourcemap.project.json} (76%) delete mode 100644 dev-sourcemap.project.json rename dev.project.json => out.project.json (74%) create mode 100755 scripts/roblox.sh diff --git a/.darklua-dev.json b/.darklua-dev.json index d87fd68..c30d1df 100644 --- a/.darklua-dev.json +++ b/.darklua-dev.json @@ -11,7 +11,8 @@ }, "target": { "name": "roblox", - "rojo_sourcemap": "./sourcemap.json" + "rojo_sourcemap": "./sourcemap.json", + "indexing_style": "wait_for_child" } } ] diff --git a/.darklua-roblox.json b/.darklua-roblox.json new file mode 100644 index 0000000..f352416 --- /dev/null +++ b/.darklua-roblox.json @@ -0,0 +1,18 @@ +{ + "rules": [ + { + "rule": "convert_require", + "current": { + "name": "path", + "sources": { + "@pkg": "Packages" + } + }, + "target": { + "name": "roblox", + "rojo_sourcemap": "./sourcemap.json", + "indexing_style": "wait_for_child" + } + } + ] +} diff --git a/.darklua-wally.json b/.darklua-wally.json index c763410..f352416 100644 --- a/.darklua-wally.json +++ b/.darklua-wally.json @@ -10,7 +10,8 @@ }, "target": { "name": "roblox", - "rojo_sourcemap": "./sourcemap.json" + "rojo_sourcemap": "./sourcemap.json", + "indexing_style": "wait_for_child" } } ] diff --git a/Makefile b/Makefile index 6cee1e3..fd7bd55 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,15 @@ install: install-toolchain cleanup: cleanup-build +roblox-package: cleanup-build + ./scripts/roblox.sh + wally-package: cleanup-build ./scripts/wally.sh # development serve: - rojo serve dev.project.json + rojo serve out.project.json watch: Packages/ DevPackages/ sourcemap.json .darklua-dev.json darklua process -w -c .darklua-dev.json src out @@ -16,8 +19,8 @@ Packages DevPackages: wally.toml wally.lock wally-package-types --sourcemap sourcemap.json Packages/ wally-package-types --sourcemap sourcemap.json DevPackages/ -sourcemap.json: src/* dev.project.json - rojo sourcemap dev-sourcemap.project.json --output sourcemap.json +sourcemap.json: src/* default.project.json + rojo sourcemap default.project.json --output sourcemap.json # intermediate steps diff --git a/README.md b/README.md index aeb197c..7c369b0 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # better-react-components -Roblox's UI elements, but with builtin modifiers +Roblox UI elements, but with builtin modifiers -> ![NOTE] +> [!NOTE] > > Currently, I am rewriting this library from Typescript to Lua. > @@ -19,16 +19,20 @@ Respectfully, Past Me ``` -## Development +## Development & Build from Scratch 1. Install Aftman and Make 2. Run: ``` - make install # installs all toolchain, dependencies and patch sourcemap.json - make serve # starts rojo server - make build # builds .rbxmx (NOT IMPLEMENTED) - - make patch-sourcemaps # patches sourcemaps. needed after installing wally dependencies + make install # installs toolchain + + # Development stuff: + make watch # starts darklua's processer + make serve # starts rojo server + + # Buildin' stuff: + make wally-package # builds wally package at `build/wally/` + make roblox-package # builds roblox model file at `build/wally/better-react-components.rbxmx` ``` ## Supported Components/Modifiers diff --git a/build/roblox/.gitignore b/build/roblox/.gitignore new file mode 100644 index 0000000..45d22a3 --- /dev/null +++ b/build/roblox/.gitignore @@ -0,0 +1,10 @@ +# This folder used for storing Roblox package files +# +# `default.project.json` is used for generating sourcemap.json and building .rbxmx file +# +# To build roblox package run: +# $ make roblox-package + +* +!.gitignore +!default.project.json diff --git a/build/roblox/default.project.json b/build/roblox/default.project.json new file mode 100644 index 0000000..213019b --- /dev/null +++ b/build/roblox/default.project.json @@ -0,0 +1,9 @@ +{ + "name": "better-react-components", + "tree": { + "_Vendor": { + "$path": "Packages" + }, + "$path": "src" + } +} diff --git a/build/wally/.gitignore b/build/wally/.gitignore index 2af38b2..61d72fd 100644 --- a/build/wally/.gitignore +++ b/build/wally/.gitignore @@ -1,10 +1,11 @@ # This folder used for storing Wally package files # -# `wally.project.json` is used for generating sourcemap.json +# `sourcemap.project.json` is used for generating sourcemap.json # # To build wally package run: # $ make wally-package * !.gitignore -!wally.project.json +!default.project.json +!sourcemap.project.json diff --git a/build/wally/default.project.json b/build/wally/default.project.json new file mode 100644 index 0000000..8a94842 --- /dev/null +++ b/build/wally/default.project.json @@ -0,0 +1,6 @@ +{ + "name": "better-react-components", + "tree": { + "$path": "src" + } +} diff --git a/build/wally/wally.project.json b/build/wally/sourcemap.project.json similarity index 76% rename from build/wally/wally.project.json rename to build/wally/sourcemap.project.json index 095ea86..8375ec8 100644 --- a/build/wally/wally.project.json +++ b/build/wally/sourcemap.project.json @@ -1,5 +1,5 @@ { - "name": "better-react-components", + "name": "sourcemap", "tree": { "$path": "Packages", "better-react-components": { diff --git a/default.project.json b/default.project.json index 8a94842..f2cf788 100644 --- a/default.project.json +++ b/default.project.json @@ -1,6 +1,18 @@ { - "name": "better-react-components", + "name": "better-react-components-development", "tree": { - "$path": "src" + "$className": "DataModel", + "ReplicatedStorage": { + "$className": "ReplicatedStorage", + "DevPackages": { + "$path": "DevPackages" + }, + "Packages": { + "$path": "Packages" + }, + "better-react-components": { + "$path": "src" + } + } } } diff --git a/dev-sourcemap.project.json b/dev-sourcemap.project.json deleted file mode 100644 index 97cc14c..0000000 --- a/dev-sourcemap.project.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "better-react-components-stories", - "globIgnorePaths": [ - "**/package.json", - "**/tsconfig.json" - ], - "tree": { - "$className": "DataModel", - "ReplicatedStorage": { - "$className": "ReplicatedStorage", - "DevPackages": { - "$path": "DevPackages" - }, - "Packages": { - "$path": "Packages" - }, - "better-react-components": { - "$path": "src" - } - } - } -} diff --git a/dev.project.json b/out.project.json similarity index 74% rename from dev.project.json rename to out.project.json index ac81620..874445f 100644 --- a/dev.project.json +++ b/out.project.json @@ -1,9 +1,5 @@ { - "name": "better-react-components-stories", - "globIgnorePaths": [ - "**/package.json", - "**/tsconfig.json" - ], + "name": "better-react-components-development", "tree": { "$className": "DataModel", "ReplicatedStorage": { diff --git a/scripts/roblox.sh b/scripts/roblox.sh new file mode 100755 index 0000000..a90fb03 --- /dev/null +++ b/scripts/roblox.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -o pipefail +set -e + +mkdir -p build/roblox +cp README.md .darklua-roblox.json wally.toml wally.lock build/roblox/ +cp -r src build/roblox/ +rm -rf build/roblox/src/Stories +cd build/roblox + +wally install + +rojo sourcemap default.project.json --output sourcemap.json +wally-package-types --sourcemap sourcemap.json Packages/ +darklua process -c .darklua-roblox.json src out + +rm -rf src +mv out src + +# Build model +rojo build default.project.json --output better-react-components.rbxmx diff --git a/scripts/wally.sh b/scripts/wally.sh index 0e04a19..d1d52bb 100755 --- a/scripts/wally.sh +++ b/scripts/wally.sh @@ -4,14 +4,14 @@ set -o pipefail set -e mkdir -p build/wally -cp README.md default.project.json .darklua-wally.json wally.toml wally.lock build/wally/ +cp README.md .darklua-wally.json wally.toml wally.lock build/wally/ cp -r src build/wally/ rm -rf build/wally/src/Stories cd build/wally wally install -rojo sourcemap wally.project.json --output sourcemap.json +rojo sourcemap sourcemap.project.json --output sourcemap.json darklua process -c .darklua-wally.json src out rm -rf src diff --git a/wally.toml b/wally.toml index 281b9d6..907676f 100644 --- a/wally.toml +++ b/wally.toml @@ -7,8 +7,8 @@ realm = "shared" include = [ "src", "default.project.json", "README.md" ] [dependencies] -react = "jsdotlua/react@17.2.1" -react-roblox = "jsdotlua/react-roblox@17.2.1" +react = "jsdotlua/react@~17.2" +react-roblox = "jsdotlua/react-roblox@~17.2" [dev-dependencies] ui-labs = "pepeeltoro41/ui-labs@2.3.8" From 43888d1bfce4c79204d653f4cbf856dfd11e0a7d Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Sun, 4 May 2025 13:28:03 +0300 Subject: [PATCH 14/28] fix: capitalized `Stories/`, moved components --- src/{stories => Stories/Components}/Frame.story.luau | 0 src/{stories => Stories/Components}/TextLabel.story.luau | 0 src/{stories => Stories}/Layouts/FlexItem.story.luau | 0 src/{stories => Stories}/Layouts/List.story.luau | 0 src/{stories => Stories}/Modifiers/AspectRatio.story.luau | 0 src/{stories => Stories}/Modifiers/Padding.story.luau | 0 src/{stories => Stories}/Modifiers/SizeConstraint.story.luau | 0 src/{stories => Stories}/Modifiers/TextSizeConstraint.story.luau | 0 src/{stories => Stories}/Modifiers/UICorner.story.luau | 0 src/{stories => Stories}/Modifiers/UIStroke.story.luau | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename src/{stories => Stories/Components}/Frame.story.luau (100%) rename src/{stories => Stories/Components}/TextLabel.story.luau (100%) rename src/{stories => Stories}/Layouts/FlexItem.story.luau (100%) rename src/{stories => Stories}/Layouts/List.story.luau (100%) rename src/{stories => Stories}/Modifiers/AspectRatio.story.luau (100%) rename src/{stories => Stories}/Modifiers/Padding.story.luau (100%) rename src/{stories => Stories}/Modifiers/SizeConstraint.story.luau (100%) rename src/{stories => Stories}/Modifiers/TextSizeConstraint.story.luau (100%) rename src/{stories => Stories}/Modifiers/UICorner.story.luau (100%) rename src/{stories => Stories}/Modifiers/UIStroke.story.luau (100%) diff --git a/src/stories/Frame.story.luau b/src/Stories/Components/Frame.story.luau similarity index 100% rename from src/stories/Frame.story.luau rename to src/Stories/Components/Frame.story.luau diff --git a/src/stories/TextLabel.story.luau b/src/Stories/Components/TextLabel.story.luau similarity index 100% rename from src/stories/TextLabel.story.luau rename to src/Stories/Components/TextLabel.story.luau diff --git a/src/stories/Layouts/FlexItem.story.luau b/src/Stories/Layouts/FlexItem.story.luau similarity index 100% rename from src/stories/Layouts/FlexItem.story.luau rename to src/Stories/Layouts/FlexItem.story.luau diff --git a/src/stories/Layouts/List.story.luau b/src/Stories/Layouts/List.story.luau similarity index 100% rename from src/stories/Layouts/List.story.luau rename to src/Stories/Layouts/List.story.luau diff --git a/src/stories/Modifiers/AspectRatio.story.luau b/src/Stories/Modifiers/AspectRatio.story.luau similarity index 100% rename from src/stories/Modifiers/AspectRatio.story.luau rename to src/Stories/Modifiers/AspectRatio.story.luau diff --git a/src/stories/Modifiers/Padding.story.luau b/src/Stories/Modifiers/Padding.story.luau similarity index 100% rename from src/stories/Modifiers/Padding.story.luau rename to src/Stories/Modifiers/Padding.story.luau diff --git a/src/stories/Modifiers/SizeConstraint.story.luau b/src/Stories/Modifiers/SizeConstraint.story.luau similarity index 100% rename from src/stories/Modifiers/SizeConstraint.story.luau rename to src/Stories/Modifiers/SizeConstraint.story.luau diff --git a/src/stories/Modifiers/TextSizeConstraint.story.luau b/src/Stories/Modifiers/TextSizeConstraint.story.luau similarity index 100% rename from src/stories/Modifiers/TextSizeConstraint.story.luau rename to src/Stories/Modifiers/TextSizeConstraint.story.luau diff --git a/src/stories/Modifiers/UICorner.story.luau b/src/Stories/Modifiers/UICorner.story.luau similarity index 100% rename from src/stories/Modifiers/UICorner.story.luau rename to src/Stories/Modifiers/UICorner.story.luau diff --git a/src/stories/Modifiers/UIStroke.story.luau b/src/Stories/Modifiers/UIStroke.story.luau similarity index 100% rename from src/stories/Modifiers/UIStroke.story.luau rename to src/Stories/Modifiers/UIStroke.story.luau From b7ef4813164b58cebb5248f381719f1594ac87fe Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Sun, 4 May 2025 13:56:22 +0300 Subject: [PATCH 15/28] feat: added `TextButton` story, added alias `@root` for resolving root of package --- .darklua-dev.json | 3 +- .darklua-roblox.json | 3 +- .darklua-wally.json | 3 +- src/Builder.luau | 9 ++++- src/Builders/FrameBuilder.luau | 2 +- src/Builders/GuiObjectBuilder.luau | 6 ++-- src/Builders/TextBuilder.luau | 2 +- src/Components/Frame.luau | 2 +- src/Components/TextButton.luau | 2 +- src/Components/TextLabel.luau | 2 +- src/Helpers/ListLayout.luau | 5 +-- src/Stories/Components/Frame.story.luau | 4 +-- src/Stories/Components/TextButton.story.luau | 36 +++++++++++++++++++ src/Stories/Components/TextLabel.story.luau | 4 +-- src/Stories/Layouts/FlexItem.story.luau | 2 +- src/Stories/Layouts/List.story.luau | 10 +++--- src/Stories/Modifiers/AspectRatio.story.luau | 4 +-- src/Stories/Modifiers/Padding.story.luau | 8 ++--- .../Modifiers/SizeConstraint.story.luau | 4 +-- .../Modifiers/TextSizeConstraint.story.luau | 4 +-- src/Stories/Modifiers/UICorner.story.luau | 6 ++-- src/Stories/Modifiers/UIStroke.story.luau | 4 +-- src/init.luau | 6 ++-- 23 files changed, 88 insertions(+), 43 deletions(-) create mode 100644 src/Stories/Components/TextButton.story.luau diff --git a/.darklua-dev.json b/.darklua-dev.json index c30d1df..54984fd 100644 --- a/.darklua-dev.json +++ b/.darklua-dev.json @@ -6,7 +6,8 @@ "name": "path", "sources": { "@pkg": "Packages", - "@dev-pkg": "DevPackages" + "@dev-pkg": "DevPackages", + "@root": "src" } }, "target": { diff --git a/.darklua-roblox.json b/.darklua-roblox.json index f352416..0daa011 100644 --- a/.darklua-roblox.json +++ b/.darklua-roblox.json @@ -5,7 +5,8 @@ "current": { "name": "path", "sources": { - "@pkg": "Packages" + "@pkg": "Packages", + "@root": "src" } }, "target": { diff --git a/.darklua-wally.json b/.darklua-wally.json index f352416..0daa011 100644 --- a/.darklua-wally.json +++ b/.darklua-wally.json @@ -5,7 +5,8 @@ "current": { "name": "path", "sources": { - "@pkg": "Packages" + "@pkg": "Packages", + "@root": "src" } }, "target": { diff --git a/src/Builder.luau b/src/Builder.luau index 38c1317..6b5fb61 100644 --- a/src/Builder.luau +++ b/src/Builder.luau @@ -1,5 +1,5 @@ local React = require("@pkg/react") -local Utils = require("./Utils") +local Utils = require("@root/Utils") local ComponentBuilder = {} ComponentBuilder.__index = ComponentBuilder @@ -44,6 +44,13 @@ function ComponentBuilder.build( return React.forwardRef(function(props: React.ElementProps & P) local outputProps, outputChildren = self.transformer(props) + for k, v in pairs(props) do + -- 99.98% sure, that this is some special ahh key + if typeof(k) == "table" then + outputProps[k] = v + end + end + return React.createElement(robloxComponent, outputProps, outputChildren, props.children) end) :: any end diff --git a/src/Builders/FrameBuilder.luau b/src/Builders/FrameBuilder.luau index d091c35..d100647 100644 --- a/src/Builders/FrameBuilder.luau +++ b/src/Builders/FrameBuilder.luau @@ -1,4 +1,4 @@ -local GuiObjectBuilder = require(script.Parent.GuiObjectBuilder) +local GuiObjectBuilder = require("./GuiObjectBuilder") return GuiObjectBuilder:expand(function(props) return {}, {} diff --git a/src/Builders/GuiObjectBuilder.luau b/src/Builders/GuiObjectBuilder.luau index 2bdd9ce..b56e717 100644 --- a/src/Builders/GuiObjectBuilder.luau +++ b/src/Builders/GuiObjectBuilder.luau @@ -1,8 +1,8 @@ local React = require("@pkg/react") -local Builder = require("../Builder") -local Utils = require("../Utils") +local Builder = require("@root/Builder") +local Utils = require("@root/Utils") -local ListLayout = require("../Helpers/ListLayout") +local ListLayout = require("@root/Helpers/ListLayout") --- Border settings --- diff --git a/src/Builders/TextBuilder.luau b/src/Builders/TextBuilder.luau index 8ffa7f9..db6e360 100644 --- a/src/Builders/TextBuilder.luau +++ b/src/Builders/TextBuilder.luau @@ -1,6 +1,6 @@ local React = require("@pkg/react") -local Utils = require("../Utils") +local Utils = require("@root/Utils") local GuiObjectBuilder = require("./GuiObjectBuilder") export type TextProps = { diff --git a/src/Components/Frame.luau b/src/Components/Frame.luau index 3094e7d..47e5d3e 100644 --- a/src/Components/Frame.luau +++ b/src/Components/Frame.luau @@ -1,3 +1,3 @@ -local FrameBuilder = require("../Builders/FrameBuilder") +local FrameBuilder = require("@root/Builders/FrameBuilder") return FrameBuilder:build("Frame") diff --git a/src/Components/TextButton.luau b/src/Components/TextButton.luau index bbb7839..a479a02 100644 --- a/src/Components/TextButton.luau +++ b/src/Components/TextButton.luau @@ -1,3 +1,3 @@ -local TextButtonBuilder = require("../Builders/TextButtonBuilder") +local TextButtonBuilder = require("@root/Builders/TextButtonBuilder") return TextButtonBuilder:build("TextButton") diff --git a/src/Components/TextLabel.luau b/src/Components/TextLabel.luau index b85fab6..e9321cf 100644 --- a/src/Components/TextLabel.luau +++ b/src/Components/TextLabel.luau @@ -1,3 +1,3 @@ -local TextLabelBuilder = require("../Builders/TextLabelBuilder") +local TextLabelBuilder = require("@root/Builders/TextLabelBuilder") return TextLabelBuilder:build("TextLabel") diff --git a/src/Helpers/ListLayout.luau b/src/Helpers/ListLayout.luau index a223e0b..9b6dfcc 100644 --- a/src/Helpers/ListLayout.luau +++ b/src/Helpers/ListLayout.luau @@ -1,5 +1,6 @@ local React = require("@pkg/react") -local Utils = require("../Utils") + +local Utils = require("@root/Utils") export type ListLayoutProps = { Gap: Utils.UDimValue, @@ -36,7 +37,7 @@ return function(props: ListLayoutProps) } return Utils.MergeMulti(result, MainAxisResult, CrossAxisResult) - end, {props}) + end, { props }) return React.createElement("UIListLayout", rbxProps) end diff --git a/src/Stories/Components/Frame.story.luau b/src/Stories/Components/Frame.story.luau index bc78354..6c4787c 100644 --- a/src/Stories/Components/Frame.story.luau +++ b/src/Stories/Components/Frame.story.luau @@ -2,7 +2,7 @@ local React = require("@pkg/react") local ReactRoblox = require("@pkg/react-roblox") local UILabs = require("@dev-pkg/ui-labs") -local Frame = require(script.Parent.Parent.Components.Frame) +local BRC = require("@root/init") local story = { react = React, @@ -22,7 +22,7 @@ local story = { BackgroundColor = Color3.fromHex("#FF0000"), }, story = function(props) - return React.createElement(Frame, { + return React.createElement(BRC.Frame, { AnchorPoint = props.controls.AnchorPoint, Position = UDim2.new(0.5, 0, 0.5, 0), Size = UDim2.new(0.5, 0, 0.5, 0), diff --git a/src/Stories/Components/TextButton.story.luau b/src/Stories/Components/TextButton.story.luau new file mode 100644 index 0000000..6ad4a13 --- /dev/null +++ b/src/Stories/Components/TextButton.story.luau @@ -0,0 +1,36 @@ +local React = require("@pkg/react") +local ReactRoblox = require("@pkg/react-roblox") +local UILabs = require("@dev-pkg/ui-labs") + +local BRC = require("@root/init") + +local function TextButtonStory(props) + local count, setCount = React.useState(0) + + return React.createElement(BRC.TextButton, { + AnchorPoint = "m", + Position = UDim2.new(0.5, 0, 0.5, 0), + Size = UDim2.new(0.5, 0, 0.5, 0), + BackgroundColor = "#080808", + + TextColor = "#FFF", + TextSize = 18, + Text = ("This button was clicked %d times. How cool is that???"):format(count), + TextWrapped = true, + + [React.Event.Activated] = function() + setCount(function(oldCount) + return oldCount + 1 + end) + end, + }) +end + +return { + react = React, + reactRoblox = ReactRoblox, + controls = {}, + story = function(props) + return React.createElement(TextButtonStory, props) + end, +} diff --git a/src/Stories/Components/TextLabel.story.luau b/src/Stories/Components/TextLabel.story.luau index 62ccef2..37cadfe 100644 --- a/src/Stories/Components/TextLabel.story.luau +++ b/src/Stories/Components/TextLabel.story.luau @@ -2,7 +2,7 @@ local React = require("@pkg/react") local ReactRoblox = require("@pkg/react-roblox") local UILabs = require("@dev-pkg/ui-labs") -local TextLabel = require("../Components/TextLabel") +local BRC = require("@root/init") local Fonts = {} for _, font in pairs(Enum.Font:GetEnumItems()) do @@ -22,7 +22,7 @@ return { CornerRadius = 0, }, story = function(props) - return React.createElement(TextLabel, { + return React.createElement(BRC.TextLabel, { AnchorPoint = "m", Position = UDim2.new(0.5, 0, 0.5, 0), Size = UDim2.new(0.5, 0, 0.5, 0), diff --git a/src/Stories/Layouts/FlexItem.story.luau b/src/Stories/Layouts/FlexItem.story.luau index b75291c..0cc6947 100644 --- a/src/Stories/Layouts/FlexItem.story.luau +++ b/src/Stories/Layouts/FlexItem.story.luau @@ -2,7 +2,7 @@ local React = require("@pkg/react") local ReactRoblox = require("@pkg/react-roblox") local UILabs = require("@dev-pkg/ui-labs") -local BRC = require("../..") +local BRC = require("@root/init") local FlexAlignmentValues = { Start = "start", diff --git a/src/Stories/Layouts/List.story.luau b/src/Stories/Layouts/List.story.luau index 86d202c..20dee48 100644 --- a/src/Stories/Layouts/List.story.luau +++ b/src/Stories/Layouts/List.story.luau @@ -2,7 +2,7 @@ local React = require("@pkg/react") local ReactRoblox = require("@pkg/react-roblox") local UILabs = require("@dev-pkg/ui-labs") -local Frame = require("../..").Frame +local BRC = require("@root/init") local FlexAlignmentValues = { Start = "start", @@ -23,7 +23,7 @@ return { AlignItems = UILabs.EnumList(FlexAlignmentValues, "Center"), }, story = function(props) - return React.createElement(Frame, { + return React.createElement(BRC.Frame, { AnchorPoint = "m", Position = UDim2.new(0.5, 0, 0.5, 0), Size = UDim2.new(0.5, 0, 0.75, 0), @@ -40,15 +40,15 @@ return { }, }, { React.createElement( - Frame, + BRC.Frame, { Size = UDim2.new(0.5, 0, 0.25, 0), BackgroundColor = "#E80000" } ), React.createElement( - Frame, + BRC.Frame, { Size = UDim2.new(0.5, 0, 0.25, 0), BackgroundColor = "#00E800" } ), React.createElement( - Frame, + BRC.Frame, { Size = UDim2.new(0.5, 0, 0.25, 0), BackgroundColor = "#0000E8" } ), }) diff --git a/src/Stories/Modifiers/AspectRatio.story.luau b/src/Stories/Modifiers/AspectRatio.story.luau index ede032a..e42328d 100644 --- a/src/Stories/Modifiers/AspectRatio.story.luau +++ b/src/Stories/Modifiers/AspectRatio.story.luau @@ -2,7 +2,7 @@ local React = require("@pkg/react") local ReactRoblox = require("@pkg/react-roblox") local UILabs = require("@dev-pkg/ui-labs") -local Frame = require("../..").Frame +local BRC = require("@root/init") local story = { react = React, @@ -11,7 +11,7 @@ local story = { AspectRatio = UILabs.Slider(1, 0.5, 5, 0.1), }, story = function(props) - return React.createElement(Frame, { + return React.createElement(BRC.Frame, { AnchorPoint = "m", Position = UDim2.new(0.5, 0, 0.5, 0), Size = UDim2.new(0.5, 0, 0.5, 0), diff --git a/src/Stories/Modifiers/Padding.story.luau b/src/Stories/Modifiers/Padding.story.luau index 632c65c..da4f68c 100644 --- a/src/Stories/Modifiers/Padding.story.luau +++ b/src/Stories/Modifiers/Padding.story.luau @@ -1,10 +1,8 @@ local React = require("@pkg/react") local ReactRoblox = require("@pkg/react-roblox") local UILabs = require("@dev-pkg/ui-labs") -local BetterReactComponents = require("../..") -local Frame = BetterReactComponents.Frame -local TextLabel = BetterReactComponents.TextLabel +local BRC = require("@root/init") local function PaddingStory(props) local Padding = React.useMemo(function() @@ -16,7 +14,7 @@ local function PaddingStory(props) end, { props.controls.Padding }) return React.createElement( - Frame, + BRC.Frame, { AnchorPoint = "m", Position = UDim2.new(0.5, 0, 0.5, 0), @@ -25,7 +23,7 @@ local function PaddingStory(props) Padding = Padding, }, - React.createElement(TextLabel, { + React.createElement(BRC.TextLabel, { Size = UDim2.new(1, 0, 1, 0), BackgroundColor = "#FFF", Text = ("Padding = {%s}"):format(table.concat(Padding, ", ")), diff --git a/src/Stories/Modifiers/SizeConstraint.story.luau b/src/Stories/Modifiers/SizeConstraint.story.luau index 15e530e..a1812a7 100644 --- a/src/Stories/Modifiers/SizeConstraint.story.luau +++ b/src/Stories/Modifiers/SizeConstraint.story.luau @@ -3,7 +3,7 @@ local React = require("@pkg/react") local ReactRoblox = require("@pkg/react-roblox") local UILabs = require("@dev-pkg/ui-labs") -local Frame = require("../..").Frame +local BRC = require("@root/init") return { react = React, @@ -13,7 +13,7 @@ return { MaxSizeX = UILabs.Slider(200, 0, 500, 1), }, story = function(props) - return React.createElement(Frame, { + return React.createElement(BRC.Frame, { AnchorPoint = "m", Position = UDim2.new(0.5, 0, 0.5, 0), Size = UDim2.new(0.5, 0, 0.5, 0), diff --git a/src/Stories/Modifiers/TextSizeConstraint.story.luau b/src/Stories/Modifiers/TextSizeConstraint.story.luau index 087cc18..daf8080 100644 --- a/src/Stories/Modifiers/TextSizeConstraint.story.luau +++ b/src/Stories/Modifiers/TextSizeConstraint.story.luau @@ -2,7 +2,7 @@ local React = require("@pkg/react") local ReactRoblox = require("@pkg/react-roblox") local UILabs = require("@dev-pkg/ui-labs") -local TextLabel = require("../..").TextLabel +local BRC = require("@root/init") return { react = React, @@ -12,7 +12,7 @@ return { MaxTextSize = UILabs.Slider(20, 1, 100) }, story = function(props) - return React.createElement(TextLabel, { + return React.createElement(BRC.TextLabel, { AnchorPoint = "m", Position = UDim2.new(0.5, 0, 0.5, 0), Size = UDim2.new(0.5, 0, 0, props.controls.SizeY), diff --git a/src/Stories/Modifiers/UICorner.story.luau b/src/Stories/Modifiers/UICorner.story.luau index 8fb9f21..4bf16b1 100644 --- a/src/Stories/Modifiers/UICorner.story.luau +++ b/src/Stories/Modifiers/UICorner.story.luau @@ -2,7 +2,7 @@ local React = require("@pkg/react") local ReactRoblox = require("@pkg/react-roblox") local UILabs = require("@dev-pkg/ui-labs") -local Frame = require("../..").Frame +local BRC = require("@root/init") local story = { react = React, @@ -11,12 +11,12 @@ local story = { CornerRadius = UILabs.Slider(10, 0, 150, 1), }, story = function(props) - return React.createElement(Frame, { + return React.createElement(BRC.Frame, { AnchorPoint = "m", Position = UDim2.new(0.5, 0, 0.5, 0), Size = UDim2.new(0.5, 0, 0.5, 0), BackgroundColor = "#FFF", - CornerRadius = props.controls.CornerRadius, -- number + CornerRadius = props.controls.CornerRadius, -- UDim | number }) end, } diff --git a/src/Stories/Modifiers/UIStroke.story.luau b/src/Stories/Modifiers/UIStroke.story.luau index 8bb309c..24bc959 100644 --- a/src/Stories/Modifiers/UIStroke.story.luau +++ b/src/Stories/Modifiers/UIStroke.story.luau @@ -2,7 +2,7 @@ local React = require("@pkg/react") local ReactRoblox = require("@pkg/react-roblox") local UILabs = require("@dev-pkg/ui-labs") -local Frame = require("../..").Frame +local BRC = require("@root/init") local story = { react = React, @@ -12,7 +12,7 @@ local story = { Thickness = UILabs.Slider(5, 0, 50), }, story = function(props) - return React.createElement(Frame, { + return React.createElement(BRC.Frame, { AnchorPoint = "m", Position = UDim2.new(0.5, 0, 0.5, 0), Size = UDim2.new(0.5, 0, 0.5, 0), diff --git a/src/init.luau b/src/init.luau index 6c17f5b..d1b3884 100644 --- a/src/init.luau +++ b/src/init.luau @@ -1,8 +1,8 @@ local BetterReactComponents = { - Utils = require("./Utils"), - Builder = require("./Builder"), + Utils = require("@root/Utils"), + Builder = require("@root/Builder"), } -local Components = require("./Components") +local Components = require("@root/Components") return BetterReactComponents.Utils.Assign(BetterReactComponents, Components) From 9f915ecbe27f1c9abbb90dde2aac4fbece9af8e6 Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Sun, 4 May 2025 14:04:50 +0300 Subject: [PATCH 16/28] fix: removed default border Use `Border` property instead! (`UIStroke`) --- src/Builders/GuiObjectBuilder.luau | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Builders/GuiObjectBuilder.luau b/src/Builders/GuiObjectBuilder.luau index b56e717..60c8d13 100644 --- a/src/Builders/GuiObjectBuilder.luau +++ b/src/Builders/GuiObjectBuilder.luau @@ -66,6 +66,8 @@ return Builder.new(function(props: GuiObjectProps) SizeConstraint = props.SizeConstraint, LayoutOrder = props.LayoutOrder, ZIndex = props.ZIndex, + + BorderSizePixel = 0 -- we are using `UIStroke` instead. }, { BRCBorder = props.Border and React.createElement("UIStroke", { Color = props.Border.Color and Utils.ConvertColor(props.Border.Color), From f1dadf5c2c17ab80237af23fafaaf18425be6fe4 Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Sun, 4 May 2025 14:09:25 +0300 Subject: [PATCH 17/28] style: formatted using stylua --- src/Builders/GuiObjectBuilder.luau | 2 +- src/Stories/Modifiers/Padding.story.luau | 2 +- src/Stories/Modifiers/SizeConstraint.story.luau | 1 - src/Stories/Modifiers/TextSizeConstraint.story.luau | 4 ++-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Builders/GuiObjectBuilder.luau b/src/Builders/GuiObjectBuilder.luau index 60c8d13..6b9a490 100644 --- a/src/Builders/GuiObjectBuilder.luau +++ b/src/Builders/GuiObjectBuilder.luau @@ -67,7 +67,7 @@ return Builder.new(function(props: GuiObjectProps) LayoutOrder = props.LayoutOrder, ZIndex = props.ZIndex, - BorderSizePixel = 0 -- we are using `UIStroke` instead. + BorderSizePixel = 0, -- we are using `UIStroke` instead. }, { BRCBorder = props.Border and React.createElement("UIStroke", { Color = props.Border.Color and Utils.ConvertColor(props.Border.Color), diff --git a/src/Stories/Modifiers/Padding.story.luau b/src/Stories/Modifiers/Padding.story.luau index da4f68c..ed050cf 100644 --- a/src/Stories/Modifiers/Padding.story.luau +++ b/src/Stories/Modifiers/Padding.story.luau @@ -28,7 +28,7 @@ local function PaddingStory(props) BackgroundColor = "#FFF", Text = ("Padding = {%s}"):format(table.concat(Padding, ", ")), Font = Enum.Font.Roboto, - TextSize = 30 + TextSize = 30, }) ) end diff --git a/src/Stories/Modifiers/SizeConstraint.story.luau b/src/Stories/Modifiers/SizeConstraint.story.luau index a1812a7..197c617 100644 --- a/src/Stories/Modifiers/SizeConstraint.story.luau +++ b/src/Stories/Modifiers/SizeConstraint.story.luau @@ -1,4 +1,3 @@ - local React = require("@pkg/react") local ReactRoblox = require("@pkg/react-roblox") local UILabs = require("@dev-pkg/ui-labs") diff --git a/src/Stories/Modifiers/TextSizeConstraint.story.luau b/src/Stories/Modifiers/TextSizeConstraint.story.luau index daf8080..1d85b5d 100644 --- a/src/Stories/Modifiers/TextSizeConstraint.story.luau +++ b/src/Stories/Modifiers/TextSizeConstraint.story.luau @@ -9,7 +9,7 @@ return { reactRoblox = ReactRoblox, controls = { SizeY = UILabs.Slider(100, 0, 500), - MaxTextSize = UILabs.Slider(20, 1, 100) + MaxTextSize = UILabs.Slider(20, 1, 100), }, story = function(props) return React.createElement(BRC.TextLabel, { @@ -23,7 +23,7 @@ return { TextSize = "auto", Font = Enum.Font.Creepster, - MaxTextSize = props.controls.MaxTextSize + MaxTextSize = props.controls.MaxTextSize, }) end, } From d1d2ba7487c5910ba70dbf7b98ee4eeb7e7f1fc0 Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Sun, 4 May 2025 15:06:31 +0300 Subject: [PATCH 18/28] feat: `ScreenGui` --- README.md | 1 + src/Builders/ScreenGuiBuilder.luau | 21 +++++++++++++++++++++ src/Components/ScreenGui.luau | 3 +++ src/Components/init.luau | 1 + 4 files changed, 26 insertions(+) create mode 100644 src/Builders/ScreenGuiBuilder.luau create mode 100644 src/Components/ScreenGui.luau diff --git a/README.md b/README.md index 7c369b0..f26f6c9 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ Respectfully, ### Components +- [x] ScreenGui - [x] Frame - [ ] ScrollingFrame - [ ] CanvasGroup diff --git a/src/Builders/ScreenGuiBuilder.luau b/src/Builders/ScreenGuiBuilder.luau new file mode 100644 index 0000000..2e47821 --- /dev/null +++ b/src/Builders/ScreenGuiBuilder.luau @@ -0,0 +1,21 @@ +local Builder = require("@root/Builder") + +export type GuiObjectProps = { + Enabled: boolean?, + ResetOnSpawn: boolean?, + ZIndexBehavior: Enum.ZIndexBehavior?, + ScreenInsets: Enum.ScreenInsets?, + SafeAreaCompatibility: Enum.SafeAreaCompatibility?, + DisplayOrder: number?, +} + +return Builder.new(function(props: GuiObjectProps) + return { + Enabled = props.Enabled, + ResetOnSpawn = props.ResetOnSpawn, + ZIndexBehavior = props.ZIndexBehavior, + ScreenInsets = props.ScreenInsets, + SafeAreaCompatibility = props.SafeAreaCompatibility, + DisplayOrder = props.DisplayOrder, + }, {} +end) diff --git a/src/Components/ScreenGui.luau b/src/Components/ScreenGui.luau new file mode 100644 index 0000000..8251769 --- /dev/null +++ b/src/Components/ScreenGui.luau @@ -0,0 +1,3 @@ +local ScreenGuiBuilder = require("@root/Builders/ScreenGuiBuilder") + +return ScreenGuiBuilder:build("ScreenGui") diff --git a/src/Components/init.luau b/src/Components/init.luau index 5be84a6..1fed18d 100644 --- a/src/Components/init.luau +++ b/src/Components/init.luau @@ -1,4 +1,5 @@ return { + ScreenGui = require("./ScreenGui"), Frame = require("./Frame"), TextButton = require("./TextButton"), TextLabel = require("./TextLabel"), From 000ff95d225e01a969150b91a93f7c3faa23dec0 Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Sun, 4 May 2025 17:13:30 +0300 Subject: [PATCH 19/28] feat: `ScrollingFrame` --- README.md | 2 +- src/Builders/ScrollingFrameBuilder.luau | 51 +++++++++++++++++++ src/Components/ScrollingFrame.luau | 3 ++ src/Components/init.luau | 1 + .../Components/ScrollingFrame.story.luau | 24 +++++++++ 5 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/Builders/ScrollingFrameBuilder.luau create mode 100644 src/Components/ScrollingFrame.luau create mode 100644 src/Stories/Components/ScrollingFrame.story.luau diff --git a/README.md b/README.md index f26f6c9..39551b4 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Respectfully, - [x] ScreenGui - [x] Frame -- [ ] ScrollingFrame +- [x] ScrollingFrame - [ ] CanvasGroup - [ ] ViewportFrame - [x] TextButton diff --git a/src/Builders/ScrollingFrameBuilder.luau b/src/Builders/ScrollingFrameBuilder.luau new file mode 100644 index 0000000..ed2b326 --- /dev/null +++ b/src/Builders/ScrollingFrameBuilder.luau @@ -0,0 +1,51 @@ +local GuiObjectBuilder = require("./GuiObjectBuilder") +local Utils = require("@root/Utils") + +export type ScrollingFrameScrollBar = { + TopImage: string?, + MidImage: string?, + BottomImage: string?, + + Color: Utils.Color, + Transparency: number?, + + Thickness: number?, + HorizontalInset: Enum.ScrollBarInset?, + VerticalInset: Enum.ScrollBarInset?, + VerticalPosition: Enum.VerticalScrollBarPosition?, +} + +export type ScrollingFrameProps = { + AutomaticCanvasSize: Enum.AutomaticSize?, + CanvasPosition: Vector2?, + CanvasSize: UDim2?, + + ScrollingDirection: Enum.ScrollingDirection?, + ScrollingEnabled: boolean?, + + ScrollBar: ScrollingFrameScrollBar?, +} + +return GuiObjectBuilder:expand(function(props: ScrollingFrameProps) + return { + AutomaticCanvasSize = props.AutomaticCanvasSize, + CanvasPosition = props.CanvasPosition, + CanvasSize = props.CanvasSize, + + ScrollingDirection = props.ScrollingDirection, + ScrollingEnabled = props.ScrollingEnabled, + + TopImage = props.ScrollBar and props.ScrollBar.TopImage, + MidImage = props.ScrollBar and props.ScrollBar.MidImage, + BottomImage = props.ScrollBar and props.ScrollBar.BottomImage, + + ScrollBarImageColor3 = (props.ScrollBar and props.ScrollBar.Color) + and Utils.ConvertColor(props.ScrollBar.Color), + ScrollBarImageTransparency = props.ScrollBar and props.ScrollBar.Transparency, + ScrollBarThickness = props.ScrollBar and props.ScrollBar.Thickness, + + HorizontalScrollBarInset = props.ScrollBar and props.ScrollBar.HorizontalInset, + VerticalScrollBarInset = props.ScrollBar and props.ScrollBar.VerticalInset, + VerticalScrollBarPosition = props.ScrollBar and props.ScrollBar.VerticalPosition, + }, {} +end) diff --git a/src/Components/ScrollingFrame.luau b/src/Components/ScrollingFrame.luau new file mode 100644 index 0000000..0a056cf --- /dev/null +++ b/src/Components/ScrollingFrame.luau @@ -0,0 +1,3 @@ +local ScrollingFrameBuilder = require("@root/Builders/ScrollingFrameBuilder") + +return ScrollingFrameBuilder:build("ScrollingFrame") diff --git a/src/Components/init.luau b/src/Components/init.luau index 1fed18d..bae806e 100644 --- a/src/Components/init.luau +++ b/src/Components/init.luau @@ -1,6 +1,7 @@ return { ScreenGui = require("./ScreenGui"), Frame = require("./Frame"), + ScrollingFrame = require("./ScrollingFrame"), TextButton = require("./TextButton"), TextLabel = require("./TextLabel"), } diff --git a/src/Stories/Components/ScrollingFrame.story.luau b/src/Stories/Components/ScrollingFrame.story.luau new file mode 100644 index 0000000..42b800c --- /dev/null +++ b/src/Stories/Components/ScrollingFrame.story.luau @@ -0,0 +1,24 @@ +local React = require("@pkg/react") +local ReactRoblox = require("@pkg/react-roblox") +local UILabs = require("@dev-pkg/ui-labs") + +local BRC = require("@root/init") + +return { + react = React, + reactRoblox = ReactRoblox, + controls = {}, + story = function(props) + -- TODO: complete this story + return React.createElement(BRC.ScrollingFrame, { + AnchorPoint = "m", + Position = UDim2.new(0.5, 0, 0.5, 0), + Size = UDim2.new(0.5, 0, 0.5, 0), + + BackgroundColor = "#C00000", + CanvasSize = UDim2.new(1, 0, 2, 0), + }, { + React.createElement("Frame", { Size = UDim2.new(0, 100, 0, 100) }), + }) + end, +} From a49238b6bfc2afa4b94341a4aea8c5a4737d4e83 Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Mon, 5 May 2025 21:03:15 +0300 Subject: [PATCH 20/28] refactor: removed some builder (because they not needed), exported all typings --- src/Builders/FrameBuilder.luau | 5 - src/Builders/GuiObjectBuilder.luau | 52 +------- src/Builders/ScreenGuiBuilder.luau | 21 --- src/Builders/ScrollingFrameBuilder.luau | 51 ------- src/Builders/TextBuilder.luau | 21 +-- src/Builders/TextButtonBuilder.luau | 10 +- src/Builders/TextLabelBuilder.luau | 5 - src/Builders/init.luau | 5 + src/Components/Frame.luau | 4 +- src/Components/ScreenGui.luau | 14 +- src/Components/ScrollingFrame.luau | 29 +++- src/Components/TextLabel.luau | 4 +- src/Helpers/ListLayout.luau | 15 +-- src/Typings.luau | 168 ++++++++++++++++++++++++ src/Utils.luau | 71 +++------- src/init.luau | 21 +++ 16 files changed, 263 insertions(+), 233 deletions(-) delete mode 100644 src/Builders/FrameBuilder.luau delete mode 100644 src/Builders/ScreenGuiBuilder.luau delete mode 100644 src/Builders/ScrollingFrameBuilder.luau delete mode 100644 src/Builders/TextLabelBuilder.luau create mode 100644 src/Builders/init.luau create mode 100644 src/Typings.luau diff --git a/src/Builders/FrameBuilder.luau b/src/Builders/FrameBuilder.luau deleted file mode 100644 index d100647..0000000 --- a/src/Builders/FrameBuilder.luau +++ /dev/null @@ -1,5 +0,0 @@ -local GuiObjectBuilder = require("./GuiObjectBuilder") - -return GuiObjectBuilder:expand(function(props) - return {}, {} -end) diff --git a/src/Builders/GuiObjectBuilder.luau b/src/Builders/GuiObjectBuilder.luau index 6b9a490..c839b60 100644 --- a/src/Builders/GuiObjectBuilder.luau +++ b/src/Builders/GuiObjectBuilder.luau @@ -1,58 +1,12 @@ local React = require("@pkg/react") local Builder = require("@root/Builder") + local Utils = require("@root/Utils") +local Typings = require("@root/Typings") local ListLayout = require("@root/Helpers/ListLayout") ---- Border settings ---- ---- NOTE: when building, it uses `UIStroke`, instead of `GuiObject`'s border properties -export type Border = { - Color: Utils.Color?, - Thickness: number?, - LineJoinMode: Enum.LineJoinMode, - Transparency: number?, -} - -export type Aspect = { - Ratio: number, - Type: Enum.AspectType?, - DominantAxis: Enum.DominantAxis?, -} - -export type GuiObjectProps = { - AnchorPoint: Utils.AnchorPoint?, - AutomaticSize: Enum.AutomaticSize?, - BackgroundColor: Utils.Color?, - BackgroundTransparency: number?, - - --- Alias for `BackgroundTransparency = 1` - NoBackground: boolean?, - - Position: UDim2?, - Size: UDim2?, - - SizeConstraint: Enum.SizeConstraint?, - Visible: boolean?, - - LayoutOrder: number?, - ZIndex: number?, - - Border: Border?, - CornerRadius: Utils.UDimValue?, - - Aspect: Aspect?, - - Padding: Utils.PaddingValue?, - - MinSize: Vector2?, - MaxSize: Vector2?, - - ListLayout: ListLayout.ListLayoutProps?, - FlexMode: Enum.UIFlexMode?, -} - -return Builder.new(function(props: GuiObjectProps) +return Builder.new(function(props: Typings.GuiObjectProps) return { AnchorPoint = props.AnchorPoint and Utils.ConvertAnchorPoint(props.AnchorPoint), AutomaticSize = props.AutomaticSize, diff --git a/src/Builders/ScreenGuiBuilder.luau b/src/Builders/ScreenGuiBuilder.luau deleted file mode 100644 index 2e47821..0000000 --- a/src/Builders/ScreenGuiBuilder.luau +++ /dev/null @@ -1,21 +0,0 @@ -local Builder = require("@root/Builder") - -export type GuiObjectProps = { - Enabled: boolean?, - ResetOnSpawn: boolean?, - ZIndexBehavior: Enum.ZIndexBehavior?, - ScreenInsets: Enum.ScreenInsets?, - SafeAreaCompatibility: Enum.SafeAreaCompatibility?, - DisplayOrder: number?, -} - -return Builder.new(function(props: GuiObjectProps) - return { - Enabled = props.Enabled, - ResetOnSpawn = props.ResetOnSpawn, - ZIndexBehavior = props.ZIndexBehavior, - ScreenInsets = props.ScreenInsets, - SafeAreaCompatibility = props.SafeAreaCompatibility, - DisplayOrder = props.DisplayOrder, - }, {} -end) diff --git a/src/Builders/ScrollingFrameBuilder.luau b/src/Builders/ScrollingFrameBuilder.luau deleted file mode 100644 index ed2b326..0000000 --- a/src/Builders/ScrollingFrameBuilder.luau +++ /dev/null @@ -1,51 +0,0 @@ -local GuiObjectBuilder = require("./GuiObjectBuilder") -local Utils = require("@root/Utils") - -export type ScrollingFrameScrollBar = { - TopImage: string?, - MidImage: string?, - BottomImage: string?, - - Color: Utils.Color, - Transparency: number?, - - Thickness: number?, - HorizontalInset: Enum.ScrollBarInset?, - VerticalInset: Enum.ScrollBarInset?, - VerticalPosition: Enum.VerticalScrollBarPosition?, -} - -export type ScrollingFrameProps = { - AutomaticCanvasSize: Enum.AutomaticSize?, - CanvasPosition: Vector2?, - CanvasSize: UDim2?, - - ScrollingDirection: Enum.ScrollingDirection?, - ScrollingEnabled: boolean?, - - ScrollBar: ScrollingFrameScrollBar?, -} - -return GuiObjectBuilder:expand(function(props: ScrollingFrameProps) - return { - AutomaticCanvasSize = props.AutomaticCanvasSize, - CanvasPosition = props.CanvasPosition, - CanvasSize = props.CanvasSize, - - ScrollingDirection = props.ScrollingDirection, - ScrollingEnabled = props.ScrollingEnabled, - - TopImage = props.ScrollBar and props.ScrollBar.TopImage, - MidImage = props.ScrollBar and props.ScrollBar.MidImage, - BottomImage = props.ScrollBar and props.ScrollBar.BottomImage, - - ScrollBarImageColor3 = (props.ScrollBar and props.ScrollBar.Color) - and Utils.ConvertColor(props.ScrollBar.Color), - ScrollBarImageTransparency = props.ScrollBar and props.ScrollBar.Transparency, - ScrollBarThickness = props.ScrollBar and props.ScrollBar.Thickness, - - HorizontalScrollBarInset = props.ScrollBar and props.ScrollBar.HorizontalInset, - VerticalScrollBarInset = props.ScrollBar and props.ScrollBar.VerticalInset, - VerticalScrollBarPosition = props.ScrollBar and props.ScrollBar.VerticalPosition, - }, {} -end) diff --git a/src/Builders/TextBuilder.luau b/src/Builders/TextBuilder.luau index db6e360..174ad42 100644 --- a/src/Builders/TextBuilder.luau +++ b/src/Builders/TextBuilder.luau @@ -1,27 +1,12 @@ local React = require("@pkg/react") local Utils = require("@root/Utils") -local GuiObjectBuilder = require("./GuiObjectBuilder") - -export type TextProps = { - Text: string?, - - Font: Utils.FontValue?, - LineHeight: number?, - RichText: boolean?, - TextColor: Utils.Color?, - TextSize: Utils.TextSize?, - TextWrapped: boolean?, +local Typings = require("@root/Typings") - TextXAlignment: Enum.TextXAlignment?, - TextYAlignment: Enum.TextYAlignment?, - - MinTextSize: number, - MaxTextSize: number, -} +local GuiObjectBuilder = require("./GuiObjectBuilder") --- Builder for text-based objects (`TextLabel`, `TextButton`, etc.) -return GuiObjectBuilder:expand(function(props: TextProps) +return GuiObjectBuilder:expand(function(props: Typings.TextProps) return { Text = props.Text, diff --git a/src/Builders/TextButtonBuilder.luau b/src/Builders/TextButtonBuilder.luau index a4c420f..b8c10c8 100644 --- a/src/Builders/TextButtonBuilder.luau +++ b/src/Builders/TextButtonBuilder.luau @@ -1,12 +1,8 @@ -local TextLabelBuilder = require("./TextBuilder") +local Typings = require("@root/Typings") -export type TextButtonProps = { - Style: Enum.ButtonStyle?, - AutoButtonColor: boolean?, - Modal: boolean?, -} +local TextLabelBuilder = require("./TextBuilder") -return TextLabelBuilder:expand(function(props: TextButtonProps) +return TextLabelBuilder:expand(function(props: Typings.TextButtonProps) return { Style = props.Style, AutoButtonColor = props.AutoButtonColor, diff --git a/src/Builders/TextLabelBuilder.luau b/src/Builders/TextLabelBuilder.luau deleted file mode 100644 index 20ac4ba..0000000 --- a/src/Builders/TextLabelBuilder.luau +++ /dev/null @@ -1,5 +0,0 @@ -local TextBuilder = require("./TextBuilder") - -return TextBuilder:expand(function(props) - return {}, {} -end) diff --git a/src/Builders/init.luau b/src/Builders/init.luau new file mode 100644 index 0000000..1977744 --- /dev/null +++ b/src/Builders/init.luau @@ -0,0 +1,5 @@ +return { + GuiObjectBuilder = require("./GuiObjectBuilder"), + TextBuilder = require("./TextBuilder"), + TextButtonBuilder = require("./TextButtonBuilder"), +} diff --git a/src/Components/Frame.luau b/src/Components/Frame.luau index 47e5d3e..f209985 100644 --- a/src/Components/Frame.luau +++ b/src/Components/Frame.luau @@ -1,3 +1,3 @@ -local FrameBuilder = require("@root/Builders/FrameBuilder") +local GuiObjectBuilder = require("@root/Builders/GuiObjectBuilder") -return FrameBuilder:build("Frame") +return GuiObjectBuilder:build("Frame") diff --git a/src/Components/ScreenGui.luau b/src/Components/ScreenGui.luau index 8251769..c948c4a 100644 --- a/src/Components/ScreenGui.luau +++ b/src/Components/ScreenGui.luau @@ -1,3 +1,13 @@ -local ScreenGuiBuilder = require("@root/Builders/ScreenGuiBuilder") +local Builder = require("@root/Builder") +local Typings = require("@root/Typings") -return ScreenGuiBuilder:build("ScreenGui") +return Builder.new(function(props: Typings.ScreenGuiProps) + return { + Enabled = props.Enabled, + ResetOnSpawn = props.ResetOnSpawn, + ZIndexBehavior = props.ZIndexBehavior, + ScreenInsets = props.ScreenInsets, + SafeAreaCompatibility = props.SafeAreaCompatibility, + DisplayOrder = props.DisplayOrder, + }, {} +end):build("ScreenGui") diff --git a/src/Components/ScrollingFrame.luau b/src/Components/ScrollingFrame.luau index 0a056cf..8751656 100644 --- a/src/Components/ScrollingFrame.luau +++ b/src/Components/ScrollingFrame.luau @@ -1,3 +1,28 @@ -local ScrollingFrameBuilder = require("@root/Builders/ScrollingFrameBuilder") +local GuiObjectBuilder = require("@root/Builders/GuiObjectBuilder") -return ScrollingFrameBuilder:build("ScrollingFrame") +local Utils = require("@root/Utils") +local Typings = require("@root/Typings") + +return GuiObjectBuilder:expand(function(props: Typings.ScrollingFrameProps) + return { + AutomaticCanvasSize = props.AutomaticCanvasSize, + CanvasPosition = props.CanvasPosition, + CanvasSize = props.CanvasSize, + + ScrollingDirection = props.ScrollingDirection, + ScrollingEnabled = props.ScrollingEnabled, + + TopImage = props.ScrollBar and props.ScrollBar.TopImage, + MidImage = props.ScrollBar and props.ScrollBar.MidImage, + BottomImage = props.ScrollBar and props.ScrollBar.BottomImage, + + ScrollBarImageColor3 = (props.ScrollBar and props.ScrollBar.Color) + and Utils.ConvertColor(props.ScrollBar.Color), + ScrollBarImageTransparency = props.ScrollBar and props.ScrollBar.Transparency, + ScrollBarThickness = props.ScrollBar and props.ScrollBar.Thickness, + + HorizontalScrollBarInset = props.ScrollBar and props.ScrollBar.HorizontalInset, + VerticalScrollBarInset = props.ScrollBar and props.ScrollBar.VerticalInset, + VerticalScrollBarPosition = props.ScrollBar and props.ScrollBar.VerticalPosition, + }, {} +end):build("ScrollingFrame") diff --git a/src/Components/TextLabel.luau b/src/Components/TextLabel.luau index e9321cf..c313b13 100644 --- a/src/Components/TextLabel.luau +++ b/src/Components/TextLabel.luau @@ -1,3 +1,3 @@ -local TextLabelBuilder = require("@root/Builders/TextLabelBuilder") +local TextBuilder = require("@root/Builders/TextBuilder") -return TextLabelBuilder:build("TextLabel") +return TextBuilder:build("TextLabel") diff --git a/src/Helpers/ListLayout.luau b/src/Helpers/ListLayout.luau index 9b6dfcc..fff82ba 100644 --- a/src/Helpers/ListLayout.luau +++ b/src/Helpers/ListLayout.luau @@ -1,20 +1,9 @@ local React = require("@pkg/react") local Utils = require("@root/Utils") +local Typings = require("@root/Typings") -export type ListLayoutProps = { - Gap: Utils.UDimValue, - - Direction: Enum.FillDirection?, - Order: Enum.SortOrder?, - Wraps: boolean?, - - JustifyContent: Utils.FlexAlignment?, - AlignItems: Utils.FlexAlignment?, - -- TODO: ItemLineAlignment -} - -return function(props: ListLayoutProps) +return function(props: Typings.ListLayoutProps) local rbxProps = React.useMemo(function() local MainAxis = props.Direction or Enum.FillDirection.Vertical local CrossAxis = if MainAxis == Enum.FillDirection.Horizontal diff --git a/src/Typings.luau b/src/Typings.luau new file mode 100644 index 0000000..7e345ee --- /dev/null +++ b/src/Typings.luau @@ -0,0 +1,168 @@ +local React = require("@pkg/react") + +--- User-friendly anchor points +--- +--- center +--- left | right +--- | | | +--- `tl`, `t`, `tr` – top +--- `ml`, `m`, `mr` – middle +--- `bl`, `b`, `br` – bottom +export type AnchorPoint = "tl" | "t" | "tr" | "ml" | "m" | "mr" | "bl" | "b" | "br" | Vector2 + +--- Color +--- +--- It's either `Color3` or hexcode of color (e.g. `#FF0000`) +export type Color = Color3 | string + +--- Text Size +--- +--- `"auto"` enables `TextScaled` property. Otherwise sets TextSize +export type TextSize = number | "auto" + +--- Font +export type FontValue = Font | EnumItem + +--- UDim +--- +--- Takes `UDim` or `number` as offset +export type UDimValue = UDim | number + +--- Padding (like in CSS) +--- +--- There's 3 scenarios +--- 1. Value is `UDimValue` or `{UDimValue}`: padding `UDimValue` is applied to all sides +--- 2. Value is `{UDimValue, UDimValue}`: first value is vertical padding, second - horizontal padding +--- 3. Value is `{UDimValue, UDimValue, UDimValue}`: where each value is {Top, Horizontal, Bottom} +--- 4. Value is `{UDimValue, UDimValue, UDimValue, UDimValue}`: where each value is {Top, Right, Bottom, Left} +export type PaddingValue = UDimValue | { UDimValue } + +export type BindingOrValue = React.Binding | T + +export type FlexAlignment = + "start" + | "end" + | "center" + | "space-between" + | "space-around" + | "space-evenly" + | "stretch" + +export type ScreenGuiProps = { + Enabled: boolean?, + ResetOnSpawn: boolean?, + ZIndexBehavior: Enum.ZIndexBehavior?, + ScreenInsets: Enum.ScreenInsets?, + SafeAreaCompatibility: Enum.SafeAreaCompatibility?, + DisplayOrder: number?, +} + +--- Border settings +--- +--- NOTE: when building, it uses `UIStroke`, instead of `GuiObject`'s border properties +export type Border = { + Color: Color?, + Thickness: number?, + LineJoinMode: Enum.LineJoinMode, + Transparency: number?, +} + +export type Aspect = { + Ratio: number, + Type: Enum.AspectType?, + DominantAxis: Enum.DominantAxis?, +} + +export type ListLayoutProps = { + Gap: UDimValue, + + Direction: Enum.FillDirection?, + Order: Enum.SortOrder?, + Wraps: boolean?, + + JustifyContent: FlexAlignment?, + AlignItems: FlexAlignment?, + -- TODO: ItemLineAlignment +} + +export type GuiObjectProps = { + AnchorPoint: AnchorPoint?, + AutomaticSize: Enum.AutomaticSize?, + BackgroundColor: Color?, + BackgroundTransparency: number?, + + --- Alias for `BackgroundTransparency = 1` + NoBackground: boolean?, + + Position: UDim2?, + Size: UDim2?, + + SizeConstraint: Enum.SizeConstraint?, + Visible: boolean?, + + LayoutOrder: number?, + ZIndex: number?, + + Border: Border?, + CornerRadius: UDimValue?, + + Aspect: Aspect?, + + Padding: PaddingValue?, + + MinSize: Vector2?, + MaxSize: Vector2?, + + ListLayout: ListLayoutProps?, + FlexMode: Enum.UIFlexMode?, +} + +export type TextProps = { + Text: string?, + + Font: FontValue?, + LineHeight: number?, + RichText: boolean?, + TextColor: Color?, + TextSize: TextSize?, + TextWrapped: boolean?, + + TextXAlignment: Enum.TextXAlignment?, + TextYAlignment: Enum.TextYAlignment?, + + MinTextSize: number, + MaxTextSize: number, +} + +export type TextButtonProps = { + Style: Enum.ButtonStyle?, + AutoButtonColor: boolean?, + Modal: boolean?, +} + +export type ScrollingFrameScrollBar = { + TopImage: string?, + MidImage: string?, + BottomImage: string?, + + Color: Color, + Transparency: number?, + + Thickness: number?, + HorizontalInset: Enum.ScrollBarInset?, + VerticalInset: Enum.ScrollBarInset?, + VerticalPosition: Enum.VerticalScrollBarPosition?, +} + +export type ScrollingFrameProps = { + AutomaticCanvasSize: Enum.AutomaticSize?, + CanvasPosition: Vector2?, + CanvasSize: UDim2?, + + ScrollingDirection: Enum.ScrollingDirection?, + ScrollingEnabled: boolean?, + + ScrollBar: ScrollingFrameScrollBar?, +} + +return {} diff --git a/src/Utils.luau b/src/Utils.luau index 19361b4..2f4105f 100644 --- a/src/Utils.luau +++ b/src/Utils.luau @@ -1,54 +1,8 @@ local React = require("@pkg/react") -local Utils = {} - ---- User-friendly anchor points ---- ---- center ---- left | right ---- | | | ---- `tl`, `t`, `tr` – top ---- `ml`, `m`, `mr` – middle ---- `bl`, `b`, `br` – bottom -export type AnchorPoint = "tl" | "t" | "tr" | "ml" | "m" | "mr" | "bl" | "b" | "br" | Vector2 - ---- Color ---- ---- It's either `Color3` or hexcode of color (e.g. `#FF0000`) -export type Color = Color3 | string - ---- Text Size ---- ---- `"auto"` enables `TextScaled` property. Otherwise sets TextSize -export type TextSize = number | "auto" - ---- Font -export type FontValue = Font | EnumItem +local Typings = require("@root/Typings") ---- UDim ---- ---- Takes `UDim` or `number` as offset -export type UDimValue = UDim | number - ---- Padding (like in CSS) ---- ---- There's 3 scenarios ---- 1. Value is `UDimValue` or `{UDimValue}`: padding `UDimValue` is applied to all sides ---- 2. Value is `{UDimValue, UDimValue}`: first value is vertical padding, second - horizontal padding ---- 3. Value is `{UDimValue, UDimValue, UDimValue}`: where each value is {Top, Horizontal, Bottom} ---- 4. Value is `{UDimValue, UDimValue, UDimValue, UDimValue}`: where each value is {Top, Right, Bottom, Left} -export type PaddingValue = UDimValue | { UDimValue } - -export type BindingOrValue = React.Binding | T - -export type FlexAlignment = - "start" - | "end" - | "center" - | "space-between" - | "space-around" - | "space-evenly" - | "stretch" +local Utils = {} local AnchorPointMapping = setmetatable({ tl = Vector2.new(0, 0), @@ -73,7 +27,10 @@ local function isBinding(value: any): boolean end -- TODO: create separate package "better-react-hooks" -function Utils.mapBinding(bindingOrValue: BindingOrValue, map: (value: T) -> R): React.Binding +function Utils.mapBinding( + bindingOrValue: Typings.BindingOrValue, + map: (value: T) -> R +): React.Binding local binding = if isBinding(bindingOrValue) then bindingOrValue :: React.Binding else React.createBinding(bindingOrValue :: T) @@ -118,7 +75,7 @@ function Utils.MergeMulti(...): any return result end -function Utils.ConvertColor(value: BindingOrValue): React.Binding +function Utils.ConvertColor(value: Typings.BindingOrValue): React.Binding return Utils.mapBinding(value, function(value) if typeof(value) == "Color3" then return value @@ -135,7 +92,9 @@ function Utils.ConvertColor(value: BindingOrValue): React.Bindi end) end -function Utils.ConvertAnchorPoint(value: BindingOrValue): React.Binding +function Utils.ConvertAnchorPoint( + value: Typings.BindingOrValue +): React.Binding return Utils.mapBinding(value, function(value) if typeof(value) == "Vector2" then return value @@ -152,7 +111,7 @@ function Utils.ConvertAnchorPoint(value: BindingOrValue): React.Bin end) end -function Utils.ConvertFont(value: BindingOrValue): React.Binding +function Utils.ConvertFont(value: Typings.BindingOrValue): React.Binding return Utils.mapBinding(value, function(value) if typeof(value) == "EnumItem" then return Font.fromEnum(value :: Enum.Font) @@ -169,7 +128,7 @@ function Utils.ConvertFont(value: BindingOrValue): React.Binding): React.Binding +function Utils.ConvertUDim(value: Typings.BindingOrValue): React.Binding return Utils.mapBinding(value, ConvertUDimInternal) end @@ -237,7 +196,7 @@ local function ConvertPaddingInternal(value: { UDim }): ConvertPaddingResult end function Utils.ConvertPadding( - value: BindingOrValue + value: Typings.BindingOrValue ): React.Binding return Utils.mapBinding(value, function(value) if typeof(value) == "table" then @@ -270,7 +229,7 @@ export type ConvertFlexValueResult = { --- Converts CSS like JustifyContent and AlignItems values to Roblox's ListLayout props function Utils.ConvertFlexValue( direction: Enum.FillDirection, - value: FlexAlignment + value: Typings.FlexAlignment ): ConvertFlexValueResult if value == "start" then if direction == Enum.FillDirection.Horizontal then diff --git a/src/init.luau b/src/init.luau index d1b3884..d3b7c1d 100644 --- a/src/init.luau +++ b/src/init.luau @@ -1,3 +1,24 @@ +local Typings = require("@root/Typings") + +-- reexports of typings: +export type Color = Typings.Color +export type TextSize = Typings.TextSize +export type UDimValue = Typings.UDimValue +export type FontValue = Typings.FontValue +export type AnchorPoint = Typings.AnchorPoint +export type PaddingValue = Typings.PaddingValue +export type FlexAlignment = Typings.FlexAlignment +export type BindingOrValue = Typings.BindingOrValue + +export type Aspect = Typings.Aspect +export type Border = Typings.Border +export type GuiObjectProps = Typings.GuiObjectProps +export type ScreenGuiProps = Typings.ScreenGuiProps +export type TextButtonProps = Typings.TextButtonProps +export type ListLayoutProps = Typings.ListLayoutProps +export type ScrollingFrameProps = Typings.ScrollingFrameProps +export type ScrollingFrameScrollBar = Typings.ScrollingFrameScrollBar + local BetterReactComponents = { Utils = require("@root/Utils"), Builder = require("@root/Builder"), From e304f1107c6f8ae72752a3569d2835b65a38eb29 Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Sat, 16 Aug 2025 10:43:27 +0300 Subject: [PATCH 21/28] feat: add `CalculateAspectRatio` util, add `React.forwardRef` in builder, fix LSP issues --- .luaurc | 7 +++++++ src/Builder.luau | 35 ++++++++++++++++++++++------------- src/Components/init.luau | 10 +++++----- src/Utils.luau | 8 ++++++++ src/init.luau | 6 +++++- 5 files changed, 47 insertions(+), 19 deletions(-) create mode 100644 .luaurc diff --git a/.luaurc b/.luaurc new file mode 100644 index 0000000..8091482 --- /dev/null +++ b/.luaurc @@ -0,0 +1,7 @@ +{ + "aliases": { + "pkg": "./Packages", + "dev-pkg": "./DevPackages", + "root": "./src" + } +} diff --git a/src/Builder.luau b/src/Builder.luau index 6b5fb61..2b19ce1 100644 --- a/src/Builder.luau +++ b/src/Builder.luau @@ -37,21 +37,30 @@ function ComponentBuilder.expand( end) end -function ComponentBuilder.build( +function ComponentBuilder.build( self: ComponentBuilder, robloxComponent: string -): (P, any) -> React.ReactNode - return React.forwardRef(function(props: React.ElementProps & P) - local outputProps, outputChildren = self.transformer(props) - - for k, v in pairs(props) do - -- 99.98% sure, that this is some special ahh key - if typeof(k) == "table" then - outputProps[k] = v - end - end +): (P, React.Ref) -> React.ReactNode + return React.forwardRef( + function(props: React.ElementProps & P, ref: React.Ref) + local outputProps, outputChildren = self.transformer(props) + + outputProps.ref = ref - return React.createElement(robloxComponent, outputProps, outputChildren, props.children) - end) :: any + for k, v in pairs(props) do + -- 99.98% sure, that this is some special ahh key + if typeof(k) == "table" then + outputProps[k] = v + end + end + + return React.createElement( + robloxComponent, + outputProps, + outputChildren, + props.children + ) + end + ) :: any end return ComponentBuilder diff --git a/src/Components/init.luau b/src/Components/init.luau index bae806e..9de8c75 100644 --- a/src/Components/init.luau +++ b/src/Components/init.luau @@ -1,7 +1,7 @@ return { - ScreenGui = require("./ScreenGui"), - Frame = require("./Frame"), - ScrollingFrame = require("./ScrollingFrame"), - TextButton = require("./TextButton"), - TextLabel = require("./TextLabel"), + ScreenGui = require(script.ScreenGui), + Frame = require(script.Frame), + ScrollingFrame = require(script.ScrollingFrame), + TextButton = require(script.TextButton), + TextLabel = require(script.TextLabel), } diff --git a/src/Utils.luau b/src/Utils.luau index 2f4105f..5c21d2f 100644 --- a/src/Utils.luau +++ b/src/Utils.luau @@ -75,6 +75,14 @@ function Utils.MergeMulti(...): any return result end +function Utils.CalculateAspectRatio(instance: GuiObject) + local AbsoluteSize = instance.AbsoluteSize + + print("===============================") + print(("Calculated Aspect Ratio: %.3f"):format(AbsoluteSize.X / AbsoluteSize.Y)) + print("===============================") +end + function Utils.ConvertColor(value: Typings.BindingOrValue): React.Binding return Utils.mapBinding(value, function(value) if typeof(value) == "Color3" then diff --git a/src/init.luau b/src/init.luau index d3b7c1d..302f14f 100644 --- a/src/init.luau +++ b/src/init.luau @@ -25,5 +25,9 @@ local BetterReactComponents = { } local Components = require("@root/Components") +local Builders = require("@root/Builders") -return BetterReactComponents.Utils.Assign(BetterReactComponents, Components) +return BetterReactComponents.Utils.Assign( + BetterReactComponents.Utils.Assign(BetterReactComponents, Components), + Builders +) From 5f5b7dd59b61bc3d80aa298d8dcabb8991678e37 Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Sat, 16 Aug 2025 14:21:15 +0300 Subject: [PATCH 22/28] feat: added `collections` as dependency, added todos example --- src/Stories/Examples/Todos.story.luau | 156 ++++++++++++++++++++++++++ wally.lock | 2 +- wally.toml | 1 + 3 files changed, 158 insertions(+), 1 deletion(-) create mode 100644 src/Stories/Examples/Todos.story.luau diff --git a/src/Stories/Examples/Todos.story.luau b/src/Stories/Examples/Todos.story.luau new file mode 100644 index 0000000..7471583 --- /dev/null +++ b/src/Stories/Examples/Todos.story.luau @@ -0,0 +1,156 @@ +local React = require("@pkg/react") +local ReactRoblox = require("@pkg/react-roblox") +local Collections = require("@pkg/collections") + +local UILabs = require("@dev-pkg/ui-labs") + +local BRC = require("@root/") + +type Todo = { text: string, completed: boolean } + +local function MenuStory(props) + local todos, setTodos = React.useState({} :: { Todo }) + + print(todos) + + return React.createElement(BRC.Frame, { + -- PRO TIP: use this utillity to calcalate aspect ratio for component: + -- > ref = BRC.Utils.CalculateAspectRatio, + AnchorPoint = "m", + Size = UDim2.new(0.5, 0, 0.8, 0), + Position = UDim2.new(0.5, 0, 0.5, 0), + Aspect = { Ratio = 1.6 }, + + BackgroundColor = "#FFF", + BackgroundTransparency = 0.3, + + CornerRadius = 8 + 8, -- inner corner + padding + Padding = 8, + + ListLayout = { + Gap = 8, + Order = Enum.SortOrder.LayoutOrder, + }, + }, { + Header = React.createElement(BRC.Frame, { + LayoutOrder = 1, + + Size = UDim2.new(1, 0, 0, 48), + NoBackground = true, + + ListLayout = { + Gap = 8, + Direction = Enum.FillDirection.Horizontal, + Order = Enum.SortOrder.LayoutOrder, + }, + }, { + TodoText = React.createElement(BRC.TextLabel, { + LayoutOrder = 1, + + Size = UDim2.new(0, 0, 1, 0), + FlexMode = Enum.UIFlexMode.Fill, + + CornerRadius = 8, + }), + CreateTodo = React.createElement(BRC.TextButton, { + LayoutOrder = 2, + Size = UDim2.new(0.2, 0, 1, 0), + + CornerRadius = 8, + + BackgroundColor = "#00a63e", + TextColor = "#FFFFFF", + + Text = "Add", + + [React.Event.Activated] = function() + setTodos(function(todos) + return BRC.Utils.MergeMulti( + todos, + { [#todos + 1] = { text = "Test", completed = false } } + ) + end) + end, + }), + }), + + Content = React.createElement( + BRC.ScrollingFrame, + { + LayoutOrder = 2, + + NoBackground = true, + + Padding = { 6, 18, 6, 0 }, + Size = UDim2.new(1, 0, 1, 0), + FlexMode = Enum.UIFlexMode.Fill, + + + ListLayout = { + Gap = 8, + Direction = Enum.FillDirection.Vertical, + Order = Enum.SortOrder.LayoutOrder, + }, + }, + + Collections.Array.map(todos, function(child: Todo, index: number) + return React.createElement(BRC.Frame, { + LayoutOrder = 10 + index, + Size = UDim2.new(1, 0, 0, 42), + + CornerRadius = 8, + Padding = 4, + + ListLayout = { + Gap = 8, + Direction = Enum.FillDirection.Horizontal, + Order = Enum.SortOrder.LayoutOrder, + AlignItems = "center", + }, + }, { + React.createElement(BRC.TextLabel, { + LayoutOrder = 1, + + Size = UDim2.new(1, 0, 1, 0), + FlexMode = Enum.UIFlexMode.Fill, + NoBackground = true, + + Padding = { 0, 8 }, + + TextXAlignment = Enum.TextXAlignment.Left, + }), + React.createElement(BRC.TextButton, { + LayoutOrder = 2, + + AnchorPoint = "mr", + Size = UDim2.new(0.1, 0, 1, 0), + Aspect = { Ratio = 1 }, + + CornerRadius = 4, + + BackgroundColor = child.completed and "#DC2626" or "#00A63E", + TextColor = "#FFF", + Text = child.completed and "X" or "√", + + [React.Event.Activated] = function() + setTodos(function(todos) + return BRC.Utils.MergeMulti(todos, { + [index] = { completed = not todos[index].completed }, + }) + end) + end, + }), + }) + end) + ), + }) +end + +return { + react = React, + reactRoblox = ReactRoblox, + controls = {}, + story = function(props) + return React.createElement(MenuStory, props) + end, +} diff --git a/wally.lock b/wally.lock index f4a703d..ec5a0a1 100644 --- a/wally.lock +++ b/wally.lock @@ -5,7 +5,7 @@ registry = "test" [[package]] name = "idkncc/better-react-components" version = "3.0.0" -dependencies = [["react", "jsdotlua/react@17.2.1"], ["react-roblox", "jsdotlua/react-roblox@17.2.1"], ["ui-labs", "pepeeltoro41/ui-labs@2.3.8"]] +dependencies = [["collections", "jsdotlua/collections@1.2.7"], ["react", "jsdotlua/react@17.2.1"], ["react-roblox", "jsdotlua/react-roblox@17.2.1"], ["ui-labs", "pepeeltoro41/ui-labs@2.3.8"]] [[package]] name = "jsdotlua/boolean" diff --git a/wally.toml b/wally.toml index 907676f..f1e48be 100644 --- a/wally.toml +++ b/wally.toml @@ -9,6 +9,7 @@ include = [ "src", "default.project.json", "README.md" ] [dependencies] react = "jsdotlua/react@~17.2" react-roblox = "jsdotlua/react-roblox@~17.2" +collections = "jsdotlua/collections@~1.2" [dev-dependencies] ui-labs = "pepeeltoro41/ui-labs@2.3.8" From 7ded9569c136d411fbd2f5543ae0d527231f69d0 Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Sat, 16 Aug 2025 14:35:20 +0300 Subject: [PATCH 23/28] feat: `TextBox` --- src/Components/TextBox.luau | 3 +++ src/Components/init.luau | 1 + src/Stories/Examples/Todos.story.luau | 23 +++++++++++++++-------- 3 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 src/Components/TextBox.luau diff --git a/src/Components/TextBox.luau b/src/Components/TextBox.luau new file mode 100644 index 0000000..6531c20 --- /dev/null +++ b/src/Components/TextBox.luau @@ -0,0 +1,3 @@ +local TextBuilder = require("@root/Builders/TextBuilder") + +return TextBuilder:build("TextBox") diff --git a/src/Components/init.luau b/src/Components/init.luau index 9de8c75..9aaa35f 100644 --- a/src/Components/init.luau +++ b/src/Components/init.luau @@ -4,4 +4,5 @@ return { ScrollingFrame = require(script.ScrollingFrame), TextButton = require(script.TextButton), TextLabel = require(script.TextLabel), + TextBox = require(script.TextBox), } diff --git a/src/Stories/Examples/Todos.story.luau b/src/Stories/Examples/Todos.story.luau index 7471583..eb5c55b 100644 --- a/src/Stories/Examples/Todos.story.luau +++ b/src/Stories/Examples/Todos.story.luau @@ -10,8 +10,7 @@ type Todo = { text: string, completed: boolean } local function MenuStory(props) local todos, setTodos = React.useState({} :: { Todo }) - - print(todos) + local input, setInput = React.useBinding("") return React.createElement(BRC.Frame, { -- PRO TIP: use this utillity to calcalate aspect ratio for component: @@ -44,13 +43,18 @@ local function MenuStory(props) Order = Enum.SortOrder.LayoutOrder, }, }, { - TodoText = React.createElement(BRC.TextLabel, { + TodoText = React.createElement(BRC.TextBox, { LayoutOrder = 1, Size = UDim2.new(0, 0, 1, 0), FlexMode = Enum.UIFlexMode.Fill, CornerRadius = 8, + + Text = input, + [React.Change.Text] = function(inst) + setInput(inst.Text) + end, }), CreateTodo = React.createElement(BRC.TextButton, { LayoutOrder = 2, @@ -65,9 +69,12 @@ local function MenuStory(props) [React.Event.Activated] = function() setTodos(function(todos) + local text = input:getValue() + setInput("") + return BRC.Utils.MergeMulti( todos, - { [#todos + 1] = { text = "Test", completed = false } } + { [#todos + 1] = { text = text, completed = false } } ) end) end, @@ -85,7 +92,6 @@ local function MenuStory(props) Size = UDim2.new(1, 0, 1, 0), FlexMode = Enum.UIFlexMode.Fill, - ListLayout = { Gap = 8, Direction = Enum.FillDirection.Vertical, @@ -93,7 +99,7 @@ local function MenuStory(props) }, }, - Collections.Array.map(todos, function(child: Todo, index: number) + Collections.Array.map(todos, function(todo: Todo, index: number) return React.createElement(BRC.Frame, { LayoutOrder = 10 + index, Size = UDim2.new(1, 0, 0, 42), @@ -118,6 +124,7 @@ local function MenuStory(props) Padding = { 0, 8 }, TextXAlignment = Enum.TextXAlignment.Left, + Text = todo.text, }), React.createElement(BRC.TextButton, { LayoutOrder = 2, @@ -128,9 +135,9 @@ local function MenuStory(props) CornerRadius = 4, - BackgroundColor = child.completed and "#DC2626" or "#00A63E", + BackgroundColor = todo.completed and "#DC2626" or "#00A63E", TextColor = "#FFF", - Text = child.completed and "X" or "√", + Text = todo.completed and "X" or "√", [React.Event.Activated] = function() setTodos(function(todos) From fb77e1de4a5fa886440629149db017b1d104df24 Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Sat, 16 Aug 2025 16:16:39 +0300 Subject: [PATCH 24/28] build: update/reformat Makefile and scripts --- Makefile | 28 +++++++++++++--------------- scripts/roblox.sh | 7 +++++-- scripts/wally.sh | 7 ++++--- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index fd7bd55..bd1c50d 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,25 @@ -install: install-toolchain -cleanup: cleanup-build +.PHONY: install cleanup install-toolchain cleanup-build serve watch build/* -roblox-package: cleanup-build - ./scripts/roblox.sh +install: + aftman install -wally-package: cleanup-build - ./scripts/wally.sh +cleanup: + git clean -Xf build # remove ignored files in build/ -# development serve: rojo serve out.project.json watch: Packages/ DevPackages/ sourcemap.json .darklua-dev.json darklua process -w -c .darklua-dev.json src out +# files/folders + +build/roblox: + ./scripts/roblox.sh + +build/wally: + ./scripts/wally.sh + Packages DevPackages: wally.toml wally.lock wally install wally-package-types --sourcemap sourcemap.json Packages/ @@ -21,11 +27,3 @@ Packages DevPackages: wally.toml wally.lock sourcemap.json: src/* default.project.json rojo sourcemap default.project.json --output sourcemap.json - -# intermediate steps - -install-toolchain: - aftman install - -cleanup-build: - git clean -Xf build # remove ignored files in build/ diff --git a/scripts/roblox.sh b/scripts/roblox.sh index a90fb03..b2fa355 100755 --- a/scripts/roblox.sh +++ b/scripts/roblox.sh @@ -3,10 +3,13 @@ set -o pipefail set -e +git clean -Xf build/roblox mkdir -p build/roblox + cp README.md .darklua-roblox.json wally.toml wally.lock build/roblox/ cp -r src build/roblox/ -rm -rf build/roblox/src/Stories +rm -r build/roblox/src/Stories + cd build/roblox wally install @@ -15,7 +18,7 @@ rojo sourcemap default.project.json --output sourcemap.json wally-package-types --sourcemap sourcemap.json Packages/ darklua process -c .darklua-roblox.json src out -rm -rf src +rm -r src mv out src # Build model diff --git a/scripts/wally.sh b/scripts/wally.sh index d1d52bb..7159b77 100755 --- a/scripts/wally.sh +++ b/scripts/wally.sh @@ -3,17 +3,18 @@ set -o pipefail set -e +git clean -Xf build/roblox mkdir -p build/wally + cp README.md .darklua-wally.json wally.toml wally.lock build/wally/ cp -r src build/wally/ -rm -rf build/wally/src/Stories +rm -r build/wally/src/Stories + cd build/wally wally install - rojo sourcemap sourcemap.project.json --output sourcemap.json darklua process -c .darklua-wally.json src out rm -rf src mv out src - From e7696e99731e670c7757ae30babbcc94658f19ed Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Sat, 16 Aug 2025 16:54:13 +0300 Subject: [PATCH 25/28] feat: add (almost) all `GuiObject` props --- src/Builders/GuiObjectBuilder.luau | 21 +++++++++--- src/Typings.luau | 52 +++++++++++++++++++++--------- 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/src/Builders/GuiObjectBuilder.luau b/src/Builders/GuiObjectBuilder.luau index c839b60..65bcd79 100644 --- a/src/Builders/GuiObjectBuilder.luau +++ b/src/Builders/GuiObjectBuilder.luau @@ -8,20 +8,33 @@ local ListLayout = require("@root/Helpers/ListLayout") return Builder.new(function(props: Typings.GuiObjectProps) return { + Active = props.Active, + AnchorPoint = props.AnchorPoint and Utils.ConvertAnchorPoint(props.AnchorPoint), AutomaticSize = props.AutomaticSize, BackgroundColor3 = props.BackgroundColor and Utils.ConvertColor(props.BackgroundColor), BackgroundTransparency = props.NoBackground and 1 or props.BackgroundTransparency, + BorderSizePixel = 0, -- we are using `UIStroke` instead. + + ClipsDescendants = props.ClipsDescendants, + Interactable = props.Interactable, + + LayoutOrder = props.LayoutOrder, + Position = props.Position, - Size = props.Size, + Rotation = props.Rotation, + Selectable = props.Selectable, + + Size = props.Size, SizeConstraint = props.SizeConstraint, - LayoutOrder = props.LayoutOrder, - ZIndex = props.ZIndex, - BorderSizePixel = 0, -- we are using `UIStroke` instead. + Transparency = props.Transparency, + Visible = props.Visible, + + ZIndex = props.ZIndex, }, { BRCBorder = props.Border and React.createElement("UIStroke", { Color = props.Border.Color and Utils.ConvertColor(props.Border.Color), diff --git a/src/Typings.luau b/src/Typings.luau index 7e345ee..773ba06 100644 --- a/src/Typings.luau +++ b/src/Typings.luau @@ -85,37 +85,59 @@ export type ListLayoutProps = { -- TODO: ItemLineAlignment } +export type GuiObjectModifiers = { + CornerRadius: UDimValue?, + + Aspect: Aspect?, + + Padding: PaddingValue?, + + MinSize: Vector2?, + MaxSize: Vector2?, + + ListLayout: ListLayoutProps?, + FlexMode: Enum.UIFlexMode?, +} + export type GuiObjectProps = { + Active: boolean, + AnchorPoint: AnchorPoint?, AutomaticSize: Enum.AutomaticSize?, + BackgroundColor: Color?, BackgroundTransparency: number?, - --- Alias for `BackgroundTransparency = 1` NoBackground: boolean?, - Position: UDim2?, - Size: UDim2?, + Border: Border?, - SizeConstraint: Enum.SizeConstraint?, - Visible: boolean?, + ClipsDescendants: boolean?, + -- TODO: GuiState:Enum.GuiState, + Interactable: boolean, LayoutOrder: number?, - ZIndex: number?, - Border: Border?, - CornerRadius: UDimValue?, + -- TODO: NextSelectionDown:GuiObject + -- TODO: NextSelectionLeft:GuiObject + -- TODO: NextSelectionRight:GuiObject + -- TODO: NextSelectionUp:GuiObject - Aspect: Aspect?, + Position: UDim2?, + Rotation: number?, - Padding: PaddingValue?, + Selectable: boolean?, + -- TODO: SelectionImageObject:GuiObject, + -- TODO: SelectionOrder:number - MinSize: Vector2?, - MaxSize: Vector2?, + Size: UDim2?, + SizeConstraint: Enum.SizeConstraint?, - ListLayout: ListLayoutProps?, - FlexMode: Enum.UIFlexMode?, -} + Transparency: number, + Visible: boolean?, + + ZIndex: number?, +} & GuiObjectModifiers export type TextProps = { Text: string?, From 3f4b4a6b96e127f8af1c72ae0fbeb4db81ec2c28 Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Sun, 17 Aug 2025 09:47:26 +0300 Subject: [PATCH 26/28] feat: removed `SafeAreaCompatibility` in ScreenGui, added `Settings` suffix to modifiers, updated docs for typings --- src/Components/ScreenGui.luau | 2 +- src/Helpers/ListLayout.luau | 2 +- src/Typings.luau | 43 ++++++++++++++++++++--------------- src/init.luau | 15 +++++++----- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/src/Components/ScreenGui.luau b/src/Components/ScreenGui.luau index c948c4a..479da28 100644 --- a/src/Components/ScreenGui.luau +++ b/src/Components/ScreenGui.luau @@ -6,8 +6,8 @@ return Builder.new(function(props: Typings.ScreenGuiProps) Enabled = props.Enabled, ResetOnSpawn = props.ResetOnSpawn, ZIndexBehavior = props.ZIndexBehavior, + ScreenInsets = props.ScreenInsets, - SafeAreaCompatibility = props.SafeAreaCompatibility, DisplayOrder = props.DisplayOrder, }, {} end):build("ScreenGui") diff --git a/src/Helpers/ListLayout.luau b/src/Helpers/ListLayout.luau index fff82ba..55c01ec 100644 --- a/src/Helpers/ListLayout.luau +++ b/src/Helpers/ListLayout.luau @@ -3,7 +3,7 @@ local React = require("@pkg/react") local Utils = require("@root/Utils") local Typings = require("@root/Typings") -return function(props: Typings.ListLayoutProps) +return function(props: Typings.ListLayoutSettings) local rbxProps = React.useMemo(function() local MainAxis = props.Direction or Enum.FillDirection.Vertical local CrossAxis = if MainAxis == Enum.FillDirection.Horizontal diff --git a/src/Typings.luau b/src/Typings.luau index 773ba06..7c3e512 100644 --- a/src/Typings.luau +++ b/src/Typings.luau @@ -30,15 +30,14 @@ export type UDimValue = UDim | number --- Padding (like in CSS) --- ---- There's 3 scenarios +--- There's 4 scenarios --- 1. Value is `UDimValue` or `{UDimValue}`: padding `UDimValue` is applied to all sides --- 2. Value is `{UDimValue, UDimValue}`: first value is vertical padding, second - horizontal padding --- 3. Value is `{UDimValue, UDimValue, UDimValue}`: where each value is {Top, Horizontal, Bottom} --- 4. Value is `{UDimValue, UDimValue, UDimValue, UDimValue}`: where each value is {Top, Right, Bottom, Left} export type PaddingValue = UDimValue | { UDimValue } -export type BindingOrValue = React.Binding | T - +--- Flex alignments (like in CSS) export type FlexAlignment = "start" | "end" @@ -48,32 +47,29 @@ export type FlexAlignment = | "space-evenly" | "stretch" -export type ScreenGuiProps = { - Enabled: boolean?, - ResetOnSpawn: boolean?, - ZIndexBehavior: Enum.ZIndexBehavior?, - ScreenInsets: Enum.ScreenInsets?, - SafeAreaCompatibility: Enum.SafeAreaCompatibility?, - DisplayOrder: number?, -} +--- Type alias for either Binding or Value +export type BindingOrValue = React.Binding | T --- Border settings --- --- NOTE: when building, it uses `UIStroke`, instead of `GuiObject`'s border properties -export type Border = { +export type BorderSettings = { Color: Color?, Thickness: number?, LineJoinMode: Enum.LineJoinMode, Transparency: number?, } -export type Aspect = { +--- Aspect ratio settings +-- TODO: support number-only +export type AspectSettings = { Ratio: number, Type: Enum.AspectType?, DominantAxis: Enum.DominantAxis?, } -export type ListLayoutProps = { +--- List layout settings +export type ListLayoutSettings = { Gap: UDimValue, Direction: Enum.FillDirection?, @@ -85,17 +81,20 @@ export type ListLayoutProps = { -- TODO: ItemLineAlignment } +--- Gui Object modifers export type GuiObjectModifiers = { + Border: BorderSettings?, + CornerRadius: UDimValue?, - Aspect: Aspect?, + Aspect: AspectSettings?, Padding: PaddingValue?, MinSize: Vector2?, MaxSize: Vector2?, - ListLayout: ListLayoutProps?, + ListLayout: ListLayoutSettings?, FlexMode: Enum.UIFlexMode?, } @@ -110,8 +109,6 @@ export type GuiObjectProps = { --- Alias for `BackgroundTransparency = 1` NoBackground: boolean?, - Border: Border?, - ClipsDescendants: boolean?, -- TODO: GuiState:Enum.GuiState, Interactable: boolean, @@ -139,6 +136,15 @@ export type GuiObjectProps = { ZIndex: number?, } & GuiObjectModifiers +export type ScreenGuiProps = { + Enabled: boolean?, + ResetOnSpawn: boolean?, + ZIndexBehavior: Enum.ZIndexBehavior?, + + ScreenInsets: Enum.ScreenInsets?, + DisplayOrder: number?, +} + export type TextProps = { Text: string?, @@ -178,6 +184,7 @@ export type ScrollingFrameScrollBar = { export type ScrollingFrameProps = { AutomaticCanvasSize: Enum.AutomaticSize?, + CanvasPosition: Vector2?, CanvasSize: UDim2?, diff --git a/src/init.luau b/src/init.luau index 302f14f..096b9b7 100644 --- a/src/init.luau +++ b/src/init.luau @@ -1,23 +1,26 @@ local Typings = require("@root/Typings") -- reexports of typings: +export type AnchorPoint = Typings.AnchorPoint export type Color = Typings.Color export type TextSize = Typings.TextSize -export type UDimValue = Typings.UDimValue export type FontValue = Typings.FontValue -export type AnchorPoint = Typings.AnchorPoint +export type UDimValue = Typings.UDimValue export type PaddingValue = Typings.PaddingValue export type FlexAlignment = Typings.FlexAlignment export type BindingOrValue = Typings.BindingOrValue -export type Aspect = Typings.Aspect -export type Border = Typings.Border +export type BorderSettings = Typings.BorderSettings +export type AspectSettings = Typings.AspectSettings +export type ListLayoutSettings = Typings.ListLayoutSettings +export type GuiObjectModifiers = Typings.GuiObjectModifiers + export type GuiObjectProps = Typings.GuiObjectProps export type ScreenGuiProps = Typings.ScreenGuiProps +export type TextProps = Typings.TextProps export type TextButtonProps = Typings.TextButtonProps -export type ListLayoutProps = Typings.ListLayoutProps -export type ScrollingFrameProps = Typings.ScrollingFrameProps export type ScrollingFrameScrollBar = Typings.ScrollingFrameScrollBar +export type ScrollingFrameProps = Typings.ScrollingFrameProps local BetterReactComponents = { Utils = require("@root/Utils"), From cadc632504c066df31c0356948806beaed9cf70a Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Sun, 17 Aug 2025 11:01:40 +0300 Subject: [PATCH 27/28] feat: added all props of `Text*` elements --- src/Builders/TextBoxBuilder.luau | 17 +++++++++++++++ src/Builders/TextBuilder.luau | 17 ++++++++++----- src/Builders/TextButtonBuilder.luau | 5 ++++- src/Builders/init.luau | 7 +++--- src/Typings.luau | 33 +++++++++++++++++++++++------ src/init.luau | 3 ++- 6 files changed, 66 insertions(+), 16 deletions(-) create mode 100644 src/Builders/TextBoxBuilder.luau diff --git a/src/Builders/TextBoxBuilder.luau b/src/Builders/TextBoxBuilder.luau new file mode 100644 index 0000000..49289d3 --- /dev/null +++ b/src/Builders/TextBoxBuilder.luau @@ -0,0 +1,17 @@ +local Typings = require("@root/Typings") +local Utils = require("@root/Utils") + +local TextBuilder = require("./TextBuilder") + +return TextBuilder:expand(function(props: Typings.TextBoxProps) + return { + ClearTextOnFocus = props.ClearTextOnFocus, + CursorPosition = props.CursorPosition, + MultiLine = props.MultiLine, + PlaceholderColor3 = props.PlaceholderColor and Utils.ConvertColor(props.PlaceholderColor), + PlaceholderText = props.PlaceholderText, + SelectionStart = props.SelectionStart, + ShowNativeInput = props.ShowNativeInput, + TextEditable = props.TextEditable, + }, {} +end) diff --git a/src/Builders/TextBuilder.luau b/src/Builders/TextBuilder.luau index 174ad42..db4f2bb 100644 --- a/src/Builders/TextBuilder.luau +++ b/src/Builders/TextBuilder.luau @@ -8,19 +8,26 @@ local GuiObjectBuilder = require("./GuiObjectBuilder") --- Builder for text-based objects (`TextLabel`, `TextButton`, etc.) return GuiObjectBuilder:expand(function(props: Typings.TextProps) return { - Text = props.Text, - FontFace = props.Font and Utils.ConvertFont(props.Font), LineHeight = props.LineHeight, + + MaxVisibleGraphemes = props.MaxVisibleGraphemes, + RichText = props.RichText, + Text = props.Text, + TextColor3 = props.TextColor and Utils.ConvertColor(props.TextColor), + TextDirection = props.TextDirection, + TextFits = props.TextFits, + TextTransparency = props.TextTransparency, + TextTruncate = props.TextTruncate, TextWrapped = props.TextWrapped, - TextSize = props.TextSize == "auto" and 1 or props.TextSize, - TextScaled = props.TextSize == "auto", - TextXAlignment = props.TextXAlignment, TextYAlignment = props.TextYAlignment, + + TextSize = props.TextSize == "auto" and 1 or props.TextSize, + TextScaled = props.TextSize == "auto", }, { BRCTextSizeConstraint = (props.MinTextSize ~= nil or props.MaxTextSize ~= nil) and React.createElement("UITextSizeConstraint", { diff --git a/src/Builders/TextButtonBuilder.luau b/src/Builders/TextButtonBuilder.luau index b8c10c8..321b4bc 100644 --- a/src/Builders/TextButtonBuilder.luau +++ b/src/Builders/TextButtonBuilder.luau @@ -4,8 +4,11 @@ local TextLabelBuilder = require("./TextBuilder") return TextLabelBuilder:expand(function(props: Typings.TextButtonProps) return { - Style = props.Style, AutoButtonColor = props.AutoButtonColor, + HoverHapticEffect = props.HoverHapticEffect, + PressHapticEffect = props.PressHapticEffect, Modal = props.Modal, + Selected = props.Selected, + Style = props.Style, }, {} end) diff --git a/src/Builders/init.luau b/src/Builders/init.luau index 1977744..f5cda9e 100644 --- a/src/Builders/init.luau +++ b/src/Builders/init.luau @@ -1,5 +1,6 @@ return { - GuiObjectBuilder = require("./GuiObjectBuilder"), - TextBuilder = require("./TextBuilder"), - TextButtonBuilder = require("./TextButtonBuilder"), + GuiObjectBuilder = require(script.GuiObjectBuilder), + TextBuilder = require(script.TextBuilder), + TextBoxBuilder = require(script.TextBoxBuilder), + TextButtonBuilder = require(script.TextButtonBuilder), } diff --git a/src/Typings.luau b/src/Typings.luau index 7c3e512..fc4c222 100644 --- a/src/Typings.luau +++ b/src/Typings.luau @@ -84,7 +84,7 @@ export type ListLayoutSettings = { --- Gui Object modifers export type GuiObjectModifiers = { Border: BorderSettings?, - + CornerRadius: UDimValue?, Aspect: AspectSettings?, @@ -146,26 +146,47 @@ export type ScreenGuiProps = { } export type TextProps = { - Text: string?, - Font: FontValue?, LineHeight: number?, + + MaxVisibleGraphemes: number, + RichText: boolean?, + Text: string?, + TextColor: Color?, + TextDirection: Enum.TextDirection?, + TextFits: boolean?, TextSize: TextSize?, + TextTransparency: number?, + TextTruncate: Enum.TextTruncate?, TextWrapped: boolean?, TextXAlignment: Enum.TextXAlignment?, TextYAlignment: Enum.TextYAlignment?, - MinTextSize: number, - MaxTextSize: number, + MinTextSize: number?, + MaxTextSize: number?, +} + +export type TextBoxProps = { + ClearTextOnFocus: boolean?, + CursorPosition: number?, + MultiLine: boolean?, + PlaceholderColor: Color?, + PlaceholderText: string?, + SelectionStart: number?, + ShowNativeInput: boolean?, + TextEditable: boolean?, } export type TextButtonProps = { - Style: Enum.ButtonStyle?, AutoButtonColor: boolean?, + HoverHapticEffect: HapticEffect?, + PressHapticEffect: HapticEffect?, Modal: boolean?, + Selected: boolean?, + Style: Enum.ButtonStyle?, } export type ScrollingFrameScrollBar = { diff --git a/src/init.luau b/src/init.luau index 096b9b7..107387e 100644 --- a/src/init.luau +++ b/src/init.luau @@ -18,6 +18,7 @@ export type GuiObjectModifiers = Typings.GuiObjectModifiers export type GuiObjectProps = Typings.GuiObjectProps export type ScreenGuiProps = Typings.ScreenGuiProps export type TextProps = Typings.TextProps +export type TextBoxProps = Typings.TextBoxProps export type TextButtonProps = Typings.TextButtonProps export type ScrollingFrameScrollBar = Typings.ScrollingFrameScrollBar export type ScrollingFrameProps = Typings.ScrollingFrameProps @@ -32,5 +33,5 @@ local Builders = require("@root/Builders") return BetterReactComponents.Utils.Assign( BetterReactComponents.Utils.Assign(BetterReactComponents, Components), - Builders + { Builders = require("@root/Builders") } ) From 8083be79d48c1a6413c9db07e5991d05fe7f4e63 Mon Sep 17 00:00:00 2001 From: Dmitry <58977309+Mon4ik@users.noreply.github.com> Date: Sun, 17 Aug 2025 11:13:13 +0300 Subject: [PATCH 28/28] docs: add more todos --- README.md | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 39551b4..f19eaea 100644 --- a/README.md +++ b/README.md @@ -39,28 +39,36 @@ Respectfully, ### Components -- [x] ScreenGui -- [x] Frame -- [x] ScrollingFrame - [ ] CanvasGroup -- [ ] ViewportFrame -- [x] TextButton -- [x] TextLabel -- [ ] TextBox +- [x] Frame - [ ] ImageButton +- [x] TextButton - [ ] ImageLabel +- [x] TextLabel +- [x] ScrollingFrame +- [x] TextBox +- [ ] VideoDisplay +- [ ] VideoFrame +- [ ] ViewportFrame +- [ ] LayerCollector + - [ ] BillboardGui + - [x] ScreenGui + - [ ] SurfaceGui - [ ] Path2D ### Modifiers -- [x] UIAspectRatioConstraint +- [x] UIConstraint + - [x] UIAspectRatioConstraint + - [x] UISizeConstraint + - [x] UITextSizeConstraint - [x] UICorner (`CornerRadius` property) -- [x] UIStroke (`Border` property) +- [ ] UIDragDetector +- [ ] UIGradient - [x] UIPadding -- [x] UISizeConstraint -- [x] UITextSizeConstraint - [ ] UIScale -- [ ] UIGradient +- [x] UIStroke (`Border` property) + ### Layouts