Add bunext playground for local component development
Runs at http://localhost:7010 via bun run dev. The playground imports the library from src through a committed symlink and tsconfig path alias, so component edits hot-reload without a build step. It has its own install root and is excluded from the published package.
This commit is contained in:
@@ -33,3 +33,6 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
# build output
|
# build output
|
||||||
dist
|
dist
|
||||||
|
|
||||||
|
# dev playground artifacts (bunext)
|
||||||
|
.bunext
|
||||||
|
|||||||
@@ -15,6 +15,9 @@
|
|||||||
| `bun run typecheck` | `tsc --noEmit` - must be clean before finishing any change |
|
| `bun run typecheck` | `tsc --noEmit` - must be clean before finishing any change |
|
||||||
| `bun test src` | Unit tests (`src/**/*.test.ts`) |
|
| `bun test src` | Unit tests (`src/**/*.test.ts`) |
|
||||||
| `bun run compile` | Clean build into `dist/` + post-compile step |
|
| `bun run compile` | Clean build into `dist/` + post-compile step |
|
||||||
|
| `bun run dev` | Start the playground in `dev/` at http://localhost:7010 |
|
||||||
|
| `bun run dev:types` | `tsc --watch`, for consumers working against `dist/` |
|
||||||
|
| `bun run dev:typecheck` | Typecheck the playground (`dev/tsconfig.json`) |
|
||||||
| `./publish.sh "msg"`| Typecheck → test → compile → `git commit/push` → `bun publish` |
|
| `./publish.sh "msg"`| Typecheck → test → compile → `git commit/push` → `bun publish` |
|
||||||
|
|
||||||
`publish.sh` pushes and publishes - only run it when explicitly asked. Bump `version` in `package.json` before publishing.
|
`publish.sh` pushes and publishes - only run it when explicitly asked. Bump `version` in `package.json` before publishing.
|
||||||
@@ -37,6 +40,7 @@
|
|||||||
| `src/(partials)/`, `src/(functions)/`, `src/(utils)/` | Internal helpers, not meant for direct import |
|
| `src/(partials)/`, `src/(functions)/`, `src/(utils)/` | Internal helpers, not meant for direct import |
|
||||||
| `src/types.ts` | Shared types/constants |
|
| `src/types.ts` | Shared types/constants |
|
||||||
| `scripts/post-compile.ts` | Copies `base.css`, adds `.js` to relative imports, prepends `"use client"` |
|
| `scripts/post-compile.ts` | Copies `base.css`, adds `.js` to relative imports, prepends `"use client"` |
|
||||||
|
| `dev/` | Local bunext playground, own `node_modules`/lockfile; never published. `dev/twui -> ../src` drives HMR for library edits |
|
||||||
|
|
||||||
## Public API Rules (MANDATORY)
|
## Public API Rules (MANDATORY)
|
||||||
|
|
||||||
|
|||||||
@@ -78,6 +78,26 @@ bun test src
|
|||||||
bun run compile # tsc → dist/, then scripts/post-compile.ts
|
bun run compile # tsc → dist/, then scripts/post-compile.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Playground
|
||||||
|
|
||||||
|
`dev/` is a local [bunext](https://git.tben.me/Moduletrace/bunext) app that imports the library straight from `src/` (through the committed `dev/twui` symlink and a tsconfig path alias), so component edits hot-reload without running `tsc`. It has its own `node_modules` and is excluded from the published package by the `files` whitelist.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd dev
|
||||||
|
bun install
|
||||||
|
cd ..
|
||||||
|
bun run dev # http://localhost:7010
|
||||||
|
```
|
||||||
|
|
||||||
|
Install the library dependencies at the repo root first: the symlinked sources resolve `react`, `tailwind-merge`, `lucide-react` and the optional peers from the root `node_modules`.
|
||||||
|
|
||||||
|
Other useful scripts:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
bun run dev:types # tsc --watch, for consumers working against dist/
|
||||||
|
bun run dev:typecheck # typecheck the playground pages and the sources they import
|
||||||
|
```
|
||||||
|
|
||||||
### Publishing
|
### Publishing
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
@moduletrace:registry=https://git.tben.me/api/packages/moduletrace/npm/
|
||||||
+277
@@ -0,0 +1,277 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 2,
|
||||||
|
"configVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"name": "@moduletrace/twui-dev",
|
||||||
|
"dependencies": {
|
||||||
|
"@moduletrace/bunext": "^1.3.4",
|
||||||
|
"lucide-react": "^1.59.0",
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5.9.3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@alloc/quick-lru": ["@alloc/[email protected]", "", {}, "sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA=="],
|
||||||
|
|
||||||
|
"@esbuild/aix-ppc64": ["@esbuild/[email protected]", "", { "os": "aix", "cpu": "ppc64" }, "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ=="],
|
||||||
|
|
||||||
|
"@esbuild/android-arm": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "arm" }, "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg=="],
|
||||||
|
|
||||||
|
"@esbuild/android-arm64": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A=="],
|
||||||
|
|
||||||
|
"@esbuild/android-x64": ["@esbuild/[email protected]", "", { "os": "android", "cpu": "x64" }, "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q=="],
|
||||||
|
|
||||||
|
"@esbuild/darwin-arm64": ["@esbuild/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw=="],
|
||||||
|
|
||||||
|
"@esbuild/darwin-x64": ["@esbuild/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw=="],
|
||||||
|
|
||||||
|
"@esbuild/freebsd-arm64": ["@esbuild/[email protected]", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw=="],
|
||||||
|
|
||||||
|
"@esbuild/freebsd-x64": ["@esbuild/[email protected]", "", { "os": "freebsd", "cpu": "x64" }, "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-arm": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-arm64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-ia32": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "ia32" }, "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-loong64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-mips64el": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-ppc64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-riscv64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-s390x": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "s390x" }, "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg=="],
|
||||||
|
|
||||||
|
"@esbuild/linux-x64": ["@esbuild/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ=="],
|
||||||
|
|
||||||
|
"@esbuild/netbsd-arm64": ["@esbuild/[email protected]", "", { "os": "none", "cpu": "arm64" }, "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw=="],
|
||||||
|
|
||||||
|
"@esbuild/netbsd-x64": ["@esbuild/[email protected]", "", { "os": "none", "cpu": "x64" }, "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw=="],
|
||||||
|
|
||||||
|
"@esbuild/openbsd-arm64": ["@esbuild/[email protected]", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ=="],
|
||||||
|
|
||||||
|
"@esbuild/openbsd-x64": ["@esbuild/[email protected]", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw=="],
|
||||||
|
|
||||||
|
"@esbuild/openharmony-arm64": ["@esbuild/[email protected]", "", { "os": "none", "cpu": "arm64" }, "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q=="],
|
||||||
|
|
||||||
|
"@esbuild/sunos-x64": ["@esbuild/[email protected]", "", { "os": "sunos", "cpu": "x64" }, "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-arm64": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-ia32": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "ia32" }, "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA=="],
|
||||||
|
|
||||||
|
"@esbuild/win32-x64": ["@esbuild/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g=="],
|
||||||
|
|
||||||
|
"@jridgewell/gen-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||||
|
|
||||||
|
"@jridgewell/remapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||||
|
|
||||||
|
"@jridgewell/resolve-uri": ["@jridgewell/[email protected]", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||||
|
|
||||||
|
"@jridgewell/sourcemap-codec": ["@jridgewell/[email protected]", "", {}, "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw=="],
|
||||||
|
|
||||||
|
"@jridgewell/trace-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
|
||||||
|
|
||||||
|
"@moduletrace/bunext": ["@moduletrace/[email protected]", "https://git.tben.me/api/packages/Moduletrace/npm/%40moduletrace%2Fbunext/-/1.3.4/bunext-1.3.4.tgz", { "dependencies": { "@tailwindcss/postcss": "^4.3.3", "@types/bun": "1.3.14", "@types/node": "^24.10.0", "@types/react": "^19.3.0", "@types/react-dom": "^19.3.0", "bun-plugin-tailwind": "^0.1.2", "chalk": "^5.6.2", "chokidar": "^5.0.0", "commander": "^14.0.2", "esbuild": "^0.28.2", "lightningcss-wasm": "^1.32.0", "lodash": "^4.18.1", "ora": "^9.0.0", "postcss": "^8.5.30", "react": "^19.3.0", "react-dom": "^19.3.0", "tailwindcss": "^4.3.3", "typescript": "^5.0.0", "uuid": "^14.0.2" }, "bin": { "bunext": "dist/commands/index.js" } }, "sha512-+WERTs024+7c2RiynwxngPhShPOexnA2qriVe38JTDAEzl2bqHq97WBecJhD6z7R+VTMibfx+ODikHBXcWEj8Q=="],
|
||||||
|
|
||||||
|
"@oven/bun-darwin-aarch64": ["@oven/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-u2wAM9RzqyF4baO5oFVhcFUVWO1CCMLG6yC5/aPWCYGZPfL5ULMbeHt+0/3I0h3yaPx1m+PKSANO/61jPtDZcQ=="],
|
||||||
|
|
||||||
|
"@oven/bun-darwin-x64": ["@oven/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-wRGeciWOyKyG/BfTHq4CAyqoOfuzj5UJkKA8FlJETW2yhtDh+Ba3EgPnsVclFyzIw7E0HJxpsonoj17g3HPK/Q=="],
|
||||||
|
|
||||||
|
"@oven/bun-freebsd-aarch64": ["@oven/[email protected]", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-z4LiDRPTJMKA8JvtP+Z+I5m1syJr8HGYFainIJoCAdkWaHMyQMaEEYr0N+FzTK4pECUHUmscCTNfC9XJwTe4dQ=="],
|
||||||
|
|
||||||
|
"@oven/bun-freebsd-x64": ["@oven/[email protected]", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YpwGnfvtSMRMnnViJI5wCGmS5HpCBAl0wLhBflJfmZYkgOHfplzeKUkUVDW2bNOZyrym3YN+EDaLWooaJpX8Eg=="],
|
||||||
|
|
||||||
|
"@oven/bun-linux-aarch64": ["@oven/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-ylogDFxDD+aRKO0TJauwDzCWM+rULvc85gRNZQ9CnY6rm2BU4GC2XUDi20bh67CIuB4Wt/0PL1xpsJ2hqplMww=="],
|
||||||
|
|
||||||
|
"@oven/bun-linux-aarch64-android": ["@oven/[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-KCsYD+HHawd6xht4BOffTnLZXNf+xzq8l52MsIF2yRtIfUvGTCumdnJPXxuo969iXukb7cWtz+WMkpYlKMG+sg=="],
|
||||||
|
|
||||||
|
"@oven/bun-linux-aarch64-musl": ["@oven/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-5OJIbcVspZxIN/zQtrFYwLuJgaOlciGcynmwCtwKyg0rmXHJoNG0QyspiiLnDUDoVTSGY/nrn0QEbWKhu0QbEQ=="],
|
||||||
|
|
||||||
|
"@oven/bun-linux-x64": ["@oven/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-RArgnpjeXjaGzjp5h7Rs89cUYJutO8IyqR3itE8OBaBzEt8KNm2jp/YozxBHq4eFovwrQLryeSwTAcmiT+zcoA=="],
|
||||||
|
|
||||||
|
"@oven/bun-linux-x64-android": ["@oven/[email protected]", "", { "os": "android", "cpu": "x64" }, "sha512-5CdcvAc+iTqf/p6S0VkoGF8harKMjTfEkMOXJHslwBzl5GcY5alth1mkhD3T7SqLA5aUal95vzfiNjoAZXkPOw=="],
|
||||||
|
|
||||||
|
"@oven/bun-linux-x64-musl": ["@oven/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-L0gTTjTncLqv7VsV/2iLz7Zk4vd969xqRgbtGnskmV3MvL+IJgzTyQr5zm8GGGWSm/SO/ab2vnn4GXgPomoGbg=="],
|
||||||
|
|
||||||
|
"@oven/bun-windows-aarch64": ["@oven/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-a3Vyu9KI0J9vfIS+/LJMmjWI/qsBpph8u0R7d6Tm+YaSOqZeN2LRULfVqz13dgQRTvqg6aWBxOv9jE4Rl0lAAQ=="],
|
||||||
|
|
||||||
|
"@oven/bun-windows-x64": ["@oven/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-9pV16qXHTFvZDH61S8hpdpVBbWmvEqekczLF54zTnQHKP28D6yRCNLrD/7jVbXYR61fQYAx4FSBgzCxGCKb3rQ=="],
|
||||||
|
|
||||||
|
"@tailwindcss/node": ["@tailwindcss/[email protected]", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.3" } }, "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide": ["@tailwindcss/[email protected]", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.3.3", "@tailwindcss/oxide-darwin-arm64": "4.3.3", "@tailwindcss/oxide-darwin-x64": "4.3.3", "@tailwindcss/oxide-freebsd-x64": "4.3.3", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", "@tailwindcss/oxide-linux-x64-musl": "4.3.3", "@tailwindcss/oxide-wasm32-wasi": "4.3.3", "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" } }, "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/[email protected]", "", { "os": "freebsd", "cpu": "x64" }, "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/[email protected]", "", { "dependencies": { "@emnapi/core": "^1.11.1", "@emnapi/runtime": "^1.11.1", "@emnapi/wasi-threads": "^1.2.2", "@napi-rs/wasm-runtime": "^1.1.4", "@tybys/wasm-util": "^0.10.2", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw=="],
|
||||||
|
|
||||||
|
"@tailwindcss/postcss": ["@tailwindcss/[email protected]", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.3.3", "@tailwindcss/oxide": "4.3.3", "postcss": "^8.5.16", "tailwindcss": "4.3.3" } }, "sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg=="],
|
||||||
|
|
||||||
|
"@types/bun": ["@types/[email protected]", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
|
||||||
|
|
||||||
|
"@types/node": ["@types/[email protected]", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ=="],
|
||||||
|
|
||||||
|
"@types/react": ["@types/[email protected]", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg=="],
|
||||||
|
|
||||||
|
"@types/react-dom": ["@types/[email protected]", "", { "peerDependencies": { "@types/react": "^19.3.0" } }, "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q=="],
|
||||||
|
|
||||||
|
"ansi-regex": ["[email protected]", "", {}, "sha512-KzTVk2tCWAHtYrvvvaP8bJKJq2pVinhLcGEQdtLIYPbmNGNyYe8QwNaTUYQp2J7/vIsUKt5QCqAfUkYyG9DkOw=="],
|
||||||
|
|
||||||
|
"bun": ["[email protected]", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.4.3", "@oven/bun-darwin-x64": "1.4.3", "@oven/bun-freebsd-aarch64": "1.4.3", "@oven/bun-freebsd-x64": "1.4.3", "@oven/bun-linux-aarch64": "1.4.3", "@oven/bun-linux-aarch64-android": "1.4.3", "@oven/bun-linux-aarch64-musl": "1.4.3", "@oven/bun-linux-x64": "1.4.3", "@oven/bun-linux-x64-android": "1.4.3", "@oven/bun-linux-x64-musl": "1.4.3", "@oven/bun-windows-aarch64": "1.4.3", "@oven/bun-windows-x64": "1.4.3" }, "os": [ "!aix", "!sunos", "!openbsd", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-EvQQ2dUJlixjKgJjeXZvdsHUWIKnVfYufrQNDHa++0YM4XksQ4GtGofEdRnQSiyNp/yUWiX6SlqS8zugZCo+2A=="],
|
||||||
|
|
||||||
|
"bun-plugin-tailwind": ["[email protected]", "", { "peerDependencies": { "bun": ">=1.0.0" } }, "sha512-41jNC1tZRSK3s1o7pTNrLuQG8kL/0vR/JgiTmZAJ1eHwe0w5j6HFPKeqEk0WAD13jfrUC7+ULuewFBBCoADPpg=="],
|
||||||
|
|
||||||
|
"bun-types": ["[email protected]", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
||||||
|
|
||||||
|
"chalk": ["[email protected]", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||||
|
|
||||||
|
"chokidar": ["[email protected]", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
||||||
|
|
||||||
|
"cli-cursor": ["[email protected]", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
|
||||||
|
|
||||||
|
"cli-spinners": ["[email protected]", "", {}, "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw=="],
|
||||||
|
|
||||||
|
"commander": ["[email protected]", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
|
||||||
|
|
||||||
|
"csstype": ["[email protected]", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||||
|
|
||||||
|
"detect-libc": ["[email protected]", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||||
|
|
||||||
|
"enhanced-resolve": ["[email protected]", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw=="],
|
||||||
|
|
||||||
|
"esbuild": ["[email protected]", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.2", "@esbuild/android-arm": "0.28.2", "@esbuild/android-arm64": "0.28.2", "@esbuild/android-x64": "0.28.2", "@esbuild/darwin-arm64": "0.28.2", "@esbuild/darwin-x64": "0.28.2", "@esbuild/freebsd-arm64": "0.28.2", "@esbuild/freebsd-x64": "0.28.2", "@esbuild/linux-arm": "0.28.2", "@esbuild/linux-arm64": "0.28.2", "@esbuild/linux-ia32": "0.28.2", "@esbuild/linux-loong64": "0.28.2", "@esbuild/linux-mips64el": "0.28.2", "@esbuild/linux-ppc64": "0.28.2", "@esbuild/linux-riscv64": "0.28.2", "@esbuild/linux-s390x": "0.28.2", "@esbuild/linux-x64": "0.28.2", "@esbuild/netbsd-arm64": "0.28.2", "@esbuild/netbsd-x64": "0.28.2", "@esbuild/openbsd-arm64": "0.28.2", "@esbuild/openbsd-x64": "0.28.2", "@esbuild/openharmony-arm64": "0.28.2", "@esbuild/sunos-x64": "0.28.2", "@esbuild/win32-arm64": "0.28.2", "@esbuild/win32-ia32": "0.28.2", "@esbuild/win32-x64": "0.28.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA=="],
|
||||||
|
|
||||||
|
"get-east-asian-width": ["[email protected]", "", {}, "sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q=="],
|
||||||
|
|
||||||
|
"graceful-fs": ["[email protected]", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||||
|
|
||||||
|
"is-interactive": ["[email protected]", "", {}, "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ=="],
|
||||||
|
|
||||||
|
"is-unicode-supported": ["[email protected]", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
|
||||||
|
|
||||||
|
"jiti": ["[email protected]", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
|
||||||
|
|
||||||
|
"lightningcss": ["[email protected]", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
|
||||||
|
|
||||||
|
"lightningcss-android-arm64": ["[email protected]", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
|
||||||
|
|
||||||
|
"lightningcss-darwin-arm64": ["[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
|
||||||
|
|
||||||
|
"lightningcss-darwin-x64": ["[email protected]", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
|
||||||
|
|
||||||
|
"lightningcss-freebsd-x64": ["[email protected]", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-arm-gnueabihf": ["[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-arm64-gnu": ["[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-arm64-musl": ["[email protected]", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-x64-gnu": ["[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
|
||||||
|
|
||||||
|
"lightningcss-linux-x64-musl": ["[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
|
||||||
|
|
||||||
|
"lightningcss-wasm": ["[email protected]", "", { "dependencies": { "napi-wasm": "^1.0.1" } }, "sha512-OLAtqEyInBSVWjPrTjpLzcZUMUHO0q+2PFBXKr86nxZOu0P38givj/ZMtRaZ0d38pMTb9wQx+LtaLtHclv+sEA=="],
|
||||||
|
|
||||||
|
"lightningcss-win32-arm64-msvc": ["[email protected]", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
|
||||||
|
|
||||||
|
"lightningcss-win32-x64-msvc": ["[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
|
||||||
|
|
||||||
|
"lodash": ["[email protected]", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="],
|
||||||
|
|
||||||
|
"log-symbols": ["[email protected]", "", { "dependencies": { "is-unicode-supported": "^2.0.0", "yoctocolors": "^2.1.1" } }, "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg=="],
|
||||||
|
|
||||||
|
"lucide-react": ["[email protected]", "", { "peerDependencies": { "@types/react": "*", "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-5qgMyyCPU6OvnKpLS2t693+ZtiqA8oaSweWoUows0BLFKFf6V75m4+CptpmohZHpD5IuOVIBXHBBiv2vpLJmtw=="],
|
||||||
|
|
||||||
|
"magic-string": ["[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||||
|
|
||||||
|
"mimic-function": ["[email protected]", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
|
||||||
|
|
||||||
|
"nanoid": ["[email protected]", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ=="],
|
||||||
|
|
||||||
|
"onetime": ["[email protected]", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
|
||||||
|
|
||||||
|
"ora": ["[email protected]", "", { "dependencies": { "chalk": "^5.6.2", "cli-cursor": "^5.0.0", "cli-spinners": "^3.2.0", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.1.0", "log-symbols": "^7.0.1", "stdin-discarder": "^0.3.2", "string-width": "^8.1.0" } }, "sha512-6VlU9MLXbjVQD04AZCMX28hVtA5bUoadvUqO76MUCVA0ilwJbMiHsITRPfyVm6p/BC0Av/BXMujx39WCe1LEqw=="],
|
||||||
|
|
||||||
|
"picocolors": ["[email protected]", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||||
|
|
||||||
|
"postcss": ["[email protected]", "", { "dependencies": { "nanoid": "^3.3.20", "picocolors": "^1.1.1", "source-map-js": "^1.2.2" } }, "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg=="],
|
||||||
|
|
||||||
|
"react": ["[email protected]", "", {}, "sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw=="],
|
||||||
|
|
||||||
|
"react-dom": ["[email protected]", "", { "dependencies": { "scheduler": "^0.28.0" }, "peerDependencies": { "react": "^19.3.1" } }, "sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ=="],
|
||||||
|
|
||||||
|
"readdirp": ["[email protected]", "", {}, "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA=="],
|
||||||
|
|
||||||
|
"restore-cursor": ["[email protected]", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
|
||||||
|
|
||||||
|
"scheduler": ["[email protected]", "", {}, "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw=="],
|
||||||
|
|
||||||
|
"signal-exit": ["[email protected]", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||||
|
|
||||||
|
"source-map-js": ["[email protected]", "", {}, "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw=="],
|
||||||
|
|
||||||
|
"stdin-discarder": ["[email protected]", "", {}, "sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A=="],
|
||||||
|
|
||||||
|
"string-width": ["[email protected]", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-ZbmZM0JCihQN91dWnxoipT2KOEyHqEyfRXUyjuRhW8b/xnqPDoq4gWEVApTVa9db2wN8mmoikgFBbjh71+cGeQ=="],
|
||||||
|
|
||||||
|
"strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
|
||||||
|
|
||||||
|
"tailwindcss": ["[email protected]", "", {}, "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ=="],
|
||||||
|
|
||||||
|
"tapable": ["[email protected]", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="],
|
||||||
|
|
||||||
|
"typescript": ["[email protected]", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||||
|
|
||||||
|
"undici-types": ["[email protected]", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
||||||
|
|
||||||
|
"uuid": ["[email protected]", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-Sc5u1VVln93y1Xnz4R+CunOZc7R9LUcTmhj7Rc7Eg3HVcf7rVn2FOoa812fe0W8p3jD7xeoGSYbfqCGGH2B6xw=="],
|
||||||
|
|
||||||
|
"yoctocolors": ["[email protected]", "", {}, "sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/[email protected]", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.3", "tslib": "^2.4.0" }, "bundled": true }, "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/[email protected]", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" }, "bundled": true }, "sha512-HshSvXzmBxNzqejd3k/KemgQ3hdREUYFRRrgxOZ+gPljDIsO2SDZrVZMd4Z/HsxudQbE62OVyVFE8z9BCurfjA=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A=="],
|
||||||
|
|
||||||
|
"@tailwindcss/oxide-wasm32-wasi/tslib": ["[email protected]", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
|
"bun-types/@types/node": ["@types/[email protected]", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg=="],
|
||||||
|
|
||||||
|
"lightningcss-wasm/napi-wasm": ["[email protected]", "", { "bundled": true }, "sha512-h/4nMGsHjZDCYmQVNODIrYACVJ+I9KItbG+0si6W/jSjdA9JbWDoU4LLeMXVcEQGHjttI2tuXqDrbGF7qkUHHg=="],
|
||||||
|
|
||||||
|
"bun-types/@types/node/undici-types": ["[email protected]", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import type { BunextConfig } from "@moduletrace/bunext/types";
|
||||||
|
|
||||||
|
const config: BunextConfig = {
|
||||||
|
port: 7010,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "@moduletrace/twui-dev",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "bunx bunext dev",
|
||||||
|
"build": "bunx bunext build",
|
||||||
|
"start": "bunx bunext start",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@moduletrace/bunext": "^1.3.4",
|
||||||
|
"lucide-react": "^1.59.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import type { PropsWithChildren, ReactNode } from "react";
|
||||||
|
import H2 from "@moduletrace/twui/layout/H2";
|
||||||
|
import P from "@moduletrace/twui/layout/P";
|
||||||
|
|
||||||
|
type Props = PropsWithChildren<{
|
||||||
|
title: string;
|
||||||
|
description?: ReactNode;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A consistently spaced block for the demo pages.
|
||||||
|
*/
|
||||||
|
export default function DemoSection({ title, description, children }: Props) {
|
||||||
|
return (
|
||||||
|
<section className="w-full border-b border-slate-200 border-solid pb-10 dark:border-white/10">
|
||||||
|
<H2>{title}</H2>
|
||||||
|
{description ? (
|
||||||
|
<P className="!pt-0 text-sm opacity-60">{description}</P>
|
||||||
|
) : null}
|
||||||
|
<div className="mt-4 w-full">{children}</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import type { BunextRootComponentProps } from "@moduletrace/bunext/types";
|
||||||
|
import ColorSchemeSelector from "@moduletrace/twui/elements/ColorSchemeSelector";
|
||||||
|
import "../styles/global.css";
|
||||||
|
|
||||||
|
const NAV = [
|
||||||
|
{ href: "/", label: "Overview" },
|
||||||
|
{ href: "/buttons", label: "Buttons" },
|
||||||
|
{ href: "/forms", label: "Forms" },
|
||||||
|
{ href: "/elements", label: "Elements" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Root({ children }: BunextRootComponentProps) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-background-light text-foreground-light dark:bg-background-dark dark:text-foreground-dark">
|
||||||
|
<header className="sticky top-0 z-40 border-b border-slate-200 border-solid bg-background-light/90 backdrop-blur dark:border-white/10 dark:bg-background-dark/90">
|
||||||
|
<div className="mx-auto flex w-full max-w-container flex-wrap items-center justify-between gap-4 px-4 py-3 sm:px-10">
|
||||||
|
<div className="flex flex-wrap items-center gap-6">
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
className="text-lg font-bold no-underline"
|
||||||
|
>
|
||||||
|
twui
|
||||||
|
</a>
|
||||||
|
<nav className="flex flex-wrap items-center gap-4 text-sm">
|
||||||
|
{NAV.map((item) => (
|
||||||
|
<a
|
||||||
|
key={item.href}
|
||||||
|
href={item.href}
|
||||||
|
className="opacity-60 no-underline transition-opacity hover:opacity-100"
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<ColorSchemeSelector />
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main className="mx-auto w-full max-w-container px-4 py-10 sm:px-10">
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer className="mx-auto w-full max-w-container px-4 pb-10 text-xs opacity-50 sm:px-10">
|
||||||
|
Local playground for @moduletrace/twui. Edit anything in{" "}
|
||||||
|
<code>src/</code> and the page updates without a build step.
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { ArrowRight, Download, Plus, Sparkles, Trash2 } from "lucide-react";
|
||||||
|
import Button, {
|
||||||
|
TWUIButtonColors,
|
||||||
|
TWUIButtonSizes,
|
||||||
|
TWUIButtonVariants,
|
||||||
|
} from "@moduletrace/twui/layout/Button";
|
||||||
|
import Divider from "@moduletrace/twui/layout/Divider";
|
||||||
|
import H1 from "@moduletrace/twui/layout/H1";
|
||||||
|
import P from "@moduletrace/twui/layout/P";
|
||||||
|
import Row from "@moduletrace/twui/layout/Row";
|
||||||
|
import Span from "@moduletrace/twui/layout/Span";
|
||||||
|
import Stack from "@moduletrace/twui/layout/Stack";
|
||||||
|
import DemoSection from "../(components)/DemoSection";
|
||||||
|
|
||||||
|
export default function ButtonsPage() {
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
setSaving(true);
|
||||||
|
window.setTimeout(() => setSaving(false), 1500);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack className="w-full gap-10">
|
||||||
|
<Stack className="gap-2">
|
||||||
|
<H1 className="!mb-0">Buttons</H1>
|
||||||
|
<P className="!pt-0 opacity-70">
|
||||||
|
Every color is paired with the three variants. Classes are
|
||||||
|
merged with <code>twMerge</code>, so a consumer{" "}
|
||||||
|
<code>className</code> always wins.
|
||||||
|
</P>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{TWUIButtonVariants.map((variant) => (
|
||||||
|
<DemoSection
|
||||||
|
key={variant}
|
||||||
|
title={`${variant} variant`}
|
||||||
|
description={`variant="${variant}" across TWUIButtonColors.`}
|
||||||
|
>
|
||||||
|
<Row className="gap-3">
|
||||||
|
{TWUIButtonColors.map((color) => (
|
||||||
|
<Button
|
||||||
|
key={color}
|
||||||
|
title={`${color} ${variant} button`}
|
||||||
|
color={color}
|
||||||
|
variant={variant}
|
||||||
|
>
|
||||||
|
{color}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
</DemoSection>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<DemoSection
|
||||||
|
title="Sizes"
|
||||||
|
description="smaller, small, normal, large and larger."
|
||||||
|
>
|
||||||
|
<Row className="items-center gap-3">
|
||||||
|
{TWUIButtonSizes.map((size) => (
|
||||||
|
<Button
|
||||||
|
key={size}
|
||||||
|
title={`${size} button`}
|
||||||
|
size={size}
|
||||||
|
>
|
||||||
|
{size}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
</DemoSection>
|
||||||
|
|
||||||
|
<DemoSection
|
||||||
|
title="Icons and loading"
|
||||||
|
description="Icons accept a lucide name or a React element. Loading keeps the label in place and swaps in a spinner."
|
||||||
|
>
|
||||||
|
<Row className="gap-3">
|
||||||
|
<Button
|
||||||
|
title="Create button"
|
||||||
|
beforeIcon={<Plus size={16} />}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
title="Sparkle button"
|
||||||
|
color="secondary"
|
||||||
|
afterIcon={<Sparkles size={16} />}
|
||||||
|
>
|
||||||
|
Enhance
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
title="Download button"
|
||||||
|
color="success"
|
||||||
|
after_icon_name="Download"
|
||||||
|
>
|
||||||
|
Download
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
title="Delete button"
|
||||||
|
color="error"
|
||||||
|
variant="outlined"
|
||||||
|
beforeIcon={<Trash2 size={16} />}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
title="Saving button"
|
||||||
|
color="primary"
|
||||||
|
loading={saving}
|
||||||
|
onClick={save}
|
||||||
|
>
|
||||||
|
{saving ? "Saving" : "Save changes"}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
title="Disabled button"
|
||||||
|
disabled
|
||||||
|
color="gray"
|
||||||
|
variant="outlined"
|
||||||
|
>
|
||||||
|
Disabled
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
</DemoSection>
|
||||||
|
|
||||||
|
<DemoSection
|
||||||
|
title="Links and arrows"
|
||||||
|
description="Passing href renders an anchor around the button markup."
|
||||||
|
>
|
||||||
|
<Stack className="gap-3">
|
||||||
|
<Row className="gap-3">
|
||||||
|
<Button
|
||||||
|
title="Open buttons page"
|
||||||
|
href="#"
|
||||||
|
afterIcon={<ArrowRight size={16} />}
|
||||||
|
>
|
||||||
|
Anchor button
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
title="Download from link"
|
||||||
|
href="#"
|
||||||
|
color="secondary"
|
||||||
|
variant="outlined"
|
||||||
|
before_icon_name="Download"
|
||||||
|
>
|
||||||
|
Outlined link
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
<Divider />
|
||||||
|
<Row className="gap-2">
|
||||||
|
<Span variant="faded" size="small">
|
||||||
|
Sizes map to the lucide icon scale too:
|
||||||
|
</Span>
|
||||||
|
<Button
|
||||||
|
title="Smaller download"
|
||||||
|
size="smaller"
|
||||||
|
variant="ghost"
|
||||||
|
beforeIcon={<Download size={14} />}
|
||||||
|
>
|
||||||
|
Smaller
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
title="Larger arrow"
|
||||||
|
size="larger"
|
||||||
|
variant="ghost"
|
||||||
|
afterIcon={<ArrowRight size={20} />}
|
||||||
|
>
|
||||||
|
Larger
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
</Stack>
|
||||||
|
</DemoSection>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,376 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import {
|
||||||
|
ArrowUpRight,
|
||||||
|
Bell,
|
||||||
|
Box,
|
||||||
|
CheckCircle2,
|
||||||
|
ChevronDown,
|
||||||
|
Inbox,
|
||||||
|
} from "lucide-react";
|
||||||
|
import Border from "@moduletrace/twui/elements/Border";
|
||||||
|
import Breadcrumbs from "@moduletrace/twui/elements/Breadcrumbs";
|
||||||
|
import Card from "@moduletrace/twui/elements/Card";
|
||||||
|
import CheckBulletPoints from "@moduletrace/twui/elements/CheckBulletPoints";
|
||||||
|
import CodeBlock from "@moduletrace/twui/elements/CodeBlock";
|
||||||
|
import Dropdown from "@moduletrace/twui/elements/Dropdown";
|
||||||
|
import EmptyContent from "@moduletrace/twui/elements/EmptyContent";
|
||||||
|
import Loading from "@moduletrace/twui/elements/Loading";
|
||||||
|
import Modal from "@moduletrace/twui/elements/Modal";
|
||||||
|
import Pagination from "@moduletrace/twui/elements/Pagination";
|
||||||
|
import Paper from "@moduletrace/twui/elements/Paper";
|
||||||
|
import Popover from "@moduletrace/twui/elements/Popover";
|
||||||
|
import StarRating from "@moduletrace/twui/elements/StarRating";
|
||||||
|
import Table from "@moduletrace/twui/elements/Table";
|
||||||
|
import Tabs from "@moduletrace/twui/elements/Tabs";
|
||||||
|
import Tag from "@moduletrace/twui/elements/Tag";
|
||||||
|
import Toast from "@moduletrace/twui/elements/Toast";
|
||||||
|
import Button from "@moduletrace/twui/layout/Button";
|
||||||
|
import Divider from "@moduletrace/twui/layout/Divider";
|
||||||
|
import H1 from "@moduletrace/twui/layout/H1";
|
||||||
|
import P from "@moduletrace/twui/layout/P";
|
||||||
|
import Row from "@moduletrace/twui/layout/Row";
|
||||||
|
import Span from "@moduletrace/twui/layout/Span";
|
||||||
|
import Stack from "@moduletrace/twui/layout/Stack";
|
||||||
|
import { TWUIButtonColors } from "@moduletrace/twui/layout/Button";
|
||||||
|
import DemoSection from "../(components)/DemoSection";
|
||||||
|
|
||||||
|
const TABLE_DATA = [
|
||||||
|
{ component: "Button", folder: "layout", status: "stable" },
|
||||||
|
{ component: "Input", folder: "form", status: "stable" },
|
||||||
|
{ component: "Tabs", folder: "elements", status: "stable" },
|
||||||
|
{ component: "AceEditor", folder: "editors", status: "optional peer" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const TAG_COLORS = TWUIButtonColors.filter((color) =>
|
||||||
|
["primary", "secondary", "success", "error", "warning", "gray"].includes(
|
||||||
|
color,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function ElementsPage() {
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [toastOpen, setToastOpen] = useState(false);
|
||||||
|
const [rating, setRating] = useState(3);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack className="w-full gap-10">
|
||||||
|
<Stack className="gap-2">
|
||||||
|
<H1 className="!mb-0">Elements</H1>
|
||||||
|
<P className="!pt-0 opacity-70">
|
||||||
|
Composite components. Overlays render into portals appended
|
||||||
|
to <code>document.body</code>, so they escape any stacking
|
||||||
|
context in the page.
|
||||||
|
</P>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<DemoSection
|
||||||
|
title="Cards, tags and surfaces"
|
||||||
|
description="Card is a link when href is passed. Tag reuses the button color palette."
|
||||||
|
>
|
||||||
|
<Stack className="w-full gap-4">
|
||||||
|
<Row className="items-stretch gap-4">
|
||||||
|
<Card className="w-full max-w-sm flex-col items-start gap-2">
|
||||||
|
<Span className="font-semibold">Plain card</Span>
|
||||||
|
<Span size="small" variant="faded">
|
||||||
|
A bordered surface with no link behaviour.
|
||||||
|
</Span>
|
||||||
|
</Card>
|
||||||
|
<Card
|
||||||
|
href="#"
|
||||||
|
className="w-full max-w-sm flex-col items-start gap-2"
|
||||||
|
>
|
||||||
|
<Span className="font-semibold">
|
||||||
|
Linked card
|
||||||
|
<ArrowUpRight
|
||||||
|
size={14}
|
||||||
|
className="ml-1 inline-block"
|
||||||
|
/>
|
||||||
|
</Span>
|
||||||
|
<Span size="small" variant="faded">
|
||||||
|
The whole card is clickable.
|
||||||
|
</Span>
|
||||||
|
</Card>
|
||||||
|
<Paper className="w-full max-w-sm">
|
||||||
|
<Span className="font-semibold">Paper</Span>
|
||||||
|
<Span size="small" variant="faded">
|
||||||
|
Used for grouped content and forms.
|
||||||
|
</Span>
|
||||||
|
</Paper>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Row className="gap-3">
|
||||||
|
{TAG_COLORS.map((color) => (
|
||||||
|
<Tag key={color} color={color}>
|
||||||
|
{color}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
{TAG_COLORS.map((color) => (
|
||||||
|
<Tag
|
||||||
|
key={`${color}-outlined`}
|
||||||
|
color={color}
|
||||||
|
variant="outlined"
|
||||||
|
>
|
||||||
|
{color}
|
||||||
|
</Tag>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Border className="w-full">
|
||||||
|
<Row className="w-full justify-between">
|
||||||
|
<Span size="small">
|
||||||
|
Border with default spacing
|
||||||
|
</Span>
|
||||||
|
<Tag color="gray" variant="ghost">
|
||||||
|
variant matrix
|
||||||
|
</Tag>
|
||||||
|
</Row>
|
||||||
|
</Border>
|
||||||
|
</Stack>
|
||||||
|
</DemoSection>
|
||||||
|
|
||||||
|
<DemoSection
|
||||||
|
title="Tabs"
|
||||||
|
description="Tab content stays mounted; switching is client-side state."
|
||||||
|
>
|
||||||
|
<Tabs
|
||||||
|
className="w-full"
|
||||||
|
tabsContentArray={[
|
||||||
|
{
|
||||||
|
title: "Overview",
|
||||||
|
content: (
|
||||||
|
<P className="!pt-2">
|
||||||
|
The first tab is active by default. Tab
|
||||||
|
values are slugified from the title unless
|
||||||
|
you provide one.
|
||||||
|
</P>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Details",
|
||||||
|
content: (
|
||||||
|
<CheckBulletPoints
|
||||||
|
className="pt-2"
|
||||||
|
bulletPoints={[
|
||||||
|
{
|
||||||
|
title: "Accepts an array of tabs",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Supports a default value",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Can sync to the URL hash",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Disabled-looking",
|
||||||
|
content: (
|
||||||
|
<P className="!pt-2 opacity-60">
|
||||||
|
This is just a third tab; there is no
|
||||||
|
disabled state in the current API.
|
||||||
|
</P>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</DemoSection>
|
||||||
|
|
||||||
|
<DemoSection
|
||||||
|
title="Table and pagination"
|
||||||
|
description="Table derives columns from the keys of the first row."
|
||||||
|
>
|
||||||
|
<Stack className="w-full gap-4">
|
||||||
|
<Border className="w-full overflow-hidden !p-0">
|
||||||
|
<Table data={TABLE_DATA} />
|
||||||
|
</Border>
|
||||||
|
<Pagination
|
||||||
|
page={page}
|
||||||
|
setPage={setPage}
|
||||||
|
count={42}
|
||||||
|
limit={5}
|
||||||
|
className="justify-center"
|
||||||
|
/>
|
||||||
|
<Span size="small" variant="faded">
|
||||||
|
Page {page} of {Math.ceil(42 / 5)}
|
||||||
|
</Span>
|
||||||
|
</Stack>
|
||||||
|
</DemoSection>
|
||||||
|
|
||||||
|
<DemoSection
|
||||||
|
title="Overlays"
|
||||||
|
description="Modal and Popover share the same implementation; Popover renders in place while Modal is a full dialog."
|
||||||
|
>
|
||||||
|
<Row className="items-start gap-4">
|
||||||
|
<OverlayDemo />
|
||||||
|
</Row>
|
||||||
|
</DemoSection>
|
||||||
|
|
||||||
|
<DemoSection
|
||||||
|
title="Dropdown"
|
||||||
|
description="A lightweight positioned panel with click/hover targets."
|
||||||
|
>
|
||||||
|
<Dropdown
|
||||||
|
target={
|
||||||
|
<Button
|
||||||
|
title="Open dropdown"
|
||||||
|
color="gray"
|
||||||
|
variant="outlined"
|
||||||
|
afterIcon={<ChevronDown size={16} />}
|
||||||
|
>
|
||||||
|
Actions
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
contentWrapperProps={{ className: "min-w-48" }}
|
||||||
|
>
|
||||||
|
<Stack className="gap-1">
|
||||||
|
<Span size="small">Edit item</Span>
|
||||||
|
<Span size="small">Duplicate item</Span>
|
||||||
|
<Divider />
|
||||||
|
<Span size="small" className="text-error">
|
||||||
|
Delete item
|
||||||
|
</Span>
|
||||||
|
</Stack>
|
||||||
|
</Dropdown>
|
||||||
|
</DemoSection>
|
||||||
|
|
||||||
|
<DemoSection
|
||||||
|
title="Toasts"
|
||||||
|
description="Toast portals into a body-level root and auto-closes after four seconds."
|
||||||
|
>
|
||||||
|
<Row className="gap-3">
|
||||||
|
<Button
|
||||||
|
title="Show success toast"
|
||||||
|
color="success"
|
||||||
|
onClick={() => setToastOpen(true)}
|
||||||
|
beforeIcon={<Bell size={16} />}
|
||||||
|
>
|
||||||
|
Show toast
|
||||||
|
</Button>
|
||||||
|
<Span size="small" variant="faded">
|
||||||
|
Hovering the toast pauses the timer.
|
||||||
|
</Span>
|
||||||
|
</Row>
|
||||||
|
<Toast
|
||||||
|
open={toastOpen}
|
||||||
|
setOpen={setToastOpen}
|
||||||
|
color="success"
|
||||||
|
>
|
||||||
|
Build finished successfully.
|
||||||
|
</Toast>
|
||||||
|
</DemoSection>
|
||||||
|
|
||||||
|
<DemoSection
|
||||||
|
title="Status and feedback"
|
||||||
|
description="Loading, empty states, code blocks and ratings."
|
||||||
|
>
|
||||||
|
<div className="grid w-full gap-6 md:grid-cols-2">
|
||||||
|
<Stack className="w-full gap-3">
|
||||||
|
<Span className="font-semibold">Loading</Span>
|
||||||
|
<Row className="gap-4">
|
||||||
|
<Loading size="smaller" />
|
||||||
|
<Loading size="small" />
|
||||||
|
<Loading size="normal" />
|
||||||
|
<Loading size="large" />
|
||||||
|
</Row>
|
||||||
|
<Span className="font-semibold">Star rating</Span>
|
||||||
|
<StarRating
|
||||||
|
value={rating}
|
||||||
|
allowRating
|
||||||
|
changeHandler={setRating}
|
||||||
|
/>
|
||||||
|
<Span size="small" variant="faded">
|
||||||
|
Current rating: {rating}
|
||||||
|
</Span>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<EmptyContent
|
||||||
|
title="No results — EmptyContent takes an optional link and icon."
|
||||||
|
icon={<Inbox size={24} />}
|
||||||
|
url="#"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Stack className="mt-6 w-full gap-2">
|
||||||
|
<Span className="font-semibold">Code block</Span>
|
||||||
|
<CodeBlock data-title="install">
|
||||||
|
{`bun add @moduletrace/twui`}
|
||||||
|
</CodeBlock>
|
||||||
|
</Stack>
|
||||||
|
</DemoSection>
|
||||||
|
|
||||||
|
<DemoSection
|
||||||
|
title="Breadcrumbs"
|
||||||
|
description="Infer crumbs from the current path, or pass pageUrl to render deterministically."
|
||||||
|
>
|
||||||
|
<Stack className="w-full gap-4">
|
||||||
|
<Breadcrumbs pageUrl="/elements/overlays/modal" />
|
||||||
|
<Row className="gap-2">
|
||||||
|
<CheckCircle2 size={16} className="text-success" />
|
||||||
|
<Span size="small" variant="faded">
|
||||||
|
The divider and back button are configurable.
|
||||||
|
</Span>
|
||||||
|
</Row>
|
||||||
|
</Stack>
|
||||||
|
</DemoSection>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function OverlayDemo() {
|
||||||
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
title="Open modal"
|
||||||
|
onClick={() => setModalOpen(true)}
|
||||||
|
beforeIcon={<Box size={16} />}
|
||||||
|
>
|
||||||
|
Open modal
|
||||||
|
</Button>
|
||||||
|
<Modal
|
||||||
|
open={modalOpen}
|
||||||
|
setOpen={setModalOpen}
|
||||||
|
target={null}
|
||||||
|
no_cancel_button
|
||||||
|
>
|
||||||
|
<Stack className="w-full gap-3">
|
||||||
|
<Span className="text-lg font-semibold">Modal content</Span>
|
||||||
|
<P className="!pt-0">
|
||||||
|
The modal is controlled by the <code>open</code> and{" "}
|
||||||
|
<code>setOpen</code> props. Without a target it renders
|
||||||
|
nothing until you set it.
|
||||||
|
</P>
|
||||||
|
<Row className="justify-end">
|
||||||
|
<Button
|
||||||
|
title="Close modal"
|
||||||
|
color="gray"
|
||||||
|
variant="outlined"
|
||||||
|
onClick={() => setModalOpen(false)}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<Popover
|
||||||
|
trigger="hover"
|
||||||
|
target={
|
||||||
|
<Tag color="secondary" variant="outlined">
|
||||||
|
Hover for a popover
|
||||||
|
</Tag>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Stack className="w-56 gap-1">
|
||||||
|
<Span className="font-semibold">Popover</Span>
|
||||||
|
<Span size="small" variant="faded">
|
||||||
|
Same props as Modal, plus hover trigger support.
|
||||||
|
</Span>
|
||||||
|
</Stack>
|
||||||
|
</Popover>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { AtSign, Info, Mail, Search } from "lucide-react";
|
||||||
|
import Button from "@moduletrace/twui/layout/Button";
|
||||||
|
import H1 from "@moduletrace/twui/layout/H1";
|
||||||
|
import P from "@moduletrace/twui/layout/P";
|
||||||
|
import Row from "@moduletrace/twui/layout/Row";
|
||||||
|
import Span from "@moduletrace/twui/layout/Span";
|
||||||
|
import Stack from "@moduletrace/twui/layout/Stack";
|
||||||
|
import Checkbox from "@moduletrace/twui/form/Checkbox";
|
||||||
|
import Form from "@moduletrace/twui/form/Form";
|
||||||
|
import Input from "@moduletrace/twui/form/Input";
|
||||||
|
import Radios from "@moduletrace/twui/form/Radios";
|
||||||
|
import SearchSelect from "@moduletrace/twui/form/SearchSelect";
|
||||||
|
import Select from "@moduletrace/twui/form/Select";
|
||||||
|
import Textarea from "@moduletrace/twui/form/Textarea";
|
||||||
|
import Toggle from "@moduletrace/twui/elements/Toggle";
|
||||||
|
import DemoSection from "../(components)/DemoSection";
|
||||||
|
|
||||||
|
const FRUIT_OPTIONS = [
|
||||||
|
{ value: "apple", title: "Apple" },
|
||||||
|
{ value: "banana", title: "Banana" },
|
||||||
|
{ value: "cherry", title: "Cherry" },
|
||||||
|
{ value: "dragonfruit", title: "Dragon fruit" },
|
||||||
|
{ value: "elderberry", title: "Elderberry" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const ROLE_OPTIONS = [
|
||||||
|
{ value: "viewer", title: "Viewer", default: true },
|
||||||
|
{ value: "editor", title: "Editor" },
|
||||||
|
{ value: "admin", title: "Admin" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function FormsPage() {
|
||||||
|
const [notifications, setNotifications] = useState(true);
|
||||||
|
const [autoSave, setAutoSave] = useState(true);
|
||||||
|
const [submitted, setSubmitted] = useState<Record<string, unknown> | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack className="w-full gap-10">
|
||||||
|
<Stack className="gap-2">
|
||||||
|
<H1 className="!mb-0">Forms</H1>
|
||||||
|
<P className="!pt-0 opacity-70">
|
||||||
|
The input family shares one shell: label, optional info,
|
||||||
|
validation state, clear button and prefix/suffix slots.
|
||||||
|
</P>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<DemoSection
|
||||||
|
title="Inputs"
|
||||||
|
description="Variants, labels, icons, prefixes and suffixes."
|
||||||
|
>
|
||||||
|
<div className="grid w-full gap-4 md:grid-cols-2">
|
||||||
|
<Input label="Full name" placeholder="Ada Lovelace" />
|
||||||
|
<Input
|
||||||
|
label="Email"
|
||||||
|
type="email"
|
||||||
|
placeholder="[email protected]"
|
||||||
|
prefix={<AtSign size={16} />}
|
||||||
|
info="We never share your address."
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Password"
|
||||||
|
type="password"
|
||||||
|
defaultValue="hunter2"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Search"
|
||||||
|
placeholder="Search components..."
|
||||||
|
prefix={<Search size={16} />}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Amount"
|
||||||
|
placeholder="0.00"
|
||||||
|
prefix="$"
|
||||||
|
suffix="USD"
|
||||||
|
inputMode="decimal"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Error state"
|
||||||
|
variant="error"
|
||||||
|
defaultValue="not-an-email"
|
||||||
|
info="This field is marked invalid."
|
||||||
|
clearInputProps={{ className: "hidden" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DemoSection>
|
||||||
|
|
||||||
|
<DemoSection
|
||||||
|
title="Textarea and number input"
|
||||||
|
description="Textarea is Input with istextarea. Number buttons are available through NumberInputButtons."
|
||||||
|
>
|
||||||
|
<div className="grid w-full gap-4 md:grid-cols-2">
|
||||||
|
<Textarea
|
||||||
|
label="Notes"
|
||||||
|
placeholder="Anything we should know?"
|
||||||
|
rows={4}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Quantity"
|
||||||
|
type="number"
|
||||||
|
defaultValue={3}
|
||||||
|
min={0}
|
||||||
|
max={10}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DemoSection>
|
||||||
|
|
||||||
|
<DemoSection
|
||||||
|
title="Selects"
|
||||||
|
description="Select for short lists; SearchSelect adds filtering and multi-select."
|
||||||
|
>
|
||||||
|
<div className="grid w-full gap-4 md:grid-cols-2">
|
||||||
|
<Select label="Role" options={ROLE_OPTIONS} />
|
||||||
|
<SearchSelect
|
||||||
|
label="Favourite fruit"
|
||||||
|
options={FRUIT_OPTIONS}
|
||||||
|
placeholder="Pick a fruit"
|
||||||
|
/>
|
||||||
|
<SearchSelect
|
||||||
|
label="Fruits (multiple)"
|
||||||
|
options={FRUIT_OPTIONS}
|
||||||
|
defaultValues={["apple", "cherry"]}
|
||||||
|
multiple
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DemoSection>
|
||||||
|
|
||||||
|
<DemoSection
|
||||||
|
title="Toggles and choices"
|
||||||
|
description="Checkbox and Radios render custom markup; Toggle is a pill switch."
|
||||||
|
>
|
||||||
|
<div className="grid w-full items-start gap-6 md:grid-cols-2">
|
||||||
|
<Stack className="gap-4">
|
||||||
|
<Checkbox
|
||||||
|
title="Email notifications"
|
||||||
|
label="Send me an email when a build finishes."
|
||||||
|
defaultChecked
|
||||||
|
changeHandler={setNotifications}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
title="Beta features"
|
||||||
|
label="Opt into experimental components."
|
||||||
|
/>
|
||||||
|
<Row className="gap-3">
|
||||||
|
<Span size="small" variant="faded">
|
||||||
|
Notifications are {notifications ? "on" : "off"}
|
||||||
|
</Span>
|
||||||
|
</Row>
|
||||||
|
<Row className="gap-3">
|
||||||
|
<Toggle
|
||||||
|
default_active={autoSave}
|
||||||
|
changeHandler={setAutoSave}
|
||||||
|
/>
|
||||||
|
<Span size="small" variant="faded">
|
||||||
|
Auto-save is {autoSave ? "on" : "off"}
|
||||||
|
</Span>
|
||||||
|
</Row>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Radios
|
||||||
|
name="Plan"
|
||||||
|
values={[
|
||||||
|
{ value: "free", title: "Free" },
|
||||||
|
{ value: "pro", title: "Pro", default: true },
|
||||||
|
{ value: "team", title: "Team" },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DemoSection>
|
||||||
|
|
||||||
|
<DemoSection
|
||||||
|
title="Form wrapper"
|
||||||
|
description="Form collects FormData on submit. Inputs need a name to appear."
|
||||||
|
>
|
||||||
|
<div className="grid w-full items-start gap-6 md:grid-cols-2">
|
||||||
|
<Form
|
||||||
|
submitHandler={(_event, data) => {
|
||||||
|
setSubmitted(data);
|
||||||
|
}}
|
||||||
|
changeHandler={() => {
|
||||||
|
setSubmitted(null);
|
||||||
|
}}
|
||||||
|
className="gap-4"
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
label="Project name"
|
||||||
|
name="project"
|
||||||
|
placeholder="my-project"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
label="Email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="[email protected]"
|
||||||
|
prefix={<Mail size={16} />}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Textarea
|
||||||
|
label="Description"
|
||||||
|
name="description"
|
||||||
|
rows={3}
|
||||||
|
placeholder="What are you building?"
|
||||||
|
/>
|
||||||
|
<Row className="gap-3">
|
||||||
|
<Button title="Submit form" type="submit">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
title="Reset form"
|
||||||
|
type="reset"
|
||||||
|
color="gray"
|
||||||
|
variant="outlined"
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
</Form>
|
||||||
|
|
||||||
|
<Stack className="w-full gap-2">
|
||||||
|
<Span size="small" variant="faded">
|
||||||
|
submitHandler payload
|
||||||
|
</Span>
|
||||||
|
<pre className="w-full overflow-x-auto rounded-default border border-slate-200 border-solid bg-gray/40 p-4 text-xs dark:border-white/10 dark:bg-white/5">
|
||||||
|
{submitted
|
||||||
|
? JSON.stringify(submitted, null, 2)
|
||||||
|
: "Submit the form to see the collected data."}
|
||||||
|
</pre>
|
||||||
|
<Row className="items-start gap-2">
|
||||||
|
<Info size={16} className="mt-0.5 shrink-0" />
|
||||||
|
<Span size="small" variant="faded">
|
||||||
|
Checkbox and Toggle are div-based and are not
|
||||||
|
part of FormData. Radios do submit a value.
|
||||||
|
</Span>
|
||||||
|
</Row>
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
</DemoSection>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import Card from "@moduletrace/twui/elements/Card";
|
||||||
|
import Tag from "@moduletrace/twui/elements/Tag";
|
||||||
|
import H1 from "@moduletrace/twui/layout/H1";
|
||||||
|
import P from "@moduletrace/twui/layout/P";
|
||||||
|
import Row from "@moduletrace/twui/layout/Row";
|
||||||
|
import Stack from "@moduletrace/twui/layout/Stack";
|
||||||
|
import Span from "@moduletrace/twui/layout/Span";
|
||||||
|
|
||||||
|
const PAGES = [
|
||||||
|
{
|
||||||
|
href: "/buttons",
|
||||||
|
title: "Buttons",
|
||||||
|
description: "Colors, variants, sizes, icons, loading and links.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/forms",
|
||||||
|
title: "Forms",
|
||||||
|
description:
|
||||||
|
"Input, textarea, selects, checkboxes, radios and the Form wrapper.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
href: "/elements",
|
||||||
|
title: "Elements",
|
||||||
|
description:
|
||||||
|
"Cards, tags, tables, tabs, overlays, toasts and status blocks.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const TOKENS = [
|
||||||
|
{ className: "bg-primary", name: "primary" },
|
||||||
|
{ className: "bg-secondary", name: "secondary" },
|
||||||
|
{ className: "bg-accent", name: "accent" },
|
||||||
|
{ className: "bg-gray", name: "gray" },
|
||||||
|
{ className: "bg-success", name: "success" },
|
||||||
|
{ className: "bg-error", name: "error" },
|
||||||
|
{ className: "bg-warning", name: "warning" },
|
||||||
|
{ className: "bg-link", name: "link" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function OverviewPage() {
|
||||||
|
return (
|
||||||
|
<Stack className="w-full gap-10">
|
||||||
|
<Stack className="gap-3">
|
||||||
|
<H1 className="!mb-0">twui development playground</H1>
|
||||||
|
<P className="!pt-0 max-w-3xl opacity-70">
|
||||||
|
This app imports the library straight from{" "}
|
||||||
|
<code>../src</code> through a symlinked path alias, so edits
|
||||||
|
to components hot-reload here without running{" "}
|
||||||
|
<code>tsc --watch</code>. It is a local development tool and
|
||||||
|
is not part of the published package.
|
||||||
|
</P>
|
||||||
|
<Row className="gap-2">
|
||||||
|
<Tag color="success" variant="outlined">
|
||||||
|
bunext
|
||||||
|
</Tag>
|
||||||
|
<Tag color="secondary" variant="outlined">
|
||||||
|
Tailwind v4
|
||||||
|
</Tag>
|
||||||
|
<Tag color="gray" variant="outlined">
|
||||||
|
SSR + HMR
|
||||||
|
</Tag>
|
||||||
|
</Row>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Row className="items-stretch gap-4">
|
||||||
|
{PAGES.map((page) => (
|
||||||
|
<Card
|
||||||
|
key={page.href}
|
||||||
|
href={page.href}
|
||||||
|
className="w-full max-w-sm flex-col items-start gap-2 sm:w-72"
|
||||||
|
>
|
||||||
|
<Span className="text-lg font-semibold">
|
||||||
|
{page.title}
|
||||||
|
</Span>
|
||||||
|
<Span size="small" variant="faded">
|
||||||
|
{page.description}
|
||||||
|
</Span>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<Stack className="gap-3">
|
||||||
|
<H1 className="!mb-0 text-xl">Default theme tokens</H1>
|
||||||
|
<P className="!pt-0 text-sm opacity-60">
|
||||||
|
Swatches read the values defined in{" "}
|
||||||
|
<code>src/base.css</code>. Override them in a consumer's{" "}
|
||||||
|
<code>@theme inline</code> block.
|
||||||
|
</P>
|
||||||
|
<Row className="gap-3">
|
||||||
|
{TOKENS.map((token) => (
|
||||||
|
<Stack key={token.name} className="items-center gap-1">
|
||||||
|
<div
|
||||||
|
className={`h-12 w-16 rounded-default border border-slate-200 border-solid dark:border-white/10 ${token.className}`}
|
||||||
|
/>
|
||||||
|
<Span size="smaller" variant="faded">
|
||||||
|
{token.name}
|
||||||
|
</Span>
|
||||||
|
</Stack>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* Reuse the published base stylesheet so the playground renders with the same
|
||||||
|
* tokens and preflight consumers get. `twui` is a symlink to the library `src`,
|
||||||
|
* so `base.css`'s own `@source "./"` scans the real component sources.
|
||||||
|
*/
|
||||||
|
@import "../../twui/base.css";
|
||||||
|
|
||||||
|
/* Classes used only by the playground pages themselves. */
|
||||||
|
@source "../";
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@moduletrace/twui": ["./twui/index.ts"],
|
||||||
|
"@moduletrace/twui/*": ["./twui/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src", "bunext.config.ts"]
|
||||||
|
}
|
||||||
+3
-1
@@ -38,7 +38,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsc --watch",
|
"dev": "bun run --cwd dev dev",
|
||||||
|
"dev:types": "tsc --watch",
|
||||||
|
"dev:typecheck": "bun run --cwd dev typecheck",
|
||||||
"compile": "rm -rf dist tsconfig.tsbuildinfo && tsc -p tsconfig.json && bun scripts/post-compile.ts",
|
"compile": "rm -rf dist tsconfig.tsbuildinfo && tsc -p tsconfig.json && bun scripts/post-compile.ts",
|
||||||
"typecheck": "tsc -p tsconfig.json --noEmit",
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
||||||
"test": "bun test src",
|
"test": "bun test src",
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ export type TWUISearchSelectProps<
|
|||||||
defaultValues?: KeyType[];
|
defaultValues?: KeyType[];
|
||||||
changeHandler?: (value: KeyType | KeyType[] | "", data?: T | T[]) => void;
|
changeHandler?: (value: KeyType | KeyType[] | "", data?: T | T[]) => void;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
/**
|
||||||
|
* Whether the dropdown should still be open
|
||||||
|
* after a selection is made
|
||||||
|
*/
|
||||||
|
keep_dropdown_open_on_commit?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,6 +69,7 @@ export default function SearchSelect<
|
|||||||
placeholder,
|
placeholder,
|
||||||
multiple = false,
|
multiple = false,
|
||||||
defaultValues,
|
defaultValues,
|
||||||
|
keep_dropdown_open_on_commit,
|
||||||
...props
|
...props
|
||||||
}: TWUISearchSelectProps<KeyType, T>) {
|
}: TWUISearchSelectProps<KeyType, T>) {
|
||||||
const rootRef = React.useRef<HTMLDivElement>(null);
|
const rootRef = React.useRef<HTMLDivElement>(null);
|
||||||
@@ -160,17 +166,18 @@ export default function SearchSelect<
|
|||||||
setSelected(next);
|
setSelected(next);
|
||||||
notify(next);
|
notify(next);
|
||||||
setQuery("");
|
setQuery("");
|
||||||
setOpen(true);
|
|
||||||
setActiveIndex(0);
|
|
||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
} else {
|
} else {
|
||||||
const next = [option];
|
const next = [option];
|
||||||
setSelected(next);
|
setSelected(next);
|
||||||
setQuery(getOptionLabel(option));
|
setQuery(getOptionLabel(option));
|
||||||
setOpen(true);
|
|
||||||
setActiveIndex(0);
|
|
||||||
notify(next);
|
notify(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const commit_open_status = keep_dropdown_open_on_commit || false;
|
||||||
|
|
||||||
|
setActiveIndex(0);
|
||||||
|
setOpen(commit_open_status);
|
||||||
},
|
},
|
||||||
[multiple, notify, selected],
|
[multiple, notify, selected],
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user