diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 8d0ca95..522cb9d 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -24,11 +24,10 @@ module.exports = { rules: { 'no-cond-assign': 'error', 'eqeqeq': 'error', - 'indent': ['error', 4, { 'SwitchCase': 1 }], 'prettier/prettier': [ 'error', { - endOfLine: 'auto' + endOfLine: 'auto', } ], 'react-refresh/only-export-components': [ diff --git a/index.html b/index.html index fdacc4a..93b7658 100644 --- a/index.html +++ b/index.html @@ -2,9 +2,9 @@ - + - Vite + React + TS + FatWeb
diff --git a/package-lock.json b/package-lock.json index 16c4295..c67c05d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "name": "fatweb-ui", "version": "0.0.0", "dependencies": { - "@ant-design/icons": "^5.2.5", - "antd": "^5.8.5", + "@ant-design/icons": "^5.2.6", + "antd": "^5.8.6", "axios": "^1.5.0", "jwt-decode": "^3.1.2", "localforage": "^1.10.0", @@ -18,19 +18,18 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-router": "^6.15.0", - "react-router-dom": "^6.15.0", - "sort-by": "^0.0.2" + "react-router-dom": "^6.15.0" }, "devDependencies": { "@svgr/core": "^8.1.0", "@svgr/plugin-jsx": "^8.1.0", "@types/jsdom": "^21.1.2", - "@types/lodash": "^4.14.197", - "@types/node": "^20.5.7", + "@types/lodash": "^4.14.198", + "@types/node": "^20.5.9", "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7", - "@typescript-eslint/eslint-plugin": "^6.5.0", - "@typescript-eslint/parser": "^6.5.0", + "@typescript-eslint/eslint-plugin": "^6.6.0", + "@typescript-eslint/parser": "^6.6.0", "@vitejs/plugin-react": "^4.0.4", "eslint": "^8.48.0", "eslint-config-prettier": "^9.0.0", @@ -42,10 +41,11 @@ "eslint-plugin-react-refresh": "^0.4.3", "jsdom": "^22.1.0", "prettier": "^3.0.3", + "sass": "^1.66.1", "stylelint-config-prettier": "^9.0.5", "typescript": "^5.2.2", "unplugin-auto-import": "^0.16.6", - "unplugin-icons": "^0.16.6", + "unplugin-icons": "^0.17.0", "vite": "^4.4.9" } }, @@ -1508,7 +1508,7 @@ }, "node_modules/@types/json-schema": { "version": "7.0.12", - "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.12.tgz", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, @@ -1519,9 +1519,9 @@ "dev": true }, "node_modules/@types/lodash": { - "version": "4.14.197", - "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.197.tgz", - "integrity": "sha512-BMVOiWs0uNxHVlHBgzTIqJYmj+PgCo4euloGF+5m4okL3rEYzM2EEv78mw8zWSMM57dM7kVIgJ2QDvwHSoCI5g==", + "version": "4.14.198", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.198.tgz", + "integrity": "sha512-trNJ/vtMZYMLhfN45uLq4ShQSw0/S7xCTLLVM+WM1rmFpba/VS42jVUgaO3w/NOLiWR/09lnYk0yMaA/atdIsg==", "dev": true }, "node_modules/@types/minimist": { @@ -1585,7 +1585,7 @@ }, "node_modules/@types/semver": { "version": "7.5.1", - "resolved": "https://registry.npmmirror.com/@types/semver/-/semver-7.5.1.tgz", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==", "dev": true }, @@ -1596,16 +1596,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.5.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.5.0.tgz", - "integrity": "sha512-2pktILyjvMaScU6iK3925uvGU87E+N9rh372uGZgiMYwafaw9SXq86U04XPq3UH6tzRvNgBsub6x2DacHc33lw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.6.0.tgz", + "integrity": "sha512-CW9YDGTQnNYMIo5lMeuiIG08p4E0cXrXTbcZ2saT/ETE7dWUrNxlijsQeU04qAAKkILiLzdQz+cGFxCJjaZUmA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.5.0", - "@typescript-eslint/type-utils": "6.5.0", - "@typescript-eslint/utils": "6.5.0", - "@typescript-eslint/visitor-keys": "6.5.0", + "@typescript-eslint/scope-manager": "6.6.0", + "@typescript-eslint/type-utils": "6.6.0", + "@typescript-eslint/utils": "6.6.0", + "@typescript-eslint/visitor-keys": "6.6.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -1616,6 +1616,10 @@ "engines": { "node": "^16.0.0 || >=18.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, "peerDependencies": { "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", "eslint": "^7.0.0 || ^8.0.0" @@ -1660,20 +1664,24 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "6.5.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-6.5.0.tgz", - "integrity": "sha512-LMAVtR5GN8nY0G0BadkG0XIe4AcNMeyEy3DyhKGAh9k4pLSMBO7rF29JvDBpZGCmp5Pgz5RLHP6eCpSYZJQDuQ==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.6.0.tgz", + "integrity": "sha512-setq5aJgUwtzGrhW177/i+DMLqBaJbdwGj2CPIVFFLE0NCliy5ujIdLHd2D1ysmlmsjdL2GWW+hR85neEfc12w==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.5.0", - "@typescript-eslint/types": "6.5.0", - "@typescript-eslint/typescript-estree": "6.5.0", - "@typescript-eslint/visitor-keys": "6.5.0", + "@typescript-eslint/scope-manager": "6.6.0", + "@typescript-eslint/types": "6.6.0", + "@typescript-eslint/typescript-estree": "6.6.0", + "@typescript-eslint/visitor-keys": "6.6.0", "debug": "^4.3.4" }, "engines": { "node": "^16.0.0 || >=18.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" }, @@ -1684,32 +1692,40 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.5.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-6.5.0.tgz", - "integrity": "sha512-A8hZ7OlxURricpycp5kdPTH3XnjG85UpJS6Fn4VzeoH4T388gQJ/PGP4ole5NfKt4WDVhmLaQ/dBLNDC4Xl/Kw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.6.0.tgz", + "integrity": "sha512-pT08u5W/GT4KjPUmEtc2kSYvrH8x89cVzkA0Sy2aaOUIw6YxOIjA8ilwLr/1fLjOedX1QAuBpG9XggWqIIfERw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.5.0", - "@typescript-eslint/visitor-keys": "6.5.0" + "@typescript-eslint/types": "6.6.0", + "@typescript-eslint/visitor-keys": "6.6.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.5.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-6.5.0.tgz", - "integrity": "sha512-f7OcZOkRivtujIBQ4yrJNIuwyCQO1OjocVqntl9dgSIZAdKqicj3xFDqDOzHDlGCZX990LqhLQXWRnQvsapq8A==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.6.0.tgz", + "integrity": "sha512-8m16fwAcEnQc69IpeDyokNO+D5spo0w1jepWWY2Q6y5ZKNuj5EhVQXjtVAeDDqvW6Yg7dhclbsz6rTtOvcwpHg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.5.0", - "@typescript-eslint/utils": "6.5.0", + "@typescript-eslint/typescript-estree": "6.6.0", + "@typescript-eslint/utils": "6.6.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" }, @@ -1720,22 +1736,26 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.5.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/types/-/types-6.5.0.tgz", - "integrity": "sha512-eqLLOEF5/lU8jW3Bw+8auf4lZSbbljHR2saKnYqON12G/WsJrGeeDHWuQePoEf9ro22+JkbPfWQwKEC5WwLQ3w==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.6.0.tgz", + "integrity": "sha512-CB6QpJQ6BAHlJXdwUmiaXDBmTqIE2bzGTDLADgvqtHWuhfNP3rAOK7kAgRMAET5rDRr9Utt+qAzRBdu3AhR3sg==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.5.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.5.0.tgz", - "integrity": "sha512-q0rGwSe9e5Kk/XzliB9h2LBc9tmXX25G0833r7kffbl5437FPWb2tbpIV9wAATebC/018pGa9fwPDuvGN+LxWQ==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.6.0.tgz", + "integrity": "sha512-hMcTQ6Al8MP2E6JKBAaSxSVw5bDhdmbCEhGW/V8QXkb9oNsFkA4SBuOMYVPxD3jbtQ4R/vSODBsr76R6fP3tbA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.5.0", - "@typescript-eslint/visitor-keys": "6.5.0", + "@typescript-eslint/types": "6.6.0", + "@typescript-eslint/visitor-keys": "6.6.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1745,6 +1765,10 @@ "engines": { "node": "^16.0.0 || >=18.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, "peerDependenciesMeta": { "typescript": { "optional": true @@ -1753,7 +1777,7 @@ }, "node_modules/@typescript-eslint/typescript-estree/node_modules/lru-cache": { "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "dependencies": { @@ -1765,7 +1789,7 @@ }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { "version": "7.5.4", - "resolved": "https://registry.npmmirror.com/semver/-/semver-7.5.4.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { @@ -1780,34 +1804,38 @@ }, "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "6.5.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-6.5.0.tgz", - "integrity": "sha512-9nqtjkNykFzeVtt9Pj6lyR9WEdd8npPhhIPM992FWVkZuS6tmxHfGVnlUcjpUP2hv8r4w35nT33mlxd+Be1ACQ==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.6.0.tgz", + "integrity": "sha512-mPHFoNa2bPIWWglWYdR0QfY9GN0CfvvXX1Sv6DlSTive3jlMTUy+an67//Gysc+0Me9pjitrq0LJp0nGtLgftw==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.5.0", - "@typescript-eslint/types": "6.5.0", - "@typescript-eslint/typescript-estree": "6.5.0", + "@typescript-eslint/scope-manager": "6.6.0", + "@typescript-eslint/types": "6.6.0", + "@typescript-eslint/typescript-estree": "6.6.0", "semver": "^7.5.4" }, "engines": { "node": "^16.0.0 || >=18.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" } }, "node_modules/@typescript-eslint/utils/node_modules/lru-cache": { "version": "6.0.0", - "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-6.0.0.tgz", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "dependencies": { @@ -1819,7 +1847,7 @@ }, "node_modules/@typescript-eslint/utils/node_modules/semver": { "version": "7.5.4", - "resolved": "https://registry.npmmirror.com/semver/-/semver-7.5.4.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { @@ -1834,21 +1862,25 @@ }, "node_modules/@typescript-eslint/utils/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.5.0", - "resolved": "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.5.0.tgz", - "integrity": "sha512-yCB/2wkbv3hPsh02ZS8dFQnij9VVQXJMN/gbQsaaY+zxALkZnxa/wagvLEFsAWMPv7d7lxQmNsIzGU1w/T/WyA==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.6.0.tgz", + "integrity": "sha512-L61uJT26cMOfFQ+lMZKoJNbAEckLe539VhTxiGHrWl5XSKQgA0RTBZJW2HFPy5T0ZvPVSD93QsrTKDkfNwJGyQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.5.0", + "@typescript-eslint/types": "6.6.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, "node_modules/@vitejs/plugin-react": { @@ -4018,6 +4050,12 @@ "resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz", "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" }, + "node_modules/immutable": { + "version": "4.3.4", + "resolved": "https://registry.npmmirror.com/immutable/-/immutable-4.3.4.tgz", + "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", + "dev": true + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.0.tgz", @@ -6444,6 +6482,23 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "node_modules/sass": { + "version": "1.66.1", + "resolved": "https://registry.npmmirror.com/sass/-/sass-1.66.1.tgz", + "integrity": "sha512-50c+zTsZOJVgFfTgwwEzkjA3/QACgdNsKueWPyAR0mRINIvLAStVQBbPg14iuqEQ74NPDbXzJARJ/O4SI1zftA==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmmirror.com/saxes/-/saxes-6.0.0.tgz", @@ -6592,11 +6647,6 @@ "tslib": "^2.0.3" } }, - "node_modules/sort-by": { - "version": "0.0.2", - "resolved": "https://registry.npmmirror.com/sort-by/-/sort-by-0.0.2.tgz", - "integrity": "sha512-iOX5oHA4a0eqTMFiWrHYqv924UeRKFBLhym7iwSVG37Egg2wApgZKAjyzM9WZjMwKv6+8Zi+nIaJ7FYsO9EkoA==" - }, "node_modules/source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz", @@ -7340,9 +7390,9 @@ } }, "node_modules/unplugin-icons": { - "version": "0.16.6", - "resolved": "https://registry.npmmirror.com/unplugin-icons/-/unplugin-icons-0.16.6.tgz", - "integrity": "sha512-jL70sAC7twp4hI/MTfm+vyvTRtHqiEIzf3XOjJz7yzhMEEQnk5Ey5YIXRAU03Mc4BF99ITvvnBzfyRZee86OeA==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/unplugin-icons/-/unplugin-icons-0.17.0.tgz", + "integrity": "sha512-gMv66eY/Hj64heM55XrfDH3LUCWI51mtkBVUPVl9VkpvLgAYhdVe9nRuzu6p+idmCLSQVq7xiPxQcD4aXCgW5A==", "dev": true, "dependencies": { "@antfu/install-pkg": "^0.1.1", @@ -7353,6 +7403,9 @@ "local-pkg": "^0.4.3", "unplugin": "^1.4.0" }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, "peerDependencies": { "@svgr/core": ">=7.0.0", "@svgx/core": "^1.0.1", diff --git a/package.json b/package.json index d5ad9f7..6ec90f0 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,15 @@ "scripts": { "dev": "vite", "dev-host": "vite --host 0.0.0.0", - "build": "vite build && tsc && vite build", + "build": "vite build", + "clean": "rimraf dist .eslintrc-auto-import.json auto-imports.d.ts", "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "format": "prettier --write src/", "preview": "vite preview" }, "dependencies": { - "@ant-design/icons": "^5.2.5", - "antd": "^5.8.5", + "@ant-design/icons": "^5.2.6", + "antd": "^5.8.6", "axios": "^1.5.0", "jwt-decode": "^3.1.2", "localforage": "^1.10.0", @@ -22,19 +23,18 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-router": "^6.15.0", - "react-router-dom": "^6.15.0", - "sort-by": "^0.0.2" + "react-router-dom": "^6.15.0" }, "devDependencies": { "@svgr/core": "^8.1.0", "@svgr/plugin-jsx": "^8.1.0", "@types/jsdom": "^21.1.2", - "@types/lodash": "^4.14.197", - "@types/node": "^20.5.7", + "@types/lodash": "^4.14.198", + "@types/node": "^20.5.9", "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7", - "@typescript-eslint/eslint-plugin": "^6.5.0", - "@typescript-eslint/parser": "^6.5.0", + "@typescript-eslint/eslint-plugin": "^6.6.0", + "@typescript-eslint/parser": "^6.6.0", "@vitejs/plugin-react": "^4.0.4", "eslint": "^8.48.0", "eslint-config-prettier": "^9.0.0", @@ -46,10 +46,11 @@ "eslint-plugin-react-refresh": "^0.4.3", "jsdom": "^22.1.0", "prettier": "^3.0.3", + "sass": "^1.66.1", "stylelint-config-prettier": "^9.0.5", "typescript": "^5.2.2", "unplugin-auto-import": "^0.16.6", - "unplugin-icons": "^0.16.6", + "unplugin-icons": "^0.17.0", "vite": "^4.4.9" } } diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..f3703eb Binary files /dev/null and b/public/logo.png differ diff --git a/public/logo.svg b/public/logo.svg new file mode 100644 index 0000000..e2769cb --- /dev/null +++ b/public/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/App.css b/src/App.css deleted file mode 100644 index b9d355d..0000000 --- a/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/src/App.tsx b/src/App.tsx index 3561770..869499d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,13 @@ import React from 'react' import router from '@/router' +import LoadingMask from '@/components/common/LoadingMask.tsx' const App: React.FC = () => { return ( <> - + }> + + ) } diff --git a/src/assets/css/_mixins.scss b/src/assets/css/_mixins.scss new file mode 100644 index 0000000..47308ed --- /dev/null +++ b/src/assets/css/_mixins.scss @@ -0,0 +1,22 @@ +@mixin keyframes($animationName) { + @-webkit-keyframes #{$animationName} { + @content + } + @-moz-keyframes #{$animationName} { + @content + } + @-o-keyframes #{$animationName} { + @content + } + @keyframes #{$animationName} { + @content + } +} + +@mixin unique-keyframes { + $animationName: unique-id(); + animation-name: $animationName; + @include keyframes($animationName) { + @content + } +} \ No newline at end of file diff --git a/src/assets/css/base.css b/src/assets/css/base.css deleted file mode 100644 index 1426524..0000000 --- a/src/assets/css/base.css +++ /dev/null @@ -1,63 +0,0 @@ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html { - scroll-behavior: smooth; -} - -em, -i { - font-style: normal -} - -li { - list-style: none -} - -img { - border: 0; - vertical-align: middle -} - -button { - cursor: pointer -} - -a { - color: #666; - text-decoration: none -} - -button, -input { - font-family: Microsoft YaHei, Heiti SC, tahoma, arial, Hiragino Sans GB, "\5B8B\4F53", sans-serif; - border: 0; - outline: none; -} - -body { - -webkit-font-smoothing: antialiased; - background-color: #fff; - font: 12px/1.5 Microsoft YaHei, Heiti SC, tahoma, arial, Hiragino Sans GB, "\5B8B\4F53", sans-serif; - color: #666 -} - -.hide, -.none { - display: none -} - -.clearfix:after { - visibility: hidden; - clear: both; - display: block; - content: "."; - height: 0 -} - -.clearfix { - *zoom: 1 -} \ No newline at end of file diff --git a/src/assets/css/base.scss b/src/assets/css/base.scss new file mode 100644 index 0000000..8bb5df0 --- /dev/null +++ b/src/assets/css/base.scss @@ -0,0 +1,63 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +em, +i { + font-style: normal +} + +li { + list-style: none +} + +img { + border: 0; + vertical-align: middle +} + +button { + cursor: pointer +} + +a { + color: #666; + text-decoration: none +} + +button, +input { + font-family: Microsoft YaHei, Heiti SC, tahoma, arial, Hiragino Sans GB, "\5B8B\4F53", sans-serif; + border: 0; + outline: none; +} + +body { + -webkit-font-smoothing: antialiased; + background-color: #fff; + font: 12px/1.5 Microsoft YaHei, Heiti SC, tahoma, arial, Hiragino Sans GB, "\5B8B\4F53", sans-serif; + color: #666 +} + +.hide, +.none { + display: none +} + +.clearfix:after { + visibility: hidden; + clear: both; + display: block; + content: "."; + height: 0 +} + +.clearfix { + *zoom: 1 +} \ No newline at end of file diff --git a/src/assets/css/common.css b/src/assets/css/common.css deleted file mode 100644 index 1435e89..0000000 --- a/src/assets/css/common.css +++ /dev/null @@ -1,106 +0,0 @@ -:root { - --main-color: #00D4FF; - --background-color: #F5F5F5; - --font-main-color: #4D4D4D; - --font-secondary-color: #9E9E9E; -} - -.body { - color: var(--font-main-color); - user-select: none; - min-width: 1800px; - min-height: 400px; -} - -.fill { - height: 100%; - width: 100%; -} - -.fill-with { - width: 100%; -} - -.fill-height { - height: 100%; -} - -.background-white { - background-color: white; -} - -.center-box { - display: flex; - justify-content: center; - align-items: center; -} - -.vertical-center-box { - display: flex; - align-items: center; -} - -.horizontal-center-box { - display: flex; - justify-content: center; -} - -.icon-size-xs { - width: 16px; - height: 16px; -} - -.icon-size-xs > use { - width: 16px; - height: 16px; -} - -.icon-size-sm { - width: 20px; - height: 20px; -} - -.icon-size-sm > use { - width: 20px; - height: 20px; -} - -.icon-size-md { - width: 24px; - height: 24px; -} - -.icon-size-md > use { - width: 24px; - height: 24px; -} - -.icon-size-lg { - width: 32px; - height: 32px; -} - -.icon-size-lg > use { - width: 32px; - height: 32px; -} - -.icon-size-xl { - width: 64px; - height: 64px; -} - -.icon-size-xl > use { - width: 64px; - height: 64px; -} - -.icon-size-menu { - width: 23px; - height: 23px; -} - -.icon-size-menu > use { - width: 23px; - height: 23px; -} diff --git a/src/assets/css/common.scss b/src/assets/css/common.scss new file mode 100644 index 0000000..e1716f7 --- /dev/null +++ b/src/assets/css/common.scss @@ -0,0 +1,115 @@ +@use '@/assets/css/constants.scss' as constants; + +#root { + height: 100vh; + width: 100vw; +} + +.body { + color: constants.$font-main-color; + user-select: none; + min-width: 900px; + min-height: 400px; +} + +.fill { + height: 100%; + width: 100%; +} + +.fill-with { + width: 100%; +} + +.fill-height { + height: 100%; +} + +.background-white { + background-color: white; +} + +.center-box { + display: flex; + justify-content: center; + align-items: center; +} + +.vertical-center-box { + display: flex; + align-items: center; +} + +.horizontal-center-box { + display: flex; + justify-content: center; +} + +.icon-size-xs { + width: 16px; + height: 16px; + + > use { + width: 16px; + height: 16px; + } +} + +.icon-size-sm { + width: 20px; + height: 20px; + + > use { + width: 20px; + height: 20px; + } +} + +.icon-size-md { + width: 24px; + height: 24px; + + > use { + width: 24px; + height: 24px; + } +} + +.icon-size-lg { + width: 32px; + height: 32px; + + > use { + + width: 32px; + height: 32px; + } +} + +.icon-size-xl { + width: 64px; + height: 64px; + + > use { + width: 64px; + height: 64px; + } +} + +.icon-size-menu { + width: 23px; + height: 23px; + + > use { + width: 23px; + height: 23px; + } +} + +.flex-horizontal { + flex-direction: row; +} + +.flex-vertical { + flex-direction: column; +} \ No newline at end of file diff --git a/src/assets/css/components/common/fit-center.scss b/src/assets/css/components/common/fit-center.scss new file mode 100644 index 0000000..fd07971 --- /dev/null +++ b/src/assets/css/components/common/fit-center.scss @@ -0,0 +1,7 @@ +.fit-center { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; +} \ No newline at end of file diff --git a/src/assets/css/components/common/fit-fullscreen.scss b/src/assets/css/components/common/fit-fullscreen.scss new file mode 100644 index 0000000..83d0ff6 --- /dev/null +++ b/src/assets/css/components/common/fit-fullscreen.scss @@ -0,0 +1,5 @@ +.fit-fullscreen { + position: relative; + width: 100%; + height: 100vh; +} \ No newline at end of file diff --git a/src/assets/css/components/common/hide-scrollbar.scss b/src/assets/css/components/common/hide-scrollbar.scss new file mode 100644 index 0000000..799a993 --- /dev/null +++ b/src/assets/css/components/common/hide-scrollbar.scss @@ -0,0 +1,68 @@ +@use '@/assets/css/constants' as constants; + +.hide-scrollbar-mask { + width: 100%; + height: 100%; + overflow: hidden; + + .hide-scrollbar-selection { + position: relative; + overflow: scroll; + scrollbar-width: none; + -ms-overflow-style: none; + + .hide-scrollbar-content { + display: inline-block; + width: 100%; + min-width: 900px; + } + } + + ::-webkit-scrollbar { + display: none; + } + + .scrollbar { + position: absolute; + z-index: 1000; + opacity: .5; + touch-action: none; + + .box { + position: relative; + width: 100%; + height: 100%; + border-radius: 8px; + overflow: hidden; + + + .block { + position: absolute; + width: 100%; + height: 100%; + border-radius: 8px; + background-color: constants.$font-secondary-color; + transition: background-color .2s; + } + :hover { + background-color: constants.$font-main-color; + } + } + } + + .vertical-scrollbar { + padding: 12px 4px; + width: 16px; + height: 100%; + right: 0; + top: 0; + } + + .horizontal-scrollbar { + padding: 4px 12px; + width: 100%; + height: 16px; + left: 0; + bottom: 0; + } +} \ No newline at end of file diff --git a/src/assets/css/components/common/indicator.scss b/src/assets/css/components/common/indicator.scss new file mode 100644 index 0000000..efe8e76 --- /dev/null +++ b/src/assets/css/components/common/indicator.scss @@ -0,0 +1,34 @@ +@use '@/assets/css/constants.scss' as constants; + +.dot-list { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + width: 100%; + + .item { + flex: auto; + cursor: pointer; + + .dot { + width: 10px; + height: 10px; + border-radius: 50%; + border: { + width: 2px; + color: constants.$font-secondary-color; + style: solid; + }; + transition: all .2s; + } + + :hover { + background-color: constants.$focus-color; + } + } + + .active > * { + background-color: constants.$font-secondary-color !important; + } +} \ No newline at end of file diff --git a/src/assets/css/components/common/loading-mask.scss b/src/assets/css/components/common/loading-mask.scss new file mode 100644 index 0000000..ad20dde --- /dev/null +++ b/src/assets/css/components/common/loading-mask.scss @@ -0,0 +1,10 @@ +.loading-mask { + position: absolute; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + z-index: 100; + background-color: rgba(200, 200, 200, 0.2); +} \ No newline at end of file diff --git a/src/assets/css/components/home/footer.scss b/src/assets/css/components/home/footer.scss new file mode 100644 index 0000000..7abf726 --- /dev/null +++ b/src/assets/css/components/home/footer.scss @@ -0,0 +1,19 @@ +.icons { + display: flex; + gap: 20px; + + .icon { + font-size: 8em; + color: white; + } +} + +.links { + font-size: 2em; + text-decoration: underline; + color: white; + + > * { + color: white; + } +} \ No newline at end of file diff --git a/src/assets/css/components/home/home.scss b/src/assets/css/components/home/home.scss new file mode 100644 index 0000000..f339e4f --- /dev/null +++ b/src/assets/css/components/home/home.scss @@ -0,0 +1,11 @@ +.indicator { + position: fixed; + margin: { + right: 20px; + }; + width: 20px; + height: 100px; + right: 0; + top: 50%; + transform: translateY(-50%); +} \ No newline at end of file diff --git a/src/assets/css/components/home/slogan.scss b/src/assets/css/components/home/slogan.scss new file mode 100644 index 0000000..018eec3 --- /dev/null +++ b/src/assets/css/components/home/slogan.scss @@ -0,0 +1,63 @@ +@use "@/assets/css/mixins" as mixins; + +.center-box { + display: flex; + flex-direction: column; +} + +.big-logo { + font: { + size: 5em; + weight: bold; + }; + color: #666; +} + +.slogan { + font: { + size: 1.3em; + style: italic; + }; + color: #666; + + .cursor { + font-style: normal; + animation: 1s infinite; + + @include mixins.unique-keyframes { + 0% { + opacity: 0; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0; + } + } + } +} + +.scroll-down { + position: absolute; + bottom: 10px; + padding: 20px; + cursor: pointer; + animation: 1.5s infinite; + @include mixins.unique-keyframes { + 0%, + 100% { + -ms-filter: none; + filter: none; + opacity: 1; + transform: translateY(10px); + } + + 50% { + -ms-filter: alpha(opacity=40); + filter: alpha(opacity=40); + opacity: .4; + transform: translateY(-10px); + } + } +} \ No newline at end of file diff --git a/src/assets/css/constants.scss b/src/assets/css/constants.scss new file mode 100644 index 0000000..edf5734 --- /dev/null +++ b/src/assets/css/constants.scss @@ -0,0 +1,8 @@ +$main-color: #00D4FF; +$background-color: #F5F5F5; +$font-main-color: #4D4D4D; +$font-secondary-color: #9E9E9E; +$focus-color: #DDDDDD; +$border-color: rgba(204, 204, 204, 0.33); +$url-color: rgba(102, 102, 102, .8); +$url-active-color: #ccc \ No newline at end of file diff --git a/src/assets/css/login.css b/src/assets/css/login.css deleted file mode 100644 index fd5fe6e..0000000 --- a/src/assets/css/login.css +++ /dev/null @@ -1,63 +0,0 @@ -.login-background { - display: flex; - height: 100vh; - width: 100vw; - justify-content: center; - align-items: center; - background-color: #B3E5FC; -} - -.login-box { - display: flex; - width: 800px; - height: 450px; - background-color: #448AFF; -} - -.login-box-left { - display: flex; - justify-content: center; - align-items: center; - height: 100%; - flex: 2; -} - -.login-box-left-text { - font-size: 3rem; - color: white; - font-weight: bold; -} - -.login-box-left-text>div:last-child { - font-size: 1.8rem; - font-weight: normal; -} - -.login-box-right { - position: relative; - height: 100%; - flex: 3; - background-color: white; -} - -.login-from-text { - position: absolute; - top: 60px; - left: 40px; - font-weight: bold; - font-size: 2rem; -} - -.login-from { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - margin-top: 30px; - width: 100%; - height: 100%; -} - -.login-from-item { - width: 80%; -} \ No newline at end of file diff --git a/src/assets/css/manager.css b/src/assets/css/manager.css deleted file mode 100644 index 17c4829..0000000 --- a/src/assets/css/manager.css +++ /dev/null @@ -1,35 +0,0 @@ -.top-bar { - display: flex; - justify-content: right; - background-color: #317ece; - padding: 10px 20px; -} - -.top-bar > button:hover { - color: #F5F5F5; - border-color: #F5F5F5; -} - -.search-row { - display: flex; - gap: 20px; - margin: 10px 10px; -} - -.search-row > * { - display: flex; - gap: 5px; - align-items: center; - flex: 1; - font-size: 1rem; -} - -.search-row > *:not(.operation-buttons) > *:last-child { - flex: 1; -} - -.operation-buttons { - display: flex; - justify-content: right; - gap: 10px; -} \ No newline at end of file diff --git a/src/assets/css/pages/header.scss b/src/assets/css/pages/header.scss new file mode 100644 index 0000000..eedd195 --- /dev/null +++ b/src/assets/css/pages/header.scss @@ -0,0 +1,164 @@ +@use "@/assets/css/mixins" as mixins; +@use "@/assets/css/constants" as constants; + +.nav { + display: flex; + position: fixed; + align-items: center; + z-index: 1; + width: 100%; + height: 70px; + background-color: white; + border: { + bottom: { + width: 1px; + style: solid; + color: constants.$border-color; + } + } + animation: .5s ease both; + + @include mixins.unique-keyframes { + 0% { + transform: translateY(-100%); + } + 100% { + transform: translateY(0); + } + } + + .logo { + padding: 0 40px; + + .title { + font-size: 2.8em; + font-family: century gothic, texgyreadventor, stheiti, sans-serif; + } + } + + nav { + display: flex; + justify-content: end; + flex: 1; + transition: { + property: all; + duration: .5s; + }; + + .menu { + padding: 0 30px; + + .item { + display: inline-block; + font-size: 1.5em; + transition: { + property: all; + duration: .3s; + }; + + a { + padding: 5px 20px; + color: constants.$url-color; + } + } + + .active { + border: { + bottom: { + width: 2px; + style: solid; + color: constants.$url-active-color; + }; + }; + } + + :hover { + transform: translateY(-5px); + } + } + + .dropdown-menu-button { + display: none; + margin: 0 20px; + width: 45px; + height: 45px; + justify-content: center; + align-items: center; + border-radius: 6px; + } + + .dropdown-menu-button.active { + background-color: transparentize(constants.$focus-color, 0.8); + border: { + width: 1px; + color: constants.$focus-color; + style: solid; + }; + } + + @media screen and (max-width: 900px) { + .menu { + display: none; + } + + .dropdown-menu-button { + display: flex; + } + } + } +} + +.dropdown-menu-content { + display: none; + + @media screen and (max-width: 900px) { + display: none; + position: fixed; + width: 100%; + top: 70px; + border: { + width: 1px; + color: constants.$border-color; + style: solid; + }; + background-color: white; + z-index: 1; + + ul { + li { + font-size: 1.2em; + text-align: center; + + a { + display: block; + width: 100%; + height: 100%; + padding: 10px; + } + + :hover { + background-color: constants.$focus-color; + } + } + } + } +} + +@media screen and (max-width: 900px){ + .dropdown-menu-content.show { + display: block; + } +} + +.nav.hide { + animation: .5s ease both; + + @include mixins.unique-keyframes { + 0% { + transform: translateY(0); + } + 100% { + transform: translateY(-100%); + } + } +} \ No newline at end of file diff --git a/src/assets/css/pages/login.scss b/src/assets/css/pages/login.scss new file mode 100644 index 0000000..22629a8 --- /dev/null +++ b/src/assets/css/pages/login.scss @@ -0,0 +1,63 @@ +.login-background { + display: flex; + height: 100vh; + width: 100vw; + justify-content: center; + align-items: center; + background-color: #B3E5FC; +} + +.login-box { + display: flex; + width: 800px; + height: 450px; + background-color: #448AFF; +} + +.login-box-left { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + flex: 2; +} + +.login-box-left-text { + font-size: 3rem; + color: white; + font-weight: bold; + + > div:last-child { + font-size: 1.8rem; + font-weight: normal; + } +} + +.login-box-right { + position: relative; + height: 100%; + flex: 3; + background-color: white; +} + +.login-from-text { + position: absolute; + top: 60px; + left: 40px; + font-weight: bold; + font-size: 2rem; +} + +.login-from { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + margin-top: 30px; + width: 100%; + height: 100%; +} + +.login-from-item { + width: 80%; +} \ No newline at end of file diff --git a/src/assets/svg/down.svg b/src/assets/svg/down.svg new file mode 100644 index 0000000..872d226 --- /dev/null +++ b/src/assets/svg/down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/github.svg b/src/assets/svg/github.svg new file mode 100644 index 0000000..fdf2ea5 --- /dev/null +++ b/src/assets/svg/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/home.svg b/src/assets/svg/home.svg deleted file mode 100644 index 689ddce..0000000 --- a/src/assets/svg/home.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/svg/jenkins.svg b/src/assets/svg/jenkins.svg new file mode 100644 index 0000000..51fbbc6 --- /dev/null +++ b/src/assets/svg/jenkins.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/loading.svg b/src/assets/svg/loading.svg new file mode 100644 index 0000000..a7c3d67 --- /dev/null +++ b/src/assets/svg/loading.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/logo.svg b/src/assets/svg/logo.svg new file mode 100644 index 0000000..243d161 --- /dev/null +++ b/src/assets/svg/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/menu.svg b/src/assets/svg/menu.svg new file mode 100644 index 0000000..46ac707 --- /dev/null +++ b/src/assets/svg/menu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/common/FitCenter.tsx b/src/components/common/FitCenter.tsx new file mode 100644 index 0000000..b33d8e6 --- /dev/null +++ b/src/components/common/FitCenter.tsx @@ -0,0 +1,23 @@ +import React from 'react' +import '@/assets/css/components/common/fit-center.scss' + +interface FitCenterProps + extends React.DetailedHTMLProps, HTMLDivElement> { + vertical?: boolean +} + +const FitCenter: React.FC = (props) => { + const { className, vertical, ..._props } = props + return ( + <> +
+ + ) +} + +export default FitCenter diff --git a/src/components/common/FitFullScreen.tsx b/src/components/common/FitFullScreen.tsx new file mode 100644 index 0000000..c61515d --- /dev/null +++ b/src/components/common/FitFullScreen.tsx @@ -0,0 +1,30 @@ +import React from 'react' +import '@/assets/css/components/common/fit-fullscreen.scss' + +interface FitFullscreenProps + extends React.DetailedHTMLProps, HTMLDivElement> { + zIndex?: number + backgroundColor?: string +} + +const FitFullScreen = forwardRef((props, ref) => { + const { zIndex, backgroundColor, className, style, ..._props } = props + return ( + <> +
+ {props.children} +
+ + ) +}) + +export default FitFullScreen diff --git a/src/components/common/HideScrollbar.tsx b/src/components/common/HideScrollbar.tsx new file mode 100644 index 0000000..de3a10a --- /dev/null +++ b/src/components/common/HideScrollbar.tsx @@ -0,0 +1,541 @@ +import React from 'react' +import '@/assets/css/components/common/hide-scrollbar.scss' + +interface HideScrollbarProps + extends React.DetailedHTMLProps, HTMLDivElement> { + isPreventScroll?: boolean + isPreventVerticalScroll?: boolean + isPreventHorizontalScroll?: boolean + isShowVerticalScrollbar?: boolean + isHiddenVerticalScrollbarWhenFull?: boolean + isShowHorizontalScrollbar?: boolean + isHiddenHorizontalScrollbarWhenFull?: boolean +} + +export interface HideScrollbarElement { + scrollTo(x: number, y: number, smooth?: boolean): void + scrollX(x: number, smooth?: boolean): void + scrollY(y: number, smooth?: boolean): void + scrollLeft(length: number, smooth?: boolean): void + scrollRight(length: number, smooth?: boolean): void + scrollUp(length: number, smooth?: boolean): void + scrollDown(length: number, smooth?: boolean): void + getX(): number + getY(): number + addEventListenerWithType( + type: K, + listener: (this: HTMLDivElement, ev: HTMLElementEventMap[K]) => never, + options?: boolean | AddEventListenerOptions + ): void + addEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | AddEventListenerOptions + ): void + removeEventListenerWithType( + type: K, + listener: (this: HTMLDivElement, ev: HTMLElementEventMap[K]) => never, + options?: boolean | EventListenerOptions + ): void + removeEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | EventListenerOptions + ): void +} + +const HideScrollbar = forwardRef((props, ref) => { + useImperativeHandle( + ref, + () => { + return { + scrollTo(x, y, smooth?: boolean) { + rootRef.current?.scrollTo({ + left: x, + top: y, + behavior: smooth === false ? 'instant' : 'smooth' + }) + }, + scrollX(x, smooth?: boolean) { + rootRef.current?.scrollTo({ + left: x, + behavior: smooth === false ? 'instant' : 'smooth' + }) + }, + scrollY(y, smooth?: boolean) { + rootRef.current?.scrollTo({ + top: y, + behavior: smooth === false ? 'instant' : 'smooth' + }) + }, + scrollLeft(length, smooth?: boolean) { + rootRef.current?.scrollTo({ + left: rootRef.current?.scrollLeft - length, + behavior: smooth === false ? 'instant' : 'smooth' + }) + }, + scrollRight(length, smooth?: boolean) { + rootRef.current?.scrollTo({ + left: rootRef.current?.scrollLeft + length, + behavior: smooth === false ? 'instant' : 'smooth' + }) + }, + scrollUp(length, smooth?: boolean) { + rootRef.current?.scrollTo({ + top: rootRef.current?.scrollTop - length, + behavior: smooth === false ? 'instant' : 'smooth' + }) + }, + scrollDown(length, smooth?: boolean) { + rootRef.current?.scrollTo({ + top: rootRef.current?.scrollTop + length, + behavior: smooth === false ? 'instant' : 'smooth' + }) + }, + getX() { + return rootRef.current?.scrollLeft ?? 0 + }, + getY() { + return rootRef.current?.scrollTop ?? 0 + }, + addEventListenerWithType( + type: K, + listener: (this: HTMLDivElement, ev: HTMLElementEventMap[K]) => never, + options?: boolean | AddEventListenerOptions + ): void { + rootRef.current?.addEventListener(type, listener, options) + }, + addEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | AddEventListenerOptions + ): void { + rootRef.current?.addEventListener(type, listener, options) + }, + removeEventListenerWithType( + type: K, + listener: (this: HTMLDivElement, ev: HTMLElementEventMap[K]) => never, + options?: boolean | EventListenerOptions + ): void { + rootRef.current?.removeEventListener(type, listener, options) + }, + removeEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | EventListenerOptions + ): void { + rootRef.current?.removeEventListener(type, listener, options) + } + } + }, + [] + ) + + const maskRef = useRef(null) + const rootRef = useRef(null) + const contentRef = useRef(null) + const wheelListenerRef = useRef<(event: WheelEvent) => void>(() => undefined) + const lastScrollbarClickPositionRef = useRef({ x: -1, y: -1 }) + const lastScrollbarTouchPositionRef = useRef({ x: -1, y: -1 }) + const lastTouchPositionRef = useRef({ x: -1, y: -1 }) + const [verticalScrollbarWidth, setVerticalScrollbarWidth] = useState(0) + const [verticalScrollbarLength, setVerticalScrollbarLength] = useState(100) + const [verticalScrollbarPosition, setVerticalScrollbarPosition] = useState(0) + const [verticalScrollbarOnClick, setVerticalScrollbarOnClick] = useState(false) + const [verticalScrollbarOnTouch, setVerticalScrollbarOnTouch] = useState(false) + const [horizontalScrollbarWidth, setHorizontalScrollbarWidth] = useState(0) + const [horizontalScrollbarLength, setHorizontalScrollbarLength] = useState(100) + const [horizontalScrollbarPosition, setHorizontalScrollbarPosition] = useState(0) + const [horizontalScrollbarOnClick, setHorizontalScrollbarOnClick] = useState(false) + const [horizontalScrollbarOnTouch, setHorizontalScrollbarOnTouch] = useState(false) + + const { + isPreventScroll, + isPreventVerticalScroll, + isPreventHorizontalScroll, + isShowVerticalScrollbar, + isHiddenVerticalScrollbarWhenFull, + isShowHorizontalScrollbar, + isHiddenHorizontalScrollbarWhenFull, + ..._props + } = props + + const isPreventAnyScroll = + isPreventScroll || isPreventVerticalScroll || isPreventHorizontalScroll + + const handleDefaultTouchStart = useCallback( + (event: React.TouchEvent) => { + if (event.touches.length !== 1 || isPreventScroll) { + lastTouchPositionRef.current = { x: -1, y: -1 } + return + } + + const { clientX, clientY } = event.touches[0] + lastTouchPositionRef.current = { x: clientX, y: clientY } + }, + [isPreventScroll] + ) + + const handleDefaultTouchmove = useCallback( + (event: React.TouchEvent) => { + if (event.touches.length !== 1 || isPreventScroll) { + lastTouchPositionRef.current = { x: -1, y: -1 } + return + } + const { clientX, clientY } = event.touches[0] + + if (!isPreventVerticalScroll) { + rootRef.current?.scrollTo({ + top: rootRef.current?.scrollTop + (lastTouchPositionRef.current.y - clientY), + behavior: 'instant' + }) + } + + if (!isPreventHorizontalScroll) { + rootRef.current?.scrollTo({ + left: rootRef.current?.scrollLeft + (lastTouchPositionRef.current.x - clientX), + behavior: 'instant' + }) + } + + lastTouchPositionRef.current = { x: clientX, y: clientY } + }, + [isPreventHorizontalScroll, isPreventScroll, isPreventVerticalScroll] + ) + + const handleDefaultMouseDown = (event: React.MouseEvent) => { + if (isPreventAnyScroll) + if (event.button === 1) { + event.preventDefault() + } + } + + const handleDefaultKeyDown = useCallback( + (event: React.KeyboardEvent) => { + if ( + isPreventScroll && + [ + 'ArrowUp', + 'ArrowDown', + 'ArrowLeft', + 'ArrowRight', + ' ', + '', + 'PageUp', + 'PageDown', + 'Home', + 'End' + ].find((value) => value === event.key) + ) { + event.preventDefault() + } + if ( + isPreventVerticalScroll && + ['ArrowUp', 'ArrowDown', ' ', '', 'PageUp', 'PageDown', 'Home', 'End'].find( + (value) => value === event.key + ) + ) { + event.preventDefault() + } + if ( + isPreventHorizontalScroll && + ['ArrowLeft', 'ArrowRight'].find((value) => value === event.key) + ) { + event.preventDefault() + } + }, + [isPreventHorizontalScroll, isPreventScroll, isPreventVerticalScroll] + ) + + const handleScrollbarMouseEvent = (eventFlag: string, scrollbarFlag: string) => { + return (event: React.MouseEvent) => { + switch (eventFlag) { + case 'down': + lastScrollbarClickPositionRef.current = { x: event.clientX, y: event.clientY } + switch (scrollbarFlag) { + case 'vertical': + setVerticalScrollbarOnClick(true) + break + case 'horizontal': + setHorizontalScrollbarOnClick(true) + break + } + break + case 'up': + case 'leave': + setVerticalScrollbarOnClick(false) + setHorizontalScrollbarOnClick(false) + break + case 'move': + if (verticalScrollbarOnClick) { + rootRef.current?.scrollTo({ + top: + rootRef.current?.scrollTop + + ((event.clientY - lastScrollbarClickPositionRef.current.y) / + (rootRef.current?.clientHeight ?? 1)) * + (contentRef.current?.clientHeight ?? 0), + behavior: 'instant' + }) + } + if (horizontalScrollbarOnClick) { + rootRef.current?.scrollTo({ + left: + rootRef.current?.scrollLeft + + ((event.clientX - lastScrollbarClickPositionRef.current.x) / + (rootRef.current?.clientWidth ?? 1)) * + (contentRef.current?.clientWidth ?? 0), + behavior: 'instant' + }) + } + lastScrollbarClickPositionRef.current = { + x: event.clientX, + y: event.clientY + } + } + } + } + + const handleScrollbarTouchEvent = (eventFlag: string, scrollbarFlag: string) => { + return (event: React.TouchEvent) => { + switch (eventFlag) { + case 'start': + if (event.touches.length !== 1) { + return + } + lastScrollbarTouchPositionRef.current = { + x: event.touches[0].clientX, + y: event.touches[0].clientY + } + switch (scrollbarFlag) { + case 'vertical': + setVerticalScrollbarOnTouch(true) + break + case 'horizontal': + setHorizontalScrollbarOnTouch(true) + break + } + break + case 'end': + case 'cancel': + setVerticalScrollbarOnTouch(false) + setHorizontalScrollbarOnTouch(false) + break + case 'move': + if (event.touches.length !== 1) { + return + } + if (verticalScrollbarOnTouch) { + rootRef.current?.scrollTo({ + top: + rootRef.current?.scrollTop + + ((event.touches[0].clientY - + lastScrollbarClickPositionRef.current.y) / + (rootRef.current?.clientHeight ?? 1)) * + (contentRef.current?.clientHeight ?? 0), + behavior: 'instant' + }) + } + if (horizontalScrollbarOnTouch) { + rootRef.current?.scrollTo({ + left: + rootRef.current?.scrollLeft + + ((event.touches[0].clientX - + lastScrollbarClickPositionRef.current.x) / + (rootRef.current?.clientWidth ?? 1)) * + (contentRef.current?.clientWidth ?? 0), + behavior: 'instant' + }) + } + lastScrollbarClickPositionRef.current = { + x: event.touches[0].clientX, + y: event.touches[0].clientY + } + } + } + } + + const handleDefaultScroll = () => { + setVerticalScrollbarPosition( + ((rootRef.current?.scrollTop ?? 0) / (contentRef.current?.clientHeight ?? 1)) * 100 + ) + setHorizontalScrollbarPosition( + ((rootRef.current?.scrollLeft ?? 0) / (contentRef.current?.clientWidth ?? 1)) * 100 + ) + } + + useEffect(() => { + const windowResizeListener = () => { + setVerticalScrollbarWidth( + (rootRef.current?.offsetWidth ?? 0) - (rootRef.current?.clientWidth ?? 0) + ) + setHorizontalScrollbarWidth( + (rootRef.current?.offsetHeight ?? 0) - (rootRef.current?.clientHeight ?? 0) + ) + + rootRef.current && + setVerticalScrollbarLength( + (rootRef.current.clientHeight / (contentRef.current?.clientHeight ?? 0)) * 100 + ) + + rootRef.current && + setHorizontalScrollbarLength( + (rootRef.current.clientWidth / (contentRef.current?.clientWidth ?? 0)) * 100 + ) + + return windowResizeListener + } + setTimeout(() => { + windowResizeListener() + }, 1000) + window.addEventListener('resize', windowResizeListener()) + + rootRef.current?.removeEventListener('wheel', wheelListenerRef.current) + if (isPreventAnyScroll) { + const handleDefaultWheel = (event: WheelEvent) => { + if (!event.altKey && !event.ctrlKey) { + if (isPreventScroll) { + event.preventDefault() + return + } + if (isPreventVerticalScroll && !event.shiftKey && !event.deltaX) { + event.preventDefault() + return + } + if (isPreventHorizontalScroll && (event.shiftKey || !event.deltaY)) { + event.preventDefault() + return + } + } + } + wheelListenerRef.current = handleDefaultWheel + rootRef.current?.addEventListener('wheel', handleDefaultWheel, { passive: false }) + } + + rootRef.current && + setVerticalScrollbarLength( + (rootRef.current.clientHeight / (contentRef.current?.clientHeight ?? 0)) * 100 + ) + + rootRef.current && + setHorizontalScrollbarLength( + (rootRef.current.clientWidth / (contentRef.current?.clientWidth ?? 0)) * 100 + ) + + return () => { + window.removeEventListener('resize', windowResizeListener) + } + }, [ + horizontalScrollbarLength, + isPreventAnyScroll, + isPreventHorizontalScroll, + isPreventScroll, + isPreventVerticalScroll + ]) + + return ( + <> +
+
+
+ {props.children} +
+
+