diff --git a/.darklua-dev.json b/.darklua-dev.json new file mode 100644 index 0000000..54984fd --- /dev/null +++ b/.darklua-dev.json @@ -0,0 +1,20 @@ +{ + "rules": [ + { + "rule": "convert_require", + "current": { + "name": "path", + "sources": { + "@pkg": "Packages", + "@dev-pkg": "DevPackages", + "@root": "src" + } + }, + "target": { + "name": "roblox", + "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..0daa011 --- /dev/null +++ b/.darklua-roblox.json @@ -0,0 +1,19 @@ +{ + "rules": [ + { + "rule": "convert_require", + "current": { + "name": "path", + "sources": { + "@pkg": "Packages", + "@root": "src" + } + }, + "target": { + "name": "roblox", + "rojo_sourcemap": "./sourcemap.json", + "indexing_style": "wait_for_child" + } + } + ] +} diff --git a/.darklua-wally.json b/.darklua-wally.json new file mode 100644 index 0000000..0daa011 --- /dev/null +++ b/.darklua-wally.json @@ -0,0 +1,19 @@ +{ + "rules": [ + { + "rule": "convert_require", + "current": { + "name": "path", + "sources": { + "@pkg": "Packages", + "@root": "src" + } + }, + "target": { + "name": "roblox", + "rojo_sourcemap": "./sourcemap.json", + "indexing_style": "wait_for_child" + } + } + ] +} 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/.gitignore b/.gitignore index f484755..a5fea83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,9 @@ -/node_modules -/out -/include +Packages/ +DevPackages/ +out/ + +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/.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/.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..bd1c50d --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +.PHONY: install cleanup install-toolchain cleanup-build serve watch build/* + +install: + aftman install + +cleanup: + git clean -Xf build # remove ignored files in build/ + +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/ + wally-package-types --sourcemap sourcemap.json DevPackages/ + +sourcemap.json: src/* default.project.json + rojo sourcemap default.project.json --output sourcemap.json diff --git a/README.md b/README.md index 0164bd5..f19eaea 100644 --- a/README.md +++ b/README.md @@ -1,83 +1,79 @@ -# 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 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 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` + ``` - -## Support - -### TODO - -- [ ] Upgrade ESLint to `9.*.*` version -- [ ] Better documentation +## Supported Components/Modifiers ### Components -- [X] Frame -- [X] ScrollableFrame -- [X] Button +- [ ] CanvasGroup +- [x] Frame - [ ] ImageButton -- [X] Image -- [X] Text -- [X] TextBox -- [X] CanvasGroup +- [x] TextButton +- [ ] ImageLabel +- [x] TextLabel +- [x] ScrollingFrame +- [x] TextBox +- [ ] VideoDisplay +- [ ] VideoFrame +- [ ] ViewportFrame +- [ ] LayerCollector + - [ ] BillboardGui + - [x] ScreenGui + - [ ] SurfaceGui +- [ ] Path2D ### 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 -- [ ] UIPageLayout -- [X] UIScale -- [X] UISizeConstraint -- [X] UIStroke -- [ ] UITableLayout -- [X] UITextSizeConstraint +- [x] UIConstraint + - [x] UIAspectRatioConstraint + - [x] UISizeConstraint + - [x] UITextSizeConstraint +- [x] UICorner (`CornerRadius` property) +- [ ] UIDragDetector +- [ ] UIGradient +- [x] UIPadding +- [ ] UIScale +- [x] UIStroke (`Border` property) + -### Custom Modifiers +### Layouts -- ~~[ ] 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) ) +- [ ] UIGridLayout +- [x] UIListLayout +- [x] UIFlexItem +- [ ] UITableLayout +- [ ] UIPageLayout diff --git a/aftman.toml b/aftman.toml index e989d3b..368754c 100644 --- a/aftman.toml +++ b/aftman.toml @@ -1,7 +1,5 @@ -# 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" +darklua = "seaofvoices/darklua@0.16.0" 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 new file mode 100644 index 0000000..61d72fd --- /dev/null +++ b/build/wally/.gitignore @@ -0,0 +1,11 @@ +# This folder used for storing Wally package files +# +# `sourcemap.project.json` is used for generating sourcemap.json +# +# To build wally package run: +# $ make wally-package + +* +!.gitignore +!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/sourcemap.project.json b/build/wally/sourcemap.project.json new file mode 100644 index 0000000..8375ec8 --- /dev/null +++ b/build/wally/sourcemap.project.json @@ -0,0 +1,9 @@ +{ + "name": "sourcemap", + "tree": { + "$path": "Packages", + "better-react-components": { + "$path": "src" + } + } +} diff --git a/default.project.json b/default.project.json new file mode 100644 index 0000000..f2cf788 --- /dev/null +++ b/default.project.json @@ -0,0 +1,18 @@ +{ + "name": "better-react-components-development", + "tree": { + "$className": "DataModel", + "ReplicatedStorage": { + "$className": "ReplicatedStorage", + "DevPackages": { + "$path": "DevPackages" + }, + "Packages": { + "$path": "Packages" + }, + "better-react-components": { + "$path": "src" + } + } + } +} diff --git a/out.project.json b/out.project.json new file mode 100644 index 0000000..874445f --- /dev/null +++ b/out.project.json @@ -0,0 +1,18 @@ +{ + "name": "better-react-components-development", + "tree": { + "$className": "DataModel", + "ReplicatedStorage": { + "$className": "ReplicatedStorage", + "DevPackages": { + "$path": "DevPackages" + }, + "Packages": { + "$path": "Packages" + }, + "better-react-components": { + "$path": "out" + } + } + } +} 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/scripts/roblox.sh b/scripts/roblox.sh new file mode 100755 index 0000000..b2fa355 --- /dev/null +++ b/scripts/roblox.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +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 -r 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 -r 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 new file mode 100755 index 0000000..7159b77 --- /dev/null +++ b/scripts/wally.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +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 -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 diff --git a/src/Builder.luau b/src/Builder.luau new file mode 100644 index 0000000..2b19ce1 --- /dev/null +++ b/src/Builder.luau @@ -0,0 +1,66 @@ +local React = require("@pkg/react") +local Utils = require("@root/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 +): (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 + + 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/Builders/GuiObjectBuilder.luau b/src/Builders/GuiObjectBuilder.luau new file mode 100644 index 0000000..65bcd79 --- /dev/null +++ b/src/Builders/GuiObjectBuilder.luau @@ -0,0 +1,65 @@ +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") + +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, + Rotation = props.Rotation, + + Selectable = props.Selectable, + + Size = props.Size, + SizeConstraint = props.SizeConstraint, + + 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), + Thickness = props.Border.Thickness, + LineJoinMode = props.Border.LineJoinMode, + Transparency = props.Border.Transparency, + }), + 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()), + + BRCSizeConstraint = (props.MinSize ~= nil or props.MaxSize ~= nil) + and React.createElement("UISizeConstraint", { + MinSize = props.MinSize, + 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/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 new file mode 100644 index 0000000..db4f2bb --- /dev/null +++ b/src/Builders/TextBuilder.luau @@ -0,0 +1,38 @@ +local React = require("@pkg/react") + +local Utils = require("@root/Utils") +local Typings = require("@root/Typings") + +local GuiObjectBuilder = require("./GuiObjectBuilder") + +--- Builder for text-based objects (`TextLabel`, `TextButton`, etc.) +return GuiObjectBuilder:expand(function(props: Typings.TextProps) + return { + 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, + + 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", { + MinTextSize = props.MinTextSize, + MaxTextSize = props.MaxTextSize, + }), + } +end) diff --git a/src/Builders/TextButtonBuilder.luau b/src/Builders/TextButtonBuilder.luau new file mode 100644 index 0000000..321b4bc --- /dev/null +++ b/src/Builders/TextButtonBuilder.luau @@ -0,0 +1,14 @@ +local Typings = require("@root/Typings") + +local TextLabelBuilder = require("./TextBuilder") + +return TextLabelBuilder:expand(function(props: Typings.TextButtonProps) + return { + 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 new file mode 100644 index 0000000..f5cda9e --- /dev/null +++ b/src/Builders/init.luau @@ -0,0 +1,6 @@ +return { + GuiObjectBuilder = require(script.GuiObjectBuilder), + TextBuilder = require(script.TextBuilder), + TextBoxBuilder = require(script.TextBoxBuilder), + TextButtonBuilder = require(script.TextButtonBuilder), +} diff --git a/src/Components/Frame.luau b/src/Components/Frame.luau new file mode 100644 index 0000000..f209985 --- /dev/null +++ b/src/Components/Frame.luau @@ -0,0 +1,3 @@ +local GuiObjectBuilder = require("@root/Builders/GuiObjectBuilder") + +return GuiObjectBuilder:build("Frame") diff --git a/src/Components/ScreenGui.luau b/src/Components/ScreenGui.luau new file mode 100644 index 0000000..479da28 --- /dev/null +++ b/src/Components/ScreenGui.luau @@ -0,0 +1,13 @@ +local Builder = require("@root/Builder") +local Typings = require("@root/Typings") + +return Builder.new(function(props: Typings.ScreenGuiProps) + return { + Enabled = props.Enabled, + ResetOnSpawn = props.ResetOnSpawn, + ZIndexBehavior = props.ZIndexBehavior, + + ScreenInsets = props.ScreenInsets, + DisplayOrder = props.DisplayOrder, + }, {} +end):build("ScreenGui") diff --git a/src/Components/ScrollingFrame.luau b/src/Components/ScrollingFrame.luau new file mode 100644 index 0000000..8751656 --- /dev/null +++ b/src/Components/ScrollingFrame.luau @@ -0,0 +1,28 @@ +local GuiObjectBuilder = require("@root/Builders/GuiObjectBuilder") + +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/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/TextButton.luau b/src/Components/TextButton.luau new file mode 100644 index 0000000..a479a02 --- /dev/null +++ b/src/Components/TextButton.luau @@ -0,0 +1,3 @@ +local TextButtonBuilder = require("@root/Builders/TextButtonBuilder") + +return TextButtonBuilder:build("TextButton") diff --git a/src/Components/TextLabel.luau b/src/Components/TextLabel.luau new file mode 100644 index 0000000..c313b13 --- /dev/null +++ b/src/Components/TextLabel.luau @@ -0,0 +1,3 @@ +local TextBuilder = require("@root/Builders/TextBuilder") + +return TextBuilder:build("TextLabel") diff --git a/src/Components/init.luau b/src/Components/init.luau new file mode 100644 index 0000000..9aaa35f --- /dev/null +++ b/src/Components/init.luau @@ -0,0 +1,8 @@ +return { + ScreenGui = require(script.ScreenGui), + Frame = require(script.Frame), + ScrollingFrame = require(script.ScrollingFrame), + TextButton = require(script.TextButton), + TextLabel = require(script.TextLabel), + TextBox = require(script.TextBox), +} diff --git a/src/Helpers/ListLayout.luau b/src/Helpers/ListLayout.luau new file mode 100644 index 0000000..55c01ec --- /dev/null +++ b/src/Helpers/ListLayout.luau @@ -0,0 +1,32 @@ +local React = require("@pkg/react") + +local Utils = require("@root/Utils") +local Typings = require("@root/Typings") + +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 + 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/Stories/Components/Frame.story.luau b/src/Stories/Components/Frame.story.luau new file mode 100644 index 0000000..6c4787c --- /dev/null +++ b/src/Stories/Components/Frame.story.luau @@ -0,0 +1,34 @@ +local React = require("@pkg/react") +local ReactRoblox = require("@pkg/react-roblox") +local UILabs = require("@dev-pkg/ui-labs") + +local BRC = require("@root/init") + +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"), + }, + story = function(props) + 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), + BackgroundColor = props.controls.BackgroundColor, + }) + end, +} + +return story 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, +} 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 new file mode 100644 index 0000000..37cadfe --- /dev/null +++ b/src/Stories/Components/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 BRC = require("@root/init") + +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(BRC.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, +} diff --git a/src/Stories/Examples/Todos.story.luau b/src/Stories/Examples/Todos.story.luau new file mode 100644 index 0000000..eb5c55b --- /dev/null +++ b/src/Stories/Examples/Todos.story.luau @@ -0,0 +1,163 @@ +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 }) + local input, setInput = React.useBinding("") + + 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.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, + Size = UDim2.new(0.2, 0, 1, 0), + + CornerRadius = 8, + + BackgroundColor = "#00a63e", + TextColor = "#FFFFFF", + + Text = "Add", + + [React.Event.Activated] = function() + setTodos(function(todos) + local text = input:getValue() + setInput("") + + return BRC.Utils.MergeMulti( + todos, + { [#todos + 1] = { text = text, 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(todo: 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, + Text = todo.text, + }), + React.createElement(BRC.TextButton, { + LayoutOrder = 2, + + AnchorPoint = "mr", + Size = UDim2.new(0.1, 0, 1, 0), + Aspect = { Ratio = 1 }, + + CornerRadius = 4, + + BackgroundColor = todo.completed and "#DC2626" or "#00A63E", + TextColor = "#FFF", + Text = todo.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/src/Stories/Layouts/FlexItem.story.luau b/src/Stories/Layouts/FlexItem.story.luau new file mode 100644 index 0000000..0cc6947 --- /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("@root/init") + +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, +} diff --git a/src/Stories/Layouts/List.story.luau b/src/Stories/Layouts/List.story.luau new file mode 100644 index 0000000..20dee48 --- /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 BRC = require("@root/init") + +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(BRC.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( + BRC.Frame, + { Size = UDim2.new(0.5, 0, 0.25, 0), BackgroundColor = "#E80000" } + ), + React.createElement( + BRC.Frame, + { Size = UDim2.new(0.5, 0, 0.25, 0), BackgroundColor = "#00E800" } + ), + React.createElement( + BRC.Frame, + { Size = UDim2.new(0.5, 0, 0.25, 0), BackgroundColor = "#0000E8" } + ), + }) + end, +} diff --git a/src/Stories/Modifiers/AspectRatio.story.luau b/src/Stories/Modifiers/AspectRatio.story.luau new file mode 100644 index 0000000..e42328d --- /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 BRC = require("@root/init") + +local story = { + react = React, + reactRoblox = ReactRoblox, + controls = { + AspectRatio = UILabs.Slider(1, 0.5, 5, 0.1), + }, + story = function(props) + 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", + + 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..ed050cf --- /dev/null +++ b/src/Stories/Modifiers/Padding.story.luau @@ -0,0 +1,47 @@ +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 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( + BRC.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(BRC.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 diff --git a/src/Stories/Modifiers/SizeConstraint.story.luau b/src/Stories/Modifiers/SizeConstraint.story.luau new file mode 100644 index 0000000..197c617 --- /dev/null +++ b/src/Stories/Modifiers/SizeConstraint.story.luau @@ -0,0 +1,25 @@ +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 = { + MinSizeX = UILabs.Slider(100, 0, 255, 1), + MaxSizeX = UILabs.Slider(200, 0, 500, 1), + }, + story = function(props) + 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", + + 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..1d85b5d --- /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 BRC = require("@root/init") + +return { + react = React, + reactRoblox = ReactRoblox, + controls = { + SizeY = UILabs.Slider(100, 0, 500), + MaxTextSize = UILabs.Slider(20, 1, 100), + }, + story = function(props) + 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), + BackgroundColor = "#FFF", + TextColor = "#F00", + + Text = "creepy ahh text", + TextSize = "auto", + Font = Enum.Font.Creepster, + + MaxTextSize = props.controls.MaxTextSize, + }) + end, +} diff --git a/src/Stories/Modifiers/UICorner.story.luau b/src/Stories/Modifiers/UICorner.story.luau new file mode 100644 index 0000000..4bf16b1 --- /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 BRC = require("@root/init") + +local story = { + react = React, + reactRoblox = ReactRoblox, + controls = { + CornerRadius = UILabs.Slider(10, 0, 150, 1), + }, + story = function(props) + 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, -- UDim | 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..24bc959 --- /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 BRC = require("@root/init") + +local story = { + react = React, + reactRoblox = ReactRoblox, + controls = { + Color = Color3.fromHex("#FF0000"), + Thickness = UILabs.Slider(5, 0, 50), + }, + story = function(props) + 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 = 15, + + Border = { + Color = props.controls.Color, -- Color3 | number + Thickness = props.controls.Thickness, -- number + }, + }) + end, +} + +return story diff --git a/src/Typings.luau b/src/Typings.luau new file mode 100644 index 0000000..fc4c222 --- /dev/null +++ b/src/Typings.luau @@ -0,0 +1,218 @@ +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 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 } + +--- Flex alignments (like in CSS) +export type FlexAlignment = + "start" + | "end" + | "center" + | "space-between" + | "space-around" + | "space-evenly" + | "stretch" + +--- 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 BorderSettings = { + Color: Color?, + Thickness: number?, + LineJoinMode: Enum.LineJoinMode, + Transparency: number?, +} + +--- Aspect ratio settings +-- TODO: support number-only +export type AspectSettings = { + Ratio: number, + Type: Enum.AspectType?, + DominantAxis: Enum.DominantAxis?, +} + +--- List layout settings +export type ListLayoutSettings = { + Gap: UDimValue, + + Direction: Enum.FillDirection?, + Order: Enum.SortOrder?, + Wraps: boolean?, + + JustifyContent: FlexAlignment?, + AlignItems: FlexAlignment?, + -- TODO: ItemLineAlignment +} + +--- Gui Object modifers +export type GuiObjectModifiers = { + Border: BorderSettings?, + + CornerRadius: UDimValue?, + + Aspect: AspectSettings?, + + Padding: PaddingValue?, + + MinSize: Vector2?, + MaxSize: Vector2?, + + ListLayout: ListLayoutSettings?, + FlexMode: Enum.UIFlexMode?, +} + +export type GuiObjectProps = { + Active: boolean, + + AnchorPoint: AnchorPoint?, + AutomaticSize: Enum.AutomaticSize?, + + BackgroundColor: Color?, + BackgroundTransparency: number?, + --- Alias for `BackgroundTransparency = 1` + NoBackground: boolean?, + + ClipsDescendants: boolean?, + -- TODO: GuiState:Enum.GuiState, + Interactable: boolean, + + LayoutOrder: number?, + + -- TODO: NextSelectionDown:GuiObject + -- TODO: NextSelectionLeft:GuiObject + -- TODO: NextSelectionRight:GuiObject + -- TODO: NextSelectionUp:GuiObject + + Position: UDim2?, + Rotation: number?, + + Selectable: boolean?, + -- TODO: SelectionImageObject:GuiObject, + -- TODO: SelectionOrder:number + + Size: UDim2?, + SizeConstraint: Enum.SizeConstraint?, + + Transparency: number, + Visible: boolean?, + + ZIndex: number?, +} & GuiObjectModifiers + +export type ScreenGuiProps = { + Enabled: boolean?, + ResetOnSpawn: boolean?, + ZIndexBehavior: Enum.ZIndexBehavior?, + + ScreenInsets: Enum.ScreenInsets?, + DisplayOrder: number?, +} + +export type TextProps = { + 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?, +} + +export type TextBoxProps = { + ClearTextOnFocus: boolean?, + CursorPosition: number?, + MultiLine: boolean?, + PlaceholderColor: Color?, + PlaceholderText: string?, + SelectionStart: number?, + ShowNativeInput: boolean?, + TextEditable: boolean?, +} + +export type TextButtonProps = { + AutoButtonColor: boolean?, + HoverHapticEffect: HapticEffect?, + PressHapticEffect: HapticEffect?, + Modal: boolean?, + Selected: boolean?, + Style: Enum.ButtonStyle?, +} + +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 new file mode 100644 index 0000000..5c21d2f --- /dev/null +++ b/src/Utils.luau @@ -0,0 +1,336 @@ +local React = require("@pkg/react") + +local Typings = require("@root/Typings") + +local Utils = {} + +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, +}) + +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: Typings.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 + local result = table1 :: any + + for k, v in pairs(table2 :: any) do + result[k] = v + end + + 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.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 + 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"', + typeof(value) + ) + ) + end + end) +end + +function Utils.ConvertAnchorPoint( + value: Typings.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"', + typeof(value) + ) + ) + end + end) +end + +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) + elseif typeof(value) == "Font" then + return value + else + error( + string.format( + 'Invalid value in ConvertFont. Expected Font or Enum.Font, found "%s"', + typeof(value) + ) + ) + end + end) +end + +local function ConvertUDimInternal(value: Typings.UDimValue): UDim + 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 +end + +function Utils.ConvertUDim(value: Typings.BindingOrValue): 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: Typings.BindingOrValue +): React.Binding + return Utils.mapBinding(value, function(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 ConvertPadding. Expected UDimValue or {UDimValue}, found "%s"', + typeof(value) + ) + ) + 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: Typings.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/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..107387e --- /dev/null +++ b/src/init.luau @@ -0,0 +1,37 @@ +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 FontValue = Typings.FontValue +export type UDimValue = Typings.UDimValue +export type PaddingValue = Typings.PaddingValue +export type FlexAlignment = Typings.FlexAlignment +export type BindingOrValue = Typings.BindingOrValue + +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 TextBoxProps = Typings.TextBoxProps +export type TextButtonProps = Typings.TextButtonProps +export type ScrollingFrameScrollBar = Typings.ScrollingFrameScrollBar +export type ScrollingFrameProps = Typings.ScrollingFrameProps + +local BetterReactComponents = { + Utils = require("@root/Utils"), + Builder = require("@root/Builder"), +} + +local Components = require("@root/Components") +local Builders = require("@root/Builders") + +return BetterReactComponents.Utils.Assign( + BetterReactComponents.Utils.Assign(BetterReactComponents, Components), + { Builders = require("@root/Builders") } +) 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/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 deleted file mode 100644 index a96604f..0000000 --- a/story.project.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "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" - } - } - }, - "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..ec5a0a1 --- /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 = [["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" +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..f1e48be --- /dev/null +++ b/wally.toml @@ -0,0 +1,15 @@ +[package] +name = "idkncc/better-react-components" +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" +react-roblox = "jsdotlua/react-roblox@~17.2" +collections = "jsdotlua/collections@~1.2" + +[dev-dependencies] +ui-labs = "pepeeltoro41/ui-labs@2.3.8"