diff options
author | Anderson Shindy Oki <[email protected]> | 2024-06-07 12:00:42 +0900 |
---|---|---|
committer | GitHub <[email protected]> | 2024-06-06 23:00:42 -0400 |
commit | be8f2d6d183e5206f932be1113e0da7db2a2277a (patch) | |
tree | 1bfb7ae673c081ab865cb86b6e59fb7f6a4b88e8 /frontend | |
parent | bb8233b599fa0bd8133b092897a4fbcfd736c8cd (diff) | |
download | bazarr-be8f2d6d183e5206f932be1113e0da7db2a2277a.tar.gz bazarr-be8f2d6d183e5206f932be1113e0da7db2a2277a.zip |
Upgraded mantine to v7.xv1.4.4-beta.0
Diffstat (limited to 'frontend')
106 files changed, 1187 insertions, 1241 deletions
diff --git a/frontend/package-lock.json b/frontend/package-lock.json index bfc24c17f..f19cbab4d 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,12 +9,12 @@ "version": "1.0.0", "license": "GPL-3", "dependencies": { - "@mantine/core": "^6.0.21", - "@mantine/dropzone": "^6.0.21", - "@mantine/form": "^6.0.21", - "@mantine/hooks": "^6.0.21", - "@mantine/modals": "^6.0.21", - "@mantine/notifications": "^6.0.21", + "@mantine/core": "^7.10.1", + "@mantine/dropzone": "^7.10.1", + "@mantine/form": "^7.10.1", + "@mantine/hooks": "^7.10.1", + "@mantine/modals": "^7.10.1", + "@mantine/notifications": "^7.10.1", "axios": "^1.6.8", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -49,6 +49,8 @@ "husky": "^9.0.11", "jsdom": "^24.0.0", "lodash": "^4.17.21", + "postcss-preset-mantine": "^1.14.4", + "postcss-simple-vars": "^7.0.1", "prettier": "^3.2.5", "prettier-plugin-organize-imports": "^3.2.4", "pretty-quick": "^4.0.0", @@ -94,6 +96,7 @@ "version": "7.24.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "dev": true, "dependencies": { "@babel/highlight": "^7.24.2", "picocolors": "^1.0.0" @@ -335,6 +338,7 @@ "version": "7.24.3", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "dev": true, "dependencies": { "@babel/types": "^7.24.0" }, @@ -456,6 +460,7 @@ "version": "7.24.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "dev": true, "engines": { "node": ">=6.9.0" } @@ -464,6 +469,7 @@ "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, "engines": { "node": ">=6.9.0" } @@ -509,6 +515,7 @@ "version": "7.24.2", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz", "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==", + "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", @@ -523,6 +530,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -534,6 +542,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -547,6 +556,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -554,12 +564,14 @@ "node_modules/@babel/highlight/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true }, "node_modules/@babel/highlight/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, "engines": { "node": ">=0.8.0" } @@ -568,6 +580,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, "engines": { "node": ">=4" } @@ -576,6 +589,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -2193,6 +2207,7 @@ "version": "7.24.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", @@ -2208,120 +2223,6 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, - "node_modules/@emotion/babel-plugin": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", - "integrity": "sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==", - "peer": true, - "dependencies": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/serialize": "^1.1.2", - "babel-plugin-macros": "^3.1.0", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^4.0.0", - "find-root": "^1.1.0", - "source-map": "^0.5.7", - "stylis": "4.2.0" - } - }, - "node_modules/@emotion/cache": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", - "integrity": "sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==", - "peer": true, - "dependencies": { - "@emotion/memoize": "^0.8.1", - "@emotion/sheet": "^1.2.2", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", - "stylis": "4.2.0" - } - }, - "node_modules/@emotion/hash": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", - "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==", - "peer": true - }, - "node_modules/@emotion/memoize": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", - "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", - "peer": true - }, - "node_modules/@emotion/react": { - "version": "11.11.4", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.4.tgz", - "integrity": "sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==", - "peer": true, - "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.11.0", - "@emotion/cache": "^11.11.0", - "@emotion/serialize": "^1.1.3", - "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", - "@emotion/utils": "^1.2.1", - "@emotion/weak-memoize": "^0.3.1", - "hoist-non-react-statics": "^3.3.1" - }, - "peerDependencies": { - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@emotion/serialize": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.4.tgz", - "integrity": "sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==", - "peer": true, - "dependencies": { - "@emotion/hash": "^0.9.1", - "@emotion/memoize": "^0.8.1", - "@emotion/unitless": "^0.8.1", - "@emotion/utils": "^1.2.1", - "csstype": "^3.0.2" - } - }, - "node_modules/@emotion/sheet": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", - "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==", - "peer": true - }, - "node_modules/@emotion/unitless": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", - "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", - "peer": true - }, - "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", - "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", - "peer": true, - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/@emotion/utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", - "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==", - "peer": true - }, - "node_modules/@emotion/weak-memoize": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", - "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==", - "peer": true - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", @@ -2791,13 +2692,13 @@ } }, "node_modules/@floating-ui/react": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.19.2.tgz", - "integrity": "sha512-JyNk4A0Ezirq8FlXECvRtQOX/iBe5Ize0W/pLkrZjfHW9GUV7Xnq6zm6fyZuQzaHHqEnVizmvlA96e1/CkZv+w==", + "version": "0.26.12", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.12.tgz", + "integrity": "sha512-D09o62HrWdIkstF2kGekIKAC0/N/Dl6wo3CQsnLcOmO3LkW6Ik8uIb3kw8JYkwxNCcg+uJ2bpWUiIijTBep05w==", "dependencies": { - "@floating-ui/react-dom": "^1.3.0", - "aria-hidden": "^1.1.3", - "tabbable": "^6.0.1" + "@floating-ui/react-dom": "^2.0.0", + "@floating-ui/utils": "^0.2.0", + "tabbable": "^6.0.0" }, "peerDependencies": { "react": ">=16.8.0", @@ -2805,11 +2706,11 @@ } }, "node_modules/@floating-ui/react-dom": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-1.3.0.tgz", - "integrity": "sha512-htwHm67Ji5E/pROEAr7f8IKFShuiCKHwUC/UY4vC3I5jiSvGFAYnSYiZO5MlGmads+QqvUkR9ANHEguGrDv72g==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", "dependencies": { - "@floating-ui/dom": "^1.2.1" + "@floating-ui/dom": "^1.6.1" }, "peerDependencies": { "react": ">=16.8.0", @@ -3034,120 +2935,107 @@ } }, "node_modules/@mantine/core": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@mantine/core/-/core-6.0.21.tgz", - "integrity": "sha512-Kx4RrRfv0I+cOCIcsq/UA2aWcYLyXgW3aluAuW870OdXnbII6qg7RW28D+r9D76SHPxWFKwIKwmcucAG08Divg==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.10.1.tgz", + "integrity": "sha512-l9ypojKN3PjwO1CSLIsqxi7mA25+7w+xc71Q+JuCCREI0tuGwkZsKbIOpuTATIJOjPh8ycLiW7QxX1LYsRTq6w==", + "license": "MIT", "dependencies": { - "@floating-ui/react": "^0.19.1", - "@mantine/styles": "6.0.21", - "@mantine/utils": "6.0.21", - "@radix-ui/react-scroll-area": "1.0.2", - "react-remove-scroll": "^2.5.5", - "react-textarea-autosize": "8.3.4" + "@floating-ui/react": "^0.26.9", + "clsx": "^2.1.1", + "react-number-format": "^5.3.1", + "react-remove-scroll": "^2.5.7", + "react-textarea-autosize": "8.5.3", + "type-fest": "^4.12.0" }, "peerDependencies": { - "@mantine/hooks": "6.0.21", - "react": ">=16.8.0", - "react-dom": ">=16.8.0" + "@mantine/hooks": "7.10.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "node_modules/@mantine/core/node_modules/type-fest": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.15.0.tgz", + "integrity": "sha512-tB9lu0pQpX5KJq54g+oHOLumOx+pMep4RaM6liXh2PKmVRFF+/vAtUP0ZaJ0kOySfVNjF6doBWPHhBhISKdlIA==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@mantine/dropzone": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-6.0.21.tgz", - "integrity": "sha512-v63tL4x7R1CvBNnxJVaVPhBVnQcfROQvyOV0xK/v0ZGNAzFxjJoiCRMGdlBjxnEawM0dRhNs/46ItpBgjQIr6g==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@mantine/dropzone/-/dropzone-7.10.1.tgz", + "integrity": "sha512-WzOLwMf8RJtakivPzSYBy3ZLuCtsZEbkJpAXNmM5PdsA8s9MKmY3jYA1MuZis1/NStAGm1d4twofH1KjGCMphg==", + "license": "MIT", "dependencies": { - "@mantine/utils": "6.0.21", - "react-dropzone": "14.2.3" + "react-dropzone-esm": "15.0.1" }, "peerDependencies": { - "@mantine/core": "6.0.21", - "@mantine/hooks": "6.0.21", - "react": ">=16.8.0", - "react-dom": ">=16.8.0" + "@mantine/core": "7.10.1", + "@mantine/hooks": "7.10.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" } }, "node_modules/@mantine/form": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@mantine/form/-/form-6.0.21.tgz", - "integrity": "sha512-d4tlxyZic7MSDnaPx/WliCX1sRFDkUd2nxx4MxxO2T4OSek0YDqTlSBCxeoveu60P+vrQQN5rbbsVsaOJBe4SQ==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@mantine/form/-/form-7.10.1.tgz", + "integrity": "sha512-mZwzg4GEWKEDKEIZu9FmSpGFzYYhFD2YArVOXUM0MMciUqX7yxSCon1PaPJxrV8ldc6FE+JLVI2+G2KVxJ3ZXA==", + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", - "klona": "^2.0.5" + "klona": "^2.0.6" }, "peerDependencies": { - "react": ">=16.8.0" + "react": "^18.2.0" } }, "node_modules/@mantine/hooks": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-6.0.21.tgz", - "integrity": "sha512-sYwt5wai25W6VnqHbS5eamey30/HD5dNXaZuaVEAJ2i2bBv8C0cCiczygMDpAFiSYdXoSMRr/SZ2CrrPTzeNew==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.10.1.tgz", + "integrity": "sha512-0EH9WBWUdtQLGU3Ak+csQ77EtUxI6pPNfwZdRJQWcaA3f8SFOLo9h9CGxiikFExerhvuCeUlaTf3s+TB9Op/rw==", + "license": "MIT", "peerDependencies": { - "react": ">=16.8.0" + "react": "^18.2.0" } }, "node_modules/@mantine/modals": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@mantine/modals/-/modals-6.0.21.tgz", - "integrity": "sha512-Gx2D/ZHMUuYF197JKMWey4K9FeGP9rxYp4lmAEXUrjXiST2fEhLZOdiD75KuOHXd1/sYAU9NcNRo9wXrlF/gUA==", - "dependencies": { - "@mantine/utils": "6.0.21" - }, + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@mantine/modals/-/modals-7.10.1.tgz", + "integrity": "sha512-2riQSNpVV7f0baizlqcggz9hx9/+y6SQTnW3zEkl/RIkuyK9dpeMFUG6M+M8ntwP79b7x9n7Em9PMWxRbgi28A==", + "license": "MIT", "peerDependencies": { - "@mantine/core": "6.0.21", - "@mantine/hooks": "6.0.21", - "react": ">=16.8.0", - "react-dom": ">=16.8.0" + "@mantine/core": "7.10.1", + "@mantine/hooks": "7.10.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" } }, "node_modules/@mantine/notifications": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-6.0.21.tgz", - "integrity": "sha512-qsrqxuJHK8b67sf9Pfk+xyhvpf9jMsivW8vchfnJfjv7yz1lLvezjytMFp4fMDoYhjHnDPOEc/YFockK4muhOw==", + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.10.1.tgz", + "integrity": "sha512-cx3JR3BJzEzH6t2EF1ysrWVY/rdJk0WbSBQo/qFamJd2sbU+8XAHriI8Cx6hNo7uRGCwd8VGAj7Cf3aWK2VC5A==", + "license": "MIT", "dependencies": { - "@mantine/utils": "6.0.21", - "react-transition-group": "4.4.2" + "@mantine/store": "7.10.1", + "react-transition-group": "4.4.5" }, "peerDependencies": { - "@mantine/core": "6.0.21", - "@mantine/hooks": "6.0.21", - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@mantine/styles": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@mantine/styles/-/styles-6.0.21.tgz", - "integrity": "sha512-PVtL7XHUiD/B5/kZ/QvZOZZQQOj12QcRs3Q6nPoqaoPcOX5+S7bMZLMH0iLtcGq5OODYk0uxlvuJkOZGoPj8Mg==", - "dependencies": { - "clsx": "1.1.1", - "csstype": "3.0.9" - }, - "peerDependencies": { - "@emotion/react": ">=11.9.0", - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@mantine/styles/node_modules/clsx": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", - "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==", - "engines": { - "node": ">=6" + "@mantine/core": "7.10.1", + "@mantine/hooks": "7.10.1", + "react": "^18.2.0", + "react-dom": "^18.2.0" } }, - "node_modules/@mantine/styles/node_modules/csstype": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.9.tgz", - "integrity": "sha512-rpw6JPxK6Rfg1zLOYCSwle2GFOOsnjmDYDaBwEcwoOg4qlsIVCN789VkBZDJAGi4T07gI4YSutR43t9Zz4Lzuw==" - }, - "node_modules/@mantine/utils": { - "version": "6.0.21", - "resolved": "https://registry.npmjs.org/@mantine/utils/-/utils-6.0.21.tgz", - "integrity": "sha512-33RVDRop5jiWFao3HKd3Yp7A9mEq4HAJxJPTuYm1NkdqX6aTKOQK7wT8v8itVodBp+sb4cJK6ZVdD1UurK/txQ==", + "node_modules/@mantine/store": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@mantine/store/-/store-7.10.1.tgz", + "integrity": "sha512-KrGBsSoMsfrYeLxPwf5rFv0s2Nl/4wf+AaF/U1SpQrMgPI8vYokPXx52Wp3jCmlo12NCZnCIG+/6YHAdTWH1qQ==", + "license": "MIT", "peerDependencies": { - "react": ">=16.8.0" + "react": "^18.2.0" } }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { @@ -3222,137 +3110,6 @@ "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==", "dev": true }, - "node_modules/@radix-ui/number": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.0.tgz", - "integrity": "sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA==", - "dependencies": { - "@babel/runtime": "^7.13.10" - } - }, - "node_modules/@radix-ui/primitive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz", - "integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==", - "dependencies": { - "@babel/runtime": "^7.13.10" - } - }, - "node_modules/@radix-ui/react-compose-refs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz", - "integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@radix-ui/react-context": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz", - "integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@radix-ui/react-direction": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.0.tgz", - "integrity": "sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@radix-ui/react-presence": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.0.tgz", - "integrity": "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.0", - "@radix-ui/react-use-layout-effect": "1.0.0" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@radix-ui/react-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.1.tgz", - "integrity": "sha512-fHbmislWVkZaIdeF6GZxF0A/NH/3BjrGIYj+Ae6eTmTCr7EB0RQAAVEiqsXK6p3/JcRqVSBQoceZroj30Jj3XA==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-slot": "1.0.1" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@radix-ui/react-scroll-area": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.0.2.tgz", - "integrity": "sha512-k8VseTxI26kcKJaX0HPwkvlNBPTs56JRdYzcZ/vzrNUkDlvXBy8sMc7WvCpYzZkHgb+hd72VW9MqkqecGtuNgg==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/number": "1.0.0", - "@radix-ui/primitive": "1.0.0", - "@radix-ui/react-compose-refs": "1.0.0", - "@radix-ui/react-context": "1.0.0", - "@radix-ui/react-direction": "1.0.0", - "@radix-ui/react-presence": "1.0.0", - "@radix-ui/react-primitive": "1.0.1", - "@radix-ui/react-use-callback-ref": "1.0.0", - "@radix-ui/react-use-layout-effect": "1.0.0" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz", - "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.0" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz", - "integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz", - "integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0" - } - }, "node_modules/@remix-run/router": { "version": "1.15.3", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", @@ -3899,7 +3656,8 @@ "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "dev": true }, "node_modules/@types/prop-types": { "version": "15.7.12", @@ -4640,17 +4398,6 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "node_modules/aria-hidden": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz", - "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/aria-query": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", @@ -4848,14 +4595,6 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, - "node_modules/attr-accept": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", - "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==", - "engines": { - "node": ">=4" - } - }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -4903,6 +4642,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -5107,10 +4847,20 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "engines": { "node": ">=6" } }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001607", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001607.tgz", @@ -5229,10 +4979,10 @@ } }, "node_modules/clsx": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", - "integrity": "sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg==", - "dev": true, + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -5286,12 +5036,6 @@ "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", "dev": true }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "peer": true - }, "node_modules/core-js-compat": { "version": "3.36.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.36.1.tgz", @@ -5309,6 +5053,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -5324,6 +5069,7 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, "engines": { "node": ">= 6" } @@ -5348,6 +5094,18 @@ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", "dev": true }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/cssstyle": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.0.1.tgz", @@ -5779,6 +5537,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, "dependencies": { "is-arrayish": "^0.2.1" } @@ -5992,6 +5751,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "engines": { "node": ">=10" }, @@ -6606,17 +6366,6 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/file-selector": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", - "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==", - "dependencies": { - "tslib": "^2.4.0" - }, - "engines": { - "node": ">= 12" - } - }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -6629,12 +6378,6 @@ "node": ">=8" } }, - "node_modules/find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "peer": true - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -6758,6 +6501,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7035,6 +6779,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -7042,15 +6787,6 @@ "node": ">= 0.4" } }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "peer": true, - "dependencies": { - "react-is": "^16.7.0" - } - }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", @@ -7150,6 +6886,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -7243,7 +6980,8 @@ "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true }, "node_modules/is-async-function": { "version": "2.0.0", @@ -7316,6 +7054,7 @@ "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, "dependencies": { "hasown": "^2.0.0" }, @@ -7934,7 +7673,8 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -8053,7 +7793,8 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true }, "node_modules/local-pkg": { "version": "0.5.0", @@ -8621,6 +8362,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -8632,6 +8374,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -8686,12 +8429,14 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, "engines": { "node": ">=8" } @@ -8714,7 +8459,8 @@ "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true }, "node_modules/picomatch": { "version": "2.3.1", @@ -8776,6 +8522,108 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-mixins": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/postcss-mixins/-/postcss-mixins-9.0.4.tgz", + "integrity": "sha512-XVq5jwQJDRu5M1XGkdpgASqLk37OqkH4JCFDXl/Dn7janOJjCTEKL+36cnRVy7bMtoBzALfO7bV7nTIsFnUWLA==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.11", + "postcss-js": "^4.0.0", + "postcss-simple-vars": "^7.0.0", + "sugarss": "^4.0.1" + }, + "engines": { + "node": ">=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-preset-mantine": { + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/postcss-preset-mantine/-/postcss-preset-mantine-1.14.4.tgz", + "integrity": "sha512-T1K3MVhU1hA9mJWfqoGvMcK5WKcHpVi4JUX6AYTbESvp78WneB/KFONUi+eXDG9Lpw62W/KNxEYl1ic3Dpm88w==", + "dev": true, + "dependencies": { + "postcss-mixins": "^9.0.4", + "postcss-nested": "^6.0.1" + }, + "peerDependencies": { + "postcss": ">=8.0.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-simple-vars": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-simple-vars/-/postcss-simple-vars-7.0.1.tgz", + "integrity": "sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A==", + "dev": true, + "engines": { + "node": ">=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.1" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -8967,13 +8815,11 @@ "react": "^18.2.0" } }, - "node_modules/react-dropzone": { - "version": "14.2.3", - "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz", - "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==", + "node_modules/react-dropzone-esm": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/react-dropzone-esm/-/react-dropzone-esm-15.0.1.tgz", + "integrity": "sha512-RdeGpqwHnoV/IlDFpQji7t7pTtlC2O1i/Br0LWkRZ9hYtLyce814S71h5NolnCZXsIN5wrZId6+8eQj2EBnEzg==", "dependencies": { - "attr-accept": "^2.2.2", - "file-selector": "^0.6.0", "prop-types": "^15.8.1" }, "engines": { @@ -8988,6 +8834,18 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-number-format": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.3.4.tgz", + "integrity": "sha512-2hHN5mbLuCDUx19bv0Q8wet67QqYK6xmtLQeY5xx+h7UXiMmRtaCwqko4mMPoKXLc6xAzwRrutg8XbTRlsfjRg==", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-query": { "version": "3.39.3", "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz", @@ -9112,22 +8970,6 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/react-smooth/node_modules/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" - } - }, "node_modules/react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -9164,11 +9006,11 @@ } }, "node_modules/react-textarea-autosize": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.3.4.tgz", - "integrity": "sha512-CdtmP8Dc19xL8/R6sWvtknD/eCXkQr30dtvC4VmGInhRsfF8X/ihXCq6+9l9qbxmKRiq407/7z5fxE7cVWQNgQ==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz", + "integrity": "sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==", "dependencies": { - "@babel/runtime": "^7.10.2", + "@babel/runtime": "^7.20.13", "use-composed-ref": "^1.3.0", "use-latest": "^1.2.1" }, @@ -9180,9 +9022,9 @@ } }, "node_modules/react-transition-group": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", - "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", @@ -9375,6 +9217,7 @@ "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -9391,6 +9234,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "engines": { "node": ">=4" } @@ -9701,15 +9545,6 @@ "node": ">=10.0.0" } }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", @@ -9905,11 +9740,21 @@ "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", "dev": true }, - "node_modules/stylis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", - "peer": true + "node_modules/sugarss": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-4.0.1.tgz", + "integrity": "sha512-WCjS5NfuVJjkQzK10s8WOBY+hhDxxNt/N6ZaGwxFZ+wN3/lKKFSaaKUNecULcTTvE4urLcKaZFQD8vO0mOZujw==", + "dev": true, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } }, "node_modules/supports-color": { "version": "7.2.0", @@ -9927,6 +9772,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, "engines": { "node": ">= 0.4" }, @@ -9999,6 +9845,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, "engines": { "node": ">=4" } @@ -10432,6 +10279,12 @@ } } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, "node_modules/v8-to-istanbul": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index eadb26496..971065e4d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,12 +13,12 @@ }, "private": true, "dependencies": { - "@mantine/core": "^6.0.21", - "@mantine/dropzone": "^6.0.21", - "@mantine/form": "^6.0.21", - "@mantine/hooks": "^6.0.21", - "@mantine/modals": "^6.0.21", - "@mantine/notifications": "^6.0.21", + "@mantine/core": "^7.10.1", + "@mantine/dropzone": "^7.10.1", + "@mantine/form": "^7.10.1", + "@mantine/hooks": "^7.10.1", + "@mantine/modals": "^7.10.1", + "@mantine/notifications": "^7.10.1", "axios": "^1.6.8", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -53,6 +53,8 @@ "husky": "^9.0.11", "jsdom": "^24.0.0", "lodash": "^4.17.21", + "postcss-preset-mantine": "^1.14.4", + "postcss-simple-vars": "^7.0.1", "prettier": "^3.2.5", "prettier-plugin-organize-imports": "^3.2.4", "pretty-quick": "^4.0.0", diff --git a/frontend/postcss.config.cjs b/frontend/postcss.config.cjs new file mode 100644 index 000000000..e817f567b --- /dev/null +++ b/frontend/postcss.config.cjs @@ -0,0 +1,14 @@ +module.exports = { + plugins: { + "postcss-preset-mantine": {}, + "postcss-simple-vars": { + variables: { + "mantine-breakpoint-xs": "36em", + "mantine-breakpoint-sm": "48em", + "mantine-breakpoint-md": "62em", + "mantine-breakpoint-lg": "75em", + "mantine-breakpoint-xl": "88em", + }, + }, + }, +}; diff --git a/frontend/src/App/Header.module.scss b/frontend/src/App/Header.module.scss new file mode 100644 index 000000000..85b3661a9 --- /dev/null +++ b/frontend/src/App/Header.module.scss @@ -0,0 +1,9 @@ +.header { + @include light { + color: var(--mantine-color-gray-0); + } + + @include dark { + color: var(--mantine-color-dark-0); + } +} diff --git a/frontend/src/App/Header.tsx b/frontend/src/App/Header.tsx index c15071045..6bb47f5b1 100644 --- a/frontend/src/App/Header.tsx +++ b/frontend/src/App/Header.tsx @@ -1,6 +1,5 @@ import { useSystem, useSystemSettings } from "@/apis/hooks"; import { Action, Search } from "@/components"; -import { Layout } from "@/constants"; import { useNavbar } from "@/contexts/Navbar"; import { useIsOnline } from "@/contexts/Online"; import { Environment, useGotoHomepage } from "@/utilities"; @@ -12,27 +11,16 @@ import { import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Anchor, + AppShell, Avatar, Badge, Burger, Divider, Group, - Header, - MediaQuery, Menu, - createStyles, } from "@mantine/core"; import { FunctionComponent } from "react"; - -const useStyles = createStyles((theme) => { - const headerBackgroundColor = - theme.colorScheme === "light" ? theme.colors.gray[0] : theme.colors.dark[4]; - return { - header: { - backgroundColor: headerBackgroundColor, - }, - }; -}); +import styles from "./Header.module.scss"; const AppHeader: FunctionComponent = () => { const { data: settings } = useSystemSettings(); @@ -47,39 +35,28 @@ const AppHeader: FunctionComponent = () => { const goHome = useGotoHomepage(); - const { classes } = useStyles(); - return ( - <Header p="md" height={Layout.HEADER_HEIGHT} className={classes.header}> - <Group position="apart" noWrap> - <Group noWrap> - <MediaQuery - smallerThan={Layout.MOBILE_BREAKPOINT} - styles={{ display: "none" }} - > - <Anchor onClick={goHome}> - <Avatar - alt="brand" - size={32} - src={`${Environment.baseUrl}/images/logo64.png`} - ></Avatar> - </Anchor> - </MediaQuery> - <MediaQuery - largerThan={Layout.MOBILE_BREAKPOINT} - styles={{ display: "none" }} - > - <Burger - opened={showed} - onClick={() => show(!showed)} - size="sm" - ></Burger> - </MediaQuery> + <AppShell.Header p="md" className={styles.header}> + <Group justify="space-between" wrap="nowrap"> + <Group wrap="nowrap"> + <Anchor onClick={goHome} visibleFrom="sm"> + <Avatar + alt="brand" + size={32} + src={`${Environment.baseUrl}/images/logo64.png`} + ></Avatar> + </Anchor> + <Burger + opened={showed} + onClick={() => show(!showed)} + size="sm" + hiddenFrom="sm" + ></Burger> <Badge size="lg" radius="sm"> Bazarr </Badge> </Group> - <Group spacing="xs" position="right" noWrap> + <Group gap="xs" justify="right" wrap="nowrap"> <Search></Search> <Menu> <Menu.Target> @@ -95,13 +72,13 @@ const AppHeader: FunctionComponent = () => { </Menu.Target> <Menu.Dropdown> <Menu.Item - icon={<FontAwesomeIcon icon={faArrowRotateLeft} />} + leftSection={<FontAwesomeIcon icon={faArrowRotateLeft} />} onClick={() => restart()} > Restart </Menu.Item> <Menu.Item - icon={<FontAwesomeIcon icon={faPowerOff} />} + leftSection={<FontAwesomeIcon icon={faPowerOff} />} onClick={() => shutdown()} > Shutdown @@ -114,7 +91,7 @@ const AppHeader: FunctionComponent = () => { </Menu> </Group> </Group> - </Header> + </AppShell.Header> ); }; diff --git a/frontend/src/App/Navbar.module.scss b/frontend/src/App/Navbar.module.scss new file mode 100644 index 000000000..ddb444e4d --- /dev/null +++ b/frontend/src/App/Navbar.module.scss @@ -0,0 +1,56 @@ +.anchor { + border-color: var(--mantine-color-gray-5); + text-decoration: none; + + @include dark { + border-color: var(--mantine-color-dark-5); + } + + &.active { + border-left: 2px solid $color-brand-4; + background-color: var(--mantine-color-gray-1); + + @include dark { + border-left: 2px solid $color-brand-8; + background-color: var(--mantine-color-dark-8); + } + } + + &.hover { + background-color: var(--mantine-color-gray-0); + + @include dark { + background-color: var(--mantine-color-dark-7); + } + } +} + +.badge { + margin-left: auto; + text-decoration: none; + box-shadow: var(--mantine-shadow-xs); +} + +.icon { + width: 1.4rem; + margin-right: var(--mantine-spacing-xs); +} + +.nav { + background-color: var(--mantine-color-gray-2); + + @include dark { + background-color: var(--mantine-color-dark-8); + } +} + +.text { + display: inline-flex; + align-items: center; + width: 100%; + color: var(--mantine-color-gray-8); + + @include dark { + color: var(--mantine-color-gray-5); + } +} diff --git a/frontend/src/App/Navbar.tsx b/frontend/src/App/Navbar.tsx index c626dc257..8254f2fc0 100644 --- a/frontend/src/App/Navbar.tsx +++ b/frontend/src/App/Navbar.tsx @@ -1,5 +1,4 @@ import { Action } from "@/components"; -import { Layout } from "@/constants"; import { useNavbar } from "@/contexts/Navbar"; import { useRouteItems } from "@/Router"; import { CustomRouteObject, Route } from "@/Router/type"; @@ -14,19 +13,19 @@ import { import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Anchor, + AppShell, Badge, Collapse, - createStyles, Divider, Group, - Navbar as MantineNavbar, Stack, Text, + useComputedColorScheme, useMantineColorScheme, } from "@mantine/core"; import { useHover } from "@mantine/hooks"; import clsx from "clsx"; -import { +import React, { createContext, FunctionComponent, useContext, @@ -35,6 +34,7 @@ import { useState, } from "react"; import { matchPath, NavLink, RouteObject, useLocation } from "react-router-dom"; +import styles from "./Navbar.module.scss"; const Selection = createContext<{ selection: string | null; @@ -97,11 +97,12 @@ function useIsActive(parent: string, route: RouteObject) { } const AppNavbar: FunctionComponent = () => { - const { showed } = useNavbar(); const [selection, select] = useState<string | null>(null); - const { colorScheme, toggleColorScheme } = useMantineColorScheme(); - const dark = colorScheme === "dark"; + const { toggleColorScheme } = useMantineColorScheme(); + const computedColorScheme = useComputedColorScheme("light"); + + const dark = computedColorScheme === "dark"; const routes = useRouteItems(); @@ -111,23 +112,10 @@ const AppNavbar: FunctionComponent = () => { }, [pathname]); return ( - <MantineNavbar - p="xs" - hiddenBreakpoint={Layout.MOBILE_BREAKPOINT} - hidden={!showed} - width={{ [Layout.MOBILE_BREAKPOINT]: Layout.NAVBAR_WIDTH }} - styles={(theme) => ({ - root: { - backgroundColor: - theme.colorScheme === "light" - ? theme.colors.gray[2] - : theme.colors.dark[6], - }, - })} - > + <AppShell.Navbar p="xs" className={styles.nav}> <Selection.Provider value={{ selection, select }}> - <MantineNavbar.Section grow> - <Stack spacing={0}> + <AppShell.Section grow> + <Stack gap={0}> {routes.map((route, idx) => ( <RouteItem key={BuildKey("nav", idx)} @@ -136,10 +124,10 @@ const AppNavbar: FunctionComponent = () => { ></RouteItem> ))} </Stack> - </MantineNavbar.Section> + </AppShell.Section> <Divider></Divider> - <MantineNavbar.Section mt="xs"> - <Group spacing="xs"> + <AppShell.Section mt="xs"> + <Group gap="xs"> <Action label="Change Theme" color={dark ? "yellow" : "indigo"} @@ -159,9 +147,9 @@ const AppNavbar: FunctionComponent = () => { ></Action> </Anchor> </Group> - </MantineNavbar.Section> + </AppShell.Section> </Selection.Provider> - </MantineNavbar> + </AppShell.Navbar> ); }; @@ -186,7 +174,7 @@ const RouteItem: FunctionComponent<{ if (children !== undefined) { const elements = ( - <Stack spacing={0}> + <Stack gap={0}> {children.map((child, idx) => ( <RouteItem parent={link} @@ -199,7 +187,7 @@ const RouteItem: FunctionComponent<{ if (name) { return ( - <Stack spacing={0}> + <Stack gap={0}> <NavbarItem primary name={name} @@ -244,53 +232,6 @@ const RouteItem: FunctionComponent<{ } }; -const useStyles = createStyles((theme) => { - const borderColor = - theme.colorScheme === "light" ? theme.colors.gray[5] : theme.colors.dark[4]; - - const activeBorderColor = - theme.colorScheme === "light" - ? theme.colors.brand[4] - : theme.colors.brand[8]; - - const activeBackgroundColor = - theme.colorScheme === "light" ? theme.colors.gray[1] : theme.colors.dark[8]; - - const hoverBackgroundColor = - theme.colorScheme === "light" ? theme.colors.gray[0] : theme.colors.dark[7]; - - const textColor = - theme.colorScheme === "light" ? theme.colors.gray[8] : theme.colors.gray[5]; - - return { - text: { - display: "inline-flex", - alignItems: "center", - width: "100%", - color: textColor, - }, - anchor: { - textDecoration: "none", - borderLeft: `2px solid ${borderColor}`, - }, - active: { - backgroundColor: activeBackgroundColor, - borderLeft: `2px solid ${activeBorderColor}`, - boxShadow: theme.shadows.xs, - }, - hover: { - backgroundColor: hoverBackgroundColor, - }, - icon: { width: "1.4rem", marginRight: theme.spacing.xs }, - badge: { - marginLeft: "auto", - textDecoration: "none", - boxShadow: theme.shadows.xs, - color: textColor, - }, - }; -}); - interface NavbarItemProps { name: string; link: string; @@ -308,8 +249,6 @@ const NavbarItem: FunctionComponent<NavbarItemProps> = ({ onClick, primary = false, }) => { - const { classes } = useStyles(); - const { show } = useNavbar(); const { ref, hovered } = useHover(); @@ -335,9 +274,9 @@ const NavbarItem: FunctionComponent<NavbarItemProps> = ({ }} className={({ isActive }) => clsx( - clsx(classes.anchor, { - [classes.active]: isActive, - [classes.hover]: hovered, + clsx(styles.anchor, { + [styles.active]: isActive, + [styles.hover]: hovered, }), ) } @@ -347,18 +286,19 @@ const NavbarItem: FunctionComponent<NavbarItemProps> = ({ inline p="xs" size="sm" - weight={primary ? "bold" : "normal"} - className={classes.text} + fw={primary ? "bold" : "normal"} + className={styles.text} + span > {icon && ( <FontAwesomeIcon - className={classes.icon} + className={styles.icon} icon={icon} ></FontAwesomeIcon> )} {name} - {shouldHideBadge === false && ( - <Badge className={classes.badge} radius="xs"> + {!shouldHideBadge && ( + <Badge className={styles.badge} radius="xs"> {badge} </Badge> )} diff --git a/frontend/src/App/ThemeLoader.tsx b/frontend/src/App/ThemeLoader.tsx new file mode 100644 index 000000000..2bc7e4005 --- /dev/null +++ b/frontend/src/App/ThemeLoader.tsx @@ -0,0 +1,39 @@ +import { useCallback, useEffect, useState } from "react"; +import { MantineColorScheme, useMantineColorScheme } from "@mantine/core"; +import { useSystemSettings } from "@/apis/hooks"; + +const ThemeProvider = () => { + const [localScheme, setLocalScheme] = useState<MantineColorScheme | null>( + null, + ); + const { setColorScheme } = useMantineColorScheme(); + + const settings = useSystemSettings(); + + const settingsColorScheme = settings.data?.general + .theme as MantineColorScheme; + + const setScheme = useCallback( + (colorScheme: MantineColorScheme) => { + setColorScheme(colorScheme); + }, + [setColorScheme], + ); + + useEffect(() => { + if (!settingsColorScheme) { + return; + } + + if (localScheme === settingsColorScheme) { + return; + } + + setScheme(settingsColorScheme); + setLocalScheme(settingsColorScheme); + }, [settingsColorScheme, setScheme, localScheme]); + + return <></>; +}; + +export default ThemeProvider; diff --git a/frontend/src/App/ThemeProvider.tsx b/frontend/src/App/ThemeProvider.tsx new file mode 100644 index 000000000..d3a39cdb0 --- /dev/null +++ b/frontend/src/App/ThemeProvider.tsx @@ -0,0 +1,61 @@ +import { + ActionIcon, + AppShell, + Badge, + Button, + createTheme, + MantineProvider, +} from "@mantine/core"; +import { FunctionComponent, PropsWithChildren } from "react"; +import ThemeLoader from "@/App/ThemeLoader"; +import "@mantine/core/styles.layer.css"; +import "@mantine/notifications/styles.layer.css"; +import styleVars from "@/assets/_variables.module.scss"; +import buttonClasses from "@/assets/button.module.scss"; +import actionIconClasses from "@/assets/action_icon.module.scss"; +import appShellClasses from "@/assets/app_shell.module.scss"; +import badgeClasses from "@/assets/badge.module.scss"; + +const themeProvider = createTheme({ + fontFamily: "Roboto, open sans, Helvetica Neue, Helvetica, Arial, sans-serif", + colors: { + brand: [ + styleVars.colorBrand0, + styleVars.colorBrand1, + styleVars.colorBrand2, + styleVars.colorBrand3, + styleVars.colorBrand4, + styleVars.colorBrand5, + styleVars.colorBrand6, + styleVars.colorBrand7, + styleVars.colorBrand8, + styleVars.colorBrand9, + ], + }, + primaryColor: "brand", + components: { + ActionIcon: ActionIcon.extend({ + classNames: actionIconClasses, + }), + AppShell: AppShell.extend({ + classNames: appShellClasses, + }), + Badge: Badge.extend({ + classNames: badgeClasses, + }), + Button: Button.extend({ + classNames: buttonClasses, + }), + }, +}); + +const ThemeProvider: FunctionComponent<PropsWithChildren> = ({ children }) => { + return ( + <MantineProvider theme={themeProvider} defaultColorScheme="auto"> + <ThemeLoader /> + {children} + </MantineProvider> + ); +}; + +export default ThemeProvider; diff --git a/frontend/src/App/index.tsx b/frontend/src/App/index.tsx index 4e09a97da..1b27734f4 100644 --- a/frontend/src/App/index.tsx +++ b/frontend/src/App/index.tsx @@ -1,7 +1,6 @@ import AppNavbar from "@/App/Navbar"; import { RouterNames } from "@/Router/RouterNames"; import ErrorBoundary from "@/components/ErrorBoundary"; -import { Layout } from "@/constants"; import NavbarProvider from "@/contexts/Navbar"; import OnlineProvider from "@/contexts/Online"; import { notification } from "@/modules/task"; @@ -13,6 +12,7 @@ import { showNotification } from "@mantine/notifications"; import { FunctionComponent, useEffect, useState } from "react"; import { Outlet, useNavigate } from "react-router-dom"; import AppHeader from "./Header"; +import styleVars from "@/assets/_variables.module.scss"; const App: FunctionComponent = () => { const navigate = useNavigate(); @@ -55,13 +55,19 @@ const App: FunctionComponent = () => { <NavbarProvider value={{ showed: navbar, show: setNavbar }}> <OnlineProvider value={{ online, setOnline }}> <AppShell - navbarOffsetBreakpoint={Layout.MOBILE_BREAKPOINT} - header={<AppHeader></AppHeader>} - navbar={<AppNavbar></AppNavbar>} + navbar={{ + width: styleVars.navBarWidth, + breakpoint: "sm", + collapsed: { mobile: !navbar }, + }} + header={{ height: { base: styleVars.headerHeight } }} padding={0} - fixed > - <Outlet></Outlet> + <AppHeader></AppHeader> + <AppNavbar></AppNavbar> + <AppShell.Main> + <Outlet></Outlet> + </AppShell.Main> </AppShell> </OnlineProvider> </NavbarProvider> diff --git a/frontend/src/App/theme.tsx b/frontend/src/App/theme.tsx deleted file mode 100644 index 947b4f7a8..000000000 --- a/frontend/src/App/theme.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { useSystemSettings } from "@/apis/hooks"; -import { - ColorScheme, - ColorSchemeProvider, - createEmotionCache, - MantineProvider, - MantineThemeOverride, -} from "@mantine/core"; -import { useColorScheme } from "@mantine/hooks"; -import { - FunctionComponent, - PropsWithChildren, - useCallback, - useEffect, - useState, -} from "react"; - -const theme: MantineThemeOverride = { - fontFamily: "Roboto, open sans, Helvetica Neue, Helvetica, Arial, sans-serif", - colors: { - brand: [ - "#F8F0FC", - "#F3D9FA", - "#EEBEFA", - "#E599F7", - "#DA77F2", - "#CC5DE8", - "#BE4BDB", - "#AE3EC9", - "#9C36B5", - "#862E9C", - ], - }, - primaryColor: "brand", -}; - -function useAutoColorScheme() { - const settings = useSystemSettings(); - const settingsColorScheme = settings.data?.general.theme; - - let preferredColorScheme: ColorScheme = useColorScheme(); - switch (settingsColorScheme) { - case "light": - preferredColorScheme = "light" as ColorScheme; - break; - case "dark": - preferredColorScheme = "dark" as ColorScheme; - break; - } - - const [colorScheme, setColorScheme] = useState(preferredColorScheme); - - // automatically switch dark/light theme - useEffect(() => { - setColorScheme(preferredColorScheme); - }, [preferredColorScheme]); - - const toggleColorScheme = useCallback((value?: ColorScheme) => { - setColorScheme((scheme) => value || (scheme === "dark" ? "light" : "dark")); - }, []); - - return { colorScheme, setColorScheme, toggleColorScheme }; -} - -const emotionCache = createEmotionCache({ key: "bazarr" }); - -const ThemeProvider: FunctionComponent<PropsWithChildren> = ({ children }) => { - const { colorScheme, toggleColorScheme } = useAutoColorScheme(); - - return ( - <ColorSchemeProvider - colorScheme={colorScheme} - toggleColorScheme={toggleColorScheme} - > - <MantineProvider - withGlobalStyles - withNormalizeCSS - theme={{ colorScheme, ...theme }} - emotionCache={emotionCache} - > - {children} - </MantineProvider> - </ColorSchemeProvider> - ); -}; - -export default ThemeProvider; diff --git a/frontend/src/Router/index.tsx b/frontend/src/Router/index.tsx index 335cf2d75..efed2edb2 100644 --- a/frontend/src/Router/index.tsx +++ b/frontend/src/Router/index.tsx @@ -53,7 +53,9 @@ import Redirector from "./Redirector"; import { RouterNames } from "./RouterNames"; import { CustomRouteObject } from "./type"; -const HistoryStats = lazy(() => import("@/pages/History/Statistics")); +const HistoryStats = lazy( + () => import("@/pages/History/Statistics/HistoryStats"), +); const SystemStatusView = lazy(() => import("@/pages/System/Status")); function useRoutes(): CustomRouteObject[] { diff --git a/frontend/src/assets/_bazarr.scss b/frontend/src/assets/_bazarr.scss new file mode 100644 index 000000000..6c23aac1a --- /dev/null +++ b/frontend/src/assets/_bazarr.scss @@ -0,0 +1,40 @@ +$color-brand-0: #f8f0fc; +$color-brand-1: #f3d9fa; +$color-brand-2: #eebefa; +$color-brand-3: #e599f7; +$color-brand-4: #da77f2; +$color-brand-5: #cc5de8; +$color-brand-6: #be4bdb; +$color-brand-7: #ae3ec9; +$color-brand-8: #9c36b5; +$color-brand-9: #862e9c; + +$header-height: 64px; + +:global { + .table-long-break { + overflow-wrap: anywhere; + } + + .table-primary { + display: inline-block; + + font-size: var(--mantine-font-size-sm); + + @include smaller-than($mantine-breakpoint-sm) { + min-width: 12rem; + } + } + + .table-no-wrap { + white-space: nowrap; + } + + .table-select { + display: inline-block; + + @include smaller-than($mantine-breakpoint-sm) { + min-width: 10rem; + } + } +} diff --git a/frontend/src/assets/_mantine.scss b/frontend/src/assets/_mantine.scss new file mode 100644 index 000000000..93412c636 --- /dev/null +++ b/frontend/src/assets/_mantine.scss @@ -0,0 +1,61 @@ +@use "sass:math"; + +$mantine-breakpoint-xs: "36em"; +$mantine-breakpoint-sm: "48em"; +$mantine-breakpoint-md: "62em"; +$mantine-breakpoint-lg: "75em"; +$mantine-breakpoint-xl: "88em"; + +@function rem($value) { + @return #{math.div(math.div($value, $value * 0 + 1), 16)}rem; +} + +@mixin light { + [data-mantine-color-scheme="light"] & { + @content; + } +} + +@mixin dark { + [data-mantine-color-scheme="dark"] & { + @content; + } +} + +@mixin hover { + @media (hover: hover) { + &:hover { + @content; + } + } + + @media (hover: none) { + &:active { + @content; + } + } +} + +@mixin smaller-than($breakpoint) { + @media (max-width: $breakpoint) { + @content; + } +} + +@mixin larger-than($breakpoint) { + @media (min-width: $breakpoint) { + @content; + } +} + +@mixin rtl { + [dir="rtl"] & { + @content; + } +} + +@mixin ltr { + [dir="ltr"] & { + @content; + } +} diff --git a/frontend/src/assets/_variables.module.scss b/frontend/src/assets/_variables.module.scss new file mode 100644 index 000000000..262d285b2 --- /dev/null +++ b/frontend/src/assets/_variables.module.scss @@ -0,0 +1,18 @@ +$navbar-width: 200; + +:export { + colorBrand0: $color-brand-0; + colorBrand1: $color-brand-1; + colorBrand2: $color-brand-2; + colorBrand3: $color-brand-3; + colorBrand4: $color-brand-4; + colorBrand5: $color-brand-5; + colorBrand6: $color-brand-6; + colorBrand7: $color-brand-7; + colorBrand8: $color-brand-8; + colorBrand9: $color-brand-9; + + headerHeight: $header-height; + + navBarWidth: $navbar-width; +} diff --git a/frontend/src/assets/action_icon.module.scss b/frontend/src/assets/action_icon.module.scss new file mode 100644 index 000000000..c4bf2eefa --- /dev/null +++ b/frontend/src/assets/action_icon.module.scss @@ -0,0 +1,14 @@ +@layer mantine { + .root { + &[data-variant="light"] { + color: var(--mantine-color-dark-0); + } + + @include light { + &[data-variant="light"] { + background-color: var(--mantine-color-gray-1); + color: var(--mantine-color-dark-2); + } + } + } +} diff --git a/frontend/src/assets/app_shell.module.scss b/frontend/src/assets/app_shell.module.scss new file mode 100644 index 000000000..b027c771a --- /dev/null +++ b/frontend/src/assets/app_shell.module.scss @@ -0,0 +1,5 @@ +.main { + @include dark { + background-color: rgb(26, 27, 30); + } +} diff --git a/frontend/src/assets/badge.module.scss b/frontend/src/assets/badge.module.scss new file mode 100644 index 000000000..830da2927 --- /dev/null +++ b/frontend/src/assets/badge.module.scss @@ -0,0 +1,8 @@ +.root { + background-color: var(--mantine-color-grape-light); + + @include light { + color: var(--mantine-color-dark-filled); + background-color: var(--mantine-color-grape-light); + } +} diff --git a/frontend/src/assets/button.module.scss b/frontend/src/assets/button.module.scss new file mode 100644 index 000000000..4ef306883 --- /dev/null +++ b/frontend/src/assets/button.module.scss @@ -0,0 +1,12 @@ +@layer mantine { + .root { + @include dark { + color: var(--mantine-color-dark-0); + } + + &[data-variant="danger"] { + background-color: var(--mantine-color-red-9); + color: var(--mantine-color-red-0); + } + } +} diff --git a/frontend/src/components/Search.module.scss b/frontend/src/components/Search.module.scss new file mode 100644 index 000000000..2c42098eb --- /dev/null +++ b/frontend/src/components/Search.module.scss @@ -0,0 +1,9 @@ +.result { + @include light { + color: var(--mantine-color-dark-8); + } + + @include dark { + color: var(--mantine-color-gray-1); + } +} diff --git a/frontend/src/components/Search.tsx b/frontend/src/components/Search.tsx index bc4a9f8d3..75341e0a8 100644 --- a/frontend/src/components/Search.tsx +++ b/frontend/src/components/Search.tsx @@ -5,11 +5,12 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Anchor, Autocomplete, - createStyles, - SelectItemProps, + ComboboxItem, + OptionsFilter, } from "@mantine/core"; -import { forwardRef, FunctionComponent, useMemo, useState } from "react"; +import { FunctionComponent, useMemo, useState } from "react"; import { Link } from "react-router-dom"; +import styles from "./Search.module.scss"; type SearchResultItem = { value: string; @@ -41,36 +42,35 @@ function useSearch(query: string) { ); } -const useStyles = createStyles((theme) => { - return { - result: { - color: - theme.colorScheme === "light" - ? theme.colors.dark[8] - : theme.colors.gray[1], - }, - }; -}); - -type ResultCompProps = SelectItemProps & SearchResultItem; - -const ResultComponent = forwardRef<HTMLDivElement, ResultCompProps>( - ({ link, value }, ref) => { - const styles = useStyles(); +const optionsFilter: OptionsFilter = ({ options, search }) => { + const lowercaseSearch = search.toLowerCase(); + const trimmedSearch = search.trim(); + return (options as ComboboxItem[]).filter((option) => { return ( - <Anchor - component={Link} - to={link} - underline={false} - className={styles.classes.result} - p="sm" - > - {value} - </Anchor> + option.value.toLowerCase().includes(lowercaseSearch) || + option.value + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") + .toLowerCase() + .includes(trimmedSearch) ); - }, -); + }); +}; + +const ResultComponent = ({ name, link }: { name: string; link: string }) => { + return ( + <Anchor + component={Link} + to={link} + underline="never" + className={styles.result} + p="sm" + > + {name} + </Anchor> + ); +}; const Search: FunctionComponent = () => { const [query, setQuery] = useState(""); @@ -79,22 +79,22 @@ const Search: FunctionComponent = () => { return ( <Autocomplete - icon={<FontAwesomeIcon icon={faSearch} />} - itemComponent={ResultComponent} + leftSection={<FontAwesomeIcon icon={faSearch} />} + renderOption={(input) => ( + <ResultComponent + name={input.option.value} + link={ + results.find((a) => a.value === input.option.value)?.link || "/" + } + /> + )} placeholder="Search" size="sm" data={results} value={query} onChange={setQuery} onBlur={() => setQuery("")} - filter={(value, item) => - item.value.toLowerCase().includes(value.toLowerCase().trim()) || - item.value - .normalize("NFD") - .replace(/[\u0300-\u036f]/g, "") - .toLowerCase() - .includes(value.trim()) - } + filter={optionsFilter} ></Autocomplete> ); }; diff --git a/frontend/src/components/StateIcon.tsx b/frontend/src/components/StateIcon.tsx index f9683f63a..73700a679 100644 --- a/frontend/src/components/StateIcon.tsx +++ b/frontend/src/components/StateIcon.tsx @@ -31,7 +31,7 @@ const StateIcon: FunctionComponent<StateIconProps> = ({ return <FontAwesomeIcon icon={faListCheck} />; } else { return ( - <Text color={hasIssues ? "yellow" : "green"}> + <Text c={hasIssues ? "yellow" : "green"} span> <FontAwesomeIcon icon={hasIssues ? faExclamationCircle : faCheckCircle} /> @@ -48,9 +48,9 @@ const StateIcon: FunctionComponent<StateIconProps> = ({ </Text> </Popover.Target> <Popover.Dropdown> - <Group position="left" spacing="xl" noWrap grow> - <Stack align="flex-start" justify="flex-start" spacing="xs" mb="auto"> - <Text color="green"> + <Group justify="left" gap="xl" wrap="nowrap" grow> + <Stack align="flex-start" justify="flex-start" gap="xs" mb="auto"> + <Text c="green"> <FontAwesomeIcon icon={faCheck}></FontAwesomeIcon> </Text> <List> @@ -59,8 +59,8 @@ const StateIcon: FunctionComponent<StateIconProps> = ({ ))} </List> </Stack> - <Stack align="flex-start" justify="flex-start" spacing="xs" mb="auto"> - <Text color="yellow"> + <Stack align="flex-start" justify="flex-start" gap="xs" mb="auto"> + <Text c="yellow"> <FontAwesomeIcon icon={faTimes}></FontAwesomeIcon> </Text> <List> diff --git a/frontend/src/components/SubtitleToolsMenu.tsx b/frontend/src/components/SubtitleToolsMenu.tsx index e36a1e9e1..bdadb5cb4 100644 --- a/frontend/src/components/SubtitleToolsMenu.tsx +++ b/frontend/src/components/SubtitleToolsMenu.tsx @@ -148,7 +148,7 @@ const SubtitleToolsMenu: FunctionComponent<Props> = ({ <Menu.Item key={tool.key} disabled={disabledTools} - icon={<FontAwesomeIcon icon={tool.icon}></FontAwesomeIcon>} + leftSection={<FontAwesomeIcon icon={tool.icon}></FontAwesomeIcon>} onClick={() => { if (tool.modal) { modals.openContextModal(tool.modal, { selections }); @@ -164,7 +164,7 @@ const SubtitleToolsMenu: FunctionComponent<Props> = ({ <Menu.Label>Actions</Menu.Label> <Menu.Item disabled={selections.length !== 0 || onAction === undefined} - icon={<FontAwesomeIcon icon={faSearch}></FontAwesomeIcon>} + leftSection={<FontAwesomeIcon icon={faSearch}></FontAwesomeIcon>} onClick={() => { onAction?.("search"); }} @@ -174,7 +174,7 @@ const SubtitleToolsMenu: FunctionComponent<Props> = ({ <Menu.Item disabled={selections.length === 0 || onAction === undefined} color="red" - icon={<FontAwesomeIcon icon={faTrash}></FontAwesomeIcon>} + leftSection={<FontAwesomeIcon icon={faTrash}></FontAwesomeIcon>} onClick={() => { modals.openConfirmModal({ title: "The following subtitles will be deleted", diff --git a/frontend/src/components/bazarr/AudioList.tsx b/frontend/src/components/bazarr/AudioList.tsx index ac9cce743..b73f71331 100644 --- a/frontend/src/components/bazarr/AudioList.tsx +++ b/frontend/src/components/bazarr/AudioList.tsx @@ -13,7 +13,7 @@ const AudioList: FunctionComponent<AudioListProps> = ({ ...group }) => { return ( - <Group spacing="xs" {...group}> + <Group gap="xs" {...group}> {audios.map((audio, idx) => ( <Badge color="blue" key={BuildKey(idx, audio.code2)} {...badgeProps}> {audio.name} diff --git a/frontend/src/components/bazarr/Language.test.tsx b/frontend/src/components/bazarr/Language.test.tsx index 9e0e0fab8..e4e3b42b8 100644 --- a/frontend/src/components/bazarr/Language.test.tsx +++ b/frontend/src/components/bazarr/Language.test.tsx @@ -1,4 +1,4 @@ -import { rawRender, screen } from "@/tests"; +import { render, screen } from "@/tests"; import { describe, it } from "vitest"; import { Language } from "."; @@ -9,13 +9,13 @@ describe("Language text", () => { }; it("should show short text", () => { - rawRender(<Language.Text value={testLanguage}></Language.Text>); + render(<Language.Text value={testLanguage}></Language.Text>); expect(screen.getByText(testLanguage.code2)).toBeDefined(); }); it("should show long text", () => { - rawRender(<Language.Text value={testLanguage} long></Language.Text>); + render(<Language.Text value={testLanguage} long></Language.Text>); expect(screen.getByText(testLanguage.name)).toBeDefined(); }); @@ -23,7 +23,7 @@ describe("Language text", () => { const testLanguageWithHi: Language.Info = { ...testLanguage, hi: true }; it("should show short text with HI", () => { - rawRender(<Language.Text value={testLanguageWithHi}></Language.Text>); + render(<Language.Text value={testLanguageWithHi}></Language.Text>); const expectedText = `${testLanguageWithHi.code2}:HI`; @@ -31,7 +31,7 @@ describe("Language text", () => { }); it("should show long text with HI", () => { - rawRender(<Language.Text value={testLanguageWithHi} long></Language.Text>); + render(<Language.Text value={testLanguageWithHi} long></Language.Text>); const expectedText = `${testLanguageWithHi.name} HI`; @@ -44,7 +44,7 @@ describe("Language text", () => { }; it("should show short text with Forced", () => { - rawRender(<Language.Text value={testLanguageWithForced}></Language.Text>); + render(<Language.Text value={testLanguageWithForced}></Language.Text>); const expectedText = `${testLanguageWithHi.code2}:Forced`; @@ -52,9 +52,7 @@ describe("Language text", () => { }); it("should show long text with Forced", () => { - rawRender( - <Language.Text value={testLanguageWithForced} long></Language.Text>, - ); + render(<Language.Text value={testLanguageWithForced} long></Language.Text>); const expectedText = `${testLanguageWithHi.name} Forced`; @@ -75,7 +73,7 @@ describe("Language list", () => { ]; it("should show all languages", () => { - rawRender(<Language.List value={elements}></Language.List>); + render(<Language.List value={elements}></Language.List>); elements.forEach((value) => { expect(screen.getByText(value.name)).toBeDefined(); diff --git a/frontend/src/components/bazarr/Language.tsx b/frontend/src/components/bazarr/Language.tsx index e5627c82e..ba1a884e6 100644 --- a/frontend/src/components/bazarr/Language.tsx +++ b/frontend/src/components/bazarr/Language.tsx @@ -49,7 +49,7 @@ type LanguageListProps = { const LanguageList: FunctionComponent<LanguageListProps> = ({ value }) => { return ( - <Group spacing="xs"> + <Group gap="xs"> {value.map((v) => ( <Badge key={BuildKey(v.code2, v.code2, v.hi)}>{v.name}</Badge> ))} diff --git a/frontend/src/components/forms/FrameRateForm.tsx b/frontend/src/components/forms/FrameRateForm.tsx index 7e7eca24c..aa1723183 100644 --- a/frontend/src/components/forms/FrameRateForm.tsx +++ b/frontend/src/components/forms/FrameRateForm.tsx @@ -55,15 +55,17 @@ const FrameRateForm: FunctionComponent<Props> = ({ selections, onSubmit }) => { })} > <Stack> - <Group spacing="xs" grow> + <Group gap="xs" grow> <NumberInput placeholder="From" - precision={2} + decimalScale={2} + fixedDecimalScale {...form.getInputProps("from")} ></NumberInput> <NumberInput placeholder="To" - precision={2} + decimalScale={2} + fixedDecimalScale {...form.getInputProps("to")} ></NumberInput> </Group> diff --git a/frontend/src/components/forms/ItemEditForm.tsx b/frontend/src/components/forms/ItemEditForm.tsx index 9f3856d54..64f87fb36 100644 --- a/frontend/src/components/forms/ItemEditForm.tsx +++ b/frontend/src/components/forms/ItemEditForm.tsx @@ -80,7 +80,7 @@ const ItemEditForm: FunctionComponent<Props> = ({ label="Languages Profile" ></Selector> <Divider></Divider> - <Group position="right"> + <Group justify="right"> <Button disabled={isOverlayVisible} onClick={() => { diff --git a/frontend/src/components/forms/MovieUploadForm.module.scss b/frontend/src/components/forms/MovieUploadForm.module.scss new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/frontend/src/components/forms/MovieUploadForm.module.scss diff --git a/frontend/src/components/forms/MovieUploadForm.tsx b/frontend/src/components/forms/MovieUploadForm.tsx index b51614770..1d6c64fe7 100644 --- a/frontend/src/components/forms/MovieUploadForm.tsx +++ b/frontend/src/components/forms/MovieUploadForm.tsx @@ -1,7 +1,6 @@ import { useMovieSubtitleModification } from "@/apis/hooks"; import { useModals, withModal } from "@/modules/modals"; import { TaskGroup, task } from "@/modules/task"; -import { useTableStyles } from "@/styles"; import { useArrayAction, useSelectorOptions } from "@/utilities"; import FormUtils from "@/utilities/form"; import { @@ -19,7 +18,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Button, Checkbox, - createStyles, Divider, MantineColor, Stack, @@ -79,21 +77,12 @@ interface Props { onComplete?: () => void; } -const useStyles = createStyles((theme) => { - return { - wrapper: { - overflowWrap: "anywhere", - }, - }; -}); - const MovieUploadForm: FunctionComponent<Props> = ({ files, movie, onComplete, }) => { const modals = useModals(); - const { classes } = useStyles(); const profile = useLanguageProfileBy(movie.profileId); @@ -187,7 +176,7 @@ const MovieUploadForm: FunctionComponent<Props> = ({ return ( <TextPopover text={value?.messages}> - <Text color={color} inline> + <Text c={color} inline> <FontAwesomeIcon icon={icon}></FontAwesomeIcon> </Text> </TextPopover> @@ -199,9 +188,7 @@ const MovieUploadForm: FunctionComponent<Props> = ({ id: "filename", accessor: "file", Cell: ({ value }) => { - const { classes } = useTableStyles(); - - return <Text className={classes.primary}>{value.name}</Text>; + return <Text className="table-primary">{value.name}</Text>; }, }, { @@ -236,11 +223,10 @@ const MovieUploadForm: FunctionComponent<Props> = ({ Header: "Language", accessor: "language", Cell: ({ row: { original, index }, value }) => { - const { classes } = useTableStyles(); return ( <Selector {...languageOptions} - className={classes.select} + className="table-long-break" value={value} onChange={(item) => { action.mutate(index, { ...original, language: item }); @@ -289,7 +275,7 @@ const MovieUploadForm: FunctionComponent<Props> = ({ modals.closeSelf(); })} > - <Stack className={classes.wrapper}> + <Stack className="table-long-break"> <SimpleTable columns={columns} data={form.values.files}></SimpleTable> <Divider></Divider> <Button type="submit">Upload</Button> diff --git a/frontend/src/components/forms/ProfileEditForm.module.scss b/frontend/src/components/forms/ProfileEditForm.module.scss new file mode 100644 index 000000000..d98b850ff --- /dev/null +++ b/frontend/src/components/forms/ProfileEditForm.module.scss @@ -0,0 +1,5 @@ +.content { + @include smaller-than($mantine-breakpoint-md) { + padding: 0; + } +} diff --git a/frontend/src/components/forms/ProfileEditForm.tsx b/frontend/src/components/forms/ProfileEditForm.tsx index eecacd73e..93f09d8b8 100644 --- a/frontend/src/components/forms/ProfileEditForm.tsx +++ b/frontend/src/components/forms/ProfileEditForm.tsx @@ -1,6 +1,5 @@ import { Action, Selector, SelectorOption, SimpleTable } from "@/components"; import { useModals, withModal } from "@/modules/modals"; -import { useTableStyles } from "@/styles"; import { useArrayAction, useSelectorOptions } from "@/utilities"; import { LOG } from "@/utilities/console"; import FormUtils from "@/utilities/form"; @@ -19,6 +18,7 @@ import { useForm } from "@mantine/form"; import { FunctionComponent, useCallback, useMemo } from "react"; import { Column } from "react-table"; import ChipInput from "../inputs/ChipInput"; +import styles from "./ProfileEditForm.module.scss"; export const anyCutoff = 65535; @@ -162,12 +162,10 @@ const ProfileEditForm: FunctionComponent<Props> = ({ [code], ); - const { classes } = useTableStyles(); - return ( <Selector {...languageOptions} - className={classes.select} + className="table-select" value={language} onChange={(value) => { if (value) { @@ -260,13 +258,7 @@ const ProfileEditForm: FunctionComponent<Props> = ({ multiple chevronPosition="right" defaultValue={["Languages"]} - styles={(theme) => ({ - content: { - [theme.fn.smallerThan("md")]: { - padding: 0, - }, - }, - })} + className={styles.content} > <Accordion.Item value="Languages"> <Stack> @@ -275,7 +267,7 @@ const ProfileEditForm: FunctionComponent<Props> = ({ columns={columns} data={form.values.items} ></SimpleTable> - <Button fullWidth color="light" onClick={addItem}> + <Button fullWidth onClick={addItem}> Add Language </Button> <Selector diff --git a/frontend/src/components/forms/SeriesUploadForm.tsx b/frontend/src/components/forms/SeriesUploadForm.tsx index 5ce9c821a..f0e8e0d31 100644 --- a/frontend/src/components/forms/SeriesUploadForm.tsx +++ b/frontend/src/components/forms/SeriesUploadForm.tsx @@ -5,7 +5,6 @@ import { } from "@/apis/hooks"; import { useModals, withModal } from "@/modules/modals"; import { task, TaskGroup } from "@/modules/task"; -import { useTableStyles } from "@/styles"; import { useArrayAction, useSelectorOptions } from "@/utilities"; import FormUtils from "@/utilities/form"; import { @@ -23,7 +22,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Button, Checkbox, - createStyles, Divider, MantineColor, Stack, @@ -86,21 +84,12 @@ interface Props { onComplete?: VoidFunction; } -const useStyles = createStyles((theme) => { - return { - wrapper: { - overflowWrap: "anywhere", - }, - }; -}); - const SeriesUploadForm: FunctionComponent<Props> = ({ series, files, onComplete, }) => { const modals = useModals(); - const { classes } = useStyles(); const episodes = useEpisodesBySeriesId(series.sonarrSeriesId); const episodeOptions = useSelectorOptions( episodes.data ?? [], @@ -225,8 +214,7 @@ const SeriesUploadForm: FunctionComponent<Props> = ({ id: "filename", accessor: "file", Cell: ({ value: { name } }) => { - const { classes } = useTableStyles(); - return <Text className={classes.primary}>{name}</Text>; + return <Text className="table-primary">{name}</Text>; }, }, { @@ -283,11 +271,10 @@ const SeriesUploadForm: FunctionComponent<Props> = ({ ), accessor: "language", Cell: ({ row: { original, index }, value }) => { - const { classes } = useTableStyles(); return ( <Selector {...languageOptions} - className={classes.select} + className="table-select" value={value} onChange={(item) => { action.mutate(index, { ...original, language: item }); @@ -301,12 +288,11 @@ const SeriesUploadForm: FunctionComponent<Props> = ({ Header: "Episode", accessor: "episode", Cell: ({ value, row }) => { - const { classes } = useTableStyles(); return ( <Selector {...episodeOptions} searchable - className={classes.select} + className="table-select" value={value} onChange={(item) => { action.mutate(row.index, { ...row.original, episode: item }); @@ -368,7 +354,7 @@ const SeriesUploadForm: FunctionComponent<Props> = ({ modals.closeSelf(); })} > - <Stack className={classes.wrapper}> + <Stack className="table-long-break"> <SimpleTable columns={columns} data={form.values.files}></SimpleTable> <Divider></Divider> <Button type="submit">Upload</Button> diff --git a/frontend/src/components/forms/SyncSubtitleForm.tsx b/frontend/src/components/forms/SyncSubtitleForm.tsx index b5136fc85..c2e4ae9a5 100644 --- a/frontend/src/components/forms/SyncSubtitleForm.tsx +++ b/frontend/src/components/forms/SyncSubtitleForm.tsx @@ -14,10 +14,15 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Alert, Button, Checkbox, Divider, Stack, Text } from "@mantine/core"; import { useForm } from "@mantine/form"; import { FunctionComponent } from "react"; -import { Selector, SelectorOption } from "../inputs"; +import { GroupedSelector, Selector } from "../inputs"; const TaskName = "Syncing Subtitle"; +interface SelectOptions { + group: string; + items: { value: string; label: string }[]; +} + function useReferencedSubtitles( mediaType: "episode" | "movie", mediaId: number, @@ -37,15 +42,21 @@ function useReferencedSubtitles( const mediaData = mediaType === "episode" ? episodeData : movieData; - const subtitles: { group: string; value: string; label: string }[] = []; + const subtitles: SelectOptions[] = []; if (!mediaData.data) { return []; } else { if (mediaData.data.audio_tracks.length > 0) { + const embeddedAudioGroup: SelectOptions = { + group: "Embedded audio tracks", + items: [], + }; + + subtitles.push(embeddedAudioGroup); + mediaData.data.audio_tracks.forEach((item) => { - subtitles.push({ - group: "Embedded audio tracks", + embeddedAudioGroup.items.push({ value: item.stream, label: `${item.name || item.language} (${item.stream})`, }); @@ -53,9 +64,15 @@ function useReferencedSubtitles( } if (mediaData.data.embedded_subtitles_tracks.length > 0) { + const embeddedSubtitlesTrackGroup: SelectOptions = { + group: "Embedded subtitles tracks", + items: [], + }; + + subtitles.push(embeddedSubtitlesTrackGroup); + mediaData.data.embedded_subtitles_tracks.forEach((item) => { - subtitles.push({ - group: "Embedded subtitles tracks", + embeddedSubtitlesTrackGroup.items.push({ value: item.stream, label: `${item.name || item.language} (${item.stream})`, }); @@ -63,10 +80,16 @@ function useReferencedSubtitles( } if (mediaData.data.external_subtitles_tracks.length > 0) { + const externalSubtitlesFilesGroup: SelectOptions = { + group: "External Subtitles files", + items: [], + }; + + subtitles.push(externalSubtitlesFilesGroup); + mediaData.data.external_subtitles_tracks.forEach((item) => { if (item) { - subtitles.push({ - group: "External Subtitles files", + externalSubtitlesFilesGroup.items.push({ value: item.path, label: item.name, }); @@ -105,7 +128,7 @@ const SyncSubtitleForm: FunctionComponent<Props> = ({ const mediaId = selections[0].id; const subtitlesPath = selections[0].path; - const subtitles: SelectorOption<string>[] = useReferencedSubtitles( + const subtitles: SelectOptions[] = useReferencedSubtitles( mediaType, mediaId, subtitlesPath, @@ -145,14 +168,14 @@ const SyncSubtitleForm: FunctionComponent<Props> = ({ > <Text size="sm">{selections.length} subtitles selected</Text> </Alert> - <Selector + <GroupedSelector clearable disabled={subtitles.length === 0 || selections.length !== 1} label="Reference" placeholder="Default: choose automatically within video file" options={subtitles} {...form.getInputProps("reference")} - ></Selector> + ></GroupedSelector> <Selector clearable label="Max Offset Seconds" diff --git a/frontend/src/components/forms/TimeOffsetForm.tsx b/frontend/src/components/forms/TimeOffsetForm.tsx index 2792d64d8..3802f3a3e 100644 --- a/frontend/src/components/forms/TimeOffsetForm.tsx +++ b/frontend/src/components/forms/TimeOffsetForm.tsx @@ -70,7 +70,7 @@ const TimeOffsetForm: FunctionComponent<Props> = ({ selections, onSubmit }) => { })} > <Stack> - <Group align="end" spacing="xs" noWrap> + <Group align="end" gap="xs" wrap="nowrap"> <Button color="gray" variant="filled" diff --git a/frontend/src/components/index.tsx b/frontend/src/components/index.tsx index c3d7b4763..5ea97cf04 100644 --- a/frontend/src/components/index.tsx +++ b/frontend/src/components/index.tsx @@ -1,4 +1,4 @@ export { default as Search } from "./Search"; export * from "./inputs"; export * from "./tables"; -export { default as Toolbox } from "./toolbox"; +export { default as Toolbox } from "./toolbox/Toolbox"; diff --git a/frontend/src/components/inputs/Action.test.tsx b/frontend/src/components/inputs/Action.test.tsx index 189aca076..80d24825b 100644 --- a/frontend/src/components/inputs/Action.test.tsx +++ b/frontend/src/components/inputs/Action.test.tsx @@ -1,4 +1,4 @@ -import { rawRender, screen } from "@/tests"; +import { render, screen } from "@/tests"; import { faStickyNote } from "@fortawesome/free-regular-svg-icons"; import userEvent from "@testing-library/user-event"; import { describe, it, vitest } from "vitest"; @@ -9,7 +9,7 @@ const testIcon = faStickyNote; describe("Action button", () => { it("should be a button", () => { - rawRender(<Action icon={testIcon} label={testLabel}></Action>); + render(<Action icon={testIcon} label={testLabel}></Action>); const element = screen.getByRole("button", { name: testLabel }); expect(element.getAttribute("type")).toEqual("button"); @@ -17,7 +17,7 @@ describe("Action button", () => { }); it("should show icon", () => { - rawRender(<Action icon={testIcon} label={testLabel}></Action>); + render(<Action icon={testIcon} label={testLabel}></Action>); // TODO: use getBy... const element = screen.getByRole("img", { hidden: true }); @@ -27,7 +27,7 @@ describe("Action button", () => { it("should call on-click event when clicked", async () => { const onClickFn = vitest.fn(); - rawRender( + render( <Action icon={testIcon} label={testLabel} onClick={onClickFn}></Action>, ); diff --git a/frontend/src/components/inputs/ChipInput.test.tsx b/frontend/src/components/inputs/ChipInput.test.tsx index cb52ee30c..4aa29e8b7 100644 --- a/frontend/src/components/inputs/ChipInput.test.tsx +++ b/frontend/src/components/inputs/ChipInput.test.tsx @@ -1,4 +1,4 @@ -import { rawRender, screen } from "@/tests"; +import { render, screen } from "@/tests"; import userEvent from "@testing-library/user-event"; import { describe, it, vitest } from "vitest"; import ChipInput from "./ChipInput"; @@ -8,7 +8,7 @@ describe("ChipInput", () => { // TODO: Support default value it.skip("should works with default value", () => { - rawRender(<ChipInput defaultValue={existedValues}></ChipInput>); + render(<ChipInput defaultValue={existedValues}></ChipInput>); existedValues.forEach((value) => { expect(screen.getByText(value)).toBeDefined(); @@ -16,7 +16,7 @@ describe("ChipInput", () => { }); it("should works with value", () => { - rawRender(<ChipInput value={existedValues}></ChipInput>); + render(<ChipInput value={existedValues}></ChipInput>); existedValues.forEach((value) => { expect(screen.getByText(value)).toBeDefined(); @@ -29,9 +29,7 @@ describe("ChipInput", () => { expect(values).toContain(typedValue); }); - rawRender( - <ChipInput value={existedValues} onChange={mockedFn}></ChipInput>, - ); + render(<ChipInput value={existedValues} onChange={mockedFn}></ChipInput>); const element = screen.getByRole("searchbox"); diff --git a/frontend/src/components/inputs/ChipInput.tsx b/frontend/src/components/inputs/ChipInput.tsx index 4308f7189..1fa57084c 100644 --- a/frontend/src/components/inputs/ChipInput.tsx +++ b/frontend/src/components/inputs/ChipInput.tsx @@ -1,35 +1,29 @@ -import { useSelectorOptions } from "@/utilities"; import { FunctionComponent } from "react"; -import { MultiSelector, MultiSelectorProps } from "./Selector"; +import { TagsInput } from "@mantine/core"; -export type ChipInputProps = Omit< - MultiSelectorProps<string>, - | "searchable" - | "creatable" - | "getCreateLabel" - | "onCreate" - | "options" - | "getkey" ->; - -const ChipInput: FunctionComponent<ChipInputProps> = ({ ...props }) => { - const { value, onChange } = props; - - const options = useSelectorOptions(value ?? [], (v) => v); +export interface ChipInputProps { + defaultValue?: string[] | undefined; + value?: readonly string[] | null; + label?: string; + onChange?: (value: string[]) => void; +} +const ChipInput: FunctionComponent<ChipInputProps> = ({ + defaultValue, + value, + label, + onChange, +}: ChipInputProps) => { + // TODO: Replace with our own custom implementation instead of just using the + // built-in TagsInput. https://mantine.dev/combobox/?e=MultiSelectCreatable return ( - <MultiSelector - {...props} - {...options} - creatable - searchable - getCreateLabel={(query) => `Add "${query}"`} - onCreate={(query) => { - onChange?.([...(value ?? []), query]); - return query; - }} - buildOption={(value) => value} - ></MultiSelector> + <TagsInput + defaultValue={defaultValue} + label={label} + value={value ? value?.map((v) => v) : []} + onChange={onChange} + clearable + ></TagsInput> ); }; diff --git a/frontend/src/components/inputs/DropContent.module.scss b/frontend/src/components/inputs/DropContent.module.scss new file mode 100644 index 000000000..c6c0f848a --- /dev/null +++ b/frontend/src/components/inputs/DropContent.module.scss @@ -0,0 +1,4 @@ +.container { + pointer-events: none; + min-height: 220px; +} diff --git a/frontend/src/components/inputs/DropContent.tsx b/frontend/src/components/inputs/DropContent.tsx index 38556220d..015794ead 100644 --- a/frontend/src/components/inputs/DropContent.tsx +++ b/frontend/src/components/inputs/DropContent.tsx @@ -4,24 +4,14 @@ import { faXmark, } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Group, Stack, Text, createStyles } from "@mantine/core"; +import { Group, Stack, Text } from "@mantine/core"; import { Dropzone } from "@mantine/dropzone"; import { FunctionComponent } from "react"; - -const useStyle = createStyles((theme) => { - return { - container: { - pointerEvents: "none", - minHeight: 220, - }, - }; -}); +import styles from "./DropContent.module.scss"; export const DropContent: FunctionComponent = () => { - const { classes } = useStyle(); - return ( - <Group position="center" spacing="xl" className={classes.container}> + <Group justify="center" gap="xl" className={styles.container}> <Dropzone.Idle> <FontAwesomeIcon icon={faFileCirclePlus} size="2x" /> </Dropzone.Idle> @@ -31,9 +21,9 @@ export const DropContent: FunctionComponent = () => { <Dropzone.Reject> <FontAwesomeIcon icon={faXmark} size="2x" /> </Dropzone.Reject> - <Stack spacing={0}> + <Stack gap={0}> <Text size="lg">Upload Subtitles</Text> - <Text color="dimmed" size="sm"> + <Text c="dimmed" size="sm"> Attach as many files as you like, you will need to select file metadata before uploading </Text> diff --git a/frontend/src/components/inputs/FileBrowser.tsx b/frontend/src/components/inputs/FileBrowser.tsx index ce57a4938..e1a4b5c18 100644 --- a/frontend/src/components/inputs/FileBrowser.tsx +++ b/frontend/src/components/inputs/FileBrowser.tsx @@ -1,7 +1,12 @@ import { useFileSystem } from "@/apis/hooks"; import { faFolder } from "@fortawesome/free-regular-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Autocomplete, AutocompleteProps } from "@mantine/core"; +import { + Autocomplete, + AutocompleteProps, + ComboboxItem, + OptionsFilter, +} from "@mantine/core"; import { FunctionComponent, useEffect, useMemo, useRef, useState } from "react"; // TODO: use fortawesome icons @@ -75,24 +80,28 @@ export const FileBrowser: FunctionComponent<FileBrowserProps> = ({ const ref = useRef<HTMLInputElement>(null); + const optionsFilter: OptionsFilter = ({ options, search }) => { + return (options as ComboboxItem[]).filter((option) => { + if (search === backKey) { + return true; + } + + return option.value.includes(search); + }); + }; + return ( <Autocomplete {...props} ref={ref} - icon={<FontAwesomeIcon icon={faFolder}></FontAwesomeIcon>} + leftSection={<FontAwesomeIcon icon={faFolder}></FontAwesomeIcon>} placeholder="Click to start" data={data} value={value} // Temporary solution of infinite dropdown items, fix later limit={NaN} maxDropdownHeight={240} - filter={(value, item) => { - if (item.value === backKey) { - return true; - } else { - return item.value.includes(value); - } - }} + filter={optionsFilter} onChange={(val) => { if (val !== backKey) { setValue(val); diff --git a/frontend/src/components/inputs/Selector.test.tsx b/frontend/src/components/inputs/Selector.test.tsx index a7b6cfb85..0da13e5bf 100644 --- a/frontend/src/components/inputs/Selector.test.tsx +++ b/frontend/src/components/inputs/Selector.test.tsx @@ -1,4 +1,4 @@ -import { rawRender, screen } from "@/tests"; +import { render, screen } from "@/tests"; import userEvent from "@testing-library/user-event"; import { describe, it, vitest } from "vitest"; import { Selector, SelectorOption } from "./Selector"; @@ -18,20 +18,17 @@ const testOptions: SelectorOption<string>[] = [ describe("Selector", () => { describe("options", () => { it("should work with the SelectorOption", () => { - rawRender( - <Selector name={selectorName} options={testOptions}></Selector>, - ); + render(<Selector name={selectorName} options={testOptions}></Selector>); - // TODO: selectorName - expect(screen.getByRole("searchbox")).toBeDefined(); + testOptions.forEach((o) => { + expect(screen.getByText(o.label)).toBeDefined(); + }); }); it("should display when clicked", async () => { - rawRender( - <Selector name={selectorName} options={testOptions}></Selector>, - ); + render(<Selector name={selectorName} options={testOptions}></Selector>); - const element = screen.getByRole("searchbox"); + const element = screen.getByTestId("input-selector"); await userEvent.click(element); @@ -44,7 +41,7 @@ describe("Selector", () => { it("shouldn't show default value", async () => { const option = testOptions[0]; - rawRender( + render( <Selector name={selectorName} options={testOptions} @@ -57,7 +54,7 @@ describe("Selector", () => { it("shouldn't show value", async () => { const option = testOptions[0]; - rawRender( + render( <Selector name={selectorName} options={testOptions} @@ -75,7 +72,7 @@ describe("Selector", () => { const mockedFn = vitest.fn((value: string | null) => { expect(value).toEqual(clickedOption.value); }); - rawRender( + render( <Selector name={selectorName} options={testOptions} @@ -83,13 +80,13 @@ describe("Selector", () => { ></Selector>, ); - const element = screen.getByRole("searchbox"); + const element = screen.getByTestId("input-selector"); await userEvent.click(element); await userEvent.click(screen.getByText(clickedOption.label)); - expect(mockedFn).toBeCalled(); + expect(mockedFn).toHaveBeenCalled(); }); }); @@ -115,7 +112,7 @@ describe("Selector", () => { const mockedFn = vitest.fn((value: { name: string } | null) => { expect(value).toEqual(clickedOption.value); }); - rawRender( + render( <Selector name={selectorName} options={objectOptions} @@ -124,20 +121,20 @@ describe("Selector", () => { ></Selector>, ); - const element = screen.getByRole("searchbox"); + const element = screen.getByTestId("input-selector"); await userEvent.click(element); await userEvent.click(screen.getByText(clickedOption.label)); - expect(mockedFn).toBeCalled(); + expect(mockedFn).toHaveBeenCalled(); }); }); describe("placeholder", () => { it("should show when no selection", () => { const placeholder = "Empty Selection"; - rawRender( + render( <Selector name={selectorName} options={testOptions} diff --git a/frontend/src/components/inputs/Selector.tsx b/frontend/src/components/inputs/Selector.tsx index 0af276fc4..6a25144f5 100644 --- a/frontend/src/components/inputs/Selector.tsx +++ b/frontend/src/components/inputs/Selector.tsx @@ -1,9 +1,10 @@ import { LOG } from "@/utilities/console"; import { + ComboboxItem, + ComboboxParsedItemGroup, MultiSelect, MultiSelectProps, Select, - SelectItem, SelectProps, } from "@mantine/core"; import { isNull, isUndefined } from "lodash"; @@ -14,10 +15,10 @@ export type SelectorOption<T> = Override< value: T; label: string; }, - SelectItem + ComboboxItem >; -type SelectItemWithPayload<T> = SelectItem & { +type SelectItemWithPayload<T> = ComboboxItem & { payload: T; }; @@ -34,6 +35,30 @@ function DefaultKeyBuilder<T>(value: T) { } } +export type GroupedSelectorProps<T> = Override< + { + options: ComboboxParsedItemGroup[]; + getkey?: (value: T) => string; + }, + Omit<SelectProps, "data"> +>; + +export function GroupedSelector<T>({ + value, + options, + getkey = DefaultKeyBuilder, + ...select +}: GroupedSelectorProps<T>) { + return ( + <Select + data-testid="input-selector" + comboboxProps={{ withinPortal: true }} + data={options} + {...select} + ></Select> + ); +} + export type SelectorProps<T> = Override< { value?: T | null; @@ -84,7 +109,7 @@ export function Selector<T>({ }, [defaultValue, keyRef]); const wrappedOnChange = useCallback( - (value: string) => { + (value: string | null) => { const payload = data.find((v) => v.value === value)?.payload ?? null; onChange?.(payload); }, @@ -93,7 +118,8 @@ export function Selector<T>({ return ( <Select - withinPortal={true} + data-testid="input-selector" + comboboxProps={{ withinPortal: true }} data={data} defaultValue={wrappedDefaultValue} value={wrappedValue} @@ -144,6 +170,7 @@ export function MultiSelector<T>({ () => value && value.map(labelRef.current), [value], ); + const wrappedDefaultValue = useMemo( () => defaultValue && defaultValue.map(labelRef.current), [defaultValue], @@ -168,6 +195,7 @@ export function MultiSelector<T>({ return ( <MultiSelect {...select} + hidePickedOptions value={wrappedValue} defaultValue={wrappedDefaultValue} onChange={wrappedOnChange} diff --git a/frontend/src/components/modals/ManualSearchModal.tsx b/frontend/src/components/modals/ManualSearchModal.tsx index 24799130d..9a9de605d 100644 --- a/frontend/src/components/modals/ManualSearchModal.tsx +++ b/frontend/src/components/modals/ManualSearchModal.tsx @@ -1,6 +1,5 @@ import { withModal } from "@/modules/modals"; import { task, TaskGroup } from "@/modules/task"; -import { useTableStyles } from "@/styles"; import { GetItemId } from "@/utilities"; import { faCaretDown, @@ -31,9 +30,7 @@ type SupportType = Item.Movie | Item.Episode; interface Props<T extends SupportType> { download: (item: T, result: SearchResultType) => Promise<void>; - query: ( - id?: number, - ) => UseQueryResult<SearchResultType[] | undefined, unknown>; + query: (id?: number) => UseQueryResult<SearchResultType[] | undefined>; item: T; } @@ -50,7 +47,8 @@ function ManualSearchView<T extends SupportType>(props: Props<T>) { const search = useCallback(() => { setSearchStarted(true); - results.refetch(); + + void results.refetch(); }, [results]); const columns = useMemo<Column<SearchResultType>[]>( @@ -59,8 +57,7 @@ function ManualSearchView<T extends SupportType>(props: Props<T>) { Header: "Score", accessor: "score", Cell: ({ value }) => { - const { classes } = useTableStyles(); - return <Text className={classes.noWrap}>{value}%</Text>; + return <Text className="table-no-wrap">{value}%</Text>; }, }, { @@ -84,13 +81,12 @@ function ManualSearchView<T extends SupportType>(props: Props<T>) { Header: "Provider", accessor: "provider", Cell: (row) => { - const { classes } = useTableStyles(); const value = row.value; const { url } = row.row.original; if (url) { return ( <Anchor - className={classes.noWrap} + className="table-no-wrap" href={url} target="_blank" rel="noopener noreferrer" @@ -107,7 +103,6 @@ function ManualSearchView<T extends SupportType>(props: Props<T>) { Header: "Release", accessor: "release_info", Cell: ({ value }) => { - const { classes } = useTableStyles(); const [open, setOpen] = useState(false); const items = useMemo( @@ -116,12 +111,12 @@ function ManualSearchView<T extends SupportType>(props: Props<T>) { ); if (value.length === 0) { - return <Text color="dimmed">Cannot get release info</Text>; + return <Text c="dimmed">Cannot get release info</Text>; } return ( - <Stack spacing={0} onClick={() => setOpen((o) => !o)}> - <Text className={classes.primary}> + <Stack gap={0} onClick={() => setOpen((o) => !o)}> + <Text className="table-primary" span> {value[0]} {value.length > 1 && ( <FontAwesomeIcon @@ -141,8 +136,7 @@ function ManualSearchView<T extends SupportType>(props: Props<T>) { Header: "Uploader", accessor: "uploader", Cell: ({ value }) => { - const { classes } = useTableStyles(); - return <Text className={classes.noWrap}>{value ?? "-"}</Text>; + return <Text className="table-no-wrap">{value ?? "-"}</Text>; }, }, { diff --git a/frontend/src/components/tables/BaseTable.module.scss b/frontend/src/components/tables/BaseTable.module.scss new file mode 100644 index 000000000..e1e1eff0b --- /dev/null +++ b/frontend/src/components/tables/BaseTable.module.scss @@ -0,0 +1,9 @@ +.container { + display: block; + max-width: 100%; + overflow-x: auto; +} + +.table { + border-collapse: collapse; +} diff --git a/frontend/src/components/tables/BaseTable.tsx b/frontend/src/components/tables/BaseTable.tsx index 6ec49e61a..f31e9fff0 100644 --- a/frontend/src/components/tables/BaseTable.tsx +++ b/frontend/src/components/tables/BaseTable.tsx @@ -1,8 +1,9 @@ import { useIsLoading } from "@/contexts"; import { usePageSize } from "@/utilities/storage"; -import { Box, createStyles, Skeleton, Table, Text } from "@mantine/core"; +import { Box, Skeleton, Table, Text } from "@mantine/core"; import { ReactNode, useMemo } from "react"; import { HeaderGroup, Row, TableInstance } from "react-table"; +import styles from "./BaseTable.module.scss"; export type BaseTableProps<T extends object> = TableInstance<T> & { tableStyles?: TableStyleProps<T>; @@ -18,37 +19,23 @@ export interface TableStyleProps<T extends object> { rowRenderer?: (row: Row<T>) => Nullable<JSX.Element>; } -const useStyles = createStyles((theme) => { - return { - container: { - display: "block", - maxWidth: "100%", - overflowX: "auto", - }, - table: { - borderCollapse: "collapse", - }, - header: {}, - }; -}); - function DefaultHeaderRenderer<T extends object>( headers: HeaderGroup<T>[], ): JSX.Element[] { return headers.map((col) => ( - <th style={{ whiteSpace: "nowrap" }} {...col.getHeaderProps()}> + <Table.Th style={{ whiteSpace: "nowrap" }} {...col.getHeaderProps()}> {col.render("Header")} - </th> + </Table.Th> )); } function DefaultRowRenderer<T extends object>(row: Row<T>): JSX.Element | null { return ( - <tr {...row.getRowProps()}> + <Table.Tr {...row.getRowProps()}> {row.cells.map((cell) => ( - <td {...cell.getCellProps()}>{cell.render("Cell")}</td> + <Table.Td {...cell.getCellProps()}>{cell.render("Cell")}</Table.Td> ))} - </tr> + </Table.Tr> ); } @@ -66,8 +53,6 @@ export default function BaseTable<T extends object>(props: BaseTableProps<T>) { const headersRenderer = tableStyles?.headersRenderer ?? DefaultHeaderRenderer; const rowRenderer = tableStyles?.rowRenderer ?? DefaultRowRenderer; - const { classes } = useStyles(); - const colCount = useMemo(() => { return headerGroups.reduce( (prev, curr) => (curr.headers.length > prev ? curr.headers.length : prev), @@ -88,19 +73,19 @@ export default function BaseTable<T extends object>(props: BaseTableProps<T>) { body = Array(tableStyles?.placeholder ?? pageSize) .fill(0) .map((_, i) => ( - <tr key={i}> - <td colSpan={colCount}> + <Table.Tr key={i}> + <Table.Td colSpan={colCount}> <Skeleton height={24}></Skeleton> - </td> - </tr> + </Table.Td> + </Table.Tr> )); } else if (empty && tableStyles?.emptyText) { body = ( - <tr> - <td colSpan={colCount}> - <Text align="center">{tableStyles.emptyText}</Text> - </td> - </tr> + <Table.Tr> + <Table.Td colSpan={colCount}> + <Text ta="center">{tableStyles.emptyText}</Text> + </Table.Td> + </Table.Tr> ); } else { body = rows.map((row) => { @@ -110,20 +95,20 @@ export default function BaseTable<T extends object>(props: BaseTableProps<T>) { } return ( - <Box className={classes.container}> + <Box className={styles.container}> <Table - className={classes.table} + className={styles.table} striped={tableStyles?.striped ?? true} {...getTableProps()} > - <thead className={classes.header} hidden={tableStyles?.hideHeader}> + <Table.Thead hidden={tableStyles?.hideHeader}> {headerGroups.map((headerGroup) => ( - <tr {...headerGroup.getHeaderGroupProps()}> + <Table.Tr {...headerGroup.getHeaderGroupProps()}> {headersRenderer(headerGroup.headers)} - </tr> + </Table.Tr> ))} - </thead> - <tbody {...getTableBodyProps()}>{body}</tbody> + </Table.Thead> + <Table.Tbody {...getTableBodyProps()}>{body}</Table.Tbody> </Table> </Box> ); diff --git a/frontend/src/components/tables/GroupTable.tsx b/frontend/src/components/tables/GroupTable.tsx index 3a8be3d1b..f58c868a5 100644 --- a/frontend/src/components/tables/GroupTable.tsx +++ b/frontend/src/components/tables/GroupTable.tsx @@ -1,6 +1,6 @@ import { faChevronCircleRight } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Box, Text } from "@mantine/core"; +import { Box, Text, Table } from "@mantine/core"; import { Cell, HeaderGroup, @@ -29,8 +29,8 @@ function renderRow<T extends object>(row: Row<T>) { if (cell) { const rotation = row.isExpanded ? 90 : undefined; return ( - <tr {...row.getRowProps()}> - <td {...cell.getCellProps()} colSpan={row.cells.length}> + <Table.Tr {...row.getRowProps()}> + <Table.Td {...cell.getCellProps()} colSpan={row.cells.length}> <Text {...row.getToggleRowExpandedProps()} p={2}> {cell.render("Cell")} <Box component="span" mx={12}> @@ -40,21 +40,23 @@ function renderRow<T extends object>(row: Row<T>) { ></FontAwesomeIcon> </Box> </Text> - </td> - </tr> + </Table.Td> + </Table.Tr> ); } else { return null; } } else { return ( - <tr {...row.getRowProps()}> + <Table.Tr {...row.getRowProps()}> {row.cells .filter((cell) => !cell.isPlaceholder) .map((cell) => ( - <td {...cell.getCellProps()}>{renderCell(cell, row)}</td> + <Table.Td {...cell.getCellProps()}> + {renderCell(cell, row)} + </Table.Td> ))} - </tr> + </Table.Tr> ); } } @@ -64,7 +66,9 @@ function renderHeaders<T extends object>( ): JSX.Element[] { return headers .filter((col) => !col.isGrouped) - .map((col) => <th {...col.getHeaderProps()}>{col.render("Header")}</th>); + .map((col) => ( + <Table.Th {...col.getHeaderProps()}>{col.render("Header")}</Table.Th> + )); } type Props<T extends object> = Omit< diff --git a/frontend/src/components/tables/PageControl.tsx b/frontend/src/components/tables/PageControl.tsx index 0767593de..8cf528e31 100644 --- a/frontend/src/components/tables/PageControl.tsx +++ b/frontend/src/components/tables/PageControl.tsx @@ -28,7 +28,7 @@ const PageControl: FunctionComponent<Props> = ({ }, [total, goto]); return ( - <Group p={16} position="apart"> + <Group p={16} justify="apart"> <Text size="sm"> Show {start} to {end} of {total} entries </Text> diff --git a/frontend/src/components/toolbox/Button.tsx b/frontend/src/components/toolbox/Button.tsx index 735ef3ca1..022cf9081 100644 --- a/frontend/src/components/toolbox/Button.tsx +++ b/frontend/src/components/toolbox/Button.tsx @@ -24,7 +24,7 @@ const ToolboxButton: FunctionComponent<ToolboxButtonProps> = ({ <Button color="dark" variant="subtle" - leftIcon={<FontAwesomeIcon icon={icon}></FontAwesomeIcon>} + leftSection={<FontAwesomeIcon icon={icon}></FontAwesomeIcon>} {...props} > <Text size="xs">{children}</Text> diff --git a/frontend/src/components/toolbox/Toolbox.module.scss b/frontend/src/components/toolbox/Toolbox.module.scss new file mode 100644 index 000000000..76d90ae47 --- /dev/null +++ b/frontend/src/components/toolbox/Toolbox.module.scss @@ -0,0 +1,9 @@ +.group { + @include light { + color: var(--mantine-color-gray-3); + } + + @include dark { + color: var(--mantine-color-dark-5); + } +} diff --git a/frontend/src/components/toolbox/index.tsx b/frontend/src/components/toolbox/Toolbox.tsx index 6995e111d..fbeadbc5f 100644 --- a/frontend/src/components/toolbox/index.tsx +++ b/frontend/src/components/toolbox/Toolbox.tsx @@ -1,15 +1,7 @@ -import { createStyles, Group } from "@mantine/core"; +import { Group } from "@mantine/core"; import { FunctionComponent, PropsWithChildren } from "react"; import ToolboxButton, { ToolboxMutateButton } from "./Button"; - -const useStyles = createStyles((theme) => ({ - group: { - backgroundColor: - theme.colorScheme === "light" - ? theme.colors.gray[3] - : theme.colors.dark[5], - }, -})); +import styles from "./Toolbox.module.scss"; declare type ToolboxComp = FunctionComponent<PropsWithChildren> & { Button: typeof ToolboxButton; @@ -17,9 +9,8 @@ declare type ToolboxComp = FunctionComponent<PropsWithChildren> & { }; const Toolbox: ToolboxComp = ({ children }) => { - const { classes } = useStyles(); return ( - <Group p={12} position="apart" className={classes.group}> + <Group p={12} justify="apart" className={styles.group}> {children} </Group> ); diff --git a/frontend/src/constants.ts b/frontend/src/constants.ts index 8defd1ad9..6320fa4f3 100644 --- a/frontend/src/constants.ts +++ b/frontend/src/constants.ts @@ -1,9 +1 @@ -import { MantineNumberSize } from "@mantine/core"; - export const GithubRepoRoot = "https://github.com/morpheus65535/bazarr"; - -export const Layout = { - NAVBAR_WIDTH: 200, - HEADER_HEIGHT: 64, - MOBILE_BREAKPOINT: "sm" as MantineNumberSize, -}; diff --git a/frontend/src/modules/socketio/reducer.ts b/frontend/src/modules/socketio/reducer.ts index 403fc0ce0..35a553de9 100644 --- a/frontend/src/modules/socketio/reducer.ts +++ b/frontend/src/modules/socketio/reducer.ts @@ -27,7 +27,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] { update: (msg) => { msg .map((message) => notification.info("Notification", message)) - .forEach(showNotification); + .forEach((data) => showNotification(data)); }, }, { diff --git a/frontend/src/modules/task/index.ts b/frontend/src/modules/task/index.ts index f4dc956cd..5a33a39a3 100644 --- a/frontend/src/modules/task/index.ts +++ b/frontend/src/modules/task/index.ts @@ -133,7 +133,7 @@ class TaskDispatcher { public removeProgress(ids: string[]) { setTimeout( - () => ids.forEach(hideNotification), + () => ids.forEach((id) => hideNotification(id)), notification.PROGRESS_TIMEOUT, ); } diff --git a/frontend/src/modules/task/notification.ts b/frontend/src/modules/task/notification.ts index bb796b213..97601b452 100644 --- a/frontend/src/modules/task/notification.ts +++ b/frontend/src/modules/task/notification.ts @@ -1,7 +1,7 @@ -import { NotificationProps } from "@mantine/notifications"; +import { NotificationData } from "@mantine/notifications"; export const notification = { - info: (title: string, message: string): NotificationProps => { + info: (title: string, message: string): NotificationData => { return { title, message, @@ -9,7 +9,7 @@ export const notification = { }; }, - warn: (title: string, message: string): NotificationProps => { + warn: (title: string, message: string): NotificationData => { return { title, message, @@ -18,7 +18,7 @@ export const notification = { }; }, - error: (title: string, message: string): NotificationProps => { + error: (title: string, message: string): NotificationData => { return { title, message, @@ -33,7 +33,7 @@ export const notification = { pending: ( id: string, header: string, - ): NotificationProps & { id: string } => { + ): NotificationData & { id: string } => { return { id, title: header, @@ -48,7 +48,7 @@ export const notification = { body: string, current: number, total: number, - ): NotificationProps & { id: string } => { + ): NotificationData & { id: string } => { return { id, title: header, @@ -57,7 +57,7 @@ export const notification = { autoClose: false, }; }, - end: (id: string, header: string): NotificationProps & { id: string } => { + end: (id: string, header: string): NotificationData & { id: string } => { return { id, title: header, diff --git a/frontend/src/pages/Authentication.tsx b/frontend/src/pages/Authentication.tsx index baf21f6cd..33fda6f01 100644 --- a/frontend/src/pages/Authentication.tsx +++ b/frontend/src/pages/Authentication.tsx @@ -52,7 +52,7 @@ const Authentication: FunctionComponent = () => { {...form.getInputProps("password")} ></PasswordInput> <Divider></Divider> - <Button fullWidth uppercase type="submit"> + <Button fullWidth tt="uppercase" type="submit"> Login </Button> </Stack> diff --git a/frontend/src/pages/Blacklist/Movies/table.tsx b/frontend/src/pages/Blacklist/Movies/table.tsx index 9ab06f2ba..6609d186d 100644 --- a/frontend/src/pages/Blacklist/Movies/table.tsx +++ b/frontend/src/pages/Blacklist/Movies/table.tsx @@ -3,7 +3,6 @@ import { PageTable } from "@/components"; import MutateAction from "@/components/async/MutateAction"; import Language from "@/components/bazarr/Language"; import TextPopover from "@/components/TextPopover"; -import { useTableStyles } from "@/styles"; import { faTrash } from "@fortawesome/free-solid-svg-icons"; import { Anchor, Text } from "@mantine/core"; import { FunctionComponent, useMemo } from "react"; @@ -22,9 +21,8 @@ const Table: FunctionComponent<Props> = ({ blacklist }) => { accessor: "title", Cell: (row) => { const target = `/movies/${row.row.original.radarrId}`; - const { classes } = useTableStyles(); return ( - <Anchor className={classes.primary} component={Link} to={target}> + <Anchor className="table-primary" component={Link} to={target}> {row.value} </Anchor> ); diff --git a/frontend/src/pages/Blacklist/Series/table.tsx b/frontend/src/pages/Blacklist/Series/table.tsx index a67069717..96ea1a8a7 100644 --- a/frontend/src/pages/Blacklist/Series/table.tsx +++ b/frontend/src/pages/Blacklist/Series/table.tsx @@ -3,7 +3,6 @@ import { PageTable } from "@/components"; import MutateAction from "@/components/async/MutateAction"; import Language from "@/components/bazarr/Language"; import TextPopover from "@/components/TextPopover"; -import { useTableStyles } from "@/styles"; import { faTrash } from "@fortawesome/free-solid-svg-icons"; import { Anchor, Text } from "@mantine/core"; import { FunctionComponent, useMemo } from "react"; @@ -21,10 +20,9 @@ const Table: FunctionComponent<Props> = ({ blacklist }) => { Header: "Series", accessor: "seriesTitle", Cell: (row) => { - const { classes } = useTableStyles(); const target = `/series/${row.row.original.sonarrSeriesId}`; return ( - <Anchor className={classes.primary} component={Link} to={target}> + <Anchor className="table-primary" component={Link} to={target}> {row.value} </Anchor> ); diff --git a/frontend/src/pages/Episodes/index.tsx b/frontend/src/pages/Episodes/index.tsx index 28e375744..b016490ff 100644 --- a/frontend/src/pages/Episodes/index.tsx +++ b/frontend/src/pages/Episodes/index.tsx @@ -125,7 +125,7 @@ const SeriesEpisodesView: FunctionComponent = () => { <DropContent></DropContent> </Dropzone.FullScreen> <Toolbox> - <Group spacing="xs"> + <Group gap="xs"> <Toolbox.Button icon={faSync} disabled={!available || hasTask} @@ -160,7 +160,7 @@ const SeriesEpisodesView: FunctionComponent = () => { Search </Toolbox.Button> </Group> - <Group spacing="xs"> + <Group gap="xs"> <Toolbox.Button disabled={ series === undefined || diff --git a/frontend/src/pages/Episodes/table.tsx b/frontend/src/pages/Episodes/table.tsx index 5a310c359..f1aa6a52e 100644 --- a/frontend/src/pages/Episodes/table.tsx +++ b/frontend/src/pages/Episodes/table.tsx @@ -6,7 +6,6 @@ import { AudioList } from "@/components/bazarr"; import { EpisodeHistoryModal } from "@/components/modals"; import { EpisodeSearchModal } from "@/components/modals/ManualSearchModal"; import { useModals } from "@/modules/modals"; -import { useTableStyles } from "@/styles"; import { BuildKey, filterSubtitleBy } from "@/utilities"; import { useProfileItemsToLanguages } from "@/utilities/languages"; import { faBookmark as farBookmark } from "@fortawesome/free-regular-svg-icons"; @@ -92,7 +91,7 @@ const Table: FunctionComponent<Props> = ({ { accessor: "season", Cell: (row) => { - return <Text>Season {row.value}</Text>; + return <Text span>Season {row.value}</Text>; }, }, { @@ -103,11 +102,9 @@ const Table: FunctionComponent<Props> = ({ Header: "Title", accessor: "title", Cell: ({ value, row }) => { - const { classes } = useTableStyles(); - return ( <TextPopover text={row.original.sceneName}> - <Text className={classes.primary}>{value}</Text> + <Text className="table-primary">{value}</Text> </TextPopover> ); }, @@ -156,7 +153,7 @@ const Table: FunctionComponent<Props> = ({ }, [episode, seriesId]); return ( - <Group spacing="xs" noWrap> + <Group gap="xs" wrap="nowrap"> {elements} </Group> ); @@ -168,7 +165,7 @@ const Table: FunctionComponent<Props> = ({ Cell: ({ row }) => { const modals = useModals(); return ( - <Group spacing="xs" noWrap> + <Group gap="xs" wrap="nowrap"> <Action label="Manual Search" disabled={disabled} diff --git a/frontend/src/pages/History/Movies/index.tsx b/frontend/src/pages/History/Movies/index.tsx index ee4e98df0..bdbe5966a 100644 --- a/frontend/src/pages/History/Movies/index.tsx +++ b/frontend/src/pages/History/Movies/index.tsx @@ -6,7 +6,6 @@ import Language from "@/components/bazarr/Language"; import StateIcon from "@/components/StateIcon"; import TextPopover from "@/components/TextPopover"; import HistoryView from "@/pages/views/HistoryView"; -import { useTableStyles } from "@/styles"; import { faFileExcel, faInfoCircle, @@ -29,10 +28,9 @@ const MoviesHistoryView: FunctionComponent = () => { Header: "Name", accessor: "title", Cell: ({ row, value }) => { - const { classes } = useTableStyles(); const target = `/movies/${row.original.radarrId}`; return ( - <Anchor className={classes.primary} component={Link} to={target}> + <Anchor className="table-primary" component={Link} to={target}> {value} </Anchor> ); diff --git a/frontend/src/pages/History/Series/index.tsx b/frontend/src/pages/History/Series/index.tsx index d6b1469bf..e2d2f9662 100644 --- a/frontend/src/pages/History/Series/index.tsx +++ b/frontend/src/pages/History/Series/index.tsx @@ -9,7 +9,6 @@ import Language from "@/components/bazarr/Language"; import StateIcon from "@/components/StateIcon"; import TextPopover from "@/components/TextPopover"; import HistoryView from "@/pages/views/HistoryView"; -import { useTableStyles } from "@/styles"; import { faFileExcel, faInfoCircle, @@ -32,11 +31,10 @@ const SeriesHistoryView: FunctionComponent = () => { Header: "Series", accessor: "seriesTitle", Cell: (row) => { - const { classes } = useTableStyles(); const target = `/series/${row.row.original.sonarrSeriesId}`; return ( - <Anchor className={classes.primary} component={Link} to={target}> + <Anchor className="table-primary" component={Link} to={target}> {row.value} </Anchor> ); @@ -50,8 +48,7 @@ const SeriesHistoryView: FunctionComponent = () => { Header: "Title", accessor: "episodeTitle", Cell: ({ value }) => { - const { classes } = useTableStyles(); - return <Text className={classes.noWrap}>{value}</Text>; + return <Text className="table-no-wrap">{value}</Text>; }, }, { diff --git a/frontend/src/pages/History/Statistics/HistoryStats.module.scss b/frontend/src/pages/History/Statistics/HistoryStats.module.scss new file mode 100644 index 000000000..3c7c04e10 --- /dev/null +++ b/frontend/src/pages/History/Statistics/HistoryStats.module.scss @@ -0,0 +1,9 @@ +.container { + display: flex; + flex-direction: column; + height: calc(100vh - $header-height); +} + +.chart { + height: 90%; +} diff --git a/frontend/src/pages/History/Statistics/index.tsx b/frontend/src/pages/History/Statistics/HistoryStats.tsx index 243225538..9358cac30 100644 --- a/frontend/src/pages/History/Statistics/index.tsx +++ b/frontend/src/pages/History/Statistics/HistoryStats.tsx @@ -5,16 +5,8 @@ import { } from "@/apis/hooks"; import { Selector, Toolbox } from "@/components"; import { QueryOverlay } from "@/components/async"; -import Language from "@/components/bazarr/Language"; -import { Layout } from "@/constants"; import { useSelectorOptions } from "@/utilities"; -import { - Box, - Container, - SimpleGrid, - createStyles, - useMantineTheme, -} from "@mantine/core"; +import { Box, Container, SimpleGrid, useMantineTheme } from "@mantine/core"; import { useDocumentTitle } from "@mantine/hooks"; import { merge } from "lodash"; import { FunctionComponent, useMemo, useState } from "react"; @@ -29,17 +21,7 @@ import { YAxis, } from "recharts"; import { actionOptions, timeFrameOptions } from "./options"; - -const useStyles = createStyles((theme) => ({ - container: { - display: "flex", - flexDirection: "column", - height: `calc(100vh - ${Layout.HEADER_HEIGHT}px)`, - }, - chart: { - height: "90%", - }, -})); +import styles from "./HistoryStats.module.scss"; const HistoryStats: FunctionComponent = () => { const { data: providers } = useSystemProviders(true); @@ -71,8 +53,8 @@ const HistoryStats: FunctionComponent = () => { date: v.date, series: v.count, })); - const result = merge(movies, series); - return result; + + return merge(movies, series); } else { return []; } @@ -80,20 +62,13 @@ const HistoryStats: FunctionComponent = () => { useDocumentTitle("History Statistics - Bazarr"); - const { classes } = useStyles(); const theme = useMantineTheme(); return ( - <Container fluid px={0} className={classes.container}> + <Container fluid px={0} className={styles.container}> <QueryOverlay result={stats}> <Toolbox> - <SimpleGrid - cols={4} - breakpoints={[ - { maxWidth: "sm", cols: 4 }, - { maxWidth: "xs", cols: 2 }, - ]} - > + <SimpleGrid cols={{ base: 4, xs: 2 }}> <Selector placeholder="Time..." options={timeFrameOptions} @@ -123,9 +98,9 @@ const HistoryStats: FunctionComponent = () => { ></Selector> </SimpleGrid> </Toolbox> - <Box className={classes.chart} m="xs"> + <Box className={styles.chart} m="xs"> <ResponsiveContainer> - <BarChart className={classes.chart} data={convertedData}> + <BarChart className={styles.chart} data={convertedData}> <CartesianGrid strokeDasharray="4 2"></CartesianGrid> <XAxis dataKey="date"></XAxis> <YAxis allowDecimals={false}></YAxis> diff --git a/frontend/src/pages/History/history.test.tsx b/frontend/src/pages/History/history.test.tsx index 1de1e6c5d..a348a5b91 100644 --- a/frontend/src/pages/History/history.test.tsx +++ b/frontend/src/pages/History/history.test.tsx @@ -1,7 +1,7 @@ import { renderTest, RenderTestCase } from "@/tests/render"; import MoviesHistoryView from "./Movies"; import SeriesHistoryView from "./Series"; -import HistoryStats from "./Statistics"; +import HistoryStats from "./Statistics/HistoryStats"; const cases: RenderTestCase[] = [ { diff --git a/frontend/src/pages/Movies/Details/index.tsx b/frontend/src/pages/Movies/Details/index.tsx index a6b4b0aa8..4168754f1 100644 --- a/frontend/src/pages/Movies/Details/index.tsx +++ b/frontend/src/pages/Movies/Details/index.tsx @@ -123,7 +123,7 @@ const MovieDetailView: FunctionComponent = () => { <DropContent></DropContent> </Dropzone.FullScreen> <Toolbox> - <Group spacing="xs"> + <Group gap="xs"> <Toolbox.Button icon={faSync} disabled={hasTask} @@ -168,7 +168,7 @@ const MovieDetailView: FunctionComponent = () => { Manual </Toolbox.Button> </Group> - <Group spacing="xs"> + <Group gap="xs"> <Toolbox.Button disabled={!allowEdit || movie.profileId === null || hasTask} icon={faCloudUploadAlt} @@ -205,7 +205,7 @@ const MovieDetailView: FunctionComponent = () => { </Menu.Target> <Menu.Dropdown> <Menu.Item - icon={<FontAwesomeIcon icon={faToolbox} />} + leftSection={<FontAwesomeIcon icon={faToolbox} />} onClick={() => { if (movie) { modals.openContextModal(SubtitleToolsModal, { @@ -217,7 +217,7 @@ const MovieDetailView: FunctionComponent = () => { Mass Edit </Menu.Item> <Menu.Item - icon={<FontAwesomeIcon icon={faHistory} />} + leftSection={<FontAwesomeIcon icon={faHistory} />} onClick={() => { if (movie) { modals.openContextModal(MovieHistoryModal, { movie }); diff --git a/frontend/src/pages/Movies/Details/table.tsx b/frontend/src/pages/Movies/Details/table.tsx index 0a327b745..aa7e164cc 100644 --- a/frontend/src/pages/Movies/Details/table.tsx +++ b/frontend/src/pages/Movies/Details/table.tsx @@ -4,7 +4,6 @@ import { Action, SimpleTable } from "@/components"; import Language from "@/components/bazarr/Language"; import SubtitleToolsMenu from "@/components/SubtitleToolsMenu"; import { task, TaskGroup } from "@/modules/task"; -import { useTableStyles } from "@/styles"; import { filterSubtitleBy } from "@/utilities"; import { useProfileItemsToLanguages } from "@/utilities/languages"; import { faEllipsis, faSearch } from "@fortawesome/free-solid-svg-icons"; @@ -40,17 +39,17 @@ const Table: FunctionComponent<Props> = ({ movie, profile, disabled }) => { Header: "Subtitle Path", accessor: "path", Cell: ({ value }) => { - const { classes } = useTableStyles(); - const props: TextProps = { - className: classes.primary, + className: "table-primary", }; if (isSubtitleTrack(value)) { - return <Text {...props}>Video File Subtitle Track</Text>; + return ( + <Text className="table-primary">Video File Subtitle Track</Text> + ); } else if (isSubtitleMissing(value)) { return ( - <Text {...props} color="dimmed"> + <Text {...props} c="dimmed"> {value} </Text> ); diff --git a/frontend/src/pages/Movies/index.tsx b/frontend/src/pages/Movies/index.tsx index dd9f531e1..262d56195 100644 --- a/frontend/src/pages/Movies/index.tsx +++ b/frontend/src/pages/Movies/index.tsx @@ -6,7 +6,6 @@ import LanguageProfileName from "@/components/bazarr/LanguageProfile"; import { ItemEditModal } from "@/components/forms/ItemEditForm"; import { useModals } from "@/modules/modals"; import ItemView from "@/pages/views/ItemView"; -import { useTableStyles } from "@/styles"; import { BuildKey } from "@/utilities"; import { faBookmark as farBookmark } from "@fortawesome/free-regular-svg-icons"; import { faBookmark, faWrench } from "@fortawesome/free-solid-svg-icons"; @@ -35,10 +34,9 @@ const MovieView: FunctionComponent = () => { Header: "Name", accessor: "title", Cell: ({ row, value }) => { - const { classes } = useTableStyles(); const target = `/movies/${row.original.radarrId}`; return ( - <Anchor className={classes.primary} component={Link} to={target}> + <Anchor className="table-primary" component={Link} to={target}> {value} </Anchor> ); diff --git a/frontend/src/pages/Series/index.tsx b/frontend/src/pages/Series/index.tsx index 66921347c..60206d34a 100644 --- a/frontend/src/pages/Series/index.tsx +++ b/frontend/src/pages/Series/index.tsx @@ -4,7 +4,6 @@ import LanguageProfileName from "@/components/bazarr/LanguageProfile"; import { ItemEditModal } from "@/components/forms/ItemEditForm"; import { useModals } from "@/modules/modals"; import ItemView from "@/pages/views/ItemView"; -import { useTableStyles } from "@/styles"; import { faBookmark as farBookmark } from "@fortawesome/free-regular-svg-icons"; import { faBookmark, faWrench } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -34,10 +33,9 @@ const SeriesView: FunctionComponent = () => { Header: "Name", accessor: "title", Cell: ({ row, value }) => { - const { classes } = useTableStyles(); const target = `/series/${row.original.sonarrSeriesId}`; return ( - <Anchor className={classes.primary} component={Link} to={target}> + <Anchor className="table-primary" component={Link} to={target}> {value} </Anchor> ); @@ -70,13 +68,14 @@ const SeriesView: FunctionComponent = () => { } return ( - <Progress - key={title} - size="xl" - color={episodeMissingCount === 0 ? "brand" : "yellow"} - value={progress} - label={label} - ></Progress> + <Progress.Root key={title} size="xl"> + <Progress.Section + value={progress} + color={episodeMissingCount === 0 ? "brand" : "yellow"} + > + <Progress.Label>{label}</Progress.Label> + </Progress.Section> + </Progress.Root> ); }, }, diff --git a/frontend/src/pages/Settings/General/index.tsx b/frontend/src/pages/Settings/General/index.tsx index 8cc5ea8c3..bec3f946f 100644 --- a/frontend/src/pages/Settings/General/index.tsx +++ b/frontend/src/pages/Settings/General/index.tsx @@ -4,7 +4,7 @@ import { faClipboard, faSync, } from "@fortawesome/free-solid-svg-icons"; -import { Group as MantineGroup, Text as MantineText } from "@mantine/core"; +import { Box, Group as MantineGroup, Text as MantineText } from "@mantine/core"; import { useClipboard } from "@mantine/hooks"; import { FunctionComponent, useState } from "react"; import { @@ -54,7 +54,7 @@ const SettingsGeneralView: FunctionComponent = () => { ></Number> <Text label="Base URL" - icon="/" + leftSection="/" settingKey="settings-general-base_url" settingOptions={{ onLoaded: (s) => s.general.base_url?.slice(1) ?? "", @@ -87,7 +87,7 @@ const SettingsGeneralView: FunctionComponent = () => { rightSectionWidth={95} rightSectionProps={{ style: { justifyContent: "flex-end" } }} rightSection={ - <MantineGroup spacing="xs" mx="xs" position="right"> + <MantineGroup gap="xs" mx="xs" justify="right"> { // Clipboard API is only available in secure contexts See: https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API#interfaces window.isSecureContext && ( @@ -204,13 +204,12 @@ const SettingsGeneralView: FunctionComponent = () => { <Number label="Retention" settingKey="settings-backup-retention" - styles={{ - rightSection: { width: "4rem", justifyContent: "flex-end" }, - }} rightSection={ - <MantineText size="xs" px="sm" color="dimmed"> - Days - </MantineText> + <Box w="4rem" style={{ justifyContent: "flex-end" }}> + <MantineText size="xs" px="sm" c="dimmed"> + Days + </MantineText> + </Box> } ></Number> </Section> diff --git a/frontend/src/pages/Settings/Languages/equals.tsx b/frontend/src/pages/Settings/Languages/equals.tsx index a4fe95eee..a417059ab 100644 --- a/frontend/src/pages/Settings/Languages/equals.tsx +++ b/frontend/src/pages/Settings/Languages/equals.tsx @@ -355,7 +355,7 @@ const EqualsTable: FunctionComponent<EqualsTableProps> = () => { return ( <> <SimpleTable data={equals} columns={columns}></SimpleTable> - <Button fullWidth disabled={!canAdd} color="light" onClick={add}> + <Button fullWidth disabled={!canAdd} onClick={add}> {canAdd ? "Add Equal" : "No Enabled Languages"} </Button> </> diff --git a/frontend/src/pages/Settings/Languages/table.tsx b/frontend/src/pages/Settings/Languages/table.tsx index a1ee217e8..86a227207 100644 --- a/frontend/src/pages/Settings/Languages/table.tsx +++ b/frontend/src/pages/Settings/Languages/table.tsx @@ -70,7 +70,7 @@ const Table: FunctionComponent = () => { const items = row.value; const cutoff = row.row.original.cutoff; return ( - <Group spacing="xs" noWrap> + <Group gap="xs" wrap="nowrap"> {items.map((v) => { const isCutoff = v.id === cutoff || cutoff === anyCutoff; return ( @@ -128,7 +128,7 @@ const Table: FunctionComponent = () => { Cell: ({ row }) => { const profile = row.original; return ( - <Group spacing="xs" noWrap> + <Group gap="xs" wrap="nowrap"> <Action label="Edit Profile" icon={faWrench} @@ -163,7 +163,6 @@ const Table: FunctionComponent = () => { <Button fullWidth disabled={!canAdd} - color="light" onClick={() => { const profile = { profileId: nextProfileId, diff --git a/frontend/src/pages/Settings/Notifications/components.tsx b/frontend/src/pages/Settings/Notifications/components.tsx index 1a2b20f65..cde3f3268 100644 --- a/frontend/src/pages/Settings/Notifications/components.tsx +++ b/frontend/src/pages/Settings/Notifications/components.tsx @@ -90,7 +90,7 @@ const NotificationForm: FunctionComponent<Props> = ({ ></Textarea> </div> <Divider></Divider> - <Group position="right"> + <Group justify="right"> <MutateButton mutation={test} args={() => form.values.url}> Test </MutateButton> diff --git a/frontend/src/pages/Settings/Providers/components.tsx b/frontend/src/pages/Settings/Providers/components.tsx index 91076d7de..803550ce5 100644 --- a/frontend/src/pages/Settings/Providers/components.tsx +++ b/frontend/src/pages/Settings/Providers/components.tsx @@ -9,12 +9,12 @@ import { Text as MantineText, SimpleGrid, Stack, + AutocompleteProps, } from "@mantine/core"; import { useForm } from "@mantine/form"; import { capitalize } from "lodash"; import { FunctionComponent, - forwardRef, useCallback, useMemo, useRef, @@ -50,6 +50,11 @@ interface ProviderViewProps { settingsKey: SettingsKey; } +interface ProviderSelect { + value: string; + payload: ProviderInfo; +} + export const ProviderView: FunctionComponent<ProviderViewProps> = ({ availableOptions, settingsKey, @@ -130,17 +135,16 @@ interface ProviderToolProps { settingsKey: Readonly<SettingsKey>; } -const SelectItem = forwardRef< - HTMLDivElement, - { payload: ProviderInfo; label: string } ->(({ payload: { description }, label, ...other }, ref) => { +const SelectItem: AutocompleteProps["renderOption"] = ({ option }) => { + const provider = option as ProviderSelect; + return ( - <Stack spacing={1} ref={ref} {...other}> - <MantineText size="md">{label}</MantineText> - <MantineText size="xs">{description}</MantineText> + <Stack gap={1}> + <MantineText size="md">{provider.value}</MantineText> + <MantineText size="xs">{provider.payload.description}</MantineText> </Stack> ); -}); +}; const ProviderTool: FunctionComponent<ProviderToolProps> = ({ payload, @@ -298,19 +302,19 @@ const ProviderTool: FunctionComponent<ProviderToolProps> = ({ } }); - return <Stack spacing="xs">{elements}</Stack>; + return <Stack gap="xs">{elements}</Stack>; }, [info]); return ( <SettingsProvider value={settings}> <FormContext.Provider value={form}> <Stack> - <Stack spacing="xs"> + <Stack gap="xs"> <Selector data-autofocus searchable placeholder="Click to Select a Provider" - itemComponent={SelectItem} + renderOption={SelectItem} disabled={payload !== null} {...selectorOptions} value={info} @@ -323,7 +327,7 @@ const ProviderTool: FunctionComponent<ProviderToolProps> = ({ </div> </Stack> <Divider></Divider> - <Group position="right"> + <Group justify="right"> <Button hidden={!payload} color="red" onClick={deletePayload}> Delete </Button> diff --git a/frontend/src/pages/Settings/Radarr/index.tsx b/frontend/src/pages/Settings/Radarr/index.tsx index 8cd038ab8..8805aeed8 100644 --- a/frontend/src/pages/Settings/Radarr/index.tsx +++ b/frontend/src/pages/Settings/Radarr/index.tsx @@ -30,7 +30,7 @@ const SettingsRadarrView: FunctionComponent = () => { <Number label="Port" settingKey="settings-radarr-port"></Number> <Text label="Base URL" - icon="/" + leftSection="/" settingKey="settings-radarr-base_url" settingOptions={{ onLoaded: (s) => s.radarr.base_url?.slice(1) ?? "", diff --git a/frontend/src/pages/Settings/Sonarr/index.tsx b/frontend/src/pages/Settings/Sonarr/index.tsx index 1d2125568..0f60d70a8 100644 --- a/frontend/src/pages/Settings/Sonarr/index.tsx +++ b/frontend/src/pages/Settings/Sonarr/index.tsx @@ -32,7 +32,7 @@ const SettingsSonarrView: FunctionComponent = () => { <Number label="Port" settingKey="settings-sonarr-port"></Number> <Text label="Base URL" - icon="/" + leftSection="/" settingKey="settings-sonarr-base_url" settingOptions={{ onLoaded: (s) => s.sonarr.base_url?.slice(1) ?? "", diff --git a/frontend/src/pages/Settings/Subtitles/index.tsx b/frontend/src/pages/Settings/Subtitles/index.tsx index f2dec69ac..c4dffd227 100644 --- a/frontend/src/pages/Settings/Subtitles/index.tsx +++ b/frontend/src/pages/Settings/Subtitles/index.tsx @@ -501,7 +501,7 @@ const SettingsSubtitlesView: FunctionComponent = () => { label="Command" settingKey="settings-general-postprocessing_cmd" ></Text> - <Table highlightOnHover fontSize="sm"> + <Table highlightOnHover fs="sm"> <tbody>{commandOptionElements}</tbody> </Table> </CollapseBox> diff --git a/frontend/src/pages/Settings/components/Card.module.scss b/frontend/src/pages/Settings/components/Card.module.scss new file mode 100644 index 000000000..746e55e65 --- /dev/null +++ b/frontend/src/pages/Settings/components/Card.module.scss @@ -0,0 +1,9 @@ +.card { + border-radius: var(--mantine-radius-sm); + border: 1px solid var(--mantine-color-gray-7); + + &:hover { + box-shadow: var(--mantine-shadow-md); + border: 1px solid $color-brand-5; + } +} diff --git a/frontend/src/pages/Settings/components/Card.tsx b/frontend/src/pages/Settings/components/Card.tsx index 4f3bd4fbf..6e4b58f47 100644 --- a/frontend/src/pages/Settings/components/Card.tsx +++ b/frontend/src/pages/Settings/components/Card.tsx @@ -1,30 +1,8 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - Center, - createStyles, - Stack, - Text, - UnstyledButton, -} from "@mantine/core"; +import { Center, Stack, Text, UnstyledButton } from "@mantine/core"; import { FunctionComponent } from "react"; - -const useCardStyles = createStyles((theme) => { - return { - card: { - borderRadius: theme.radius.sm, - border: `1px solid ${theme.colors.gray[7]}`, - - "&:hover": { - boxShadow: theme.shadows.md, - border: `1px solid ${theme.colors.brand[5]}`, - }, - }, - stack: { - height: "100%", - }, - }; -}); +import styles from "./Card.module.scss"; interface CardProps { header?: string; @@ -39,16 +17,15 @@ export const Card: FunctionComponent<CardProps> = ({ plus, onClick, }) => { - const { classes } = useCardStyles(); return ( - <UnstyledButton p="lg" onClick={onClick} className={classes.card}> + <UnstyledButton p="lg" onClick={onClick} className={styles.card}> {plus ? ( <Center> <FontAwesomeIcon size="2x" icon={faPlus}></FontAwesomeIcon> </Center> ) : ( - <Stack className={classes.stack} spacing={0} align="flex-start"> - <Text weight="bold">{header}</Text> + <Stack h="100%" gap={0} align="flex-start"> + <Text fw="bold">{header}</Text> <Text hidden={description === undefined}>{description}</Text> </Stack> )} diff --git a/frontend/src/pages/Settings/components/Layout.tsx b/frontend/src/pages/Settings/components/Layout.tsx index b20c8092b..5fb6dbbed 100644 --- a/frontend/src/pages/Settings/components/Layout.tsx +++ b/frontend/src/pages/Settings/components/Layout.tsx @@ -73,7 +73,7 @@ const Layout: FunctionComponent<Props> = (props) => { icon={faSave} loading={isMutating} disabled={totalStagedCount === 0} - rightIcon={ + rightSection={ <Badge size="xs" radius="sm" hidden={totalStagedCount === 0}> {totalStagedCount} </Badge> diff --git a/frontend/src/pages/Settings/components/LayoutModal.tsx b/frontend/src/pages/Settings/components/LayoutModal.tsx index cb4d5a1b5..6150295e4 100644 --- a/frontend/src/pages/Settings/components/LayoutModal.tsx +++ b/frontend/src/pages/Settings/components/LayoutModal.tsx @@ -74,7 +74,7 @@ const LayoutModal: FunctionComponent<Props> = (props) => { <Space h="md" /> <Divider></Divider> <Space h="md" /> - <Group position="right"> + <Group justify="right"> <Button type="submit" disabled={totalStagedCount === 0} diff --git a/frontend/src/pages/Settings/components/Message.tsx b/frontend/src/pages/Settings/components/Message.tsx index 301df7bab..eac84131b 100644 --- a/frontend/src/pages/Settings/components/Message.tsx +++ b/frontend/src/pages/Settings/components/Message.tsx @@ -12,7 +12,7 @@ export const Message: FunctionComponent<Props> = ({ children, }) => { return ( - <Text size="sm" color={type === "info" ? "dimmed" : "yellow"} my={0}> + <Text size="sm" c={type === "info" ? "dimmed" : "yellow"} my={0}> {children} </Text> ); diff --git a/frontend/src/pages/Settings/components/Section.test.tsx b/frontend/src/pages/Settings/components/Section.test.tsx index e7f270e0d..850a47671 100644 --- a/frontend/src/pages/Settings/components/Section.test.tsx +++ b/frontend/src/pages/Settings/components/Section.test.tsx @@ -1,4 +1,4 @@ -import { rawRender, screen } from "@/tests"; +import { render, screen } from "@/tests"; import { Text } from "@mantine/core"; import { describe, it } from "vitest"; import { Section } from "./Section"; @@ -6,7 +6,7 @@ import { Section } from "./Section"; describe("Settings section", () => { const header = "Section Header"; it("should show header", () => { - rawRender(<Section header="Section Header"></Section>); + render(<Section header="Section Header"></Section>); expect(screen.getByText(header)).toBeDefined(); expect(screen.getByRole("separator")).toBeDefined(); @@ -14,7 +14,7 @@ describe("Settings section", () => { it("should show children", () => { const text = "Section Child"; - rawRender( + render( <Section header="Section Header"> <Text>{text}</Text> </Section>, @@ -26,7 +26,7 @@ describe("Settings section", () => { it("should work with hidden", () => { const text = "Section Child"; - rawRender( + render( <Section header="Section Header" hidden> <Text>{text}</Text> </Section>, diff --git a/frontend/src/pages/Settings/components/Section.tsx b/frontend/src/pages/Settings/components/Section.tsx index 36f56ff8d..7c5727bf0 100644 --- a/frontend/src/pages/Settings/components/Section.tsx +++ b/frontend/src/pages/Settings/components/Section.tsx @@ -14,7 +14,7 @@ export const Section: FunctionComponent<Props> = ({ children, }) => { return ( - <Stack hidden={hidden} spacing="xs" my="lg"> + <Stack hidden={hidden} gap="xs" my="lg"> <Title order={4}>{header}</Title> <Divider></Divider> {children} diff --git a/frontend/src/pages/Settings/components/collapse.tsx b/frontend/src/pages/Settings/components/collapse.tsx index 1dcffbd97..c598105bd 100644 --- a/frontend/src/pages/Settings/components/collapse.tsx +++ b/frontend/src/pages/Settings/components/collapse.tsx @@ -31,7 +31,7 @@ const CollapseBox: FunctionComponent<Props> = ({ return ( <Collapse in={open} pl={indent ? "md" : undefined}> - <Stack spacing="xs">{children}</Stack> + <Stack gap="xs">{children}</Stack> </Collapse> ); }; diff --git a/frontend/src/pages/Settings/components/forms.test.tsx b/frontend/src/pages/Settings/components/forms.test.tsx index 19c66ade0..26974bad9 100644 --- a/frontend/src/pages/Settings/components/forms.test.tsx +++ b/frontend/src/pages/Settings/components/forms.test.tsx @@ -1,4 +1,4 @@ -import { rawRender, RenderOptions, screen } from "@/tests"; +import { render, RenderOptions, screen } from "@/tests"; import { useForm } from "@mantine/form"; import { FunctionComponent, PropsWithChildren, ReactElement } from "react"; import { describe, it } from "vitest"; @@ -18,7 +18,7 @@ const FormSupport: FunctionComponent<PropsWithChildren> = ({ children }) => { const formRender = ( ui: ReactElement, options?: Omit<RenderOptions, "wrapper">, -) => rawRender(ui, { wrapper: FormSupport, ...options }); +) => render(<FormSupport>{ui}</FormSupport>); describe("Settings form", () => { describe("number component", () => { diff --git a/frontend/src/pages/Settings/components/forms.tsx b/frontend/src/pages/Settings/components/forms.tsx index 3e1d3f12f..1d65b0375 100644 --- a/frontend/src/pages/Settings/components/forms.tsx +++ b/frontend/src/pages/Settings/components/forms.tsx @@ -38,6 +38,11 @@ export const Number: FunctionComponent<NumberProps> = (props) => { if (val === "") { val = 0; } + + if (typeof val === "string") { + return update(+val); + } + update(val); }} ></NumberInput> diff --git a/frontend/src/pages/Settings/components/index.tsx b/frontend/src/pages/Settings/components/index.tsx index 99d1658bc..6ac9cb86c 100644 --- a/frontend/src/pages/Settings/components/index.tsx +++ b/frontend/src/pages/Settings/components/index.tsx @@ -56,7 +56,7 @@ export const URLTestButton: FunctionComponent<{ }, [address, port, url, apikey, ssl]); return ( - <Button onClick={click} color={color} title={title}> + <Button autoContrast onClick={click} variant={color} title={title}> {title} </Button> ); @@ -107,7 +107,7 @@ export const ProviderTestButton: FunctionComponent<{ }, [testUrl]); return ( - <Button onClick={click} color={color} title={title}> + <Button onClick={click} variant={color} title={title}> {title} </Button> ); diff --git a/frontend/src/pages/Settings/components/pathMapper.tsx b/frontend/src/pages/Settings/components/pathMapper.tsx index 8bb3514b7..38fbb27e9 100644 --- a/frontend/src/pages/Settings/components/pathMapper.tsx +++ b/frontend/src/pages/Settings/components/pathMapper.tsx @@ -141,7 +141,7 @@ export const PathMappingTable: FunctionComponent<TableProps> = ({ type }) => { columns={columns} data={data} ></SimpleTable> - <Button fullWidth color="light" onClick={addRow}> + <Button fullWidth onClick={addRow}> Add </Button> </> diff --git a/frontend/src/pages/System/Announcements/table.tsx b/frontend/src/pages/System/Announcements/table.tsx index 74a160190..eb9b6e43c 100644 --- a/frontend/src/pages/System/Announcements/table.tsx +++ b/frontend/src/pages/System/Announcements/table.tsx @@ -1,7 +1,6 @@ import { useSystemAnnouncementsAddDismiss } from "@/apis/hooks"; import { SimpleTable } from "@/components"; import { MutateAction } from "@/components/async"; -import { useTableStyles } from "@/styles"; import { faWindowClose } from "@fortawesome/free-solid-svg-icons"; import { Anchor, Text } from "@mantine/core"; import { FunctionComponent, useMemo } from "react"; @@ -20,16 +19,14 @@ const Table: FunctionComponent<Props> = ({ announcements }) => { Header: "Since", accessor: "timestamp", Cell: ({ value }) => { - const { classes } = useTableStyles(); - return <Text className={classes.primary}>{value}</Text>; + return <Text className="table-primary">{value}</Text>; }, }, { Header: "Announcement", accessor: "text", Cell: ({ value }) => { - const { classes } = useTableStyles(); - return <Text className={classes.primary}>{value}</Text>; + return <Text className="table-primary">{value}</Text>; }, }, { diff --git a/frontend/src/pages/System/Backups/table.tsx b/frontend/src/pages/System/Backups/table.tsx index 4f9eeae44..42236cee5 100644 --- a/frontend/src/pages/System/Backups/table.tsx +++ b/frontend/src/pages/System/Backups/table.tsx @@ -1,7 +1,6 @@ import { useDeleteBackups, useRestoreBackups } from "@/apis/hooks"; import { Action, PageTable } from "@/components"; import { useModals } from "@/modules/modals"; -import { useTableStyles } from "@/styles"; import { Environment } from "@/utilities"; import { faHistory, faTrash } from "@fortawesome/free-solid-svg-icons"; import { Anchor, Text } from "@mantine/core"; @@ -32,16 +31,14 @@ const Table: FunctionComponent<Props> = ({ backups }) => { Header: "Size", accessor: "size", Cell: ({ value }) => { - const { classes } = useTableStyles(); - return <Text className={classes.noWrap}>{value}</Text>; + return <Text className="table-no-wrap">{value}</Text>; }, }, { Header: "Time", accessor: "date", Cell: ({ value }) => { - const { classes } = useTableStyles(); - return <Text className={classes.noWrap}>{value}</Text>; + return <Text className="table-no-wrap">{value}</Text>; }, }, { diff --git a/frontend/src/pages/System/Logs/index.tsx b/frontend/src/pages/System/Logs/index.tsx index d77e102d8..a232b8069 100644 --- a/frontend/src/pages/System/Logs/index.tsx +++ b/frontend/src/pages/System/Logs/index.tsx @@ -86,7 +86,7 @@ const SystemLogsView: FunctionComponent = () => { <Container fluid px={0}> <QueryOverlay result={logs}> <Toolbox> - <Group spacing="xs"> + <Group gap="xs"> <Toolbox.Button loading={isFetching} icon={faSync} @@ -108,7 +108,7 @@ const SystemLogsView: FunctionComponent = () => { loading={isLoading} icon={faFilter} onClick={openFilterModal} - rightIcon={ + rightSection={ suffix() !== "" ? ( <Badge size="xs" radius="sm"> {suffix()} diff --git a/frontend/src/pages/System/Releases/index.tsx b/frontend/src/pages/System/Releases/index.tsx index f205da086..66b5a1c8a 100644 --- a/frontend/src/pages/System/Releases/index.tsx +++ b/frontend/src/pages/System/Releases/index.tsx @@ -23,7 +23,7 @@ const SystemReleasesView: FunctionComponent = () => { return ( <Container size={600} py={12}> <QueryOverlay result={releases}> - <Stack spacing="lg"> + <Stack gap="lg"> {data?.map((v, idx) => ( <ReleaseCard key={BuildKey(idx, v.date)} {...v}></ReleaseCard> ))} @@ -47,7 +47,7 @@ const ReleaseCard: FunctionComponent<ReleaseInfo> = ({ return ( <Card shadow="md" p="lg"> <Group> - <Text weight="bold">{name}</Text> + <Text fw="bold">{name}</Text> <Badge color="blue">{date}</Badge> <Badge color={prerelease ? "yellow" : "green"}> {prerelease ? "Development" : "Master"} diff --git a/frontend/src/pages/System/Status/index.tsx b/frontend/src/pages/System/Status/index.tsx index 49c88ccd4..05996f7ad 100644 --- a/frontend/src/pages/System/Status/index.tsx +++ b/frontend/src/pages/System/Status/index.tsx @@ -46,7 +46,7 @@ function Row(props: InfoProps): JSX.Element { return ( <Grid columns={10}> <Grid.Col span={2}> - <Text size="sm" align="right" weight="bold"> + <Text size="sm" ta="right" fw="bold"> {title} </Text> </Grid.Col> @@ -85,9 +85,12 @@ const InfoContainer: FunctionComponent< return ( <Stack> <Divider - labelProps={{ size: "medium", weight: "bold" }} labelPosition="left" - label={title} + label={ + <Text size="md" fw="bold"> + {title} + </Text> + } ></Divider> {children} <Space /> diff --git a/frontend/src/pages/System/Status/table.tsx b/frontend/src/pages/System/Status/table.tsx index 3b8a87e8a..c139f9d93 100644 --- a/frontend/src/pages/System/Status/table.tsx +++ b/frontend/src/pages/System/Status/table.tsx @@ -1,5 +1,4 @@ import { SimpleTable } from "@/components"; -import { useTableStyles } from "@/styles"; import { Text } from "@mantine/core"; import { FunctionComponent, useMemo } from "react"; import { Column } from "react-table"; @@ -15,16 +14,14 @@ const Table: FunctionComponent<Props> = ({ health }) => { Header: "Object", accessor: "object", Cell: ({ value }) => { - const { classes } = useTableStyles(); - return <Text className={classes.noWrap}>{value}</Text>; + return <Text className="table-no-wrap">{value}</Text>; }, }, { Header: "Issue", accessor: "issue", Cell: ({ value }) => { - const { classes } = useTableStyles(); - return <Text className={classes.primary}>{value}</Text>; + return <Text className="table-primary">{value}</Text>; }, }, ], diff --git a/frontend/src/pages/System/Tasks/table.tsx b/frontend/src/pages/System/Tasks/table.tsx index ea45af49d..064438668 100644 --- a/frontend/src/pages/System/Tasks/table.tsx +++ b/frontend/src/pages/System/Tasks/table.tsx @@ -1,7 +1,6 @@ import { useRunTask } from "@/apis/hooks"; import { SimpleTable } from "@/components"; import MutateAction from "@/components/async/MutateAction"; -import { useTableStyles } from "@/styles"; import { faPlay } from "@fortawesome/free-solid-svg-icons"; import { Text } from "@mantine/core"; import { FunctionComponent, useMemo } from "react"; @@ -18,16 +17,14 @@ const Table: FunctionComponent<Props> = ({ tasks }) => { Header: "Name", accessor: "name", Cell: ({ value }) => { - const { classes } = useTableStyles(); - return <Text className={classes.primary}>{value}</Text>; + return <Text className="table-primary">{value}</Text>; }, }, { Header: "Interval", accessor: "interval", Cell: ({ value }) => { - const { classes } = useTableStyles(); - return <Text className={classes.noWrap}>{value}</Text>; + return <Text className="table-no-wrap">{value}</Text>; }, }, { diff --git a/frontend/src/pages/Wanted/Movies/index.tsx b/frontend/src/pages/Wanted/Movies/index.tsx index 57fb6d6ed..d62871416 100644 --- a/frontend/src/pages/Wanted/Movies/index.tsx +++ b/frontend/src/pages/Wanted/Movies/index.tsx @@ -39,7 +39,7 @@ const WantedMoviesView: FunctionComponent = () => { const { download } = useMovieSubtitleModification(); return ( - <Group spacing="sm"> + <Group gap="sm"> {value.map((item, idx) => ( <Badge color={download.isLoading ? "gray" : undefined} diff --git a/frontend/src/pages/Wanted/Series/index.tsx b/frontend/src/pages/Wanted/Series/index.tsx index 96507bccd..d5f4d8993 100644 --- a/frontend/src/pages/Wanted/Series/index.tsx +++ b/frontend/src/pages/Wanted/Series/index.tsx @@ -6,7 +6,6 @@ import { import Language from "@/components/bazarr/Language"; import { TaskGroup, task } from "@/modules/task"; import WantedView from "@/pages/views/WantedView"; -import { useTableStyles } from "@/styles"; import { BuildKey } from "@/utilities"; import { faSearch } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -23,9 +22,8 @@ const WantedSeriesView: FunctionComponent = () => { accessor: "seriesTitle", Cell: (row) => { const target = `/series/${row.row.original.sonarrSeriesId}`; - const { classes } = useTableStyles(); return ( - <Anchor className={classes.primary} component={Link} to={target}> + <Anchor className="table-primary" component={Link} to={target}> {row.value} </Anchor> ); @@ -49,7 +47,7 @@ const WantedSeriesView: FunctionComponent = () => { const { download } = useEpisodeSubtitleModification(); return ( - <Group spacing="sm"> + <Group gap="sm"> {value.map((item, idx) => ( <Badge color={download.isLoading ? "gray" : undefined} diff --git a/frontend/src/pages/errors/UIError.tsx b/frontend/src/pages/errors/UIError.tsx index 4f26d0d0c..55e0d5414 100644 --- a/frontend/src/pages/errors/UIError.tsx +++ b/frontend/src/pages/errors/UIError.tsx @@ -45,13 +45,11 @@ const UIError: FunctionComponent<Props> = ({ error }) => { <Center my="xl"> <Code>{stack}</Code> </Center> - <Group position="center"> + <Group justify="center"> <Anchor href={`${GithubRepoRoot}/issues/new/choose`} target="_blank"> <Button color="yellow">Report Issue</Button> </Anchor> - <Button onClick={Reload} color="light"> - Reload Page - </Button> + <Button onClick={Reload}>Reload Page</Button> </Group> </Container> ); diff --git a/frontend/src/pages/views/ItemOverview.tsx b/frontend/src/pages/views/ItemOverview.tsx index d95944db3..a5224b586 100644 --- a/frontend/src/pages/views/ItemOverview.tsx +++ b/frontend/src/pages/views/ItemOverview.tsx @@ -28,11 +28,9 @@ import { HoverCard, Image, List, - MediaQuery, Stack, Text, Title, - createStyles, } from "@mantine/core"; import { FunctionComponent, useMemo } from "react"; @@ -41,25 +39,9 @@ interface Props { details?: { icon: IconDefinition; text: string }[]; } -const useStyles = createStyles((theme) => { - return { - poster: { - maxWidth: "250px", - }, - col: { - maxWidth: "100%", - }, - group: { - maxWidth: "100%", - }, - }; -}); - const ItemOverview: FunctionComponent<Props> = (props) => { const { item, details } = props; - const { classes } = useStyles(); - const detailBadges = useMemo(() => { const badges: (JSX.Element | null)[] = []; @@ -150,21 +132,19 @@ const ItemOverview: FunctionComponent<Props> = (props) => { flexWrap: "nowrap", }} > - <MediaQuery smallerThan="sm" styles={{ display: "none" }}> - <Grid.Col span={3}> - <Image - src={item?.poster} - mx="auto" - className={classes.poster} - withPlaceholder - ></Image> - </Grid.Col> - </MediaQuery> - <Grid.Col span={8} className={classes.col}> - <Stack align="flex-start" spacing="xs" mx={6}> - <Group align="flex-start" noWrap className={classes.group}> + <Grid.Col span={3} hiddenFrom="sm"> + <Image + src={item?.poster} + mx="auto" + maw="250px" + fallbackSrc="https://placehold.co/250x250?text=Placeholder" + ></Image> + </Grid.Col> + <Grid.Col span={8} maw="100%"> + <Stack align="flex-start" gap="xs" mx={6}> + <Group align="flex-start" wrap="nowrap" maw="100%"> <Title my={0}> - <Text inherit color="white"> + <Text inherit c="white"> <Box component="span" mr={12}> <FontAwesomeIcon title={item?.monitored ? "monitored" : "unmonitored"} @@ -176,10 +156,7 @@ const ItemOverview: FunctionComponent<Props> = (props) => { </Title> <HoverCard position="bottom" withArrow> <HoverCard.Target> - <Text - hidden={item?.alternativeTitles.length === 0} - color="white" - > + <Text hidden={item?.alternativeTitles.length === 0} c="white"> <FontAwesomeIcon icon={faClone} /> </Text> </HoverCard.Target> @@ -192,16 +169,16 @@ const ItemOverview: FunctionComponent<Props> = (props) => { </HoverCard.Dropdown> </HoverCard> </Group> - <Group spacing="xs" className={classes.group}> + <Group gap="xs" maw="100%"> {detailBadges} </Group> - <Group spacing="xs" className={classes.group}> + <Group gap="xs" maw="100%"> {audioBadges} </Group> - <Group spacing="xs" className={classes.group}> + <Group gap="xs" maw="100%"> {languageBadges} </Group> - <Text size="sm" color="white"> + <Text size="sm" c="white"> {item?.overview} </Text> </Stack> diff --git a/frontend/src/pages/views/MassEditor.tsx b/frontend/src/pages/views/MassEditor.tsx index b15a55e83..ddab269bd 100644 --- a/frontend/src/pages/views/MassEditor.tsx +++ b/frontend/src/pages/views/MassEditor.tsx @@ -36,13 +36,8 @@ function MassEditor<T extends Item.Base>(props: MassEditorProps<T>) { const profileOptions = useSelectorOptions(profiles ?? [], (v) => v.name); - const profileOptionsWithAction = useMemo< - SelectorOption<Language.Profile | null>[] - >( - () => [ - { label: "Clear", value: null, group: "Action" }, - ...profileOptions.options, - ], + const profileOptionsWithAction = useMemo<SelectorOption<Language.Profile>[]>( + () => [...profileOptions.options], [profileOptions.options], ); @@ -82,6 +77,7 @@ function MassEditor<T extends Item.Base>(props: MassEditorProps<T>) { }, [selections], ); + return ( <Container fluid px={0}> <Toolbox> diff --git a/frontend/src/providers.tsx b/frontend/src/providers.tsx index 97189a23e..aa526c25b 100644 --- a/frontend/src/providers.tsx +++ b/frontend/src/providers.tsx @@ -1,4 +1,3 @@ -import ThemeProvider from "@/App/theme"; import queryClient from "@/apis/queries"; import { ModalsProvider } from "@/modules/modals"; import "@fontsource/roboto/300.css"; @@ -7,6 +6,7 @@ import { FunctionComponent, PropsWithChildren } from "react"; import { QueryClientProvider } from "react-query"; import { ReactQueryDevtools } from "react-query/devtools"; import { Environment } from "./utilities"; +import ThemeProvider from "@/App/ThemeProvider"; export const AllProviders: FunctionComponent<PropsWithChildren> = ({ children, diff --git a/frontend/src/styles/index.ts b/frontend/src/styles/index.ts deleted file mode 100644 index 0e948df9e..000000000 --- a/frontend/src/styles/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./table"; diff --git a/frontend/src/styles/table.ts b/frontend/src/styles/table.ts deleted file mode 100644 index 40d533401..000000000 --- a/frontend/src/styles/table.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { createStyles } from "@mantine/core"; - -export const useTableStyles = createStyles((theme) => ({ - primary: { - display: "inline-block", - [theme.fn.smallerThan("sm")]: { - minWidth: "12rem", - }, - }, - noWrap: { - whiteSpace: "nowrap", - }, - select: { - display: "inline-block", - [theme.fn.smallerThan("sm")]: { - minWidth: "10rem", - }, - }, -})); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 41fbaa7a4..e11fa90b3 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -31,6 +31,16 @@ export default defineConfig(async ({ mode, command }) => { enableBuild: false, }), ], + css: { + preprocessorOptions: { + scss: { + additionalData: ` + @import "./src/assets/_mantine"; + @import "./src/assets/_bazarr"; + `, + }, + }, + }, base: "./", resolve: { alias: { |