diff --git a/.prettierrc b/.prettierrc index d14d6a4e5..0dcb4b4d6 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1 +1,5 @@ -{ "plugins": ["prettier-plugin-tailwindcss"], "tailwindStylesheet": "./resources/css/app.css"} \ No newline at end of file +{ + "tabWidth": 4, + "plugins": ["prettier-plugin-tailwindcss"], + "tailwindStylesheet": "./index.css" +} diff --git a/package-lock.json b/package-lock.json index 1b82f7cbb..1370d834a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "chroma-js": "^3.1.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "gh-pages": "^6.3.0", "idb": "^8.0.3", "lucide-react": "^0.507.0", @@ -916,19 +917,32 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", - "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.14.0", + "@eslint/core": "^0.15.1", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@floating-ui/core": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.0.tgz", @@ -1288,6 +1302,134 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.14.tgz", + "integrity": "sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-focus-guards": "1.1.2", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.4", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz", + "integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.2", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", @@ -1330,6 +1472,87 @@ } } }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz", + "integrity": "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-id": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", @@ -2660,9 +2883,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2823,6 +3046,18 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", @@ -3065,9 +3300,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -3255,6 +3490,22 @@ "node": ">=6" } }, + "node_modules/cmdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3486,6 +3737,12 @@ "node": ">=8" } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -4220,15 +4477,16 @@ } }, "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -4353,6 +4611,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -6282,6 +6549,75 @@ "node": ">=0.10.0" } }, + "node_modules/react-remove-scroll": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", + "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-window": { "version": "1.8.11", "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.11.tgz", @@ -7195,6 +7531,49 @@ "punycode": "^2.1.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/use-sync-external-store": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", diff --git a/package.json b/package.json index a9e988058..a6b02872d 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "chroma-js": "^3.1.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "gh-pages": "^6.3.0", "idb": "^8.0.3", "lucide-react": "^0.507.0", diff --git a/public/sprites/front/1426.png b/public/sprites/front/1426.png index a77b3cbc3..b79bf6e05 100644 Binary files a/public/sprites/front/1426.png and b/public/sprites/front/1426.png differ diff --git a/public/sprites/front/1533.png b/public/sprites/front/1533.png index f476ab7fd..64e083779 100644 Binary files a/public/sprites/front/1533.png and b/public/sprites/front/1533.png differ diff --git a/public/sprites/front/1534.png b/public/sprites/front/1534.png new file mode 100644 index 000000000..c2cd5e509 Binary files /dev/null and b/public/sprites/front/1534.png differ diff --git a/public/sprites/front/1535.png b/public/sprites/front/1535.png new file mode 100644 index 000000000..f476ab7fd Binary files /dev/null and b/public/sprites/front/1535.png differ diff --git a/public/sprites/front_shiny/1426.png b/public/sprites/front_shiny/1426.png index 5b0b4a538..eee5f1f8b 100644 Binary files a/public/sprites/front_shiny/1426.png and b/public/sprites/front_shiny/1426.png differ diff --git a/public/sprites/front_shiny/1533.png b/public/sprites/front_shiny/1533.png index c2bda849c..eec04236e 100644 Binary files a/public/sprites/front_shiny/1533.png and b/public/sprites/front_shiny/1533.png differ diff --git a/public/sprites/front_shiny/1534.png b/public/sprites/front_shiny/1534.png new file mode 100644 index 000000000..5436bca3b Binary files /dev/null and b/public/sprites/front_shiny/1534.png differ diff --git a/public/sprites/front_shiny/1535.png b/public/sprites/front_shiny/1535.png new file mode 100644 index 000000000..c2bda849c Binary files /dev/null and b/public/sprites/front_shiny/1535.png differ diff --git a/src/components/AppHeader/HeaderBar.tsx b/src/components/AppHeader/HeaderBar.tsx index 3706d3afe..ae2b0ba7f 100644 --- a/src/components/AppHeader/HeaderBar.tsx +++ b/src/components/AppHeader/HeaderBar.tsx @@ -4,7 +4,7 @@ import { FaDiscord } from "react-icons/fa6"; import { Button } from "@headlessui/react"; const HeaderBar: React.FC = () => ( -
+
state.filters); + const resetFilters = useModularFilterStore((state) => state.resetFilters); + + // Derive pills directly from filters + const activePills = useMemo( + () => getFilterPillsFromFilters(filters), + [filters], + ); - const { filters } = useFilterStore(); + const handleRemovePill = (id: string) => { + const pillToRemove = activePills.find((pill) => pill.id === id); + if (pillToRemove) { + removeFilterPill(pillToRemove); + } else { + console.warn(`Pill with id ${id} not found.`); + } + console.log(`Remove pill: ${id}`); + }; - // Sync with filter state whenever the component renders - useEffect(() => { - syncWithFilters(); - }, [syncWithFilters, filters]); + const handleClearAll = () => { + resetFilters(); + }; - // Don't render if no active filters if (activePills.length === 0) return null; return (
{activePills.map((pill) => ( - + ))} {activePills.length > 1 && ( @@ -44,4 +48,4 @@ function CurrentFilters() { ); } -export default CurrentFilters; \ No newline at end of file +export default CurrentFilters; diff --git a/src/components/Filter/CurrentFilters/FilterPill.tsx b/src/components/Filter/CurrentFilters/FilterPill.tsx index 55b0124d7..b6e656e84 100644 --- a/src/components/Filter/CurrentFilters/FilterPill.tsx +++ b/src/components/Filter/CurrentFilters/FilterPill.tsx @@ -1,5 +1,5 @@ import { IoCloseCircle } from "react-icons/io5"; -import { FilterPill as FilterPillType } from "../../../stores/filterPillStore"; +import { FilterPill as FilterPillType } from "@/utils/filterPillUtils/types/types"; import { getTypeName } from "../../../utils/typeInfo"; import { getItemName } from "@/utils/itemUtils"; import { capitalize } from "@/utils/miscUtils"; @@ -26,24 +26,39 @@ function FilterPill({ pill, onRemove }: FilterPillProps) { // Format display value based on pill type const getDisplayValue = () => { - // First check the pill type to determine which branch of the union we're in - switch (pill.value.type) { + switch (pill.type) { case "name": - return `Name: ${pill.value.value}`; - case "type": - return `Type: ${ - pill.value.value !== undefined ? getTypeName(pill.value.value) : "All" - }`; - case "ability": - return `Ability: ${pill.value.value.name}`; + return `Name: ${pill.value as string}`; + + case "type": { + const typeId = pill.value as number; + return `Type: ${typeId !== undefined ? getTypeName(typeId) : "All"}`; + } + + case "ability": { + const ability = pill.value as { id: number; name: string }; + return `Ability: ${ability.name}`; + } + case "stat": { - const { stat, type, isMax } = pill.value.value; + const statValue = pill.value as { + stat: number | undefined; + type: string | undefined; + isMax: boolean; + }; + const { stat, type, isMax } = statValue; const statType = type ? capitalize(type) : "BST"; const operator = isMax ? "≤" : "≥"; return `${statType} ${operator} ${stat}`; } + case "move": { - const { name, source } = pill.value.value; + const moveValue = pill.value as { + id: number; + name: string; + source: string; + }; + const { name, source } = moveValue; const sourceMap: Record = { tm: "TM", levelup: "Lvl", @@ -53,8 +68,24 @@ function FilterPill({ pill, onRemove }: FilterPillProps) { const sourceLabel = sourceMap[source] ? `${sourceMap[source]} ` : ""; return `${sourceLabel}Move: ${name}`; } - case "item": - return `Item: ${getItemName(pill.value.value) || "???"}`; + + case "item": { + const itemId = pill.value as number; + return `Item: ${getItemName(itemId) || "???"}`; + } + + case "sort": { + const sortValue = pill.value as { + by: string; + stat: string | undefined; + sortDescending: boolean; + }; + const { by, stat, sortDescending: descending } = sortValue; + const direction = descending ? "↓" : "↑"; + const sortStat = stat ? ` (${stat})` : ""; + return `Sort: ${capitalize(by)}${sortStat} ${direction}`; + } + default: return pill.label; } diff --git a/src/components/Filter/FilterBar.tsx b/src/components/Filter/FilterBar.tsx index b6a269faf..cc745f912 100644 --- a/src/components/Filter/FilterBar.tsx +++ b/src/components/Filter/FilterBar.tsx @@ -1,13 +1,13 @@ -import NameCombobox from "./FilterComponents/NameCombobox"; import CurrentFilters from "./CurrentFilters/CurrentFilters"; import SidebarButton from "./SidebarButton"; +import { CommandMenu } from "./FilterCommand"; function FilterBar() { return (
-
- +
+
diff --git a/src/components/Filter/FilterCommand.tsx b/src/components/Filter/FilterCommand.tsx new file mode 100644 index 000000000..9a76e82b5 --- /dev/null +++ b/src/components/Filter/FilterCommand.tsx @@ -0,0 +1,256 @@ +import { Command } from "cmdk"; +import React, { useState, useMemo, useRef, useEffect } from "react"; +import { FaSearch } from "react-icons/fa"; +import speciesData from "../../data/speciesData.json"; +import abilityData from "../../data/abilityData.json"; +import moveData from "../../data/moveData.json"; +import { useModularFilterStore } from "../../stores/filterStore/index"; +import { useUIStore } from "@/stores/uiStore"; +import { IoMdRibbon } from "react-icons/io"; +import { LuSword } from "react-icons/lu"; +import { MdCatchingPokemon, MdKeyboardCommandKey } from "react-icons/md"; +import { useRecentSearchesStore } from "@/stores/recentSearchStore"; +import { RecentSearches } from "./RecentSearches"; + +// Types +interface SearchEntry { + name: string; + id: number; + icon: React.ReactNode; +} + +interface EntryType { + label: string; + color: string; +} + +type DataSource = typeof speciesData | typeof abilityData | typeof moveData; + +interface CommandItemsProps { + entries: SearchEntry[]; + onSelect: (entry: SearchEntry) => void; + entryType: EntryType; +} + +const entryTypes: Record = { + Pokemon: { label: "Pokemon", color: "#fb7185" }, + Ability: { label: "Ability", color: "#60A5FA" }, + Move: { label: "Move", color: "#34d399" }, +}; + +const matchesPattern = (str: string, pattern: string): boolean => { + let i = 0; + for (let c of str) { + if (c.toLowerCase() === pattern[i]?.toLowerCase()) { + i++; + if (i === pattern.length) return true; + } + } + return false; +}; + +const CommandItems: React.FC = ({ + entries, + onSelect, + entryType, +}) => ( + <> + {entries.map((entry) => ( + onSelect(entry)} + className="not-first:rounded flex cursor-pointer items-center justify-between rounded-b px-3 py-2 text-sm font-normal text-neutral-300 aria-selected:bg-zinc-700" + > + + {entry.icon} + {entry.name} + + + {entryType.label} + + + ))} + +); + +export const CommandMenu: React.FC = () => { + const open = useUIStore((state) => state.isCmdOpen); + const setOpen = useUIStore((state) => state.setCmdOpen); + const [search, setSearch] = useState(""); + const containerElement = useRef(null); + + const { isCreditsOpen, isHelpOpen, isModalOpen } = useUIStore(); + const isSafeToOpen = !isCreditsOpen && !isHelpOpen && !isModalOpen; + + const setName = useModularFilterStore((state) => state.setName); + const setAbility = useModularFilterStore((state) => state.setAbility); + const setMove = useModularFilterStore((state) => state.setMoveValue); + + // Add recent searches store + const addRecentSearch = useRecentSearchesStore((state) => state.addRecentSearch); + + + const handleSelection = (entry: SearchEntry, type: 'Pokemon' | 'Ability' | 'Move') => { + addRecentSearch({ + id: entry.id, + name: entry.name, + type: type, + }); + + // Handle the actual selection + switch (type) { + case 'Pokemon': + setName(entry.name); + break; + case 'Ability': + setAbility(entry.id); + break; + case 'Move': + setMove({ id: entry.id, name: entry.name }); + break; + } + + setOpen(false); + }; + + const handleRecentSelection = (item: { id: number; name: string; type: string }) => { + switch (item.type) { + case 'Pokemon': + setName(item.name); + break; + case 'Ability': + setAbility(item.id); + break; + case 'Move': + setMove({ id: item.id, name: item.name }); + break; + } + setOpen(false); + }; + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (isSafeToOpen && e.key === "k" && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + setOpen(!open); + setSearch(""); + } + }; + + document.addEventListener("keydown", handleKeyDown); + return () => document.removeEventListener("keydown", handleKeyDown); + }, [isSafeToOpen, open, setOpen]); + + const getFilteredEntries = ( + data: DataSource, + nameField: string, + idField: string, + icon?: React.ReactNode, + ): SearchEntry[] => { + return Object.values(data) + .filter((p) => matchesPattern(p[nameField], search.trim())) + .slice(0, 10) + .map((p) => { + const name = p[nameField]; + const id = p[idField]; + return { + name, + id, + icon: icon ? icon : , + }; + }); + }; + + const pokemonEntries = useMemo( + () => getFilteredEntries(speciesData, "nameKey", "speciesId", ), + [search], + ); + + const abilityEntries = useMemo( + () => getFilteredEntries(abilityData, "name", "abilityId", ), + [search], + ); + + const moveEntries = useMemo( + () => getFilteredEntries(moveData, "name", "moveId", ), + [search], + ); + + return ( + <> +
{setOpen(true); setSearch("")}} + > + + + {"Search for something..."} + + + + K + +
+ {open && ( + { + setOpen(isOpen); + setSearch(""); // Clear search whenever dialog state changes + }} + label="Global Command Menu" + className="fixed inset-0 z-40 flex items-start justify-center bg-black/50 p-4 backdrop-blur-md" + onClick={() => setOpen(false)} + > +
e.stopPropagation()} + > +
+ + +
+ + + No results found. Try adjusting your search. + + {!search.trim() && ( + + )} + handleSelection(entry, 'Pokemon')} + entryType={entryTypes.Pokemon} + /> + handleSelection(entry, 'Ability')} + entryType={entryTypes.Ability} + /> + + handleSelection(entry, 'Move')} + entryType={entryTypes.Move} + /> + +
+
+ )} + + ); +}; diff --git a/src/components/Filter/FilterComponents/AbilityCombobox.tsx b/src/components/Filter/FilterComponents/AbilityCombobox.tsx index 3ae836396..bb30075ff 100644 --- a/src/components/Filter/FilterComponents/AbilityCombobox.tsx +++ b/src/components/Filter/FilterComponents/AbilityCombobox.tsx @@ -1,9 +1,9 @@ import GenericComboBox, { ComboBoxEntry } from "./GenericComboBox"; import abilities from "../../../data/abilityData.json"; import { getAbilityName } from "../../../utils/abilityData"; -import { useMemo } from "react"; +import { useCallback, useMemo } from "react"; import { IoRibbon } from "react-icons/io5"; -import { useFilterStore } from "../../../stores/filterStore"; +import { useModularFilterStore } from "@/stores/filterStore/index"; const abilityIDMap: ComboBoxEntry[] = Object.keys(abilities) .map((id) => { @@ -16,8 +16,17 @@ const abilityIDMap: ComboBoxEntry[] = Object.keys(abilities) .sort((a, b) => a.name.localeCompare(b.name)); function AbilityCombobox() { - const abilityValue = useFilterStore((state) => state.abilityValue); - const setAbilityValue = useFilterStore((state) => state.setAbilityValue); + const abilityId = useModularFilterStore((state) => state.filters.abilityId); + const setAbility = useModularFilterStore((state) => state.setAbility); + + // Create abilityValue only when abilityId changes, not on every render + const abilityValue = useMemo(() => + abilityId ? { id: abilityId, name: getAbilityName(abilityId) } : null + , [abilityId]); + + const setAbilityValue = useCallback((entry: ComboBoxEntry | null) => + setAbility(entry?.id ?? 0) + , [setAbility]); const abilityEntries: ComboBoxEntry[] = useMemo(() => abilityIDMap, []); diff --git a/src/components/Filter/FilterComponents/ItemCombobox.tsx b/src/components/Filter/FilterComponents/ItemCombobox.tsx index 97eaeaafb..0a6a261a4 100644 --- a/src/components/Filter/FilterComponents/ItemCombobox.tsx +++ b/src/components/Filter/FilterComponents/ItemCombobox.tsx @@ -2,7 +2,7 @@ import GenericComboBox, { ComboBoxEntry } from "./GenericComboBox"; import itemData from "../../../data/itemData.json"; import { useMemo, useState, useEffect, useCallback } from "react"; import { MdShoppingBag} from "react-icons/md"; -import { useFilterStore } from "../../../stores/filterStore"; +import { useModularFilterStore } from "@/stores/filterStore/index"; const speciesIDMap: ComboBoxEntry[] = Object.values(itemData) .filter((i) => typeof i === "object" && !!i && "itemId" in i) @@ -12,8 +12,8 @@ const speciesIDMap: ComboBoxEntry[] = Object.values(itemData) })); function ItemCombobox() { - const heldItem = useFilterStore((state) => state.heldItem); - const setHeldItem = useFilterStore((state) => state.setHeldItem); + const heldItem = useModularFilterStore((state) => state.filters.heldItem); + const setHeldItem = useModularFilterStore((state) => state.setHeldItem); const [selectedEntry, setSelectedEntry] = useState( null, ); diff --git a/src/components/Filter/FilterComponents/MoveCombobox.tsx b/src/components/Filter/FilterComponents/MoveCombobox.tsx index ca2d6f418..b26d07764 100644 --- a/src/components/Filter/FilterComponents/MoveCombobox.tsx +++ b/src/components/Filter/FilterComponents/MoveCombobox.tsx @@ -3,7 +3,7 @@ import moveData from "../../../data/moveData.json"; import { useMemo } from "react"; import { LuSword } from "react-icons/lu"; import { Move } from "../../../types"; -import { useFilterStore } from "@/stores/filterStore"; +import { useModularFilterStore } from "@/stores/filterStore/index"; const moveIDMap: ComboBoxEntry[] = Object.values(moveData) .filter((m) => typeof m === "object" && !!m && "moveId" in m) @@ -14,7 +14,7 @@ const moveIDMap: ComboBoxEntry[] = Object.values(moveData) function MoveCombobox() { const moveEntries: ComboBoxEntry[] = useMemo(() => moveIDMap, []); - const setMoveValue = useFilterStore((state) => state.setMoveValue); + const setMoveValue = useModularFilterStore((state) => state.setMoveValue); return ( diff --git a/src/components/Filter/FilterComponents/MoveSourceDropdown.tsx b/src/components/Filter/FilterComponents/MoveSourceDropdown.tsx index 7385db846..0a5cf96c4 100644 --- a/src/components/Filter/FilterComponents/MoveSourceDropdown.tsx +++ b/src/components/Filter/FilterComponents/MoveSourceDropdown.tsx @@ -1,10 +1,10 @@ -import { useFilterStore } from "../../../stores/filterStore"; +import { useModularFilterStore } from "@/stores/filterStore/index"; import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from '@headlessui/react'; import { MdOutlineKeyboardArrowDown} from "react-icons/md"; function MoveSourceDropdown() { - const moveSource = useFilterStore(state => state.moveSource); - const setMoveSource = useFilterStore(state => state.setMoveSource); + const moveSource = useModularFilterStore(state => state.moveSource); + const setMoveSource = useModularFilterStore(state => state.setMoveSource); const options = [ { id: "all", label: "All" }, diff --git a/src/components/Filter/FilterComponents/NameCombobox.tsx b/src/components/Filter/FilterComponents/NameCombobox.tsx index 18e615789..ba1676036 100644 --- a/src/components/Filter/FilterComponents/NameCombobox.tsx +++ b/src/components/Filter/FilterComponents/NameCombobox.tsx @@ -3,7 +3,7 @@ import speciesData from "../../../data/speciesData.json"; import { Pokemon } from "../../../types"; import { useMemo, useState, useEffect } from "react"; import { MdSearch } from "react-icons/md"; -import { useFilterStore } from "../../../stores/filterStore"; +import { useModularFilterStore } from "../../../stores/filterStore/index"; const speciesIDMap: ComboBoxEntry[] = Object.values(speciesData) .filter((p) => typeof p === "object" && !!p && "nameKey" in p) @@ -13,22 +13,22 @@ const speciesIDMap: ComboBoxEntry[] = Object.values(speciesData) })); function NameCombobox() { - const nameValue = useFilterStore((state) => state.nameValue); - const setNameValue = useFilterStore((state) => state.setNameValue); + const name = useModularFilterStore((state) => state.filters.name); + const setName = useModularFilterStore((state) => state.setName); const [selectedEntry, setSelectedEntry] = useState(null); const pokemonEntries: ComboBoxEntry[] = useMemo(() => speciesIDMap, []); const handleNameSelect = (entry: ComboBoxEntry | null) => { setSelectedEntry(entry); - setNameValue(entry ? entry.name : ""); + setName(entry ? entry.name : ""); }; // Reset the component when nameValue is cleared useEffect(() => { - if (!nameValue && selectedEntry) { + if (!name && selectedEntry) { setSelectedEntry(null); } - }, [nameValue, selectedEntry]); + }, [name, selectedEntry]); return ( state.setTypeValue); - const options = useFilterStore((state) => state.typeOptions); - const selected = useFilterStore((state) => state.getSelectedType()); - - return ( - setTypeValue(t.typeID)}> -
- - {selected.typeID === undefined ? "Type" : getTypeName(selected.typeID)} - - - - {options.map((typeInfo) => ( - - {typeInfo.typeID === undefined - ? "All" - : getTypeName(typeInfo.typeID)} - - - - - ))} - -
-
- ); -} - -export default TypeDropdown; diff --git a/src/components/Filter/FilterModal.tsx b/src/components/Filter/FilterModal.tsx deleted file mode 100644 index 59a3ef878..000000000 --- a/src/components/Filter/FilterModal.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { useState } from "react"; -import { Button, Dialog, DialogPanel, DialogTitle } from "@headlessui/react"; -import { FaSliders } from "react-icons/fa6"; -import CurrentFilters from "./CurrentFilters/CurrentFilters"; -import TypeDropdown from "./FilterComponents/TypeDropdown"; -import AbilityCombobox from "./FilterComponents/AbilityCombobox"; -import MoveFilterGroup from "./FilterComponents/MoveFilterGroup"; -import StatFilter from "./StatFilter/StatFilter"; -import useBodyScrollLock from "../../hooks/useBodyScrollLock"; -import { useFilterStore } from "../../stores/filterStore"; - -function FilterModal() { - const [isOpen, setIsOpen] = useState(false); - - const { - resetFilters, - } = useFilterStore(); - - useBodyScrollLock(isOpen); - - return ( - <> - - - setIsOpen(false)}> -
- - - Filter Options - -
-
-
-
-
-
-
-
- - -
-
-
-
- - ); -} - -export default FilterModal; diff --git a/src/components/Filter/FilterRadio.tsx b/src/components/Filter/FilterRadio.tsx index 7566d2789..58c07d1ec 100644 --- a/src/components/Filter/FilterRadio.tsx +++ b/src/components/Filter/FilterRadio.tsx @@ -1,14 +1,14 @@ -import { useFilterStore } from "@/stores/filterStore"; +import { useModularFilterStore } from "@/stores/filterStore/index"; import React from "react"; -import CycleButton from "../CycleButton"; +import CycleButton from "../MiscUI/CycleButton"; import { MdBlock } from "react-icons/md"; import { LuFilterX, LuFilter } from "react-icons/lu"; const FilterRadio: React.FC = () => { - const cycleMega = useFilterStore((state) => state.cycleMega); - const megaCycle = useFilterStore((state) => state.megaCycle); - const cycleNfe = useFilterStore((state) => state.cycleNfe); - const nfeCycle = useFilterStore((state) => state.nfeCycle); + const cycleMega = useModularFilterStore((state) => state.cycleMega); + const megaCycle = useModularFilterStore((state) => state.megaCycle); + const cycleNfe = useModularFilterStore((state) => state.cycleNfe); + const nfeCycle = useModularFilterStore((state) => state.nfeCycle); return (
diff --git a/src/components/Filter/RecentSearches.tsx b/src/components/Filter/RecentSearches.tsx new file mode 100644 index 000000000..78e33c203 --- /dev/null +++ b/src/components/Filter/RecentSearches.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { Command } from 'cmdk'; +import { MdCatchingPokemon } from 'react-icons/md'; +import { IoMdRibbon } from 'react-icons/io'; +import { FaSearch } from 'react-icons/fa'; +import { LuSword } from "react-icons/lu"; +import { useRecentSearchesStore } from '../../stores/recentSearchStore'; + +interface RecentSearchesProps { + onSelect: (item: { id: number; name: string; type: string }) => void; + entryTypes: Record; +} + +const getIconForType = (type: string): React.ReactNode => { + switch (type) { + case 'Pokemon': + return ; + case 'Ability': + return ; + case 'Move': + return ; + default: + return ; + } +}; + +export const RecentSearches: React.FC = ({ + onSelect, + entryTypes, +}) => { + const recentSearches = useRecentSearchesStore((state) => state.recentSearches); + + if (recentSearches.length === 0) return null; + + return ( + + {recentSearches.map((item) => ( + onSelect(item)} + className="flex cursor-pointer items-center first:mt-2 justify-between rounded px-3 py-2 text-sm font-normal text-neutral-300 aria-selected:bg-zinc-700" + > + + {getIconForType(item.type)} + {item.name} + + + {entryTypes[item.type]?.label} + + + ))} + + ); +}; \ No newline at end of file diff --git a/src/components/Filter/StatFilter/StatFilter.tsx b/src/components/Filter/StatFilter/StatFilter.tsx index 7d737f1be..66c642b11 100644 --- a/src/components/Filter/StatFilter/StatFilter.tsx +++ b/src/components/Filter/StatFilter/StatFilter.tsx @@ -1,37 +1,25 @@ import { useEffect } from "react"; -import { useFilterStore } from "@/stores/filterStore"; +import { useModularFilterStore } from "@/stores/filterStore/index"; import StatSlider from "./StatSlider"; import StatSelector from "./StatSelector"; function StatFilter() { const { - chosenStat, + statValue, statType, - isStatMax, - setChosenStat, - setStatType, - toggleStatMax, - } = useFilterStore(); + setStatValue, + } = useModularFilterStore(); - const statOptions = [ - { value: "", label: "BST" }, - { value: "hp", label: "HP" }, - { value: "attack", label: "Atk" }, - { value: "defense", label: "Def" }, - { value: "spAtk", label: "SpA" }, - { value: "spDef", label: "SpD" }, - { value: "speed", label: "Spe" }, - ]; // Only declare these once const isBST = !statType; const min = 0; const max = isBST ? 780 : 255; - // Clamp chosenStat to max when statType changes + // Clamp statValue to max when statType changes useEffect(() => { - if (chosenStat !== undefined && chosenStat > max) { - setChosenStat(max); + if (statValue !== undefined && statValue > max) { + setStatValue(max); } }, [statType, max]); @@ -39,17 +27,9 @@ function StatFilter() {
- +
); } diff --git a/src/components/Filter/StatFilter/StatInputBox.tsx b/src/components/Filter/StatFilter/StatInputBox.tsx index 21ae5fc22..82d6767b9 100644 --- a/src/components/Filter/StatFilter/StatInputBox.tsx +++ b/src/components/Filter/StatFilter/StatInputBox.tsx @@ -1,4 +1,4 @@ -import { useFilterStore } from "@/stores/filterStore"; +import { useModularFilterStore } from "@/stores/filterStore/index"; import { Input } from "@headlessui/react"; import React from "react"; @@ -7,22 +7,22 @@ interface StatInputBoxProps { } const StatInputBox: React.FC = ({ max }) => { - const chosenStat = useFilterStore((state) => state.chosenStat); - const setChosenStat = useFilterStore((state) => state.setChosenStat); - const isStatMax = useFilterStore((state) => state.isStatMax); + const statValue = useModularFilterStore((state) => state.statValue); + const setStatValue = useModularFilterStore((state) => state.setStatValue); + const isStatMax = useModularFilterStore((state) => state.isStatMax); return (
{ let val = e.target.value ? Number(e.target.value) : undefined; if (typeof val === "number" && max !== undefined && val > max) { val = max; } - setChosenStat(val); + setStatValue(val); }} max={780} className="h-max w-12 p-0 text-center text-sm text-white" diff --git a/src/components/Filter/StatFilter/StatSelector.tsx b/src/components/Filter/StatFilter/StatSelector.tsx index d9cb4de77..85ecec8c8 100644 --- a/src/components/Filter/StatFilter/StatSelector.tsx +++ b/src/components/Filter/StatFilter/StatSelector.tsx @@ -1,23 +1,29 @@ +import { statType } from "@/stores/filterStore/constants"; +import { useModularFilterStore } from "@/stores/filterStore/index"; import { RadioGroup, Radio } from "@headlessui/react"; import { FaCircleChevronRight, FaCircleChevronLeft } from "react-icons/fa6"; -interface StatSelectorProps { - statType: string | undefined; - setStatType: (type: string | undefined) => void; - statOptions: { value: string; label: string }[]; - toggleStatMax: () => void; - isStatMax: boolean; -} +const statOptions = [ + { value: statType.BST, label: "BST" }, + { value: statType.HP, label: "HP" }, + { value: statType.ATK, label: "Atk" }, + { value: statType.DEF, label: "Def" }, + { value: statType.SPA, label: "SpA" }, + { value: statType.SPD, label: "SpD" }, + { value: statType.SPE, label: "Spe" }, +]; -export default function StatSelector(props: StatSelectorProps) { - const { statType, setStatType, statOptions, toggleStatMax, isStatMax } = - props; +export default function StatSelector() { + const statTypeValue = useModularFilterStore((state) => state.statType); + const setStatType = useModularFilterStore((state) => state.setStatType); + const isStatMax = useModularFilterStore((state) => state.isStatMax); + const toggleStatMax = useModularFilterStore((state) => state.setStatMax); - const colorScheme = isStatMax ? "group-data-checked:text-rose-400 group-data-checked:border-b-rose-400" : "group-data-checked:text-emerald-400 group-data-checked:border-b-emerald-400" + const colorScheme = isStatMax ? "group-data-checked:text-rose-400 group-data-checked:border-b-rose-400" : "group-data-checked:text-emerald-400 group-data-checked:border-b-emerald-400"; return (
@@ -35,7 +41,7 @@ export default function StatSelector(props: StatSelectorProps) {
toggleStatMax(!isStatMax)} > {isStatMax ? ( diff --git a/src/components/Filter/StatFilter/StatSlider.tsx b/src/components/Filter/StatFilter/StatSlider.tsx index 55ae41ea1..87228480d 100644 --- a/src/components/Filter/StatFilter/StatSlider.tsx +++ b/src/components/Filter/StatFilter/StatSlider.tsx @@ -1,35 +1,27 @@ import { Slider } from "@/components/ui/slider"; import StatInputBox from "./StatInputBox"; -import { useFilterStore } from "@/stores/filterStore"; +import { useModularFilterStore } from "@/stores/filterStore/index"; + -interface StatSliderProps { - max: number; - chosenStat: number | undefined; - min: number; - setChosenStat: (stat: number | undefined) => void; -} -function StatSlider({ - max, - chosenStat, - min, - setChosenStat, -}: StatSliderProps) { - const isStatMax = useFilterStore((state) => state.isStatMax) - const barColor:string = isStatMax ? "bg-rose-400" : "bg-emerald-400" +function StatSlider({ max, min }: { max: number; min: number }) { + const statValue = useModularFilterStore((state) => state.statValue); + const setStatValue = useModularFilterStore((state) => state.setStatValue); + const isStatMax = useModularFilterStore((state) => state.isStatMax); + const barColor: string = isStatMax ? "bg-rose-400" : "bg-emerald-400"; return (
setChosenStat(val)} + value={statValue !== undefined ? [statValue] : [min]} + onValueChange={([val]) => setStatValue(val)} className="flex-1" colorPrimary={barColor} /> - +
); } diff --git a/src/components/Filter/TypePanel.tsx b/src/components/Filter/TypePanel.tsx index 837e857d4..63743d0a9 100644 --- a/src/components/Filter/TypePanel.tsx +++ b/src/components/Filter/TypePanel.tsx @@ -1,10 +1,10 @@ import { validTypes } from "@/utils/typeInfo"; -import { useFilterStore } from "@/stores/filterStore"; +import { useModularFilterStore } from "@/stores/filterStore/index"; import TypeBadgeSimple from "../TypeBadges/TypeBadgeSimple"; const TypePanel: React.FC = () => { - const setTypeValue = useFilterStore((state) => state.setTypeValue); - const typeValue = useFilterStore((state) => state.typeValue); + const setTypeValue = useModularFilterStore((state) => state.setTypeValue); + const typeValue = useModularFilterStore((state) => state.filters.typeIds); return (
diff --git a/src/components/FormeView/FormeView.tsx b/src/components/FormeView/FormeView.tsx index c7e658405..631b66b33 100644 --- a/src/components/FormeView/FormeView.tsx +++ b/src/components/FormeView/FormeView.tsx @@ -1,7 +1,7 @@ import { Pokemon } from "@/types"; import React from "react"; import speciesData from "@/data/speciesData.json"; -import SpriteImage from "../SpriteImage"; +import SpriteImage from "../MiscUI/SpriteImage"; import excludeMons from "@/utils/excludeMons"; import { hasForms, getSpeciesData } from "@/utils/speciesUtils"; import { useUIStore } from "@/stores/uiStore"; diff --git a/src/components/CreditsButton.tsx b/src/components/InfoSection/CreditsButton.tsx similarity index 100% rename from src/components/CreditsButton.tsx rename to src/components/InfoSection/CreditsButton.tsx diff --git a/src/components/CreditsPanel.tsx b/src/components/InfoSection/CreditsPanel.tsx similarity index 97% rename from src/components/CreditsPanel.tsx rename to src/components/InfoSection/CreditsPanel.tsx index e016aac73..079001437 100644 --- a/src/components/CreditsPanel.tsx +++ b/src/components/InfoSection/CreditsPanel.tsx @@ -1,6 +1,6 @@ import { Dialog, DialogPanel, DialogTitle, Button} from "@headlessui/react"; -import CloseButton from "./CloseButton"; -import useBodyScrollLock from "../hooks/useBodyScrollLock"; +import CloseButton from "../MiscUI/CloseButton"; +import useBodyScrollLock from "../../hooks/useBodyScrollLock"; import { useUIStore } from "@/stores/uiStore"; import { FaGamepad, FaCode, FaUsers, FaDatabase } from "react-icons/fa"; diff --git a/src/components/HelpButton.tsx b/src/components/InfoSection/HelpButton.tsx similarity index 100% rename from src/components/HelpButton.tsx rename to src/components/InfoSection/HelpButton.tsx diff --git a/src/components/HelpPanel.tsx b/src/components/InfoSection/HelpPanel.tsx similarity index 98% rename from src/components/HelpPanel.tsx rename to src/components/InfoSection/HelpPanel.tsx index 63f7682af..1daaa8177 100644 --- a/src/components/HelpPanel.tsx +++ b/src/components/InfoSection/HelpPanel.tsx @@ -1,6 +1,6 @@ import { Dialog, DialogPanel, DialogTitle } from "@headlessui/react"; -import CloseButton from "./CloseButton"; -import useBodyScrollLock from "../hooks/useBodyScrollLock"; +import CloseButton from "../MiscUI/CloseButton"; +import useBodyScrollLock from "../../hooks/useBodyScrollLock"; import { useUIStore } from "@/stores/uiStore"; const HelpContent = () => ( diff --git a/src/components/CloseButton.tsx b/src/components/MiscUI/CloseButton.tsx similarity index 100% rename from src/components/CloseButton.tsx rename to src/components/MiscUI/CloseButton.tsx diff --git a/src/components/CycleButton.tsx b/src/components/MiscUI/CycleButton.tsx similarity index 100% rename from src/components/CycleButton.tsx rename to src/components/MiscUI/CycleButton.tsx diff --git a/src/components/FloatingActionButton.tsx b/src/components/MiscUI/FloatingActionButton.tsx similarity index 100% rename from src/components/FloatingActionButton.tsx rename to src/components/MiscUI/FloatingActionButton.tsx diff --git a/src/components/SaveFileButton.tsx b/src/components/MiscUI/SaveFileButton.tsx similarity index 95% rename from src/components/SaveFileButton.tsx rename to src/components/MiscUI/SaveFileButton.tsx index 53864de16..708faefce 100644 --- a/src/components/SaveFileButton.tsx +++ b/src/components/MiscUI/SaveFileButton.tsx @@ -1,8 +1,8 @@ import { Button, Input } from "@headlessui/react"; import React, { useEffect, useRef } from "react"; import { MdCloudUpload } from "react-icons/md"; -import { getTrainerIdFromSectors, MGBA_SIZE, splitSaveIntoChunks, TOTAL_SIZE } from "../randomiser/trainerIdExtractor"; -import { useRandomiserStore } from "../stores/randomiserStore"; +import { getTrainerIdFromSectors, MGBA_SIZE, splitSaveIntoChunks, TOTAL_SIZE } from "../../randomiser/trainerIdExtractor"; +import { useRandomiserStore } from "../../stores/randomiserStore"; export function SaveFileButton({ onSaveRead, diff --git a/src/components/AppHeader/ShinyToggle.tsx b/src/components/MiscUI/ShinyToggle.tsx similarity index 100% rename from src/components/AppHeader/ShinyToggle.tsx rename to src/components/MiscUI/ShinyToggle.tsx diff --git a/src/components/SpriteImage.tsx b/src/components/MiscUI/SpriteImage.tsx similarity index 100% rename from src/components/SpriteImage.tsx rename to src/components/MiscUI/SpriteImage.tsx diff --git a/src/components/PokeDex.tsx b/src/components/PokeDex.tsx index ad2e39749..6bfe0f6af 100644 --- a/src/components/PokeDex.tsx +++ b/src/components/PokeDex.tsx @@ -1,41 +1,44 @@ import { useEffect, useMemo, useRef, useState } from "react"; import { useInView } from "react-intersection-observer"; -import CreditsPanel from "./CreditsPanel"; +import CreditsPanel from "./InfoSection/CreditsPanel"; import FilterBar from "./Filter/FilterBar"; -import FloatingButton from "./FloatingActionButton"; +import FloatingButton from "./MiscUI/FloatingActionButton"; import PokemonList from "./PokemonList/PokemonList"; import HeaderBar from "./AppHeader/HeaderBar"; import pokemonData from "../data/speciesData.json"; -import { useFilterStore } from "../stores/filterStore"; import { useUIStore } from "../stores/uiStore"; import { Pokemon } from "../types"; import { filterPokemon } from "../utils/filterUtils/filterPokemon"; import { useRandomiserState } from "../utils/randomiserState"; -import HelpPanel from "./HelpPanel"; - +import HelpPanel from "./InfoSection/HelpPanel"; +import { useModularFilterStore } from "../stores/filterStore/index"; function PokeDex() { // Get filter state from Zustand store - const { filters } = useFilterStore(); + const { filters } = useModularFilterStore(); const randomiserState = useRandomiserState(); // Memoized filtered Pokémon list (only updates when filters or randomizer state changes) const filteredPokemon = useMemo(() => { - return filterPokemon(pokemonData as Record, filters, randomiserState.isActive); + return filterPokemon( + pokemonData as Record, + filters, + randomiserState.isActive, + ); }, [filters, randomiserState]); // Create ref to store previous randomiser state for comparison const prevRandomiserStateRef = useRef(randomiserState); - + // Scroll to top when filters change, or when randomiser state changes with relevant filters active useEffect(() => { const isAbilityFilterActive = filters.abilityId !== undefined; - + // Check if we should scroll due to randomiser state change - const shouldScrollForRandomiser = - (isAbilityFilterActive) && // Ability or move filter is active - (prevRandomiserStateRef.current.isActive !== randomiserState.isActive || - prevRandomiserStateRef.current.trainerId !== randomiserState.trainerId); - + const shouldScrollForRandomiser = + isAbilityFilterActive && // Ability or move filter is active + (prevRandomiserStateRef.current.isActive !== randomiserState.isActive || + prevRandomiserStateRef.current.trainerId !== randomiserState.trainerId); + // Always scroll if filters change, or conditionally for randomiser changes if (shouldScrollForRandomiser) { window.scrollTo({ @@ -43,7 +46,7 @@ function PokeDex() { behavior: "auto", }); } - + // Update ref with current state for next comparison prevRandomiserStateRef.current = randomiserState; }, [filters, randomiserState]); @@ -59,7 +62,6 @@ function PokeDex() { const rect = containerRef.current.getBoundingClientRect(); const distanceFromRight = window.innerWidth - rect.right; setRightOffset(distanceFromRight > 16 ? distanceFromRight : 16); - console.log("Distance from right:", distanceFromRight); } } @@ -74,14 +76,14 @@ function PokeDex() { }); return ( -
-
- +
+
+
diff --git a/src/components/PokemonList/PokemonCard.tsx b/src/components/PokemonList/PokemonCard.tsx index 7a87622d3..3736e50de 100644 --- a/src/components/PokemonList/PokemonCard.tsx +++ b/src/components/PokemonList/PokemonCard.tsx @@ -6,7 +6,7 @@ import chroma from "chroma-js"; import { useUIStore } from "@/stores/uiStore"; import { useScreenWidth } from "@/hooks/useScreenWidth"; -import SpriteImage from "../SpriteImage"; +import SpriteImage from "../MiscUI/SpriteImage"; import { randomizeAbility } from "@/randomiser/randomiser"; import { abilityWhitelist } from "@/randomiser/abilityWhitelist"; import { useRandomiserStore } from "@/stores/randomiserStore"; @@ -78,7 +78,7 @@ export function PokemonCard({ pokemon }: PokemonCardProps) {
openModal(pokemon)} className="w-full cursor-pointer">
{/* Header */} -
+
{/* Sprite and name */} diff --git a/src/components/PokemonList/PokemonList.tsx b/src/components/PokemonList/PokemonList.tsx index 31723dd44..e59db695d 100644 --- a/src/components/PokemonList/PokemonList.tsx +++ b/src/components/PokemonList/PokemonList.tsx @@ -6,7 +6,7 @@ import useBodyScrollLock from "../../hooks/useBodyScrollLock"; import { SortBar } from "./PokemonSortBar"; import excludeMons from "@/utils/excludeMons"; import { useUIStore } from "@/stores/uiStore"; -import { useFilterStore } from "@/stores/filterStore"; + type PokemonListProps = { pokemonToShow: Pokemon[]; @@ -24,13 +24,6 @@ export default function PokemonList({ isModalOpen, } = useUIStore(); - //Get filter state from store - const { - sortBy, - sortStat, - setSortBy, - setSortStat, - } = useFilterStore(); const ignoreList: number[] = [1435]; @@ -80,15 +73,8 @@ export default function PokemonList({ return (
-
- { - setSortBy(newSortBy); - setSortStat(newStatType); - }} - /> +
+
{(() => { diff --git a/src/components/PokemonList/PokemonSortBar.tsx b/src/components/PokemonList/PokemonSortBar.tsx index 83fd4f6a4..791809b51 100644 --- a/src/components/PokemonList/PokemonSortBar.tsx +++ b/src/components/PokemonList/PokemonSortBar.tsx @@ -1,68 +1,47 @@ -import { SortBy } from "@/types"; +import { sortOptions } from "@/stores/filterStore/constants"; import { PiSortAscending } from "react-icons/pi"; import { PiSortDescending } from "react-icons/pi"; import { Button } from "@headlessui/react"; import React from "react"; -import { useFilterStore } from "@/stores/filterStore"; +import { useModularFilterStore } from "@/stores/filterStore/index"; -const sortOptions: { - label: string; - value: SortBy; - statType?: string; - defaultDescending: boolean; -}[] = [ - { label: "#", value: "dexId", defaultDescending: false }, - { label: "Name", value: "name", defaultDescending: false }, - { label: "HP", value: "stat", statType: "hp", defaultDescending: true }, - { label: "Atk", value: "stat", statType: "attack", defaultDescending: true }, - { label: "Def", value: "stat", statType: "defense", defaultDescending: true }, - { label: "SpA", value: "stat", statType: "spAtk", defaultDescending: true }, - { label: "SpD", value: "stat", statType: "spDef", defaultDescending: true }, - { label: "Spe", value: "stat", statType: "speed", defaultDescending: true }, - { label: "BST", value: "stat", statType: "bst", defaultDescending: true }, -]; - -interface SortBarProps { - sortBy: SortBy; - statType?: string; - onChange: (sortBy: SortBy, statType?: string) => void; -} - -export const SortBar: React.FC = ({ - sortBy, - statType, - onChange, -}) => { - const selected = statType ? `${sortBy}:${statType}` : sortBy; - const descending = useFilterStore((state) => state.descending); - const toggleSortDirection = useFilterStore( - (state) => state.toggleSortDirection, +export const SortBar: React.FC = () => { + const sortBy = useModularFilterStore((state) => state.sortBy); + const sortStat = useModularFilterStore((state) => state.sortStat); + const isDescending = useModularFilterStore((state) => state.isDescending); + const setSortBy = useModularFilterStore((state) => state.setSortBy); + const setSortStat = useModularFilterStore((state) => state.setSortStat); + const setIsDescending = useModularFilterStore( + (state) => state.setIsDescending, ); - const setSortDirection = useFilterStore((state) => state.setSortDirection); + + const toggleSortDirection = () => setIsDescending(!isDescending); return ( -
+
{/* Sort buttons group */} -
+
{sortOptions.map((option) => { - const value = option.statType - ? `${option.value}:${option.statType}` - : option.value; - const isSelected = selected === value; + const isSelected = + option.value === "stat" + ? sortBy === "stat" && sortStat === option.statType + : sortBy === option.value; + return (
{/* Direction marker */}
{ - if (toggleSortDirection) { - toggleSortDirection(); - } - }} + className="2xs:inline-block hidden cursor-pointer select-none pr-3" + onClick={toggleSortDirection} > - {descending ? ( + {isDescending ? ( ) : ( diff --git a/src/components/PokemonList/PokemonSortDropdown.tsx b/src/components/PokemonList/PokemonSortDropdown.tsx new file mode 100644 index 000000000..a15740969 --- /dev/null +++ b/src/components/PokemonList/PokemonSortDropdown.tsx @@ -0,0 +1,54 @@ +import { SortBy } from "@/stores/filterStore/types"; +import { statType, sortOptions } from "@/stores/filterStore/constants"; +import { + Listbox, + ListboxButton, + ListboxOption, + ListboxOptions, +} from "@headlessui/react"; +import { BiChevronDown } from "react-icons/bi"; + +type StatTypeKey = keyof typeof statType; + +interface PokemonSortDropwdownProps { + sortBy: SortBy; + sortStat?: keyof typeof statType; + onChange: (sortBy: SortBy, statType: StatTypeKey) => void; +} + +const PokemonSortDropdown: React.FC = ({ + sortBy, + sortStat = "BST", + onChange, +}) => { + return ( + onChange(value, sortStat as StatTypeKey)} + > + + {sortOptions.find((option) => option.value === sortBy)?.label || + "Sort"} + + + + + + {sortOptions.map((option) => ( + + {option.label} + + ))} + + + ); +}; + +export default PokemonSortDropdown; diff --git a/src/components/PokemonModal/Ability/AbilityDescription.tsx b/src/components/PokemonModal/Ability/AbilityDescription.tsx index 1fd1c8a56..f86d3a78a 100644 --- a/src/components/PokemonModal/Ability/AbilityDescription.tsx +++ b/src/components/PokemonModal/Ability/AbilityDescription.tsx @@ -1,5 +1,5 @@ import { getAbilityName, getAbilityDescription } from "../../../utils/abilityData"; -import CloseButton from "../../CloseButton"; +import CloseButton from "../../MiscUI/CloseButton"; import { useUIStore } from "@/stores/uiStore"; const AbilityDescription: React.FC = () => { diff --git a/src/components/PokemonModal/EvolutionView/Evolution.tsx b/src/components/PokemonModal/EvolutionView/Evolution.tsx index ab3a8f102..67cfe63f5 100644 --- a/src/components/PokemonModal/EvolutionView/Evolution.tsx +++ b/src/components/PokemonModal/EvolutionView/Evolution.tsx @@ -1,5 +1,5 @@ import { Pokemon } from "@/types"; -import SpriteImage from "../../SpriteImage"; +import SpriteImage from "../../MiscUI/SpriteImage"; type EvolutionProps = { onClick: () => void; diff --git a/src/components/PokemonModal/Learnset/CompactMoveEntry.tsx b/src/components/PokemonModal/Learnset/CompactMoveEntry.tsx new file mode 100644 index 000000000..9e4ec9705 --- /dev/null +++ b/src/components/PokemonModal/Learnset/CompactMoveEntry.tsx @@ -0,0 +1,56 @@ +import { Move } from "../../../types"; +import { getTypeColor } from "@/utils/typeInfo"; +import { TypeIcon } from "@/components/TypeBadges/TypeIcon"; +import chroma from "chroma-js"; + +type CompactMoveEntryProps = { + move: Move; + level?: number; +}; + +const adjustedBgCache: Record = {}; + +const CompactMoveEntry: React.FC = ({ move, level }) => { + const typeId = move.type; + let adjustedBg = adjustedBgCache[typeId]; + if (!adjustedBg) { + const snapColor = getTypeColor(typeId)[1]; + const bgColor = chroma(snapColor); + adjustedBg = bgColor.darken(1.5).mix("black", 0.05).alpha(0.25).css(); + adjustedBgCache[typeId] = adjustedBg; + } + + return ( +
+ + {move.name} + {typeof level !== "undefined" && ( + + {level !== 0 ? `Lvl ${level}` : "Evo"} + + )} + + + + {move.power ? `${move.power}` : "—"} + + + {move.acc ? (move.acc === 999 ? "∞" : `${move.acc}%`) : "—"} + + + + + + + + +
+ ); +}; + +export default CompactMoveEntry; diff --git a/src/components/PokemonModal/Learnset/EggMoves.tsx b/src/components/PokemonModal/Learnset/EggMoves.tsx index eb280cbdb..2b88f6bea 100644 --- a/src/components/PokemonModal/Learnset/EggMoves.tsx +++ b/src/components/PokemonModal/Learnset/EggMoves.tsx @@ -5,29 +5,33 @@ import { findRootSpecies } from "@/utils/evoFamily"; import MoveEntry from "./MoveEntry"; import { MoveList } from "./MoveList"; import { memo } from "react"; +import CompactMoveEntry from "./CompactMoveEntry"; +import { useUIStore } from "@/stores/uiStore"; type EggMovesProps = { - pokemon: Pokemon; + pokemon: Pokemon; }; const EggMoves = memo(({ pokemon }: EggMovesProps) => { - // Get egg moves from root species - const rootSpeciesId = findRootSpecies(pokemon.speciesId); - const rootSpecies = getSpeciesData(rootSpeciesId); - const eggMoves = rootSpecies.eggMoves ?? []; + const learnsetView = useUIStore((state) => state.learnsetView); + // Get egg moves from root species + const rootSpeciesId = findRootSpecies(pokemon.speciesId); + const rootSpecies = getSpeciesData(rootSpeciesId); + const eggMoves = rootSpecies.eggMoves ?? []; - const moves = eggMoves.map((moveId, index) => { - const move = getMoveData(moveId); - return move ? ( - - ) : null; - }); + const moves = eggMoves.map((moveId, index) => { + const move = getMoveData(moveId); + const moveCard = move ? ( + + ) : null; - return ( - - {moves} - - ); + const moveCompactList = move ? ( + + ) : null; + return learnsetView === "card" ? moveCard : moveCompactList; + }); + + return {moves}; }); export default EggMoves; diff --git a/src/components/PokemonModal/Learnset/LevelUpMoves.tsx b/src/components/PokemonModal/Learnset/LevelUpMoves.tsx index 7951134c7..dfd1e6e9c 100644 --- a/src/components/PokemonModal/Learnset/LevelUpMoves.tsx +++ b/src/components/PokemonModal/Learnset/LevelUpMoves.tsx @@ -1,27 +1,37 @@ +import { useUIStore } from "@/stores/uiStore"; import { Pokemon } from "../../../types"; import { getMoveData } from "../../../utils/moveData"; import MoveEntry from "./MoveEntry"; import { MoveList } from "./MoveList"; import { memo } from "react"; +import CompactMoveEntry from "./CompactMoveEntry"; type LevelUpMovesProps = { - pokemon: Pokemon; + pokemon: Pokemon; }; const LevelUpMoves = memo(({ pokemon }: LevelUpMovesProps) => { - const moves = pokemon.levelUpMoves.map((arr, index) => { - const [moveId, level] = arr; - const move = getMoveData(moveId); - return move ? ( - - ) : null; - }); + const learnsetView = useUIStore((state) => state.learnsetView); + const moves = pokemon.levelUpMoves.map((arr, index) => { + const [moveId, level] = arr; + const move = getMoveData(moveId); + const moveCard = move ? ( + + ) : null; - return ( - - {moves} - - ); + const moveCompactList = move ? ( + + ) : null; + return learnsetView === 'card' ? moveCard : moveCompactList; + }); + + return ( + {moves} + ); }); export default LevelUpMoves; diff --git a/src/components/PokemonModal/Learnset/MoveEntry.tsx b/src/components/PokemonModal/Learnset/MoveEntry.tsx index 10bf5768a..a156f42c8 100644 --- a/src/components/PokemonModal/Learnset/MoveEntry.tsx +++ b/src/components/PokemonModal/Learnset/MoveEntry.tsx @@ -54,13 +54,16 @@ const MoveEntry: React.FC = memo(({ move, level }) => {
{/* Row 2: Power/Acc and MovePropBox */}
-
- +
+ {move.power ? `${move.power}` : "—"} - + {move.acc ? (move.acc === 999 ? "∞" : `${move.acc}%`) : "—"} + + { `${move.pp} PP`} +
diff --git a/src/components/PokemonModal/Learnset/TMMoves.tsx b/src/components/PokemonModal/Learnset/TMMoves.tsx index e04d65870..9f8fd2ff2 100644 --- a/src/components/PokemonModal/Learnset/TMMoves.tsx +++ b/src/components/PokemonModal/Learnset/TMMoves.tsx @@ -1,5 +1,7 @@ +import { useUIStore } from "@/stores/uiStore"; import { Pokemon } from "../../../types"; import { getMoveData } from "../../../utils/moveData"; +import CompactMoveEntry from "./CompactMoveEntry"; import MoveEntry from "./MoveEntry"; import { MoveList } from "./MoveList"; import { memo } from "react"; @@ -9,11 +11,21 @@ type TMMovesProps = { }; const TMMoves = memo(({ pokemon }: TMMovesProps) => { + const learnsetView = useUIStore((state) => state.learnsetView); const moves = (pokemon.tmMoves || []).map((moveId, index) => { const move = getMoveData(moveId); - return move ? ( - - ) : null; + + const moveCard = move ? ( + + ) : null; + + const moveCompactList = move ? ( + + ) : null; + return learnsetView === 'card' ? moveCard : moveCompactList; }); return ( diff --git a/src/components/PokemonModal/Learnset/ViewChanger.tsx b/src/components/PokemonModal/Learnset/ViewChanger.tsx new file mode 100644 index 000000000..64f781a7a --- /dev/null +++ b/src/components/PokemonModal/Learnset/ViewChanger.tsx @@ -0,0 +1,35 @@ +import { useUIStore } from "@/stores/uiStore"; +import React from "react"; +import { LuLayoutList } from "react-icons/lu"; +import { RiLayoutTopFill } from "react-icons/ri"; +import { Radio, RadioGroup } from "@headlessui/react"; + +const views = [ + { value: "card", icon: RiLayoutTopFill, label: "Card" }, + { value: "list", icon: LuLayoutList, label: "List" }, +]; + +const ViewChanger: React.FC = () => { + const learnsetView = useUIStore((state) => state.learnsetView); + const setLearnsetView = useUIStore((state) => state.setLearnsetView); + + return ( + + {views.map(({ value, icon: Icon }) => ( + + + + ))} + + ); +}; + +export default ViewChanger; diff --git a/src/components/PokemonModal/PokemonModal.tsx b/src/components/PokemonModal/PokemonModal.tsx index e8ceac01d..b9e4ea2da 100644 --- a/src/components/PokemonModal/PokemonModal.tsx +++ b/src/components/PokemonModal/PokemonModal.tsx @@ -1,7 +1,7 @@ import { useUIStore } from "@/stores/uiStore"; -import ShinyToggle from "../AppHeader/ShinyToggle"; -import RandomiserSwitch from "../AppHeader/RandomiserSwitch"; -import CloseButton from "../CloseButton"; +import ShinyToggle from "../MiscUI/ShinyToggle"; +import RandomiserSwitch from "../RandomiserSwitch"; +import CloseButton from "../MiscUI/CloseButton"; import PokemonView from "./PokemonView"; function PokemonModal() { diff --git a/src/components/PokemonModal/PokemonView.tsx b/src/components/PokemonModal/PokemonView.tsx index f58f86fd5..178c04a45 100644 --- a/src/components/PokemonModal/PokemonView.tsx +++ b/src/components/PokemonModal/PokemonView.tsx @@ -3,7 +3,7 @@ import AbilityBox from "./Ability/AbilityBox"; import AbilityDescription from "./Ability/AbilityDescription"; import { TypeBadge } from "../TypeBadges/TypeBadge"; import StatBars from "./MiscModal/StatBars"; -import SpriteImage from "../SpriteImage"; +import SpriteImage from "../MiscUI/SpriteImage"; import { getEvolutionaryFamily } from "@/utils/evoFamily"; import { hasForms, hasItems } from "@/utils/speciesUtils"; import { useUIStore } from "@/stores/uiStore"; @@ -19,130 +19,140 @@ import PokemonLearnset from "./Learnset/PokemonLearnset"; import React from "react"; import { isObtainable } from "@/utils/locationsData"; import ItemView from "./MiscModal/ItemView"; +import ViewChanger from "./Learnset/ViewChanger"; interface PokemonViewProps { - pokemon: Pokemon; + pokemon: Pokemon; } const PokemonView: React.FC = ({ pokemon }) => { - const selectedAbility = useUIStore((state) => state.selectedAbility); - const screenWidth = useScreenWidth(); - const isRandomiserActive = useRandomiserStore( - (state) => state.isRandomiserActive, - ); - const evoFamily = getEvolutionaryFamily(pokemon.speciesId); + const selectedAbility = useUIStore((state) => state.selectedAbility); + const screenWidth = useScreenWidth(); + const isRandomiserActive = useRandomiserStore( + (state) => state.isRandomiserActive, + ); + const evoFamily = getEvolutionaryFamily(pokemon.speciesId); - const randomisedAbilities = pokemon.abilities.map((_, i) => - randomizeAbility( - pokemon.speciesId, - i, - abilityWhitelist, - isRandomiserActive, - ), - ); + const randomisedAbilities = pokemon.abilities.map((_, i) => + randomizeAbility( + pokemon.speciesId, + i, + abilityWhitelist, + isRandomiserActive, + ), + ); - return ( -
- {/* Header section - Pokemon sprite and basic info */} -
-
- -
-
- {pokemon.types.map((typeId: number, index: number) => ( -
- + return ( +
+ {/* Header section - Pokemon sprite and basic info */} +
+
+ +
+
+ {pokemon.types.map((typeId: number, index: number) => ( +
+ +
+ ))} +
+
+
+ {pokemon.nameKey} +
+
+ #{pokemon.dexId} +
+
- ))} -
-
-
- {pokemon.nameKey} -
-
- #{pokemon.dexId} -
-
-
- {/* Stats section */} -
- -
+ {/* Stats section */} +
+ +
- {/* Details section */} -
- {/* Abilities section */} -
-

- Abilities -

- - {selectedAbility && } -
+ {/* Details section */} +
+ {/* Abilities section */} +
+

+ Abilities +

+ + {selectedAbility && } +
- {/* Locations section */} - {isObtainable(pokemon.speciesId) && ( -
-

- Locations -

- -
- )} + {/* Locations section */} + {isObtainable(pokemon.speciesId) && ( +
+

+ Locations +

+ +
+ )} - {/* Evolution section */} -
-

- Evolution -

- -
+ {/* Evolution section */} +
+

+ Evolution +

+ +
- {/* Forms section */} - {hasForms(pokemon) && ( -
-

- Forms -

- -
- )} + {/* Forms section */} + {hasForms(pokemon) && ( +
+

+ Forms +

+ +
+ )} - {/* Held items section */} - {hasItems(pokemon) && ( -
-

- Held Items -

- -
- )} + {/* Held items section */} + {hasItems(pokemon) && ( +
+

+ Held Items +

+ +
+ )} - {/* Type matchup section */} -
-

- Type Matchups -

-
- -
-
+ {/* Type matchup section */} +
+

+ Type Matchups +

+
+ +
+
- {/* Learnset section */} -
-

- Learnset -

- + {/* Learnset section */} +
+ + + Learnset + + + + +
+
-
-
- ); + ); }; export default PokemonView; diff --git a/src/components/AppHeader/RandomiserSwitch.tsx b/src/components/RandomiserSwitch.tsx similarity index 100% rename from src/components/AppHeader/RandomiserSwitch.tsx rename to src/components/RandomiserSwitch.tsx diff --git a/src/components/SaveInfo.tsx b/src/components/SaveInfo.tsx index 83c7dd0db..3bab089bc 100644 --- a/src/components/SaveInfo.tsx +++ b/src/components/SaveInfo.tsx @@ -1,6 +1,6 @@ import { useRandomiserStore } from "../stores/randomiserStore"; -import CloseButton from "./CloseButton"; -import { SaveFileButton } from "./SaveFileButton"; +import CloseButton from "./MiscUI/CloseButton"; +import { SaveFileButton } from "./MiscUI/SaveFileButton"; import { splitSaveIntoChunks, getTrainerIdFromSectors, diff --git a/src/components/AppHeader/SecondaryBar.tsx b/src/components/SecondaryBar.tsx similarity index 73% rename from src/components/AppHeader/SecondaryBar.tsx rename to src/components/SecondaryBar.tsx index e05a47a16..577938c11 100644 --- a/src/components/AppHeader/SecondaryBar.tsx +++ b/src/components/SecondaryBar.tsx @@ -1,7 +1,7 @@ -import ShinyToggle from "./ShinyToggle.tsx"; -import CreditsButton from "../CreditsButton.tsx"; +import ShinyToggle from "./MiscUI/ShinyToggle.tsx"; +import CreditsButton from "./InfoSection/CreditsButton.tsx"; import RandomiserSwitch from "./RandomiserSwitch.tsx"; -import HelpButton from "../HelpButton.tsx"; +import HelpButton from "./InfoSection/HelpButton.tsx"; function SecondaryBar() { return ( diff --git a/src/components/Sidebar/MainSidebar.tsx b/src/components/Sidebar/MainSidebar.tsx index d6d57d95c..a4af1e5c2 100644 --- a/src/components/Sidebar/MainSidebar.tsx +++ b/src/components/Sidebar/MainSidebar.tsx @@ -18,7 +18,7 @@ function Sidebar({ children }: SidebarProps) { useBodyScrollLock(isScrollLock); return ( -