Kaynağa Gözat

chore(custom): 构建工具提交

linyuanjie 3 ay önce
ebeveyn
işleme
80bb812c14
100 değiştirilmiş dosya ile 4603 ekleme ve 4312 silme
  1. 4 13
      .vscode/settings.json
  2. 19 9
      README.md
  3. 18 7
      README.zh-CN.md
  4. 5 5
      package.json
  5. 10 20
      pnpm-lock.yaml
  6. 7 7
      postcss.config.js
  7. 16 19
      public/mockServiceWorker.js
  8. 0 2
      src/App.tsx
  9. 5 6
      src/_mock/handlers/_demo.js
  10. 10 11
      src/_mock/handlers/_org.js
  11. 42 44
      src/_mock/handlers/_user.js
  12. 7 8
      src/_mock/index.js
  13. 7 7
      src/_mock/utils.js
  14. 0 1
      src/api/request/index.ts
  15. 5 5
      src/api/services/demoService.ts
  16. 71 0
      src/api/services/message/data.d.ts
  17. 70 0
      src/api/services/message/index.ts
  18. 6 7
      src/api/services/orgService.ts
  19. 20 24
      src/api/services/userService.ts
  20. 14 16
      src/components/animate/motion-container.tsx
  21. 8 8
      src/components/animate/motion-lazy.tsx
  22. 15 21
      src/components/animate/motion-viewport.tsx
  23. 19 19
      src/components/animate/types.ts
  24. 3 3
      src/components/animate/variants/action.ts
  25. 89 89
      src/components/animate/variants/background.ts
  26. 106 107
      src/components/animate/variants/bounce.ts
  27. 22 22
      src/components/animate/variants/container.ts
  28. 126 127
      src/components/animate/variants/fade.ts
  29. 53 54
      src/components/animate/variants/flip.ts
  30. 95 95
      src/components/animate/variants/index.ts
  31. 9 9
      src/components/animate/variants/path.ts
  32. 32 33
      src/components/animate/variants/rotate.ts
  33. 53 54
      src/components/animate/variants/scale.ts
  34. 64 65
      src/components/animate/variants/slide.ts
  35. 13 13
      src/components/animate/variants/transition.ts
  36. 129 130
      src/components/animate/variants/zoom.ts
  37. 23 24
      src/components/card/index.tsx
  38. 10 11
      src/components/chart/chart.tsx
  39. 34 34
      src/components/chart/styles.css.ts
  40. 208 209
      src/components/chart/useChart.ts
  41. 31 27
      src/components/editor/index.tsx
  42. 5 5
      src/components/editor/styles.ts
  43. 88 88
      src/components/editor/toolbar.tsx
  44. 20 26
      src/components/icon/icon-button.tsx
  45. 11 12
      src/components/icon/iconify-icon.tsx
  46. 4 4
      src/components/icon/index.ts
  47. 6 6
      src/components/loading/circle-loading.tsx
  48. 2 2
      src/components/loading/index.tsx
  49. 33 34
      src/components/loading/line-loading.tsx
  50. 24 28
      src/components/locale-picker/index.tsx
  51. 18 15
      src/components/markdown/index.tsx
  52. 4 5
      src/components/markdown/styles.ts
  53. 7 7
      src/components/progress-bar/index.css.ts
  54. 49 49
      src/components/progress-bar/index.tsx
  55. 20 21
      src/components/scroll-progress/index.tsx
  56. 25 26
      src/components/scrollbar/index.tsx
  57. 80 65
      src/components/toast/index.tsx
  58. 3 3
      src/components/upload/index.ts
  59. 5 5
      src/components/upload/styles.ts
  60. 69 72
      src/components/upload/upload-avatar.tsx
  61. 19 21
      src/components/upload/upload-box.tsx
  62. 531 401
      src/components/upload/upload-illustration.tsx
  63. 76 96
      src/components/upload/upload-list-item.tsx
  64. 37 38
      src/components/upload/upload.tsx
  65. 116 116
      src/components/upload/utils.ts
  66. 7 7
      src/components/verifition/Verify/VerifyPoints/index.jsx
  67. 6 2
      src/components/verifition/utils/ase.js
  68. 4 5
      src/components/verifition/utils/axios.js
  69. 69 7
      src/components/verifition/utils/util.js
  70. 29 29
      src/hooks/event/use-copy-to-clipboard.ts
  71. 2 2
      src/hooks/index.ts
  72. 66 64
      src/hooks/web/use-media-query.ts
  73. 52 47
      src/layouts/components/bread-crumb.tsx
  74. 8 9
      src/layouts/components/header-simple.tsx
  75. 237 242
      src/layouts/components/notice.tsx
  76. 236 241
      src/layouts/components/search-bar.tsx
  77. 5 5
      src/layouts/dashboard/config.ts
  78. 47 47
      src/layouts/dashboard/index.tsx
  79. 39 40
      src/layouts/dashboard/main.tsx
  80. 80 75
      src/layouts/dashboard/multi-tabs/components/sortable-container.tsx
  81. 35 36
      src/layouts/dashboard/multi-tabs/components/sortable-item.tsx
  82. 104 102
      src/layouts/dashboard/multi-tabs/components/tab-item.tsx
  83. 27 29
      src/layouts/dashboard/multi-tabs/hooks/use-tab-label-render.tsx
  84. 79 79
      src/layouts/dashboard/multi-tabs/hooks/use-tab-operations.ts
  85. 32 27
      src/layouts/dashboard/multi-tabs/hooks/use-tab-style.ts
  86. 109 102
      src/layouts/dashboard/multi-tabs/index.tsx
  87. 59 59
      src/layouts/dashboard/multi-tabs/providers/multi-tabs-provider.tsx
  88. 24 24
      src/layouts/dashboard/multi-tabs/types.ts
  89. 51 48
      src/layouts/dashboard/nav/nav-horizontal.tsx
  90. 10 10
      src/layouts/simple/index.tsx
  91. 17 17
      src/locales/lang/en_US/common.json
  92. 5 5
      src/locales/lang/en_US/index.ts
  93. 138 138
      src/locales/lang/en_US/sys.json
  94. 17 17
      src/locales/lang/zh_CN/common.json
  95. 5 5
      src/locales/lang/zh_CN/index.ts
  96. 139 139
      src/locales/lang/zh_CN/sys.json
  97. 42 43
      src/pages/components/animate/control-panel.tsx
  98. 21 22
      src/pages/components/animate/index.tsx
  99. 24 26
      src/pages/components/animate/views/background/container.tsx
  100. 48 52
      src/pages/components/animate/views/background/index.tsx

+ 4 - 13
.vscode/settings.json

@@ -13,22 +13,13 @@
   "editor.quickSuggestions": {
     "strings": "on"
   },
-  "tailwindCSS.experimental.classRegex": [
-    [
-      "cn\\(([^)]*)\\)",
-      "[\"'`]([^\"'`]*).*?[\"'`]"
-    ]
-  ],
+  "tailwindCSS.experimental.classRegex": [["cn\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]],
   "npm.packageManager": "pnpm",
-  "i18n-ally.localesPaths": [
-    "src/locales/lang"
-  ],
-  "i18n-ally.enabledParsers": [
-    "json"
-  ],
+  "i18n-ally.localesPaths": ["src/locales/lang"],
+  "i18n-ally.enabledParsers": ["json"],
   "i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
   "i18n-ally.keystyle": "flat",
   "i18n-ally.sortKeys": true,
   "i18n-ally.sourceLanguage": "en_US",
   "i18n-ally.displayLanguage": "zh_CN"
-}
+}

+ 19 - 9
README.md

@@ -21,18 +21,20 @@
 
 **English** | [中文](./README.zh-CN.md)
 
-##  Sponsor
+## Sponsor
+
 <div style="display: flex; gap: 50px"> 
   <img style="width:300px" src="https://d3george.github.io/github-static/pay/weixin.jpg" >
   <img style="width:280px" src="https://d3george.github.io/github-static/pay/buymeacoffee.png" />
 </div>
 
 ## Preview
-+ https://admin.slashspaces.com/
 
-|![login.png](https://d3george.github.io/github-static/slash-admin/login.jpeg)|![login_dark.png](https://d3george.github.io/github-static/slash-admin/login_dark.jpeg)
-| ----------------------------------------------------------------- | ------------------------------------------------------------------- |
-|![analysis.png](https://d3george.github.io/github-static/slash-admin/analysis.png)|![workbench.png](https://d3george.github.io/github-static/slash-admin/workbench.png)
+- https://admin.slashspaces.com/
+
+| ![login.png](https://d3george.github.io/github-static/slash-admin/login.jpeg) | ![login_dark.png](https://d3george.github.io/github-static/slash-admin/login_dark.jpeg) |
+| --- | --- |
+| ![analysis.png](https://d3george.github.io/github-static/slash-admin/analysis.png) | ![workbench.png](https://d3george.github.io/github-static/slash-admin/workbench.png) |
 
 ## Features
 
@@ -86,40 +88,48 @@ pnpm build
 
 ## Docker deployment
 
-
 ### Build image and Run container
+
 #### build image
+
 Enter the project root directory in the terminal and execute the following command to build the Docker image:
+
 ```
 docker build -t your-image-name .
 ```
-Make sure to replace `your-image-name` with your own image name 
+
+Make sure to replace `your-image-name` with your own image name
 
 #### run container
+
 Run your application in the Docker container using the following command:
+
 ```
 docker run -p 3001:80 your-image-name
 ```
+
 This will run your application on port `80`(exposed in `Dockerfile`) of the container and map it to port `3001` on your host.
 
 Now you can access http://localhost:3001 to view the deployed applications.
 
 ### use docker-compose.yaml
+
 Enter the project root directory in the terminal and execute the following command to start Docker Compose:
+
 ```
 docker-compose up -d
 ```
+
 Docker Compose will build an image based on the configuration defined by 'docker-compose. yaml' and run the container in the background.
 
 After the container runs successfully, it can also be accessed through http://localhost:3001 To view the deployed applications.
 
-
 ## Git Contribution submission specification
 
 reference[.commitlint.config.js](./commitlint.config.js)
 
 - `feat` new features
-- `fix`  fix the
+- `fix` fix the
 - `docs` documentation or comments
 - `style` code format (changes that do not affect code execution)
 - `refactor` refactor

+ 18 - 7
README.zh-CN.md

@@ -21,19 +21,21 @@
 
 **中文** | [English](./README.md)
 
-## 赞助 
+## 赞助
+
 <div style="display: flex; gap: 50px"> 
   <img style="width:300px" src="https://d3george.github.io/github-static/pay/weixin.jpg" >
   <img style="width:280px" src="https://d3george.github.io/github-static/pay/buymeacoffee.png" />
 </div>
 
-
 ## 预览
-+ https://admin.slashspaces.com/
 
-|![login.png](https://d3george.github.io/github-static/slash-admin/login.jpeg)|![login_dark.png](https://d3george.github.io/github-static/slash-admin/login_dark.jpeg)
-| ----------------------------------------------------------------- | ------------------------------------------------------------------- |
-|![analysis.png](https://d3george.github.io/github-static/slash-admin/analysis.png)|![workbench.png](https://d3george.github.io/github-static/slash-admin/workbench.png)
+- https://admin.slashspaces.com/
+
+| ![login.png](https://d3george.github.io/github-static/slash-admin/login.jpeg) | ![login_dark.png](https://d3george.github.io/github-static/slash-admin/login_dark.jpeg) |
+| --- | --- |
+| ![analysis.png](https://d3george.github.io/github-static/slash-admin/analysis.png) | ![workbench.png](https://d3george.github.io/github-static/slash-admin/workbench.png) |
+
 ## 特性
 
 - 使用 React 18 hooks 进行构建。
@@ -89,28 +91,37 @@ pnpm build
 ## 容器化部署
 
 ### 构建镜像并运行容器
+
 #### 构建镜像
+
 在终端中进入项目根目录,并执行以下命令来构建 Docker 镜像:
+
 ```
 docker build -t your-image-name .
 ```
+
 确保将 `your-image-name` 替换为你自己的镜像名称
 
 #### 运行容器
+
 使用以下命令在 Docker 容器中运行你的应用:
+
 ```
 docker run -p 3001:80 your-image-name
 ```
+
 这将在容器的端口 `80` (暴露在`Dockerfile`中) 上运行你的应用,并将其映射到你主机的端口 `3001` 上。
 
 现在,你可以通过访问 http://localhost:3001 来查看部署的应用。
 
-
 ### 使用docker-compose.yaml
+
 在终端中进入项目根目录,并执行以下命令来启动 Docker Compose:
+
 ```
 docker-compose up -d
 ```
+
 Docker Compose 根据`docker-compose.yaml`定义的配置构建镜像并在后台运行容器.
 
 容器运行成功后,同样可以通过访问 http://localhost:3001来查看部署的应用。

+ 5 - 5
package.json

@@ -12,7 +12,7 @@
     "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
     "lint:lint-staged": "lint-staged",
     "tsc": "tsc --noEmit --skipLibCheck",
-    "commit": "git-cz"
+    "commit": "git add . && cz"
   },
   "config": {
     "commitizen": {
@@ -108,8 +108,8 @@
     "zustand": "^4.4.3"
   },
   "devDependencies": {
-    "@commitlint/cli": "^17.8.1",
-    "@commitlint/config-conventional": "^17.8.1",
+    "@commitlint/cli": "^17.7.1",
+    "@commitlint/config-conventional": "^17.7.0",
     "@faker-js/faker": "^8.1.0",
     "@trivago/prettier-plugin-sort-imports": "^4.2.0",
     "@types/autosuggest-highlight": "^3.2.0",
@@ -126,7 +126,7 @@
     "@typescript-eslint/parser": "^8.23.0",
     "autoprefixer": "^10.4.16",
     "babel-eslint": "^10.1.0",
-    "commitizen": "^4.3.1",
+    "commitizen": "^4.3.0",
     "commitlint-config-cz": "^0.13.3",
     "cz-conventional-changelog": "^3.3.0",
     "cz-customizable": "^7.4.0",
@@ -135,7 +135,7 @@
     "eslint-plugin-import": "^2.31.0",
     "eslint-plugin-prettier": "^5.2.3",
     "eslint-plugin-react": "^7.37.4",
-    "husky": "^9.1.7",
+    "husky": "^8.0.3",
     "lint-staged": "^15.5.0",
     "msw": "^2.4.9",
     "postcss": "^8.4.31",

+ 10 - 20
pnpm-lock.yaml

@@ -205,10 +205,10 @@ importers:
         version: 4.5.5(@types/react@18.3.12)(react@18.2.0)
     devDependencies:
       '@commitlint/cli':
-        specifier: ^17.8.1
+        specifier: ^17.7.1
         version: 17.8.1
       '@commitlint/config-conventional':
-        specifier: ^17.8.1
+        specifier: ^17.7.0
         version: 17.8.1
       '@faker-js/faker':
         specifier: ^8.1.0
@@ -259,7 +259,7 @@ importers:
         specifier: ^10.1.0
         version: 10.1.0(eslint@8.57.1)
       commitizen:
-        specifier: ^4.3.1
+        specifier: ^4.3.0
         version: 4.3.1
       commitlint-config-cz:
         specifier: ^0.13.3
@@ -286,8 +286,8 @@ importers:
         specifier: ^7.37.4
         version: 7.37.4(eslint@8.57.1)
       husky:
-        specifier: ^9.1.7
-        version: 9.1.7
+        specifier: ^8.0.3
+        version: 8.0.3
       lint-staged:
         specifier: ^15.5.0
         version: 15.5.0
@@ -2145,10 +2145,6 @@ packages:
   create-require@1.1.1:
     resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
 
-  cross-spawn@7.0.5:
-    resolution: {integrity: sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==}
-    engines: {node: '>= 8'}
-
   cross-spawn@7.0.6:
     resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
     engines: {node: '>= 8'}
@@ -3062,9 +3058,9 @@ packages:
     resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
     engines: {node: '>=16.17.0'}
 
-  husky@9.1.7:
-    resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==}
-    engines: {node: '>=18'}
+  husky@8.0.3:
+    resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==}
+    engines: {node: '>=14'}
     hasBin: true
 
   hyphenate-style-name@1.1.0:
@@ -7953,12 +7949,6 @@ snapshots:
 
   create-require@1.1.1: {}
 
-  cross-spawn@7.0.5:
-    dependencies:
-      path-key: 3.1.1
-      shebang-command: 2.0.0
-      which: 2.0.2
-
   cross-spawn@7.0.6:
     dependencies:
       path-key: 3.1.1
@@ -8613,7 +8603,7 @@ snapshots:
 
   execa@5.1.1:
     dependencies:
-      cross-spawn: 7.0.5
+      cross-spawn: 7.0.6
       get-stream: 6.0.1
       human-signals: 2.1.0
       is-stream: 2.0.1
@@ -9125,7 +9115,7 @@ snapshots:
 
   human-signals@5.0.0: {}
 
-  husky@9.1.7: {}
+  husky@8.0.3: {}
 
   hyphenate-style-name@1.1.0: {}
 

+ 7 - 7
postcss.config.js

@@ -1,8 +1,8 @@
 export default {
-	plugins: {
-		"postcss-import": {},
-		"tailwindcss/nesting": "postcss-nesting",
-		tailwindcss: {},
-		autoprefixer: {},
-	},
-};
+  plugins: {
+    'postcss-import': {},
+    'tailwindcss/nesting': 'postcss-nesting',
+    tailwindcss: {},
+    autoprefixer: {}
+  }
+}

+ 16 - 19
public/mockServiceWorker.js

@@ -35,13 +35,13 @@ self.addEventListener('message', async function (event) {
   }
 
   const allClients = await self.clients.matchAll({
-    type: 'window',
+    type: 'window'
   })
 
   switch (event.data) {
     case 'KEEPALIVE_REQUEST': {
       sendToClient(client, {
-        type: 'KEEPALIVE_RESPONSE',
+        type: 'KEEPALIVE_RESPONSE'
       })
       break
     }
@@ -51,8 +51,8 @@ self.addEventListener('message', async function (event) {
         type: 'INTEGRITY_CHECK_RESPONSE',
         payload: {
           packageVersion: PACKAGE_VERSION,
-          checksum: INTEGRITY_CHECKSUM,
-        },
+          checksum: INTEGRITY_CHECKSUM
+        }
       })
       break
     }
@@ -65,9 +65,9 @@ self.addEventListener('message', async function (event) {
         payload: {
           client: {
             id: client.id,
-            frameType: client.frameType,
-          },
-        },
+            frameType: client.frameType
+          }
+        }
       })
       break
     }
@@ -142,10 +142,10 @@ async function handleRequest(event, requestId) {
             status: responseClone.status,
             statusText: responseClone.statusText,
             body: responseClone.body,
-            headers: Object.fromEntries(responseClone.headers.entries()),
-          },
+            headers: Object.fromEntries(responseClone.headers.entries())
+          }
         },
-        [responseClone.body],
+        [responseClone.body]
       )
     })()
   }
@@ -169,7 +169,7 @@ async function resolveMainClient(event) {
   }
 
   const allClients = await self.clients.matchAll({
-    type: 'window',
+    type: 'window'
   })
 
   return allClients
@@ -237,10 +237,10 @@ async function getResponse(event, client, requestId) {
         referrer: request.referrer,
         referrerPolicy: request.referrerPolicy,
         body: requestBuffer,
-        keepalive: request.keepalive,
-      },
+        keepalive: request.keepalive
+      }
     },
-    [requestBuffer],
+    [requestBuffer]
   )
 
   switch (clientMessage.type) {
@@ -268,10 +268,7 @@ function sendToClient(client, message, transferrables = []) {
       resolve(event.data)
     }
 
-    client.postMessage(
-      message,
-      [channel.port2].concat(transferrables.filter(Boolean)),
-    )
+    client.postMessage(message, [channel.port2].concat(transferrables.filter(Boolean)))
   })
 }
 
@@ -288,7 +285,7 @@ async function respondWithMock(response) {
 
   Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
     value: true,
-    enumerable: true,
+    enumerable: true
   })
 
   return mockedResponse

+ 0 - 2
src/App.tsx

@@ -6,8 +6,6 @@ import { ThemeProvider } from './theme/theme-provider'
 import { MotionLazy } from './components/animate/motion-lazy'
 import Toast from './components/toast'
 
-const a: boolean = 'a'
-console.log(a + '2')
 const { VITE_APP_TITLE: APPTITLE } = import.meta.env
 
 function App() {

+ 5 - 6
src/_mock/handlers/_demo.js

@@ -1,9 +1,8 @@
-import { http, HttpResponse } from "msw";
-
-import { DemoApi } from "@/api/services/demoService";
+import { http, HttpResponse } from 'msw'
+import { DemoApi } from '@/api/services/demoService'
 
 const mockTokenExpired = http.post(`/api${DemoApi.TOKEN_EXPIRED}`, () => {
-	return new HttpResponse(null, { status: 401 });
-});
+  return new HttpResponse(null, { status: 401 })
+})
 
-export default [mockTokenExpired];
+export default [mockTokenExpired]

+ 10 - 11
src/_mock/handlers/_org.js

@@ -1,14 +1,13 @@
-import { http, HttpResponse } from "msw";
-
-import { ORG_LIST } from "@/_mock/assets";
-import { OrgApi } from "@/api/services/orgService";
+import { ORG_LIST } from '@/_mock/assets'
+import { http, HttpResponse } from 'msw'
+import { OrgApi } from '@/api/services/orgService'
 
 const orgList = http.get(`/api${OrgApi.Org}`, () => {
-	return HttpResponse.json({
-		status: 0,
-		message: "",
-		data: ORG_LIST,
-	});
-});
+  return HttpResponse.json({
+    status: 0,
+    message: '',
+    data: ORG_LIST
+  })
+})
 
-export default [orgList];
+export default [orgList]

+ 42 - 44
src/_mock/handlers/_user.js

@@ -1,46 +1,44 @@
-import { faker } from "@faker-js/faker";
-import { http, HttpResponse, delay } from "msw";
-
-import { UserApi } from "@/api/services/userService";
-
-import { USER_LIST } from "../assets";
+import { faker } from '@faker-js/faker'
+import { delay, http, HttpResponse } from 'msw'
+import { UserApi } from '@/api/services/userService'
+import { USER_LIST } from '../assets'
 
 const signIn = http.post(`/api${UserApi.SignIn}`, async ({ request }) => {
-	const { username, password } = await request.json();
-
-	const user = USER_LIST.find((item) => item.username === username);
-
-	if (!user || user.password !== password) {
-		return HttpResponse.json({
-			status: 10001,
-			message: "Incorrect username or password.",
-		});
-	}
-
-	return HttpResponse.json({
-		status: 0,
-		message: "",
-		data: {
-			user,
-			accessToken: faker.string.uuid(),
-			refreshToken: faker.string.uuid(),
-		},
-	});
-});
-
-const userList = http.get("/api/user", async () => {
-	await delay(1000);
-	return HttpResponse.json(
-		Array.from({ length: 10 }).map(() => ({
-			fullname: faker.person.fullName(),
-			email: faker.internet.email(),
-			avatar: faker.image.avatarGitHub(),
-			address: faker.location.streetAddress(),
-		})),
-		{
-			status: 200,
-		},
-	);
-});
-
-export default [signIn, userList];
+  const { username, password } = await request.json()
+
+  const user = USER_LIST.find((item) => item.username === username)
+
+  if (!user || user.password !== password) {
+    return HttpResponse.json({
+      status: 10001,
+      message: 'Incorrect username or password.'
+    })
+  }
+
+  return HttpResponse.json({
+    status: 0,
+    message: '',
+    data: {
+      user,
+      accessToken: faker.string.uuid(),
+      refreshToken: faker.string.uuid()
+    }
+  })
+})
+
+const userList = http.get('/api/user', async () => {
+  await delay(1000)
+  return HttpResponse.json(
+    Array.from({ length: 10 }).map(() => ({
+      fullname: faker.person.fullName(),
+      email: faker.internet.email(),
+      avatar: faker.image.avatarGitHub(),
+      address: faker.location.streetAddress()
+    })),
+    {
+      status: 200
+    }
+  )
+})
+
+export default [signIn, userList]

+ 7 - 8
src/_mock/index.js

@@ -1,10 +1,9 @@
-import { setupWorker } from "msw/browser";
+import { setupWorker } from 'msw/browser'
+import demoMockApi from './handlers/_demo'
+import orgMockApi from './handlers/_org'
+import userMockApi from './handlers/_user'
 
-import demoMockApi from "./handlers/_demo";
-import orgMockApi from "./handlers/_org";
-import userMockApi from "./handlers/_user";
+const handlers = [...userMockApi, ...orgMockApi, ...demoMockApi]
+const worker = setupWorker(...handlers)
 
-const handlers = [...userMockApi, ...orgMockApi, ...demoMockApi];
-const worker = setupWorker(...handlers);
-
-export default worker;
+export default worker

+ 7 - 7
src/_mock/utils.js

@@ -1,9 +1,9 @@
-import { faker } from "@faker-js/faker";
+import { faker } from '@faker-js/faker'
 
 export const fakeAvatars = (count) => {
-	const result = [];
-	for (let index = 0; index < count; index += 1) {
-		result.push(faker.image.avatarGitHub());
-	}
-	return result;
-};
+  const result = []
+  for (let index = 0; index < count; index += 1) {
+    result.push(faker.image.avatarGitHub())
+  }
+  return result
+}

+ 0 - 1
src/api/request/index.ts

@@ -1,5 +1,4 @@
 import { useUserSessionStore } from '@/store/userStore'
-import { navigate } from '@/router/components/SetupNavigation'
 import { ResultEnum } from '#/enum'
 import { t } from '@/locales/i18n'
 import axios, { type AxiosError, type AxiosRequestConfig, type AxiosResponse } from 'axios'

+ 5 - 5
src/api/services/demoService.ts

@@ -1,11 +1,11 @@
-import Request from "../request";
+import Request from '../request'
 
 export enum DemoApi {
-	TOKEN_EXPIRED = "/user/tokenExpired",
+  TOKEN_EXPIRED = '/user/tokenExpired'
 }
 
-const mockTokenExpired = () => Request.post({ url: DemoApi.TOKEN_EXPIRED });
+const mockTokenExpired = () => Request.post({ url: DemoApi.TOKEN_EXPIRED })
 
 export default {
-	mockTokenExpired,
-};
+  mockTokenExpired
+}

+ 71 - 0
src/api/services/message/data.d.ts

@@ -0,0 +1,71 @@
+declare namespace API {
+  /** 用户公告分页 */
+  type UserMessagePageResultItem = {
+    msgId: string
+    messageType: string
+    title: string
+    content: string
+    sendFlag: boolean
+    allFlag: boolean
+    sortOrder: number
+    delFlag: string
+    tenantId: string
+    readFlag: string
+    createdTime: string
+  }
+  type UserMessagePageResult = UserMessagePageResultItem[]
+
+  /** 信息阅读列表 */
+  type MessageReadPageResultItem = {
+    createdBy: string
+    modifiedBy: string
+    createdTime: string
+    modifiedTime: string
+    msgId: string
+    messageType: string
+    title: string
+    content: string
+    sendFlag: boolean
+    allFlag: boolean
+    sortOrder: number
+    delFlag: string
+    tenantId: string
+  }
+  type MessageReadPageResult = MessageReadPageResultItem[]
+
+  /** 修改站内信入参 */
+  type UpdateMessageParam = {
+    allFlag: boolean
+    content: string
+    messageType: 'ANNOUNCEMENT' | 'EMAIL' | 'INNER_MSG'
+    msgId: number
+    sortOrder: number
+    title: string
+    userList: number[] | string[]
+  }
+  /** 新增站内信入参 */
+  type AddMessageParam = Omit<UpdateMessageParam, 'msgId'>
+
+  /** 查询全部部门(包含用户) */
+  interface Employee {
+    id: string
+    name: string
+    type: string
+    selected: boolean
+    avatar: string
+  }
+
+  interface ChildDepartment {
+    id: string
+    name: string
+    type: string
+    selected: boolean
+    avatar?: any
+  }
+  type AllDeptUser = {
+    titleDepartments: any[]
+    roleList: any[]
+    employees: Employee[]
+    childDepartments: ChildDepartment[]
+  }
+}

+ 70 - 0
src/api/services/message/index.ts

@@ -0,0 +1,70 @@
+import Request from '../..'
+
+// 查询用户公告
+export function getUserMessagePage(query: API.PageParams) {
+  return Request.get<API.TableListResult<API.UserMessagePageResult>>({
+    url: '/cms/message/user/page',
+    params: query
+  })
+}
+
+// 查询信息阅读列表
+export function getMessageReadPage(query: API.PageParams) {
+  return Request.get<API.TableListResult<API.MessageReadPageResult>>({
+    url: '/cms/message/user/read/page',
+    params: query
+  })
+}
+
+// 站内信分页
+export function getMessagePage(query: API.PageParams<{ title?: string }>) {
+  return Request.get<API.TableListResult<API.MessageReadPageResult>>({
+    url: '/cms/message/page',
+    params: query
+  })
+}
+
+// 新增站内信
+export function addMessage(data: API.AddMessageParam) {
+  return Request.post({
+    url: '/cms/message/create',
+    data
+  })
+}
+
+// 修改站内信
+export function updateMessage(data: API.UpdateMessageParam) {
+  return Request.post({
+    url: '/cms/message/update',
+    data
+  })
+}
+
+// 删除站内信
+export function delMessage(msgId: number | string) {
+  return Request.post({
+    url: '/cms/message/del',
+    data: { msgIds: [msgId], deleteType: 'SOFT' }
+  })
+}
+
+// 通过ID查询站内信息
+export function getMessageDetail(msg_id: string | number) {
+  return Request.get({
+    url: `/cms/message/${msg_id}`
+  })
+}
+
+// 发送站内信
+export function sendMessage(msgId: number | string) {
+  return Request.post({
+    url: `/cms/message/send/${msgId}`
+  })
+}
+
+// 读取站内信
+export function readMessage(msgId: number | string) {
+  return Request.post({
+    url: `/cms/message/read/${msgId}`
+  })
+}

+ 6 - 7
src/api/services/orgService.ts

@@ -1,13 +1,12 @@
-import Request from "../request";
-
-import type { Organization } from "#/entity";
+import type { Organization } from '#/entity'
+import Request from '../request'
 
 export enum OrgApi {
-	Org = "/org",
+  Org = '/org'
 }
 
-const getOrgList = () => Request.get<Organization[]>({ url: OrgApi.Org });
+const getOrgList = () => Request.get<Organization[]>({ url: OrgApi.Org })
 
 export default {
-	getOrgList,
-};
+  getOrgList
+}

+ 20 - 24
src/api/services/userService.ts

@@ -1,36 +1,32 @@
-import Request from '../request';
-
-import type { UserInfo, UserToken } from '#/entity';
+import type { UserInfo, UserToken } from '#/entity'
+import Request from '../request'
 
 export interface SignInReq {
-	username: string;
-	password: string;
+  username: string
+  password: string
 }
 
 export interface SignUpReq extends SignInReq {
-	email: string;
+  email: string
 }
-export type SignInRes = UserToken & { user: UserInfo };
+export type SignInRes = UserToken & { user: UserInfo }
 
 export enum UserApi {
-	SignIn = '/auth/signin',
-	SignUp = '/auth/signup',
-	Logout = '/auth/logout',
-	Refresh = '/auth/refresh',
-	User = '/user',
+  SignIn = '/auth/signin',
+  SignUp = '/auth/signup',
+  Logout = '/auth/logout',
+  Refresh = '/auth/refresh',
+  User = '/user'
 }
 
-const signin = (data: SignInReq) =>
-	Request.post<SignInRes>({ url: UserApi.SignIn, data });
-const signup = (data: SignUpReq) =>
-	Request.post<SignInRes>({ url: UserApi.SignUp, data });
-const logout = () => Request.get({ url: UserApi.Logout });
-const findById = (id: string) =>
-	Request.get<UserInfo[]>({ url: `${UserApi.User}/${id}` });
+const signin = (data: SignInReq) => Request.post<SignInRes>({ url: UserApi.SignIn, data })
+const signup = (data: SignUpReq) => Request.post<SignInRes>({ url: UserApi.SignUp, data })
+const logout = () => Request.get({ url: UserApi.Logout })
+const findById = (id: string) => Request.get<UserInfo[]>({ url: `${UserApi.User}/${id}` })
 
 export default {
-	signin,
-	signup,
-	findById,
-	logout
-};
+  signin,
+  signup,
+  findById,
+  logout
+}

+ 14 - 16
src/components/animate/motion-container.tsx

@@ -1,9 +1,8 @@
-import { type MotionProps, m } from 'framer-motion';
-
-import { varContainer } from './variants/container';
+import { m, type MotionProps } from 'framer-motion'
+import { varContainer } from './variants/container'
 
 interface Props extends MotionProps {
-	className?: string;
+  className?: string
 }
 
 /**
@@ -25,16 +24,15 @@ interface Props extends MotionProps {
  * />
  */
 export default function MotionContainer({ children, className }: Props) {
-	return (
-		<m.div
-			// 这里指定 initial、animate和exit的属性名后,子组件就不需要再重复指定
-			initial="initial"
-			animate="animate"
-			exit="exit"
-			variants={varContainer()}
-			className={className}
-		>
-			{children}
-		</m.div>
-	);
+  return (
+    <m.div
+      // 这里指定 initial、animate和exit的属性名后,子组件就不需要再重复指定
+      initial='initial'
+      animate='animate'
+      exit='exit'
+      variants={varContainer()}
+      className={className}>
+      {children}
+    </m.div>
+  )
 }

+ 8 - 8
src/components/animate/motion-lazy.tsx

@@ -1,15 +1,15 @@
-import { LazyMotion, domMax, m } from 'framer-motion';
+import { domMax, LazyMotion, m } from 'framer-motion'
 
 type Props = {
-	children: React.ReactNode;
-};
+  children: React.ReactNode
+}
 /**
  * [Reduce bundle size by lazy-loading a subset of Motion's features](https://www.framer.com/motion/lazy-motion/)
  */
 export function MotionLazy({ children }: Props) {
-	return (
-		<LazyMotion strict features={domMax}>
-			<m.div style={{ height: '100%' }}> {children} </m.div>
-		</LazyMotion>
-	);
+  return (
+    <LazyMotion strict features={domMax}>
+      <m.div style={{ height: '100%' }}> {children} </m.div>
+    </LazyMotion>
+  )
 }

+ 15 - 21
src/components/animate/motion-viewport.tsx

@@ -1,9 +1,8 @@
-import { type MotionProps, m } from "framer-motion";
-
-import { varContainer } from "./variants";
+import { m, type MotionProps } from 'framer-motion'
+import { varContainer } from './variants'
 
 interface Props extends MotionProps {
-	className?: string;
+  className?: string
 }
 /**
  * [whileInView: 元素可以在进出视口时设置动画](https://www.framer.com/motion/scroll-animations/#scroll-triggered-animations)
@@ -12,21 +11,16 @@ interface Props extends MotionProps {
  *
  *    + once: 仅触发一次
  */
-export default function MotionViewport({
-	children,
-	className,
-	...other
-}: Props) {
-	return (
-		<m.div
-			initial="initial"
-			whileInView="animate"
-			viewport={{ once: true, amount: 0.3 }}
-			variants={varContainer()}
-			className={className}
-			{...other}
-		>
-			{children}
-		</m.div>
-	);
+export default function MotionViewport({ children, className, ...other }: Props) {
+  return (
+    <m.div
+      initial='initial'
+      whileInView='animate'
+      viewport={{ once: true, amount: 0.3 }}
+      variants={varContainer()}
+      className={className}
+      {...other}>
+      {children}
+    </m.div>
+  )
 }

+ 19 - 19
src/components/animate/types.ts

@@ -1,26 +1,26 @@
 export type VariantsType = {
-	durationIn?: number;
-	durationOut?: number;
-	easeIn?: [];
-	easeOut?: [];
-	distance?: number;
-};
+  durationIn?: number
+  durationOut?: number
+  easeIn?: []
+  easeOut?: []
+  distance?: number
+}
 
 export type TranHoverType = {
-	duration?: number;
-	ease?: [];
-};
+  duration?: number
+  ease?: []
+}
 export type TranEnterType = {
-	durationIn?: number;
-	easeIn?: [];
-};
+  durationIn?: number
+  easeIn?: []
+}
 export type TranExitType = {
-	durationOut?: number;
-	easeOut?: [];
-};
+  durationOut?: number
+  easeOut?: []
+}
 
 export type BackgroundType = {
-	duration?: number;
-	ease?: [];
-	colors?: string[];
-};
+  duration?: number
+  ease?: []
+  colors?: string[]
+}

+ 3 - 3
src/components/animate/variants/action.ts

@@ -4,6 +4,6 @@
  * @param tap
  */
 export const varHover = (hover = 1.09, tap = 0.97) => ({
-	hover: { scale: hover },
-	tap: { scale: tap }
-});
+  hover: { scale: hover },
+  tap: { scale: tap }
+})

+ 89 - 89
src/components/animate/variants/background.ts

@@ -1,103 +1,103 @@
-import type { BackgroundType } from "../types";
+import type { BackgroundType } from '../types'
 
 export const varBgColor = (props?: BackgroundType) => {
-	const colors = props?.colors || ["#19dcea", "#b22cff"];
-	const duration = props?.duration || 5;
-	const ease = props?.ease || "linear";
+  const colors = props?.colors || ['#19dcea', '#b22cff']
+  const duration = props?.duration || 5
+  const ease = props?.ease || 'linear'
 
-	return {
-		animate: {
-			background: colors,
-			transition: { duration, ease },
-		},
-	};
-};
+  return {
+    animate: {
+      background: colors,
+      transition: { duration, ease }
+    }
+  }
+}
 
 // ----------------------------------------------------------------------
 
 export const varBgKenburns = (props?: BackgroundType) => {
-	const duration = props?.duration || 5;
-	const ease = props?.ease || "easeOut";
+  const duration = props?.duration || 5
+  const ease = props?.ease || 'easeOut'
 
-	return {
-		top: {
-			animate: {
-				scale: [1, 1.25],
-				y: [0, -15],
-				transformOrigin: ["50% 16%", "50% top"],
-				transition: { duration, ease },
-			},
-		},
-		bottom: {
-			animate: {
-				scale: [1, 1.25],
-				y: [0, 15],
-				transformOrigin: ["50% 84%", "50% bottom"],
-				transition: { duration, ease },
-			},
-		},
-		left: {
-			animate: {
-				scale: [1, 1.25],
-				x: [0, 20],
-				y: [0, 15],
-				transformOrigin: ["16% 50%", "0% left"],
-				transition: { duration, ease },
-			},
-		},
-		right: {
-			animate: {
-				scale: [1, 1.25],
-				x: [0, -20],
-				y: [0, -15],
-				transformOrigin: ["84% 50%", "0% right"],
-				transition: { duration, ease },
-			},
-		},
-	};
-};
+  return {
+    top: {
+      animate: {
+        scale: [1, 1.25],
+        y: [0, -15],
+        transformOrigin: ['50% 16%', '50% top'],
+        transition: { duration, ease }
+      }
+    },
+    bottom: {
+      animate: {
+        scale: [1, 1.25],
+        y: [0, 15],
+        transformOrigin: ['50% 84%', '50% bottom'],
+        transition: { duration, ease }
+      }
+    },
+    left: {
+      animate: {
+        scale: [1, 1.25],
+        x: [0, 20],
+        y: [0, 15],
+        transformOrigin: ['16% 50%', '0% left'],
+        transition: { duration, ease }
+      }
+    },
+    right: {
+      animate: {
+        scale: [1, 1.25],
+        x: [0, -20],
+        y: [0, -15],
+        transformOrigin: ['84% 50%', '0% right'],
+        transition: { duration, ease }
+      }
+    }
+  }
+}
 
 // ----------------------------------------------------------------------
 
 export const varBgPan = (props?: BackgroundType) => {
-	const colors = props?.colors || ["#ee7752", "#e73c7e", "#23a6d5", "#23d5ab"];
-	const duration = props?.duration || 5;
-	const ease = props?.ease || "linear";
+  const colors = props?.colors || ['#ee7752', '#e73c7e', '#23a6d5', '#23d5ab']
+  const duration = props?.duration || 5
+  const ease = props?.ease || 'linear'
 
-	const gradient = (deg: number) => `linear-gradient(${deg}deg, ${colors})`;
+  const gradient = (deg: number) => `linear-gradient(${deg}deg, ${colors})`
 
-	return {
-		top: {
-			animate: {
-				backgroundImage: [gradient(0), gradient(0)],
-				backgroundPosition: ["center 99%", "center 1%"],
-				backgroundSize: ["100% 600%", "100% 600%"],
-				transition: { duration, ease },
-			},
-		},
-		right: {
-			animate: {
-				backgroundPosition: ["1% center", "99% center"],
-				backgroundImage: [gradient(270), gradient(270)],
-				backgroundSize: ["600% 100%", "600% 100%"],
-				transition: { duration, ease },
-			},
-		},
-		bottom: {
-			animate: {
-				backgroundImage: [gradient(0), gradient(0)],
-				backgroundPosition: ["center 1%", "center 99%"],
-				backgroundSize: ["100% 600%", "100% 600%"],
-				transition: { duration, ease },
-			},
-		},
-		left: {
-			animate: {
-				backgroundPosition: ["99% center", "1% center"],
-				backgroundImage: [gradient(270), gradient(270)],
-				backgroundSize: ["600% 100%", "600% 100%"],
-				transition: { duration, ease },
-			},
-		},
-	};
-};
+  return {
+    top: {
+      animate: {
+        backgroundImage: [gradient(0), gradient(0)],
+        backgroundPosition: ['center 99%', 'center 1%'],
+        backgroundSize: ['100% 600%', '100% 600%'],
+        transition: { duration, ease }
+      }
+    },
+    right: {
+      animate: {
+        backgroundPosition: ['1% center', '99% center'],
+        backgroundImage: [gradient(270), gradient(270)],
+        backgroundSize: ['600% 100%', '600% 100%'],
+        transition: { duration, ease }
+      }
+    },
+    bottom: {
+      animate: {
+        backgroundImage: [gradient(0), gradient(0)],
+        backgroundPosition: ['center 1%', 'center 99%'],
+        backgroundSize: ['100% 600%', '100% 600%'],
+        transition: { duration, ease }
+      }
+    },
+    left: {
+      animate: {
+        backgroundPosition: ['99% center', '1% center'],
+        backgroundImage: [gradient(270), gradient(270)],
+        backgroundSize: ['600% 100%', '600% 100%'],
+        transition: { duration, ease }
+      }
+    }
+  }
+}

+ 106 - 107
src/components/animate/variants/bounce.ts

@@ -1,111 +1,110 @@
-import type { VariantsType } from '../types';
-
-import { varTranEnter, varTranExit } from './transition';
+import type { VariantsType } from '../types'
+import { varTranEnter, varTranExit } from './transition'
 
 export const varBounce = (props?: VariantsType) => {
-	const durationIn = props?.durationIn;
-	const durationOut = props?.durationOut;
-	const easeIn = props?.easeIn;
-	const easeOut = props?.easeOut;
+  const durationIn = props?.durationIn
+  const durationOut = props?.durationOut
+  const easeIn = props?.easeIn
+  const easeOut = props?.easeOut
 
-	return {
-		// IN
-		in: {
-			initial: {},
-			animate: {
-				scale: [0.3, 1.1, 0.9, 1.03, 0.97, 1],
-				opacity: [0, 1, 1, 1, 1, 1],
-				transition: varTranEnter({ durationIn, easeIn })
-			},
-			exit: {
-				scale: [0.9, 1.1, 0.3],
-				opacity: [1, 1, 0]
-			}
-		},
-		inUp: {
-			initial: {},
-			animate: {
-				y: [720, -24, 12, -4, 0],
-				scaleY: [4, 0.9, 0.95, 0.985, 1],
-				opacity: [0, 1, 1, 1, 1],
-				transition: { ...varTranEnter({ durationIn, easeIn }) }
-			},
-			exit: {
-				y: [12, -24, 720],
-				scaleY: [0.985, 0.9, 3],
-				opacity: [1, 1, 0],
-				transition: varTranExit({ durationOut, easeOut })
-			}
-		},
-		inDown: {
-			initial: {},
-			animate: {
-				y: [-720, 24, -12, 4, 0],
-				scaleY: [4, 0.9, 0.95, 0.985, 1],
-				opacity: [0, 1, 1, 1, 1],
-				transition: varTranEnter({ durationIn, easeIn })
-			},
-			exit: {
-				y: [-12, 24, -720],
-				scaleY: [0.985, 0.9, 3],
-				opacity: [1, 1, 0],
-				transition: varTranExit({ durationOut, easeOut })
-			}
-		},
-		inLeft: {
-			initial: {},
-			animate: {
-				x: [-720, 24, -12, 4, 0],
-				scaleX: [3, 1, 0.98, 0.995, 1],
-				opacity: [0, 1, 1, 1, 1],
-				transition: varTranEnter({ durationIn, easeIn })
-			},
-			exit: {
-				x: [0, 24, -720],
-				scaleX: [1, 0.9, 2],
-				opacity: [1, 1, 0],
-				transition: varTranExit({ durationOut, easeOut })
-			}
-		},
-		inRight: {
-			initial: {},
-			animate: {
-				x: [720, -24, 12, -4, 0],
-				scaleX: [3, 1, 0.98, 0.995, 1],
-				opacity: [0, 1, 1, 1, 1],
-				transition: varTranEnter({ durationIn, easeIn })
-			},
-			exit: {
-				x: [0, -24, 720],
-				scaleX: [1, 0.9, 2],
-				opacity: [1, 1, 0],
-				transition: varTranExit({ durationOut, easeOut })
-			}
-		},
+  return {
+    // IN
+    in: {
+      initial: {},
+      animate: {
+        scale: [0.3, 1.1, 0.9, 1.03, 0.97, 1],
+        opacity: [0, 1, 1, 1, 1, 1],
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        scale: [0.9, 1.1, 0.3],
+        opacity: [1, 1, 0]
+      }
+    },
+    inUp: {
+      initial: {},
+      animate: {
+        y: [720, -24, 12, -4, 0],
+        scaleY: [4, 0.9, 0.95, 0.985, 1],
+        opacity: [0, 1, 1, 1, 1],
+        transition: { ...varTranEnter({ durationIn, easeIn }) }
+      },
+      exit: {
+        y: [12, -24, 720],
+        scaleY: [0.985, 0.9, 3],
+        opacity: [1, 1, 0],
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
+    inDown: {
+      initial: {},
+      animate: {
+        y: [-720, 24, -12, 4, 0],
+        scaleY: [4, 0.9, 0.95, 0.985, 1],
+        opacity: [0, 1, 1, 1, 1],
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        y: [-12, 24, -720],
+        scaleY: [0.985, 0.9, 3],
+        opacity: [1, 1, 0],
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
+    inLeft: {
+      initial: {},
+      animate: {
+        x: [-720, 24, -12, 4, 0],
+        scaleX: [3, 1, 0.98, 0.995, 1],
+        opacity: [0, 1, 1, 1, 1],
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        x: [0, 24, -720],
+        scaleX: [1, 0.9, 2],
+        opacity: [1, 1, 0],
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
+    inRight: {
+      initial: {},
+      animate: {
+        x: [720, -24, 12, -4, 0],
+        scaleX: [3, 1, 0.98, 0.995, 1],
+        opacity: [0, 1, 1, 1, 1],
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        x: [0, -24, 720],
+        scaleX: [1, 0.9, 2],
+        opacity: [1, 1, 0],
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
 
-		// OUT
-		out: {
-			animate: { scale: [0.9, 1.1, 0.3], opacity: [1, 1, 0] }
-		},
-		outUp: {
-			animate: {
-				y: [-12, 24, -720],
-				scaleY: [0.985, 0.9, 3],
-				opacity: [1, 1, 0]
-			}
-		},
-		outDown: {
-			animate: {
-				y: [12, -24, 720],
-				scaleY: [0.985, 0.9, 3],
-				opacity: [1, 1, 0]
-			}
-		},
-		outLeft: {
-			animate: { x: [0, 24, -720], scaleX: [1, 0.9, 2], opacity: [1, 1, 0] }
-		},
-		outRight: {
-			animate: { x: [0, -24, 720], scaleX: [1, 0.9, 2], opacity: [1, 1, 0] }
-		}
-	};
-};
+    // OUT
+    out: {
+      animate: { scale: [0.9, 1.1, 0.3], opacity: [1, 1, 0] }
+    },
+    outUp: {
+      animate: {
+        y: [-12, 24, -720],
+        scaleY: [0.985, 0.9, 3],
+        opacity: [1, 1, 0]
+      }
+    },
+    outDown: {
+      animate: {
+        y: [12, -24, 720],
+        scaleY: [0.985, 0.9, 3],
+        opacity: [1, 1, 0]
+      }
+    },
+    outLeft: {
+      animate: { x: [0, 24, -720], scaleX: [1, 0.9, 2], opacity: [1, 1, 0] }
+    },
+    outRight: {
+      animate: { x: [0, -24, 720], scaleX: [1, 0.9, 2], opacity: [1, 1, 0] }
+    }
+  }
+}

+ 22 - 22
src/components/animate/variants/container.ts

@@ -1,26 +1,26 @@
 export type Props = {
-	staggerIn?: number;
-	delayIn?: number;
-	staggerOut?: number;
-};
+  staggerIn?: number
+  delayIn?: number
+  staggerOut?: number
+}
 
 export const varContainer = (props?: Props) => {
-	const staggerIn = props?.staggerIn || 0.05;
-	const delayIn = props?.staggerIn || 0.05;
-	const staggerOut = props?.staggerIn || 0.05;
+  const staggerIn = props?.staggerIn || 0.05
+  const delayIn = props?.staggerIn || 0.05
+  const staggerOut = props?.staggerIn || 0.05
 
-	return {
-		animate: {
-			transition: {
-				staggerChildren: staggerIn,
-				delayChildren: delayIn
-			}
-		},
-		exit: {
-			transition: {
-				staggerChildren: staggerOut,
-				staggerDirection: -1
-			}
-		}
-	};
-};
+  return {
+    animate: {
+      transition: {
+        staggerChildren: staggerIn,
+        delayChildren: delayIn
+      }
+    },
+    exit: {
+      transition: {
+        staggerChildren: staggerOut,
+        staggerDirection: -1
+      }
+    }
+  }
+}

+ 126 - 127
src/components/animate/variants/fade.ts

@@ -1,134 +1,133 @@
-import type { VariantsType } from "../types";
-
+import type { VariantsType } from '../types'
 //
-import { varTranEnter, varTranExit } from "./transition";
+import { varTranEnter, varTranExit } from './transition'
 
 // ----------------------------------------------------------------------
 
 export const varFade = (props?: VariantsType) => {
-	const distance = props?.distance || 120;
-	const durationIn = props?.durationIn;
-	const durationOut = props?.durationOut;
-	const easeIn = props?.easeIn;
-	const easeOut = props?.easeOut;
+  const distance = props?.distance || 120
+  const durationIn = props?.durationIn
+  const durationOut = props?.durationOut
+  const easeIn = props?.easeIn
+  const easeOut = props?.easeOut
 
-	return {
-		// IN
-		in: {
-			initial: { opacity: 0 },
-			animate: { opacity: 1, transition: varTranEnter },
-			exit: { opacity: 0, transition: varTranExit },
-		},
-		inUp: {
-			initial: { y: distance, opacity: 0 },
-			animate: {
-				y: 0,
-				opacity: 1,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: {
-				y: distance,
-				opacity: 0,
-				transition: varTranExit({ durationOut, easeOut }),
-			},
-		},
-		inDown: {
-			initial: { y: -distance, opacity: 0 },
-			animate: {
-				y: 0,
-				opacity: 1,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: {
-				y: -distance,
-				opacity: 0,
-				transition: varTranExit({ durationOut, easeOut }),
-			},
-		},
-		inLeft: {
-			initial: { x: -distance, opacity: 0 },
-			animate: {
-				x: 0,
-				opacity: 1,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: {
-				x: -distance,
-				opacity: 0,
-				transition: varTranExit({ durationOut, easeOut }),
-			},
-		},
-		inRight: {
-			initial: { x: distance, opacity: 0 },
-			animate: {
-				x: 0,
-				opacity: 1,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: {
-				x: distance,
-				opacity: 0,
-				transition: varTranExit({ durationOut, easeOut }),
-			},
-		},
+  return {
+    // IN
+    in: {
+      initial: { opacity: 0 },
+      animate: { opacity: 1, transition: varTranEnter },
+      exit: { opacity: 0, transition: varTranExit }
+    },
+    inUp: {
+      initial: { y: distance, opacity: 0 },
+      animate: {
+        y: 0,
+        opacity: 1,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        y: distance,
+        opacity: 0,
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
+    inDown: {
+      initial: { y: -distance, opacity: 0 },
+      animate: {
+        y: 0,
+        opacity: 1,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        y: -distance,
+        opacity: 0,
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
+    inLeft: {
+      initial: { x: -distance, opacity: 0 },
+      animate: {
+        x: 0,
+        opacity: 1,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        x: -distance,
+        opacity: 0,
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
+    inRight: {
+      initial: { x: distance, opacity: 0 },
+      animate: {
+        x: 0,
+        opacity: 1,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        x: distance,
+        opacity: 0,
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
 
-		// OUT
-		out: {
-			initial: { opacity: 1 },
-			animate: { opacity: 0, transition: varTranEnter({ durationIn, easeIn }) },
-			exit: { opacity: 1, transition: varTranExit({ durationOut, easeOut }) },
-		},
-		outUp: {
-			initial: { y: 0, opacity: 1 },
-			animate: {
-				y: -distance,
-				opacity: 0,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: {
-				y: 0,
-				opacity: 1,
-				transition: varTranExit({ durationOut, easeOut }),
-			},
-		},
-		outDown: {
-			initial: { y: 0, opacity: 1 },
-			animate: {
-				y: distance,
-				opacity: 0,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: {
-				y: 0,
-				opacity: 1,
-				transition: varTranExit({ durationOut, easeOut }),
-			},
-		},
-		outLeft: {
-			initial: { x: 0, opacity: 1 },
-			animate: {
-				x: -distance,
-				opacity: 0,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: {
-				x: 0,
-				opacity: 1,
-				transition: varTranExit({ durationOut, easeOut }),
-			},
-		},
-		outRight: {
-			initial: { x: 0, opacity: 1 },
-			animate: {
-				x: distance,
-				opacity: 0,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: {
-				x: 0,
-				opacity: 1,
-				transition: varTranExit({ durationOut, easeOut }),
-			},
-		},
-	};
-};
+    // OUT
+    out: {
+      initial: { opacity: 1 },
+      animate: { opacity: 0, transition: varTranEnter({ durationIn, easeIn }) },
+      exit: { opacity: 1, transition: varTranExit({ durationOut, easeOut }) }
+    },
+    outUp: {
+      initial: { y: 0, opacity: 1 },
+      animate: {
+        y: -distance,
+        opacity: 0,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        y: 0,
+        opacity: 1,
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
+    outDown: {
+      initial: { y: 0, opacity: 1 },
+      animate: {
+        y: distance,
+        opacity: 0,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        y: 0,
+        opacity: 1,
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
+    outLeft: {
+      initial: { x: 0, opacity: 1 },
+      animate: {
+        x: -distance,
+        opacity: 0,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        x: 0,
+        opacity: 1,
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
+    outRight: {
+      initial: { x: 0, opacity: 1 },
+      animate: {
+        x: distance,
+        opacity: 0,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        x: 0,
+        opacity: 1,
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    }
+  }
+}

+ 53 - 54
src/components/animate/variants/flip.ts

@@ -1,61 +1,60 @@
-import type { VariantsType } from "../types";
-
+import type { VariantsType } from '../types'
 //
-import { varTranEnter, varTranExit } from "./transition";
+import { varTranEnter, varTranExit } from './transition'
 
 // ----------------------------------------------------------------------
 
 export const varFlip = (props?: VariantsType) => {
-	const durationIn = props?.durationIn;
-	const durationOut = props?.durationOut;
-	const easeIn = props?.easeIn;
-	const easeOut = props?.easeOut;
+  const durationIn = props?.durationIn
+  const durationOut = props?.durationOut
+  const easeIn = props?.easeIn
+  const easeOut = props?.easeOut
 
-	return {
-		// IN
-		inX: {
-			initial: { rotateX: -180, opacity: 0 },
-			animate: {
-				rotateX: 0,
-				opacity: 1,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: {
-				rotateX: -180,
-				opacity: 0,
-				transition: varTranExit({ durationOut, easeOut }),
-			},
-		},
-		inY: {
-			initial: { rotateY: -180, opacity: 0 },
-			animate: {
-				rotateY: 0,
-				opacity: 1,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: {
-				rotateY: -180,
-				opacity: 0,
-				transition: varTranExit({ durationOut, easeOut }),
-			},
-		},
+  return {
+    // IN
+    inX: {
+      initial: { rotateX: -180, opacity: 0 },
+      animate: {
+        rotateX: 0,
+        opacity: 1,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        rotateX: -180,
+        opacity: 0,
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
+    inY: {
+      initial: { rotateY: -180, opacity: 0 },
+      animate: {
+        rotateY: 0,
+        opacity: 1,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        rotateY: -180,
+        opacity: 0,
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
 
-		// OUT
-		outX: {
-			initial: { rotateX: 0, opacity: 1 },
-			animate: {
-				rotateX: 70,
-				opacity: 0,
-				transition: varTranExit({ durationOut, easeOut }),
-			},
-		},
-		outY: {
-			initial: { rotateY: 0, opacity: 1 },
-			animate: {
-				rotateY: 70,
-				opacity: 0,
-				transition: varTranExit({ durationOut, easeOut }),
-			},
-		},
-	};
-};
+    // OUT
+    outX: {
+      initial: { rotateX: 0, opacity: 1 },
+      animate: {
+        rotateX: 70,
+        opacity: 0,
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
+    outY: {
+      initial: { rotateY: 0, opacity: 1 },
+      animate: {
+        rotateY: 70,
+        opacity: 0,
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    }
+  }
+}

+ 95 - 95
src/components/animate/variants/index.ts

@@ -1,98 +1,98 @@
-import { varBgColor, varBgKenburns, varBgPan } from "./background";
-import { varBounce } from "./bounce";
-import { varFade } from "./fade";
-import { varFlip } from "./flip";
-import { varRotate } from "./rotate";
-import { varScale } from "./scale";
-import { varSlide } from "./slide";
-import { varZoom } from "./zoom";
+import { varBgColor, varBgKenburns, varBgPan } from './background'
+import { varBounce } from './bounce'
+import { varFade } from './fade'
+import { varFlip } from './flip'
+import { varRotate } from './rotate'
+import { varScale } from './scale'
+import { varSlide } from './slide'
+import { varZoom } from './zoom'
 
-export * from "./action";
-export * from "./background";
-export * from "./bounce";
-export * from "./container";
-export * from "./fade";
-export * from "./flip";
-export * from "./path";
-export * from "./rotate";
-export * from "./scale";
-export * from "./slide";
-export * from "./transition";
-export * from "./zoom";
+export * from './action'
+export * from './background'
+export * from './bounce'
+export * from './container'
+export * from './fade'
+export * from './flip'
+export * from './path'
+export * from './rotate'
+export * from './scale'
+export * from './slide'
+export * from './transition'
+export * from './zoom'
 
-export function getVariant(variant = "slideInUp") {
-	return {
-		// Slide
-		slideInUp: varSlide().inUp,
-		slideInDown: varSlide().inDown,
-		slideInLeft: varSlide().inLeft,
-		slideInRight: varSlide().inRight,
-		slideOutUp: varSlide().outUp,
-		slideOutDown: varSlide().outDown,
-		slideOutLeft: varSlide().outLeft,
-		slideOutRight: varSlide().outRight,
-		// Fade
-		fadeIn: varFade().in,
-		fadeInUp: varFade().inUp,
-		fadeInDown: varFade().inDown,
-		fadeInLeft: varFade().inLeft,
-		fadeInRight: varFade().inRight,
-		fadeOut: varFade().out,
-		fadeOutUp: varFade().outUp,
-		fadeOutDown: varFade().outDown,
-		fadeOutLeft: varFade().outLeft,
-		fadeOutRight: varFade().outRight,
-		// Zoom
-		zoomIn: varZoom({ distance: 80 }).in,
-		zoomInUp: varZoom({ distance: 80 }).inUp,
-		zoomInDown: varZoom({ distance: 80 }).inDown,
-		zoomInLeft: varZoom({ distance: 240 }).inLeft,
-		zoomInRight: varZoom({ distance: 240 }).inRight,
-		zoomOut: varZoom().out,
-		zoomOutLeft: varZoom().outLeft,
-		zoomOutRight: varZoom().outRight,
-		zoomOutUp: varZoom().outUp,
-		zoomOutDown: varZoom().outDown,
-		// Bounce
-		bounceIn: varBounce().in,
-		bounceInUp: varBounce().inUp,
-		bounceInDown: varBounce().inDown,
-		bounceInLeft: varBounce().inLeft,
-		bounceInRight: varBounce().inRight,
-		bounceOut: varBounce().out,
-		bounceOutUp: varBounce().outUp,
-		bounceOutDown: varBounce().outDown,
-		bounceOutLeft: varBounce().outLeft,
-		bounceOutRight: varBounce().outRight,
-		// Flip
-		flipInX: varFlip().inX,
-		flipInY: varFlip().inY,
-		flipOutX: varFlip().outX,
-		flipOutY: varFlip().outY,
-		// Scale
-		scaleInX: varScale().inX,
-		scaleInY: varScale().inY,
-		scaleOutX: varScale().outX,
-		scaleOutY: varScale().outY,
-		// Rotate
-		rotateIn: varRotate().in,
-		rotateOut: varRotate().out,
-		// Background
-		kenburnsTop: varBgKenburns().top,
-		kenburnsBottom: varBgKenburns().bottom,
-		kenburnsLeft: varBgKenburns().left,
-		kenburnsRight: varBgKenburns().right,
-		panTop: varBgPan().top,
-		panBottom: varBgPan().bottom,
-		panLeft: varBgPan().left,
-		panRight: varBgPan().right,
-		color2x: varBgColor(),
-		color3x: varBgColor({ colors: ["#19dcea", "#b22cff", "#ea2222"] }),
-		color4x: varBgColor({
-			colors: ["#19dcea", "#b22cff", "#ea2222", "#f5be10"],
-		}),
-		color5x: varBgColor({
-			colors: ["#19dcea", "#b22cff", "#ea2222", "#f5be10", "#3bd80d"],
-		}),
-	}[variant];
+export function getVariant(variant = 'slideInUp') {
+  return {
+    // Slide
+    slideInUp: varSlide().inUp,
+    slideInDown: varSlide().inDown,
+    slideInLeft: varSlide().inLeft,
+    slideInRight: varSlide().inRight,
+    slideOutUp: varSlide().outUp,
+    slideOutDown: varSlide().outDown,
+    slideOutLeft: varSlide().outLeft,
+    slideOutRight: varSlide().outRight,
+    // Fade
+    fadeIn: varFade().in,
+    fadeInUp: varFade().inUp,
+    fadeInDown: varFade().inDown,
+    fadeInLeft: varFade().inLeft,
+    fadeInRight: varFade().inRight,
+    fadeOut: varFade().out,
+    fadeOutUp: varFade().outUp,
+    fadeOutDown: varFade().outDown,
+    fadeOutLeft: varFade().outLeft,
+    fadeOutRight: varFade().outRight,
+    // Zoom
+    zoomIn: varZoom({ distance: 80 }).in,
+    zoomInUp: varZoom({ distance: 80 }).inUp,
+    zoomInDown: varZoom({ distance: 80 }).inDown,
+    zoomInLeft: varZoom({ distance: 240 }).inLeft,
+    zoomInRight: varZoom({ distance: 240 }).inRight,
+    zoomOut: varZoom().out,
+    zoomOutLeft: varZoom().outLeft,
+    zoomOutRight: varZoom().outRight,
+    zoomOutUp: varZoom().outUp,
+    zoomOutDown: varZoom().outDown,
+    // Bounce
+    bounceIn: varBounce().in,
+    bounceInUp: varBounce().inUp,
+    bounceInDown: varBounce().inDown,
+    bounceInLeft: varBounce().inLeft,
+    bounceInRight: varBounce().inRight,
+    bounceOut: varBounce().out,
+    bounceOutUp: varBounce().outUp,
+    bounceOutDown: varBounce().outDown,
+    bounceOutLeft: varBounce().outLeft,
+    bounceOutRight: varBounce().outRight,
+    // Flip
+    flipInX: varFlip().inX,
+    flipInY: varFlip().inY,
+    flipOutX: varFlip().outX,
+    flipOutY: varFlip().outY,
+    // Scale
+    scaleInX: varScale().inX,
+    scaleInY: varScale().inY,
+    scaleOutX: varScale().outX,
+    scaleOutY: varScale().outY,
+    // Rotate
+    rotateIn: varRotate().in,
+    rotateOut: varRotate().out,
+    // Background
+    kenburnsTop: varBgKenburns().top,
+    kenburnsBottom: varBgKenburns().bottom,
+    kenburnsLeft: varBgKenburns().left,
+    kenburnsRight: varBgKenburns().right,
+    panTop: varBgPan().top,
+    panBottom: varBgPan().bottom,
+    panLeft: varBgPan().left,
+    panRight: varBgPan().right,
+    color2x: varBgColor(),
+    color3x: varBgColor({ colors: ['#19dcea', '#b22cff', '#ea2222'] }),
+    color4x: varBgColor({
+      colors: ['#19dcea', '#b22cff', '#ea2222', '#f5be10']
+    }),
+    color5x: varBgColor({
+      colors: ['#19dcea', '#b22cff', '#ea2222', '#f5be10', '#3bd80d']
+    })
+  }[variant]
 }

+ 9 - 9
src/components/animate/variants/path.ts

@@ -1,14 +1,14 @@
 // ----------------------------------------------------------------------
 
 export const TRANSITION = {
-	duration: 2,
-	ease: [0.43, 0.13, 0.23, 0.96],
-};
+  duration: 2,
+  ease: [0.43, 0.13, 0.23, 0.96]
+}
 
 export const varPath = {
-	animate: {
-		fillOpacity: [0, 0, 1],
-		pathLength: [1, 0.4, 0],
-		transition: TRANSITION,
-	},
-};
+  animate: {
+    fillOpacity: [0, 0, 1],
+    pathLength: [1, 0.4, 0],
+    transition: TRANSITION
+  }
+}

+ 32 - 33
src/components/animate/variants/rotate.ts

@@ -1,40 +1,39 @@
-import type { VariantsType } from "../types";
-
+import type { VariantsType } from '../types'
 //
-import { varTranEnter, varTranExit } from "./transition";
+import { varTranEnter, varTranExit } from './transition'
 
 // ----------------------------------------------------------------------
 
 export const varRotate = (props?: VariantsType) => {
-	const durationIn = props?.durationIn;
-	const durationOut = props?.durationOut;
-	const easeIn = props?.easeIn;
-	const easeOut = props?.easeOut;
+  const durationIn = props?.durationIn
+  const durationOut = props?.durationOut
+  const easeIn = props?.easeIn
+  const easeOut = props?.easeOut
 
-	return {
-		// IN
-		in: {
-			initial: { opacity: 0, rotate: -360 },
-			animate: {
-				opacity: 1,
-				rotate: 0,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: {
-				opacity: 0,
-				rotate: -360,
-				transition: varTranExit({ durationOut, easeOut }),
-			},
-		},
+  return {
+    // IN
+    in: {
+      initial: { opacity: 0, rotate: -360 },
+      animate: {
+        opacity: 1,
+        rotate: 0,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        opacity: 0,
+        rotate: -360,
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
 
-		// OUT
-		out: {
-			initial: { opacity: 1, rotate: 0 },
-			animate: {
-				opacity: 0,
-				rotate: -360,
-				transition: varTranExit({ durationOut, easeOut }),
-			},
-		},
-	};
-};
+    // OUT
+    out: {
+      initial: { opacity: 1, rotate: 0 },
+      animate: {
+        opacity: 0,
+        rotate: -360,
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    }
+  }
+}

+ 53 - 54
src/components/animate/variants/scale.ts

@@ -1,60 +1,59 @@
-import type { VariantsType } from "../types";
-
-import { varTranEnter, varTranExit } from "./transition";
+import type { VariantsType } from '../types'
+import { varTranEnter, varTranExit } from './transition'
 
 // ----------------------------------------------------------------------
 
 export const varScale = (props?: VariantsType) => {
-	const durationIn = props?.durationIn;
-	const durationOut = props?.durationOut;
-	const easeIn = props?.easeIn;
-	const easeOut = props?.easeOut;
+  const durationIn = props?.durationIn
+  const durationOut = props?.durationOut
+  const easeIn = props?.easeIn
+  const easeOut = props?.easeOut
 
-	return {
-		// IN
-		inX: {
-			initial: { scaleX: 0, opacity: 0 },
-			animate: {
-				scaleX: 1,
-				opacity: 1,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: {
-				scaleX: 0,
-				opacity: 0,
-				transition: varTranExit({ durationOut, easeOut }),
-			},
-		},
-		inY: {
-			initial: { scaleY: 0, opacity: 0 },
-			animate: {
-				scaleY: 1,
-				opacity: 1,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: {
-				scaleY: 0,
-				opacity: 0,
-				transition: varTranExit({ durationOut, easeOut }),
-			},
-		},
+  return {
+    // IN
+    inX: {
+      initial: { scaleX: 0, opacity: 0 },
+      animate: {
+        scaleX: 1,
+        opacity: 1,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        scaleX: 0,
+        opacity: 0,
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
+    inY: {
+      initial: { scaleY: 0, opacity: 0 },
+      animate: {
+        scaleY: 1,
+        opacity: 1,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        scaleY: 0,
+        opacity: 0,
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
 
-		// OUT
-		outX: {
-			initial: { scaleX: 1, opacity: 1 },
-			animate: {
-				scaleX: 0,
-				opacity: 0,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-		},
-		outY: {
-			initial: { scaleY: 1, opacity: 1 },
-			animate: {
-				scaleY: 0,
-				opacity: 0,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-		},
-	};
-};
+    // OUT
+    outX: {
+      initial: { scaleX: 1, opacity: 1 },
+      animate: {
+        scaleX: 0,
+        opacity: 0,
+        transition: varTranEnter({ durationIn, easeIn })
+      }
+    },
+    outY: {
+      initial: { scaleY: 1, opacity: 1 },
+      animate: {
+        scaleY: 0,
+        opacity: 0,
+        transition: varTranEnter({ durationIn, easeIn })
+      }
+    }
+  }
+}

+ 64 - 65
src/components/animate/variants/slide.ts

@@ -1,72 +1,71 @@
-import type { VariantsType } from "../types";
-
+import type { VariantsType } from '../types'
 //
-import { varTranEnter, varTranExit } from "./transition";
+import { varTranEnter, varTranExit } from './transition'
 
 // ----------------------------------------------------------------------
 
 export const varSlide = (props?: VariantsType) => {
-	const distance = props?.distance || 160;
-	const durationIn = props?.durationIn;
-	const durationOut = props?.durationOut;
-	const easeIn = props?.easeIn;
-	const easeOut = props?.easeOut;
+  const distance = props?.distance || 160
+  const durationIn = props?.durationIn
+  const durationOut = props?.durationOut
+  const easeIn = props?.easeIn
+  const easeOut = props?.easeOut
 
-	return {
-		// IN
-		inUp: {
-			initial: { y: distance },
-			animate: { y: 0, transition: varTranEnter({ durationIn, easeIn }) },
-			exit: { y: distance, transition: varTranExit({ durationOut, easeOut }) },
-		},
-		inDown: {
-			initial: { y: -distance },
-			animate: { y: 0, transition: varTranEnter({ durationIn, easeIn }) },
-			exit: { y: -distance, transition: varTranExit({ durationOut, easeOut }) },
-		},
-		inLeft: {
-			initial: { x: -distance },
-			animate: { x: 0, transition: varTranEnter({ durationIn, easeIn }) },
-			exit: { x: -distance, transition: varTranExit({ durationOut, easeOut }) },
-		},
-		inRight: {
-			initial: { x: distance },
-			animate: { x: 0, transition: varTranEnter({ durationIn, easeIn }) },
-			exit: { x: distance, transition: varTranExit({ durationOut, easeOut }) },
-		},
+  return {
+    // IN
+    inUp: {
+      initial: { y: distance },
+      animate: { y: 0, transition: varTranEnter({ durationIn, easeIn }) },
+      exit: { y: distance, transition: varTranExit({ durationOut, easeOut }) }
+    },
+    inDown: {
+      initial: { y: -distance },
+      animate: { y: 0, transition: varTranEnter({ durationIn, easeIn }) },
+      exit: { y: -distance, transition: varTranExit({ durationOut, easeOut }) }
+    },
+    inLeft: {
+      initial: { x: -distance },
+      animate: { x: 0, transition: varTranEnter({ durationIn, easeIn }) },
+      exit: { x: -distance, transition: varTranExit({ durationOut, easeOut }) }
+    },
+    inRight: {
+      initial: { x: distance },
+      animate: { x: 0, transition: varTranEnter({ durationIn, easeIn }) },
+      exit: { x: distance, transition: varTranExit({ durationOut, easeOut }) }
+    },
 
-		// OUT
-		outUp: {
-			initial: { y: 0 },
-			animate: {
-				y: -distance,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: { y: 0, transition: varTranExit({ durationOut, easeOut }) },
-		},
-		outDown: {
-			initial: { y: 0 },
-			animate: {
-				y: distance,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: { y: 0, transition: varTranExit({ durationOut, easeOut }) },
-		},
-		outLeft: {
-			initial: { x: 0 },
-			animate: {
-				x: -distance,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: { x: 0, transition: varTranExit({ durationOut, easeOut }) },
-		},
-		outRight: {
-			initial: { x: 0 },
-			animate: {
-				x: distance,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: { x: 0, transition: varTranExit({ durationOut, easeOut }) },
-		},
-	};
-};
+    // OUT
+    outUp: {
+      initial: { y: 0 },
+      animate: {
+        y: -distance,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: { y: 0, transition: varTranExit({ durationOut, easeOut }) }
+    },
+    outDown: {
+      initial: { y: 0 },
+      animate: {
+        y: distance,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: { y: 0, transition: varTranExit({ durationOut, easeOut }) }
+    },
+    outLeft: {
+      initial: { x: 0 },
+      animate: {
+        x: -distance,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: { x: 0, transition: varTranExit({ durationOut, easeOut }) }
+    },
+    outRight: {
+      initial: { x: 0 },
+      animate: {
+        x: distance,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: { x: 0, transition: varTranExit({ durationOut, easeOut }) }
+    }
+  }
+}

+ 13 - 13
src/components/animate/variants/transition.ts

@@ -1,25 +1,25 @@
-import type { TranEnterType, TranExitType, TranHoverType } from '../types';
+import type { TranEnterType, TranExitType, TranHoverType } from '../types'
 
 // https://www.framer.com/motion/transition/
 // A transition defines how values animate from one state to another.
 
 export const varTranHover = (props?: TranHoverType) => {
-	const duration = props?.duration || 0.32;
-	const ease = props?.ease || [0.43, 0.13, 0.23, 0.96];
+  const duration = props?.duration || 0.32
+  const ease = props?.ease || [0.43, 0.13, 0.23, 0.96]
 
-	return { duration, ease };
-};
+  return { duration, ease }
+}
 
 export const varTranEnter = (props?: TranEnterType) => {
-	const duration = props?.durationIn || 0.64;
-	const ease = props?.easeIn || [0.43, 0.13, 0.23, 0.96];
+  const duration = props?.durationIn || 0.64
+  const ease = props?.easeIn || [0.43, 0.13, 0.23, 0.96]
 
-	return { duration, ease };
-};
+  return { duration, ease }
+}
 
 export const varTranExit = (props?: TranExitType) => {
-	const duration = props?.durationOut || 0.48;
-	const ease = props?.easeOut || [0.43, 0.13, 0.23, 0.96];
+  const duration = props?.durationOut || 0.48
+  const ease = props?.easeOut || [0.43, 0.13, 0.23, 0.96]
 
-	return { duration, ease };
-};
+  return { duration, ease }
+}

+ 129 - 130
src/components/animate/variants/zoom.ts

@@ -1,137 +1,136 @@
-import type { VariantsType } from "../types";
-
+import type { VariantsType } from '../types'
 //
-import { varTranEnter, varTranExit } from "./transition";
+import { varTranEnter, varTranExit } from './transition'
 
 // ----------------------------------------------------------------------
 
 export const varZoom = (props?: VariantsType) => {
-	const distance = props?.distance || 720;
-	const durationIn = props?.durationIn;
-	const durationOut = props?.durationOut;
-	const easeIn = props?.easeIn;
-	const easeOut = props?.easeOut;
+  const distance = props?.distance || 720
+  const durationIn = props?.durationIn
+  const durationOut = props?.durationOut
+  const easeIn = props?.easeIn
+  const easeOut = props?.easeOut
 
-	return {
-		// IN
-		in: {
-			initial: { scale: 0, opacity: 0 },
-			animate: {
-				scale: 1,
-				opacity: 1,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: {
-				scale: 0,
-				opacity: 0,
-				transition: varTranExit({ durationOut, easeOut }),
-			},
-		},
-		inUp: {
-			initial: { scale: 0, opacity: 0, translateY: distance },
-			animate: {
-				scale: 1,
-				opacity: 1,
-				translateY: 0,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: {
-				scale: 0,
-				opacity: 0,
-				translateY: distance,
-				transition: varTranExit({ durationOut, easeOut }),
-			},
-		},
-		inDown: {
-			initial: { scale: 0, opacity: 0, translateY: -distance },
-			animate: {
-				scale: 1,
-				opacity: 1,
-				translateY: 0,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: {
-				scale: 0,
-				opacity: 0,
-				translateY: -distance,
-				transition: varTranExit({ durationOut, easeOut }),
-			},
-		},
-		inLeft: {
-			initial: { scale: 0, opacity: 0, translateX: -distance },
-			animate: {
-				scale: 1,
-				opacity: 1,
-				translateX: 0,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: {
-				scale: 0,
-				opacity: 0,
-				translateX: -distance,
-				transition: varTranExit({ durationOut, easeOut }),
-			},
-		},
-		inRight: {
-			initial: { scale: 0, opacity: 0, translateX: distance },
-			animate: {
-				scale: 1,
-				opacity: 1,
-				translateX: 0,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-			exit: {
-				scale: 0,
-				opacity: 0,
-				translateX: distance,
-				transition: varTranExit({ durationOut, easeOut }),
-			},
-		},
+  return {
+    // IN
+    in: {
+      initial: { scale: 0, opacity: 0 },
+      animate: {
+        scale: 1,
+        opacity: 1,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        scale: 0,
+        opacity: 0,
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
+    inUp: {
+      initial: { scale: 0, opacity: 0, translateY: distance },
+      animate: {
+        scale: 1,
+        opacity: 1,
+        translateY: 0,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        scale: 0,
+        opacity: 0,
+        translateY: distance,
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
+    inDown: {
+      initial: { scale: 0, opacity: 0, translateY: -distance },
+      animate: {
+        scale: 1,
+        opacity: 1,
+        translateY: 0,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        scale: 0,
+        opacity: 0,
+        translateY: -distance,
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
+    inLeft: {
+      initial: { scale: 0, opacity: 0, translateX: -distance },
+      animate: {
+        scale: 1,
+        opacity: 1,
+        translateX: 0,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        scale: 0,
+        opacity: 0,
+        translateX: -distance,
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
+    inRight: {
+      initial: { scale: 0, opacity: 0, translateX: distance },
+      animate: {
+        scale: 1,
+        opacity: 1,
+        translateX: 0,
+        transition: varTranEnter({ durationIn, easeIn })
+      },
+      exit: {
+        scale: 0,
+        opacity: 0,
+        translateX: distance,
+        transition: varTranExit({ durationOut, easeOut })
+      }
+    },
 
-		// OUT
-		out: {
-			initial: { scale: 1, opacity: 1 },
-			animate: {
-				scale: 0,
-				opacity: 0,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-		},
-		outUp: {
-			initial: { scale: 1, opacity: 1 },
-			animate: {
-				scale: 0,
-				opacity: 0,
-				translateY: -distance,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-		},
-		outDown: {
-			initial: { scale: 1, opacity: 1 },
-			animate: {
-				scale: 0,
-				opacity: 0,
-				translateY: distance,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-		},
-		outLeft: {
-			initial: { scale: 1, opacity: 1 },
-			animate: {
-				scale: 0,
-				opacity: 0,
-				translateX: -distance,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-		},
-		outRight: {
-			initial: { scale: 1, opacity: 1 },
-			animate: {
-				scale: 0,
-				opacity: 0,
-				translateX: distance,
-				transition: varTranEnter({ durationIn, easeIn }),
-			},
-		},
-	};
-};
+    // OUT
+    out: {
+      initial: { scale: 1, opacity: 1 },
+      animate: {
+        scale: 0,
+        opacity: 0,
+        transition: varTranEnter({ durationIn, easeIn })
+      }
+    },
+    outUp: {
+      initial: { scale: 1, opacity: 1 },
+      animate: {
+        scale: 0,
+        opacity: 0,
+        translateY: -distance,
+        transition: varTranEnter({ durationIn, easeIn })
+      }
+    },
+    outDown: {
+      initial: { scale: 1, opacity: 1 },
+      animate: {
+        scale: 0,
+        opacity: 0,
+        translateY: distance,
+        transition: varTranEnter({ durationIn, easeIn })
+      }
+    },
+    outLeft: {
+      initial: { scale: 1, opacity: 1 },
+      animate: {
+        scale: 0,
+        opacity: 0,
+        translateX: -distance,
+        transition: varTranEnter({ durationIn, easeIn })
+      }
+    },
+    outRight: {
+      initial: { scale: 1, opacity: 1 },
+      animate: {
+        scale: 0,
+        opacity: 0,
+        translateX: distance,
+        transition: varTranEnter({ durationIn, easeIn })
+      }
+    }
+  }
+}

+ 23 - 24
src/components/card/index.tsx

@@ -1,28 +1,27 @@
-import { themeVars } from '@/theme/theme.css';
-import type { CSSProperties, ReactNode } from 'react';
+import type { CSSProperties, ReactNode } from 'react'
+import { themeVars } from '@/theme/theme.css'
 
 type Props = {
-	children?: ReactNode;
-	className?: string;
-	style?: CSSProperties;
-};
+  children?: ReactNode
+  className?: string
+  style?: CSSProperties
+}
 export default function Card({ children, ...other }: Props) {
-	return (
-		<div
-			style={{
-				backgroundColor: themeVars.colors.background.paper,
-				boxShadow: themeVars.shadows.card,
-				transition: 'box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
-				borderRadius: themeVars.borderRadius.md,
-				padding: themeVars.spacing[6],
-				overflow: 'hidden',
-				position: 'relative',
-				display: 'flex',
-				alignItems: 'center'
-			}}
-			{...other}
-		>
-			{children}
-		</div>
-	);
+  return (
+    <div
+      style={{
+        backgroundColor: themeVars.colors.background.paper,
+        boxShadow: themeVars.shadows.card,
+        transition: 'box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
+        borderRadius: themeVars.borderRadius.md,
+        padding: themeVars.spacing[6],
+        overflow: 'hidden',
+        position: 'relative',
+        display: 'flex',
+        alignItems: 'center'
+      }}
+      {...other}>
+      {children}
+    </div>
+  )
 }

+ 10 - 11
src/components/chart/chart.tsx

@@ -1,15 +1,14 @@
-import { memo } from 'react';
-import ApexChart from 'react-apexcharts';
-import { chartWrapper } from './styles.css';
-
-import type { Props as ApexChartProps } from 'react-apexcharts';
+import { memo } from 'react'
+import ApexChart from 'react-apexcharts'
+import type { Props as ApexChartProps } from 'react-apexcharts'
+import { chartWrapper } from './styles.css'
 
 function Chart(props: ApexChartProps) {
-	return (
-		<div className={chartWrapper}>
-			<ApexChart {...props} />
-		</div>
-	);
+  return (
+    <div className={chartWrapper}>
+      <ApexChart {...props} />
+    </div>
+  )
 }
 
-export default memo(Chart);
+export default memo(Chart)

+ 34 - 34
src/components/chart/styles.css.ts

@@ -1,54 +1,54 @@
-import { themeVars } from '@/theme/theme.css';
-import { rgbAlpha } from '@/utils/theme';
-import { globalStyle } from '@vanilla-extract/css';
-import { style } from '@vanilla-extract/css';
+import { globalStyle } from '@vanilla-extract/css'
+import { style } from '@vanilla-extract/css'
+import { rgbAlpha } from '@/utils/theme'
+import { themeVars } from '@/theme/theme.css'
 
-export const chartWrapper = style({}, 'apexcharts-wrapper');
+export const chartWrapper = style({}, 'apexcharts-wrapper')
 
 // TOOLTIP
 globalStyle(`${chartWrapper} .apexcharts-tooltip`, {
-	color: themeVars.colors.text.primary,
-	borderRadius: themeVars.borderRadius.lg,
-	backdropFilter: 'blur(6px)',
-	backgroundColor: rgbAlpha(themeVars.colors.background.paperChannel, 0.8),
-	boxShadow: themeVars.shadows.card
-});
+  color: themeVars.colors.text.primary,
+  borderRadius: themeVars.borderRadius.lg,
+  backdropFilter: 'blur(6px)',
+  backgroundColor: rgbAlpha(themeVars.colors.background.paperChannel, 0.8),
+  boxShadow: themeVars.shadows.card
+})
 
 globalStyle(`${chartWrapper} .apexcharts-tooltip-title`, {
-	textAlign: 'center',
-	fontWeight: 'bold',
-	backgroundColor: themeVars.colors.background.neutral
-});
+  textAlign: 'center',
+  fontWeight: 'bold',
+  backgroundColor: themeVars.colors.background.neutral
+})
 
 // TOOLTIP X
 globalStyle(`${chartWrapper} .apexcharts-xaxistooltip`, {
-	color: themeVars.colors.text.primary,
-	borderRadius: themeVars.borderRadius.lg,
-	backdropFilter: 'blur(6px)',
-	borderColor: 'transparent',
-	boxShadow: themeVars.shadows.card,
-	backgroundColor: themeVars.colors.background.paper
-});
+  color: themeVars.colors.text.primary,
+  borderRadius: themeVars.borderRadius.lg,
+  backdropFilter: 'blur(6px)',
+  borderColor: 'transparent',
+  boxShadow: themeVars.shadows.card,
+  backgroundColor: themeVars.colors.background.paper
+})
 
 globalStyle(`${chartWrapper} .apexcharts-xaxistooltip::before`, {
-	borderBottomColor: rgbAlpha(themeVars.colors.background.paperChannel, 0.8)
-});
+  borderBottomColor: rgbAlpha(themeVars.colors.background.paperChannel, 0.8)
+})
 
 globalStyle(`${chartWrapper} .apexcharts-xaxistooltip::after`, {
-	borderBottomColor: themeVars.colors.background.paper
-});
+  borderBottomColor: themeVars.colors.background.paper
+})
 
 // LEGEND
 globalStyle(`${chartWrapper} .apexcharts-legend`, {
-	padding: 0
-});
+  padding: 0
+})
 
 globalStyle(`${chartWrapper} .apexcharts-legend-series`, {
-	display: 'inline-flex !important',
-	alignItems: 'center'
-});
+  display: 'inline-flex !important',
+  alignItems: 'center'
+})
 
 globalStyle(`${chartWrapper} .apexcharts-legend-text`, {
-	lineHeight: '18px',
-	textTransform: 'capitalize'
-});
+  lineHeight: '18px',
+  textTransform: 'capitalize'
+})

+ 208 - 209
src/components/chart/useChart.ts

@@ -1,212 +1,211 @@
-import { themeVars } from '@/theme/theme.css';
-import { removePx } from '@/utils/theme';
-import type { ApexOptions } from 'apexcharts';
-import { mergeDeepRight } from 'ramda';
-
-import { useSettings } from '@/store/settingStore';
-import { breakpointsTokens } from '@/theme/tokens/breakpoints';
-import { paletteColors, presetsColors } from '@/theme/tokens/color';
+import { useSettings } from '@/store/settingStore'
+import type { ApexOptions } from 'apexcharts'
+import { mergeDeepRight } from 'ramda'
+import { removePx } from '@/utils/theme'
+import { themeVars } from '@/theme/theme.css'
+import { breakpointsTokens } from '@/theme/tokens/breakpoints'
+import { paletteColors, presetsColors } from '@/theme/tokens/color'
 
 export default function useChart(options: ApexOptions) {
-	const { themeColorPresets } = useSettings();
-
-	const LABEL_TOTAL = {
-		show: true,
-		label: 'Total',
-		color: themeVars.colors.text.secondary,
-		fontSize: themeVars.typography.fontSize.sm,
-		lineHeight: themeVars.typography.lineHeight.tight
-	};
-
-	const LABEL_VALUE = {
-		offsetY: 8,
-		color: themeVars.colors.text.primary,
-		fontSize: themeVars.typography.fontSize.sm,
-		lineHeight: themeVars.typography.lineHeight.tight
-	};
-
-	const baseOptions: ApexOptions = {
-		// Colors
-		colors: [
-			presetsColors[themeColorPresets].default,
-
-			paletteColors.info.default,
-			paletteColors.warning.default,
-			paletteColors.error.default,
-			paletteColors.success.default,
-
-			paletteColors.warning.light,
-			paletteColors.info.light,
-			paletteColors.error.light,
-			paletteColors.success.light
-		],
-
-		// Chart
-		chart: {
-			toolbar: { show: false },
-			zoom: { enabled: false },
-			foreColor: themeVars.colors.text.disabled,
-			fontFamily: themeVars.typography.fontFamily.primary
-		},
-
-		// States
-		states: {
-			hover: {
-				filter: {
-					type: 'lighten',
-					value: 0.04
-				}
-			},
-			active: {
-				filter: {
-					type: 'darken',
-					value: 0.88
-				}
-			}
-		},
-
-		// Fill
-		fill: {
-			opacity: 1,
-			gradient: {
-				type: 'vertical',
-				shadeIntensity: 0,
-				opacityFrom: 0.4,
-				opacityTo: 0,
-				stops: [0, 100]
-			}
-		},
-
-		// Datalabels
-		dataLabels: {
-			enabled: false
-		},
-
-		// Stroke
-		stroke: {
-			width: 3,
-			curve: 'smooth',
-			lineCap: 'round'
-		},
-
-		// Grid
-		grid: {
-			strokeDashArray: 3,
-			borderColor: themeVars.colors.background.neutral,
-			xaxis: {
-				lines: {
-					show: false
-				}
-			}
-		},
-
-		// Xaxis
-		xaxis: {
-			axisBorder: { show: false },
-			axisTicks: { show: false }
-		},
-
-		// Markers
-		markers: {
-			size: 0
-		},
-
-		// Tooltip
-		tooltip: {
-			theme: undefined,
-			x: {
-				show: true
-			}
-		},
-
-		// Legend
-		legend: {
-			show: true,
-			fontSize: themeVars.typography.fontSize.sm,
-			position: 'top',
-			horizontalAlign: 'right',
-			markers: {
-				strokeWidth: 0
-			},
-			fontWeight: 500,
-			itemMargin: {
-				horizontal: 8
-			},
-			labels: {
-				colors: themeVars.colors.text.primary
-			}
-		},
-
-		// plotOptions
-		plotOptions: {
-			// Bar
-			bar: {
-				borderRadius: 4,
-				columnWidth: '28%',
-				borderRadiusApplication: 'end',
-				borderRadiusWhenStacked: 'last'
-			},
-
-			// Pie + Donut
-			pie: {
-				donut: {
-					labels: {
-						show: true,
-						value: LABEL_VALUE,
-						total: LABEL_TOTAL
-					}
-				}
-			},
-
-			// Radialbar
-			radialBar: {
-				track: {
-					strokeWidth: '100%'
-				},
-				dataLabels: {
-					value: LABEL_VALUE,
-					total: LABEL_TOTAL
-				}
-			},
-
-			// Radar
-			radar: {
-				polygons: {
-					fill: { colors: ['transparent'] },
-					strokeColors: themeVars.colors.background.neutral,
-					connectorColors: themeVars.colors.background.neutral
-				}
-			},
-
-			// polarArea
-			polarArea: {
-				rings: {
-					strokeColor: themeVars.colors.background.neutral
-				},
-				spokes: {
-					connectorColors: themeVars.colors.background.neutral
-				}
-			}
-		},
-
-		// Responsive
-		responsive: [
-			{
-				// sm
-				breakpoint: removePx(breakpointsTokens.sm),
-				options: {
-					plotOptions: { bar: { columnWidth: '40%' } }
-				}
-			},
-			{
-				// md
-				breakpoint: removePx(breakpointsTokens.md),
-				options: {
-					plotOptions: { bar: { columnWidth: '32%' } }
-				}
-			}
-		]
-	};
-
-	return mergeDeepRight(baseOptions, options) as ApexOptions;
+  const { themeColorPresets } = useSettings()
+
+  const LABEL_TOTAL = {
+    show: true,
+    label: 'Total',
+    color: themeVars.colors.text.secondary,
+    fontSize: themeVars.typography.fontSize.sm,
+    lineHeight: themeVars.typography.lineHeight.tight
+  }
+
+  const LABEL_VALUE = {
+    offsetY: 8,
+    color: themeVars.colors.text.primary,
+    fontSize: themeVars.typography.fontSize.sm,
+    lineHeight: themeVars.typography.lineHeight.tight
+  }
+
+  const baseOptions: ApexOptions = {
+    // Colors
+    colors: [
+      presetsColors[themeColorPresets].default,
+
+      paletteColors.info.default,
+      paletteColors.warning.default,
+      paletteColors.error.default,
+      paletteColors.success.default,
+
+      paletteColors.warning.light,
+      paletteColors.info.light,
+      paletteColors.error.light,
+      paletteColors.success.light
+    ],
+
+    // Chart
+    chart: {
+      toolbar: { show: false },
+      zoom: { enabled: false },
+      foreColor: themeVars.colors.text.disabled,
+      fontFamily: themeVars.typography.fontFamily.primary
+    },
+
+    // States
+    states: {
+      hover: {
+        filter: {
+          type: 'lighten',
+          value: 0.04
+        }
+      },
+      active: {
+        filter: {
+          type: 'darken',
+          value: 0.88
+        }
+      }
+    },
+
+    // Fill
+    fill: {
+      opacity: 1,
+      gradient: {
+        type: 'vertical',
+        shadeIntensity: 0,
+        opacityFrom: 0.4,
+        opacityTo: 0,
+        stops: [0, 100]
+      }
+    },
+
+    // Datalabels
+    dataLabels: {
+      enabled: false
+    },
+
+    // Stroke
+    stroke: {
+      width: 3,
+      curve: 'smooth',
+      lineCap: 'round'
+    },
+
+    // Grid
+    grid: {
+      strokeDashArray: 3,
+      borderColor: themeVars.colors.background.neutral,
+      xaxis: {
+        lines: {
+          show: false
+        }
+      }
+    },
+
+    // Xaxis
+    xaxis: {
+      axisBorder: { show: false },
+      axisTicks: { show: false }
+    },
+
+    // Markers
+    markers: {
+      size: 0
+    },
+
+    // Tooltip
+    tooltip: {
+      theme: undefined,
+      x: {
+        show: true
+      }
+    },
+
+    // Legend
+    legend: {
+      show: true,
+      fontSize: themeVars.typography.fontSize.sm,
+      position: 'top',
+      horizontalAlign: 'right',
+      markers: {
+        strokeWidth: 0
+      },
+      fontWeight: 500,
+      itemMargin: {
+        horizontal: 8
+      },
+      labels: {
+        colors: themeVars.colors.text.primary
+      }
+    },
+
+    // plotOptions
+    plotOptions: {
+      // Bar
+      bar: {
+        borderRadius: 4,
+        columnWidth: '28%',
+        borderRadiusApplication: 'end',
+        borderRadiusWhenStacked: 'last'
+      },
+
+      // Pie + Donut
+      pie: {
+        donut: {
+          labels: {
+            show: true,
+            value: LABEL_VALUE,
+            total: LABEL_TOTAL
+          }
+        }
+      },
+
+      // Radialbar
+      radialBar: {
+        track: {
+          strokeWidth: '100%'
+        },
+        dataLabels: {
+          value: LABEL_VALUE,
+          total: LABEL_TOTAL
+        }
+      },
+
+      // Radar
+      radar: {
+        polygons: {
+          fill: { colors: ['transparent'] },
+          strokeColors: themeVars.colors.background.neutral,
+          connectorColors: themeVars.colors.background.neutral
+        }
+      },
+
+      // polarArea
+      polarArea: {
+        rings: {
+          strokeColor: themeVars.colors.background.neutral
+        },
+        spokes: {
+          connectorColors: themeVars.colors.background.neutral
+        }
+      }
+    },
+
+    // Responsive
+    responsive: [
+      {
+        // sm
+        breakpoint: removePx(breakpointsTokens.sm),
+        options: {
+          plotOptions: { bar: { columnWidth: '40%' } }
+        }
+      },
+      {
+        // md
+        breakpoint: removePx(breakpointsTokens.md),
+        options: {
+          plotOptions: { bar: { columnWidth: '32%' } }
+        }
+      }
+    ]
+  }
+
+  return mergeDeepRight(baseOptions, options) as ApexOptions
 }

+ 31 - 27
src/components/editor/index.tsx

@@ -1,31 +1,35 @@
-/* eslint-disable import/order */
-import "@/utils/highlight";
-import ReactQuill, { type ReactQuillProps } from "react-quill";
-import { StyledEditor } from "./styles";
-import Toolbar, { formats } from "./toolbar";
+import ReactQuill, { type ReactQuillProps } from 'react-quill'
+import '@/utils/highlight'
+import { StyledEditor } from './styles'
+import Toolbar, { formats } from './toolbar'
 
 interface Props extends ReactQuillProps {
-	sample?: boolean;
+  sample?: boolean
 }
-export default function Editor({ id = "slash-quill", sample = false, ...other }: Props) {
-	const modules = {
-		toolbar: {
-			container: `#${id}`,
-		},
-		history: {
-			delay: 500,
-			maxStack: 100,
-			userOnly: true,
-		},
-		syntax: true,
-		clipboard: {
-			matchVisual: false,
-		},
-	};
-	return (
-		<StyledEditor>
-			<Toolbar id={id} isSimple={sample} />
-			<ReactQuill modules={modules} formats={formats} {...other} placeholder="Write something awesome..." />
-		</StyledEditor>
-	);
+export default function Editor({ id = 'slash-quill', sample = false, ...other }: Props) {
+  const modules = {
+    toolbar: {
+      container: `#${id}`
+    },
+    history: {
+      delay: 500,
+      maxStack: 100,
+      userOnly: true
+    },
+    syntax: true,
+    clipboard: {
+      matchVisual: false
+    }
+  }
+  return (
+    <StyledEditor>
+      <Toolbar id={id} isSimple={sample} />
+      <ReactQuill
+        modules={modules}
+        formats={formats}
+        {...other}
+        placeholder='Write something awesome...'
+      />
+    </StyledEditor>
+  )
 }

+ 5 - 5
src/components/editor/styles.ts

@@ -1,5 +1,5 @@
-import { themeVars } from "@/theme/theme.css";
-import styled from "styled-components";
+import styled from 'styled-components'
+import { themeVars } from '@/theme/theme.css'
 
 const StyledEditor = styled.div`
   h1 {
@@ -64,7 +64,7 @@ const StyledEditor = styled.div`
       background-color: ${themeVars.colors.background.neutral};
     }
   }
-`;
+`
 
 const StyledToolbar = styled.div`
   & .ql-snow.ql-toolbar button:hover .ql-fill,
@@ -185,5 +185,5 @@ const StyledToolbar = styled.div`
       background-color: ${themeVars.colors.background.paper};
     }
   }
-`;
-export { StyledEditor, StyledToolbar };
+`
+export { StyledEditor, StyledToolbar }

+ 88 - 88
src/components/editor/toolbar.tsx

@@ -1,104 +1,104 @@
-import { StyledToolbar } from "./styles";
+import { StyledToolbar } from './styles'
 
-const HEADINGS = ["Heading 1", "Heading 2", "Heading 3", "Heading 4", "Heading 5", "Heading 6"];
+const HEADINGS = ['Heading 1', 'Heading 2', 'Heading 3', 'Heading 4', 'Heading 5', 'Heading 6']
 
 export const formats = [
-	"align",
-	"background",
-	"blockquote",
-	"bold",
-	"bullet",
-	"code",
-	"code-block",
-	"color",
-	"direction",
-	"font",
-	"formula",
-	"header",
-	"image",
-	"indent",
-	"italic",
-	"link",
-	"list",
-	"script",
-	"size",
-	"strike",
-	"table",
-	"underline",
-	"video",
-];
+  'align',
+  'background',
+  'blockquote',
+  'bold',
+  'bullet',
+  'code',
+  'code-block',
+  'color',
+  'direction',
+  'font',
+  'formula',
+  'header',
+  'image',
+  'indent',
+  'italic',
+  'link',
+  'list',
+  'script',
+  'size',
+  'strike',
+  'table',
+  'underline',
+  'video'
+]
 
 type EditorToolbarProps = {
-	id: string;
-	isSimple?: boolean;
-};
+  id: string
+  isSimple?: boolean
+}
 
 export default function Toolbar({ id, isSimple }: EditorToolbarProps) {
-	return (
-		<StyledToolbar>
-			<div id={id}>
-				<div className="ql-formats">
-					<select className="ql-header" defaultValue="">
-						{HEADINGS.map((heading, index) => (
-							<option key={heading} value={index + 1}>
-								{heading}
-							</option>
-						))}
-						<option value="">Normal</option>
-					</select>
-				</div>
+  return (
+    <StyledToolbar>
+      <div id={id}>
+        <div className='ql-formats'>
+          <select className='ql-header' defaultValue=''>
+            {HEADINGS.map((heading, index) => (
+              <option key={heading} value={index + 1}>
+                {heading}
+              </option>
+            ))}
+            <option value=''>Normal</option>
+          </select>
+        </div>
 
-				<div className="ql-formats">
-					<button type="button" className="ql-bold" />
-					<button type="button" className="ql-italic" />
-					<button type="button" className="ql-underline" />
-					<button type="button" className="ql-strike" />
-				</div>
+        <div className='ql-formats'>
+          <button type='button' className='ql-bold' />
+          <button type='button' className='ql-italic' />
+          <button type='button' className='ql-underline' />
+          <button type='button' className='ql-strike' />
+        </div>
 
-				{!isSimple && (
-					<div className="ql-formats">
-						<select className="ql-color" />
-						<select className="ql-background" />
-					</div>
-				)}
+        {!isSimple && (
+          <div className='ql-formats'>
+            <select className='ql-color' />
+            <select className='ql-background' />
+          </div>
+        )}
 
-				<div className="ql-formats">
-					<button type="button" className="ql-list" value="ordered" />
-					<button type="button" className="ql-list" value="bullet" />
-					{!isSimple && <button type="button" className="ql-indent" value="-1" />}
-					{!isSimple && <button type="button" className="ql-indent" value="+1" />}
-				</div>
+        <div className='ql-formats'>
+          <button type='button' className='ql-list' value='ordered' />
+          <button type='button' className='ql-list' value='bullet' />
+          {!isSimple && <button type='button' className='ql-indent' value='-1' />}
+          {!isSimple && <button type='button' className='ql-indent' value='+1' />}
+        </div>
 
-				{!isSimple && (
-					<div className="ql-formats">
-						<button type="button" className="ql-script" value="super" />
-						<button type="button" className="ql-script" value="sub" />
-					</div>
-				)}
+        {!isSimple && (
+          <div className='ql-formats'>
+            <button type='button' className='ql-script' value='super' />
+            <button type='button' className='ql-script' value='sub' />
+          </div>
+        )}
 
-				{!isSimple && (
-					<div className="ql-formats">
-						<button type="button" className="ql-code-block" />
-						<button type="button" className="ql-blockquote" />
-					</div>
-				)}
+        {!isSimple && (
+          <div className='ql-formats'>
+            <button type='button' className='ql-code-block' />
+            <button type='button' className='ql-blockquote' />
+          </div>
+        )}
 
-				<div className="ql-formats">
-					<button type="button" className="ql-direction" value="rtl" />
-					<select className="ql-align" />
-				</div>
+        <div className='ql-formats'>
+          <button type='button' className='ql-direction' value='rtl' />
+          <select className='ql-align' />
+        </div>
 
-				<div className="ql-formats">
-					<button type="button" className="ql-link" />
-					<button type="button" className="ql-image" />
-					<button type="button" className="ql-video" />
-				</div>
+        <div className='ql-formats'>
+          <button type='button' className='ql-link' />
+          <button type='button' className='ql-image' />
+          <button type='button' className='ql-video' />
+        </div>
 
-				<div className="ql-formats">
-					{!isSimple && <button type="button" className="ql-formula" />}
-					<button type="button" className="ql-clean" />
-				</div>
-			</div>
-		</StyledToolbar>
-	);
+        <div className='ql-formats'>
+          {!isSimple && <button type='button' className='ql-formula' />}
+          <button type='button' className='ql-clean' />
+        </div>
+      </div>
+    </StyledToolbar>
+  )
 }

+ 20 - 26
src/components/icon/icon-button.tsx

@@ -1,29 +1,23 @@
-import { cn } from '@/utils';
-import type { ButtonProps } from 'antd';
-import type { CSSProperties, ReactNode } from 'react';
+import type { CSSProperties, ReactNode } from 'react'
+import { cn } from '@/utils'
+import type { ButtonProps } from 'antd'
 
 type Props = {
-	children: ReactNode;
-	className?: string;
-	style?: CSSProperties;
-} & ButtonProps;
-export default function IconButton({
-	children,
-	className,
-	style,
-	onClick
-}: Props) {
-	return (
-		<button
-			type="button"
-			style={style}
-			className={cn(
-				'flex cursor-pointer items-center justify-center rounded-full p-2 hover:bg-hover',
-				className
-			)}
-			onClick={onClick}
-		>
-			{children}
-		</button>
-	);
+  children: ReactNode
+  className?: string
+  style?: CSSProperties
+} & ButtonProps
+export default function IconButton({ children, className, style, onClick }: Props) {
+  return (
+    <button
+      type='button'
+      style={style}
+      className={cn(
+        'flex cursor-pointer items-center justify-center rounded-full p-2 hover:bg-hover',
+        className
+      )}
+      onClick={onClick}>
+      {children}
+    </button>
+  )
 }

+ 11 - 12
src/components/icon/iconify-icon.tsx

@@ -1,24 +1,23 @@
-import { Icon, disableCache } from '@iconify/react';
-import styled from 'styled-components';
-
-import type { IconProps } from '@iconify/react';
+import { disableCache, Icon } from '@iconify/react'
+import type { IconProps } from '@iconify/react'
+import styled from 'styled-components'
 
 interface Props extends IconProps {
-	size?: IconProps['width'];
+  size?: IconProps['width']
 }
 export default function Iconify({ icon, size = '1em', className = '', ...other }: Props) {
-	return (
-		<StyledIconify className="anticon">
-			<Icon icon={icon} width={size} height={size} className={`m-auto ${className}`} {...other} />
-		</StyledIconify>
-	);
+  return (
+    <StyledIconify className='anticon'>
+      <Icon icon={icon} width={size} height={size} className={`m-auto ${className}`} {...other} />
+    </StyledIconify>
+  )
 }
 
-disableCache('local');
+disableCache('local')
 const StyledIconify = styled.div`
   display: inline-flex;
   vertical-align: middle;
   svg {
     display: inline-block;
   }
-`;
+`

+ 4 - 4
src/components/icon/index.ts

@@ -1,5 +1,5 @@
-import IconButton from './icon-button';
-import Iconify from './iconify-icon';
-import SvgIcon from './svg-icon';
+import IconButton from './icon-button'
+import Iconify from './iconify-icon'
+import SvgIcon from './svg-icon'
 
-export { Iconify, SvgIcon, IconButton };
+export { Iconify, SvgIcon, IconButton }

+ 6 - 6
src/components/loading/circle-loading.tsx

@@ -1,9 +1,9 @@
-import { Spin } from 'antd';
+import { Spin } from 'antd'
 
 export function CircleLoading() {
-	return (
-		<div className="flex h-full items-center justify-center">
-			<Spin size="large" />
-		</div>
-	);
+  return (
+    <div className='flex h-full items-center justify-center'>
+      <Spin size='large' />
+    </div>
+  )
 }

+ 2 - 2
src/components/loading/index.tsx

@@ -1,2 +1,2 @@
-export * from './circle-loading';
-export * from './line-loading';
+export * from './circle-loading'
+export * from './line-loading'

+ 33 - 34
src/components/loading/line-loading.tsx

@@ -1,40 +1,39 @@
-import { Progress } from 'antd';
-import { useEffect, useState } from 'react';
-
-import { themeVars } from '@/theme/theme.css';
-import { rgbAlpha } from '@/utils/theme';
+import { useEffect, useState } from 'react'
+import { rgbAlpha } from '@/utils/theme'
+import { Progress } from 'antd'
+import { themeVars } from '@/theme/theme.css'
 
 export function LineLoading() {
-	const [percent, setPercent] = useState(10);
-	const [increasing, setIncreasing] = useState(true);
+  const [percent, setPercent] = useState(10)
+  const [increasing, setIncreasing] = useState(true)
 
-	useEffect(() => {
-		const interval = setInterval(() => {
-			if (increasing) {
-				setPercent((prevPercent) => prevPercent + 20);
-				if (percent === 100) {
-					setIncreasing(false);
-				}
-			} else {
-				setPercent(0);
-				setIncreasing(true);
-			}
-		}, 50);
+  useEffect(() => {
+    const interval = setInterval(() => {
+      if (increasing) {
+        setPercent((prevPercent) => prevPercent + 20)
+        if (percent === 100) {
+          setIncreasing(false)
+        }
+      } else {
+        setPercent(0)
+        setIncreasing(true)
+      }
+    }, 50)
 
-		return () => {
-			clearInterval(interval);
-		};
-	}, [percent, increasing]);
+    return () => {
+      clearInterval(interval)
+    }
+  }, [percent, increasing])
 
-	return (
-		<div className="m-auto flex h-full w-96 items-center justify-center">
-			<Progress
-				percent={percent}
-				trailColor={rgbAlpha(themeVars.colors.palette.primary.default, 0.8)}
-				strokeColor={themeVars.colors.palette.primary.default}
-				showInfo={false}
-				size="small"
-			/>
-		</div>
-	);
+  return (
+    <div className='m-auto flex h-full w-96 items-center justify-center'>
+      <Progress
+        percent={percent}
+        trailColor={rgbAlpha(themeVars.colors.palette.primary.default, 0.8)}
+        strokeColor={themeVars.colors.palette.primary.default}
+        showInfo={false}
+        size='small'
+      />
+    </div>
+  )
 }

+ 24 - 28
src/components/locale-picker/index.tsx

@@ -1,37 +1,33 @@
-import { Dropdown } from 'antd';
+import type { LocalEnum } from '#/enum'
+import useLocale, { LANGUAGE_MAP } from '@/locales/use-locale'
+import { Dropdown } from 'antd'
+import type { MenuProps } from 'antd'
+import { IconButton, SvgIcon } from '../icon'
 
-import useLocale, { LANGUAGE_MAP } from '@/locales/use-locale';
-
-import { IconButton, SvgIcon } from '../icon';
-
-import type { MenuProps } from 'antd';
-import type { LocalEnum } from '#/enum';
-
-type Locale = keyof typeof LocalEnum;
+type Locale = keyof typeof LocalEnum
 
 /**
  * Locale Picker
  */
 export default function LocalePicker() {
-	const { setLocale, locale } = useLocale();
+  const { setLocale, locale } = useLocale()
 
-	const localeList: MenuProps['items'] = Object.values(LANGUAGE_MAP).map((item) => {
-		return {
-			key: item.locale,
-			label: item.label,
-			icon: <SvgIcon icon={item.icon} size="20" className="rounded-md" />
-		};
-	});
+  const localeList: MenuProps['items'] = Object.values(LANGUAGE_MAP).map((item) => {
+    return {
+      key: item.locale,
+      label: item.label,
+      icon: <SvgIcon icon={item.icon} size='20' className='rounded-md' />
+    }
+  })
 
-	return (
-		<Dropdown
-			placement="bottomRight"
-			trigger={['click']}
-			menu={{ items: localeList, onClick: (e) => setLocale(e.key as Locale) }}
-		>
-			<IconButton className="h-10 w-10 hover:scale-105">
-				<SvgIcon icon={`ic-locale_${locale}`} size="24" className="rounded-md" />
-			</IconButton>
-		</Dropdown>
-	);
+  return (
+    <Dropdown
+      placement='bottomRight'
+      trigger={['click']}
+      menu={{ items: localeList, onClick: (e) => setLocale(e.key as Locale) }}>
+      <IconButton className='h-10 w-10 hover:scale-105'>
+        <SvgIcon icon={`ic-locale_${locale}`} size='24' className='rounded-md' />
+      </IconButton>
+    </Dropdown>
+  )
 }

+ 18 - 15
src/components/markdown/index.tsx

@@ -1,18 +1,21 @@
-import ReactMarkdown from "react-markdown";
-import type { ReactMarkdownOptions } from "react-markdown/lib/react-markdown";
-import rehypeHighlight from "rehype-highlight";
-import rehypeRaw from "rehype-raw";
-import remarkGfm from "remark-gfm"; // add support for strikethrough, tables, tasklists and URLs directly
-import "@/utils/highlight";
-import StyledMarkdown from "./styles";
+import ReactMarkdown from 'react-markdown'
+import type { ReactMarkdownOptions } from 'react-markdown/lib/react-markdown'
+import rehypeHighlight from 'rehype-highlight'
+import rehypeRaw from 'rehype-raw'
+import remarkGfm from 'remark-gfm'
+// add support for strikethrough, tables, tasklists and URLs directly
+import '@/utils/highlight'
+import StyledMarkdown from './styles'
 
-type Props = ReactMarkdownOptions;
+type Props = ReactMarkdownOptions
 export default function Markdown({ children }: Props) {
-	return (
-		<StyledMarkdown>
-			<ReactMarkdown rehypePlugins={[rehypeHighlight, rehypeRaw]} remarkPlugins={[[remarkGfm, { singleTilde: false }]]}>
-				{children}
-			</ReactMarkdown>
-		</StyledMarkdown>
-	);
+  return (
+    <StyledMarkdown>
+      <ReactMarkdown
+        rehypePlugins={[rehypeHighlight, rehypeRaw]}
+        remarkPlugins={[[remarkGfm, { singleTilde: false }]]}>
+        {children}
+      </ReactMarkdown>
+    </StyledMarkdown>
+  )
 }

+ 4 - 5
src/components/markdown/styles.ts

@@ -2,9 +2,8 @@
  * https://styled-components.com/
  * vscode plugin: https://github.com/styled-components/vscode-styled-components
  */
-import styled from "styled-components";
-
-import { themeVars } from "@/theme/theme.css";
+import styled from 'styled-components'
+import { themeVars } from '@/theme/theme.css'
 
 const StyledMarkdown = styled.div`
   display: grid;
@@ -154,6 +153,6 @@ const StyledMarkdown = styled.div`
       }
     }
   }
-`;
+`
 
-export default StyledMarkdown;
+export default StyledMarkdown

+ 7 - 7
src/components/progress-bar/index.css.ts

@@ -1,11 +1,11 @@
-import { themeVars } from '@/theme/theme.css';
-import { globalStyle } from '@vanilla-extract/css';
+import { globalStyle } from '@vanilla-extract/css'
+import { themeVars } from '@/theme/theme.css'
 
 globalStyle('#nprogress .bar', {
-	background: themeVars.colors.palette.primary.default,
-	boxShadow: `0 0 2px ${themeVars.colors.palette.primary.default}`
-});
+  background: themeVars.colors.palette.primary.default,
+  boxShadow: `0 0 2px ${themeVars.colors.palette.primary.default}`
+})
 
 globalStyle('#nprogress .peg', {
-	boxShadow: `0 0 10px ${themeVars.colors.palette.primary.default}, 0 0 5px ${themeVars.colors.palette.primary.default}`
-});
+  boxShadow: `0 0 10px ${themeVars.colors.palette.primary.default}, 0 0 5px ${themeVars.colors.palette.primary.default}`
+})

+ 49 - 49
src/components/progress-bar/index.tsx

@@ -1,55 +1,55 @@
-import NProgress from 'nprogress';
-import 'nprogress/nprogress.css';
-import { useEffect } from 'react';
-import './index.css';
+import { useEffect } from 'react'
+import NProgress from 'nprogress'
+import 'nprogress/nprogress.css'
+import './index.css'
 
 // 配置 NProgress
 NProgress.configure({
-	showSpinner: false,
-	minimum: 0.1,
-	trickleSpeed: 200
-});
+  showSpinner: false,
+  minimum: 0.1,
+  trickleSpeed: 200
+})
 
 export default function ProgressBar() {
-	useEffect(() => {
-		let lastHref = window.location.href;
-
-		const handleRouteChange = () => {
-			NProgress.start();
-			const timer = setTimeout(() => NProgress.done(), 100);
-			return () => {
-				clearTimeout(timer);
-				NProgress.done();
-			};
-		};
-
-		// 监听 href 变化
-		const observer = new MutationObserver(() => {
-			const currentHref = window.location.href;
-			if (currentHref !== lastHref) {
-				lastHref = currentHref;
-				handleRouteChange();
-			}
-		});
-
-		// 观察整个文档的变化
-		observer.observe(document, {
-			subtree: true,
-			childList: true
-		});
-
-		// 监听 popstate 事件(处理浏览器前进后退)
-		window.addEventListener('popstate', handleRouteChange);
-
-		// 初始加载时触发一次
-		handleRouteChange();
-
-		// 清理监听器
-		return () => {
-			observer.disconnect();
-			window.removeEventListener('popstate', handleRouteChange);
-		};
-	}, []);
-
-	return null;
+  useEffect(() => {
+    let lastHref = window.location.href
+
+    const handleRouteChange = () => {
+      NProgress.start()
+      const timer = setTimeout(() => NProgress.done(), 100)
+      return () => {
+        clearTimeout(timer)
+        NProgress.done()
+      }
+    }
+
+    // 监听 href 变化
+    const observer = new MutationObserver(() => {
+      const currentHref = window.location.href
+      if (currentHref !== lastHref) {
+        lastHref = currentHref
+        handleRouteChange()
+      }
+    })
+
+    // 观察整个文档的变化
+    observer.observe(document, {
+      subtree: true,
+      childList: true
+    })
+
+    // 监听 popstate 事件(处理浏览器前进后退)
+    window.addEventListener('popstate', handleRouteChange)
+
+    // 初始加载时触发一次
+    handleRouteChange()
+
+    // 清理监听器
+    return () => {
+      observer.disconnect()
+      window.removeEventListener('popstate', handleRouteChange)
+    }
+  }, [])
+
+  return null
 }

+ 20 - 21
src/components/scroll-progress/index.tsx

@@ -1,32 +1,31 @@
-import { type HTMLMotionProps, type MotionValue, m, useSpring } from "framer-motion";
-import type { CSSProperties } from "react";
+import type { CSSProperties } from 'react'
+import { type HTMLMotionProps, m, type MotionValue, useSpring } from 'framer-motion'
+import { useTheme } from '@/theme/hooks'
 
-import { useTheme } from "@/theme/hooks";
-
-interface Props extends HTMLMotionProps<"div"> {
-	color?: string;
-	scrollYProgress: MotionValue<number>;
-	height?: number;
+interface Props extends HTMLMotionProps<'div'> {
+  color?: string
+  scrollYProgress: MotionValue<number>
+  height?: number
 }
 /**
  * https://www.framer.com/motion/scroll-animations/##spring-smoothing
  */
 export default function ScrollProgress({ scrollYProgress, height = 4, color, ...other }: Props) {
-	const scaleX = useSpring(scrollYProgress, {
-		stiffness: 100,
-		damping: 30,
-		restDelta: 0.001,
-	});
+  const scaleX = useSpring(scrollYProgress, {
+    stiffness: 100,
+    damping: 30,
+    restDelta: 0.001
+  })
 
-	const { themeTokens } = useTheme();
+  const { themeTokens } = useTheme()
 
-	const backgroundColor = color || themeTokens.color.palette.primary.default;
+  const backgroundColor = color || themeTokens.color.palette.primary.default
 
-	const style: CSSProperties = {
-		transformOrigin: "0%",
-		height,
-		backgroundColor,
-	};
+  const style: CSSProperties = {
+    transformOrigin: '0%',
+    height,
+    backgroundColor
+  }
 
-	return <m.div style={{ scaleX, ...style }} {...other} />;
+  return <m.div style={{ scaleX, ...style }} {...other} />
 }

+ 25 - 26
src/components/scrollbar/index.tsx

@@ -1,36 +1,35 @@
-import { cn } from '@/utils';
-import { forwardRef } from 'react';
-import SimpleBar, { type Props as SimplebarProps } from 'simplebar-react';
-import styled from 'styled-components';
+import { forwardRef } from 'react'
+import { cn } from '@/utils'
+import SimpleBar, { type Props as SimplebarProps } from 'simplebar-react'
+import styled from 'styled-components'
 
 export type ScrollbarProps = SimplebarProps & {
-	fillContainer?: boolean;
-};
+  fillContainer?: boolean
+}
 
 /**
  * https://www.npmjs.com/package/simplebar-react?activeTab=readme
  */
 const Scrollbar = forwardRef<HTMLElement, ScrollbarProps>(
-	({ children, className, fillContainer = true, ...other }, ref) => {
-		return (
-			<ScrollbarRoot
-				fillContainer={fillContainer}
-				scrollableNodeProps={{ ref }}
-				clickOnTrack={false}
-				{...other}
-				className={cn('', className)}
-			>
-				{children}
-			</ScrollbarRoot>
-		);
-	}
-);
+  ({ children, className, fillContainer = true, ...other }, ref) => {
+    return (
+      <ScrollbarRoot
+        fillContainer={fillContainer}
+        scrollableNodeProps={{ ref }}
+        clickOnTrack={false}
+        {...other}
+        className={cn('', className)}>
+        {children}
+      </ScrollbarRoot>
+    )
+  }
+)
 
-export default Scrollbar;
+export default Scrollbar
 
 const ScrollbarRoot = styled(SimpleBar).withConfig({
-	shouldForwardProp: (prop: string) => !['fillContainer'].includes(prop),
-	displayName: 'ScrollbarRoot'
+  shouldForwardProp: (prop: string) => !['fillContainer'].includes(prop),
+  displayName: 'ScrollbarRoot'
 })<Pick<ScrollbarProps, 'fillContainer'>>`
   min-width: 0;
   min-height: 0;
@@ -40,8 +39,8 @@ const ScrollbarRoot = styled(SimpleBar).withConfig({
   flex-direction: column;
 
   ${({ fillContainer }) =>
-		fillContainer &&
-		`
+    fillContainer &&
+    `
     & .simplebar-content {
       display: flex;
       flex: 1 1 auto;
@@ -49,4 +48,4 @@ const ScrollbarRoot = styled(SimpleBar).withConfig({
       flex-direction: column;
     }
   `}
-`;
+`

+ 80 - 65
src/components/toast/index.tsx

@@ -1,69 +1,84 @@
-import { useSettings } from '@/store/settingStore';
-import { themeVars } from '@/theme/theme.css';
-import { rgbAlpha } from '@/utils/theme';
-import { Toaster } from 'sonner';
-import styled from 'styled-components';
-
-import { Iconify } from '../icon';
+import { useSettings } from '@/store/settingStore'
+import { Toaster } from 'sonner'
+import styled from 'styled-components'
+import { rgbAlpha } from '@/utils/theme'
+import { themeVars } from '@/theme/theme.css'
+import { Iconify } from '../icon'
 
 /**
  * https://sonner.emilkowal.ski/getting-started
  */
 export default function Toast() {
-	const { themeMode } = useSettings();
+  const { themeMode } = useSettings()
 
-	return (
-		<ToasterStyleWrapper>
-			<Toaster
-				position="top-right"
-				theme={themeMode}
-				toastOptions={{
-					duration: 3000,
-					style: {
-						backgroundColor: themeVars.colors.background.paper
-					},
-					classNames: {
-						toast: 'rounded-lg border-0',
-						description: 'text-xs text-current/45',
-						content: 'flex-1 ml-2',
-						icon: 'flex items-center justify-center px-4 rounded-lg',
-						success: 'bg-success/10',
-						error: 'bg-error/10',
-						warning: 'bg-warning/10',
-						info: 'bg-info/10'
-					}
-				}}
-				icons={{
-					success: (
-						<div className="p-2 bg-success/10 rounded-lg">
-							<Iconify icon="carbon:checkmark-filled" size={24} color={themeVars.colors.palette.success.default} />
-						</div>
-					),
-					error: (
-						<div className="p-2 bg-error/10 rounded-lg">
-							<Iconify icon="carbon:warning-hex-filled" size={24} color={themeVars.colors.palette.error.default} />
-						</div>
-					),
-					warning: (
-						<div className="p-2 bg-warning/10 rounded-lg">
-							<Iconify icon="carbon:warning-alt-filled" size={24} color={themeVars.colors.palette.warning.default} />
-						</div>
-					),
-					info: (
-						<div className="p-2 bg-info/10 rounded-lg">
-							<Iconify icon="carbon:information-filled" size={24} color={themeVars.colors.palette.info.default} />
-						</div>
-					),
-					loading: (
-						<div className="p-2 bg-gray-400/10 text-gray-400 rounded-lg">
-							<Iconify icon="svg-spinners:6-dots-scale-middle" size={24} speed={3} />
-						</div>
-					)
-				}}
-				expand
-			/>
-		</ToasterStyleWrapper>
-	);
+  return (
+    <ToasterStyleWrapper>
+      <Toaster
+        position='top-right'
+        theme={themeMode}
+        toastOptions={{
+          duration: 3000,
+          style: {
+            backgroundColor: themeVars.colors.background.paper
+          },
+          classNames: {
+            toast: 'rounded-lg border-0',
+            description: 'text-xs text-current/45',
+            content: 'flex-1 ml-2',
+            icon: 'flex items-center justify-center px-4 rounded-lg',
+            success: 'bg-success/10',
+            error: 'bg-error/10',
+            warning: 'bg-warning/10',
+            info: 'bg-info/10'
+          }
+        }}
+        icons={{
+          success: (
+            <div className='p-2 bg-success/10 rounded-lg'>
+              <Iconify
+                icon='carbon:checkmark-filled'
+                size={24}
+                color={themeVars.colors.palette.success.default}
+              />
+            </div>
+          ),
+          error: (
+            <div className='p-2 bg-error/10 rounded-lg'>
+              <Iconify
+                icon='carbon:warning-hex-filled'
+                size={24}
+                color={themeVars.colors.palette.error.default}
+              />
+            </div>
+          ),
+          warning: (
+            <div className='p-2 bg-warning/10 rounded-lg'>
+              <Iconify
+                icon='carbon:warning-alt-filled'
+                size={24}
+                color={themeVars.colors.palette.warning.default}
+              />
+            </div>
+          ),
+          info: (
+            <div className='p-2 bg-info/10 rounded-lg'>
+              <Iconify
+                icon='carbon:information-filled'
+                size={24}
+                color={themeVars.colors.palette.info.default}
+              />
+            </div>
+          ),
+          loading: (
+            <div className='p-2 bg-gray-400/10 text-gray-400 rounded-lg'>
+              <Iconify icon='svg-spinners:6-dots-scale-middle' size={24} speed={3} />
+            </div>
+          )
+        }}
+        expand
+      />
+    </ToasterStyleWrapper>
+  )
 }
 
 const ToasterStyleWrapper = styled.div`
@@ -89,7 +104,7 @@ const ToasterStyleWrapper = styled.div`
     }
 
     /* Info */
-    &[data-type="info"] [data-action] {
+    &[data-type='info'] [data-action] {
       color: ${themeVars.colors.palette.info.default};
       background-color: transparent;
       &:hover {
@@ -98,7 +113,7 @@ const ToasterStyleWrapper = styled.div`
     }
 
     /* Error */
-    &[data-type="error"] [data-action] {
+    &[data-type='error'] [data-action] {
       color: ${themeVars.colors.palette.error.default};
       background-color: transparent;
       &:hover {
@@ -107,7 +122,7 @@ const ToasterStyleWrapper = styled.div`
     }
 
     /* Success */
-    &[data-type="success"] [data-action] {
+    &[data-type='success'] [data-action] {
       color: ${themeVars.colors.palette.success.default};
       background-color: transparent;
       &:hover {
@@ -116,7 +131,7 @@ const ToasterStyleWrapper = styled.div`
     }
 
     /* Warning */
-    &[data-type="warning"] [data-action] {
+    &[data-type='warning'] [data-action] {
       color: ${themeVars.colors.palette.warning.default};
       background-color: transparent;
       &:hover {
@@ -135,4 +150,4 @@ const ToasterStyleWrapper = styled.div`
       border-color: ${themeVars.colors.common.border};
     }
   }
-`;
+`

+ 3 - 3
src/components/upload/index.ts

@@ -1,3 +1,3 @@
-export * from "./upload";
-export * from "./upload-avatar";
-export * from "./upload-box";
+export * from './upload'
+export * from './upload-avatar'
+export * from './upload-box'

+ 5 - 5
src/components/upload/styles.ts

@@ -1,14 +1,14 @@
-import styled from "styled-components";
+import styled from 'styled-components'
 
 export const StyledUpload = styled.div<{ $thumbnail?: boolean }>`
   .ant-upload {
     border: none !important;
   }
   .ant-upload-list {
-    display: ${(props) => (props.$thumbnail ? "flex" : "block")};
+    display: ${(props) => (props.$thumbnail ? 'flex' : 'block')};
     flex-wrap: wrap;
   }
-`;
+`
 
 export const StyledUploadAvatar = styled.div`
   transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
@@ -16,7 +16,7 @@ export const StyledUploadAvatar = styled.div`
   .ant-upload-select {
     border: none !important;
   }
-`;
+`
 
 export const StyledUploadBox = styled.div`
   .ant-upload {
@@ -25,4 +25,4 @@ export const StyledUploadBox = styled.div`
   .ant-upload-list {
     display: none;
   }
-`;
+`

+ 69 - 72
src/components/upload/upload-avatar.tsx

@@ -1,83 +1,80 @@
-import { themeVars } from "@/theme/theme.css";
-import { fBytes } from "@/utils/format-number";
-import { Typography, Upload } from "antd";
-import type { UploadChangeParam, UploadFile, UploadProps } from "antd/es/upload";
-import { useState } from "react";
-import { Iconify } from "../icon";
-import { StyledUploadAvatar } from "./styles";
-import { beforeAvatarUpload, getBlobUrl } from "./utils";
+import { useState } from 'react'
+import { fBytes } from '@/utils/format-number'
+import { Typography, Upload } from 'antd'
+import type { UploadChangeParam, UploadFile, UploadProps } from 'antd/es/upload'
+import { themeVars } from '@/theme/theme.css'
+import { Iconify } from '../icon'
+import { StyledUploadAvatar } from './styles'
+import { beforeAvatarUpload, getBlobUrl } from './utils'
 
 interface Props extends UploadProps {
-	defaultAvatar?: string;
-	helperText?: React.ReactElement | string;
+  defaultAvatar?: string
+  helperText?: React.ReactElement | string
 }
-export function UploadAvatar({ helperText, defaultAvatar = "", ...other }: Props) {
-	const [imageUrl, setImageUrl] = useState<string>(defaultAvatar);
+export function UploadAvatar({ helperText, defaultAvatar = '', ...other }: Props) {
+  const [imageUrl, setImageUrl] = useState<string>(defaultAvatar)
 
-	const [isHover, setIsHover] = useState(false);
-	const handelHover = (hover: boolean) => {
-		setIsHover(hover);
-	};
+  const [isHover, setIsHover] = useState(false)
+  const handelHover = (hover: boolean) => {
+    setIsHover(hover)
+  }
 
-	const handleChange: UploadProps["onChange"] = (info: UploadChangeParam<UploadFile>) => {
-		if (info.file.status === "uploading") {
-			return;
-		}
-		if (info.file.status === "done" || info.file.status === "error") {
-			// TODO: Get this url from response in real world.
-			if (info.file.originFileObj) {
-				setImageUrl(getBlobUrl(info.file.originFileObj));
-			}
-		}
-	};
+  const handleChange: UploadProps['onChange'] = (info: UploadChangeParam<UploadFile>) => {
+    if (info.file.status === 'uploading') {
+      return
+    }
+    if (info.file.status === 'done' || info.file.status === 'error') {
+      // TODO: Get this url from response in real world.
+      if (info.file.originFileObj) {
+        setImageUrl(getBlobUrl(info.file.originFileObj))
+      }
+    }
+  }
 
-	const renderPreview = <img src={imageUrl} alt="" className="absolute rounded-full" />;
+  const renderPreview = <img src={imageUrl} alt='' className='absolute rounded-full' />
 
-	const renderPlaceholder = (
-		<div
-			style={{
-				backgroundColor: !imageUrl || isHover ? themeVars.colors.background.neutral : "transparent",
-			}}
-			className="absolute z-10 flex h-full w-full flex-col items-center justify-center"
-		>
-			<Iconify icon="solar:camera-add-bold" size={32} />
-			<div className="mt-1 text-xs">Upload Photo</div>
-		</div>
-	);
+  const renderPlaceholder = (
+    <div
+      style={{
+        backgroundColor: !imageUrl || isHover ? themeVars.colors.background.neutral : 'transparent'
+      }}
+      className='absolute z-10 flex h-full w-full flex-col items-center justify-center'>
+      <Iconify icon='solar:camera-add-bold' size={32} />
+      <div className='mt-1 text-xs'>Upload Photo</div>
+    </div>
+  )
 
-	const renderContent = (
-		<div
-			className="relative flex h-full w-full items-center justify-center overflow-hidden rounded-full"
-			onMouseEnter={() => handelHover(true)}
-			onMouseLeave={() => handelHover(false)}
-		>
-			{imageUrl ? renderPreview : null}
-			{!imageUrl || isHover ? renderPlaceholder : null}
-		</div>
-	);
+  const renderContent = (
+    <div
+      className='relative flex h-full w-full items-center justify-center overflow-hidden rounded-full'
+      onMouseEnter={() => handelHover(true)}
+      onMouseLeave={() => handelHover(false)}>
+      {imageUrl ? renderPreview : null}
+      {!imageUrl || isHover ? renderPlaceholder : null}
+    </div>
+  )
 
-	const defaultHelperText = (
-		<Typography.Text type="secondary" style={{ fontSize: 12 }}>
-			Allowed *.jpeg, *.jpg, *.png, *.gif
-			<br /> max size of {fBytes(3145728)}
-		</Typography.Text>
-	);
-	const renderHelpText = <div className="text-center">{helperText || defaultHelperText}</div>;
+  const defaultHelperText = (
+    <Typography.Text type='secondary' style={{ fontSize: 12 }}>
+      Allowed *.jpeg, *.jpg, *.png, *.gif
+      <br /> max size of {fBytes(3145728)}
+    </Typography.Text>
+  )
+  const renderHelpText = <div className='text-center'>{helperText || defaultHelperText}</div>
 
-	return (
-		<StyledUploadAvatar>
-			<Upload
-				name="avatar"
-				showUploadList={false}
-				listType="picture-circle"
-				className="avatar-uploader !flex items-center justify-center"
-				{...other}
-				beforeUpload={beforeAvatarUpload}
-				onChange={handleChange}
-			>
-				{renderContent}
-			</Upload>
-			{renderHelpText}
-		</StyledUploadAvatar>
-	);
+  return (
+    <StyledUploadAvatar>
+      <Upload
+        name='avatar'
+        showUploadList={false}
+        listType='picture-circle'
+        className='avatar-uploader !flex items-center justify-center'
+        {...other}
+        beforeUpload={beforeAvatarUpload}
+        onChange={handleChange}>
+        {renderContent}
+      </Upload>
+      {renderHelpText}
+    </StyledUploadAvatar>
+  )
 }

+ 19 - 21
src/components/upload/upload-box.tsx

@@ -1,26 +1,24 @@
-import type { UploadProps } from "antd";
-import Dragger from "antd/es/upload/Dragger";
-import type { ReactElement } from "react";
-
-import { Iconify } from "../icon";
-
-import { StyledUploadBox } from "./styles";
+import type { ReactElement } from 'react'
+import type { UploadProps } from 'antd'
+import Dragger from 'antd/es/upload/Dragger'
+import { Iconify } from '../icon'
+import { StyledUploadBox } from './styles'
 
 interface Props extends UploadProps {
-	placeholder?: ReactElement;
+  placeholder?: ReactElement
 }
 export function UploadBox({ placeholder, ...other }: Props) {
-	return (
-		<StyledUploadBox>
-			<Dragger {...other} showUploadList={false}>
-				<div className="opacity-60 hover:opacity-50">
-					{placeholder || (
-						<div className="m-auto flex h-16 w-16 items-center justify-center ">
-							<Iconify icon="eva:cloud-upload-fill" size={28} />
-						</div>
-					)}
-				</div>
-			</Dragger>
-		</StyledUploadBox>
-	);
+  return (
+    <StyledUploadBox>
+      <Dragger {...other} showUploadList={false}>
+        <div className='opacity-60 hover:opacity-50'>
+          {placeholder || (
+            <div className='m-auto flex h-16 w-16 items-center justify-center '>
+              <Iconify icon='eva:cloud-upload-fill' size={28} />
+            </div>
+          )}
+        </div>
+      </Dragger>
+    </StyledUploadBox>
+  )
 }

+ 531 - 401
src/components/upload/upload-illustration.tsx

@@ -1,404 +1,534 @@
-import { useTheme } from "@/theme/hooks";
+import { useTheme } from '@/theme/hooks'
 
 export default function UploadIllustration() {
-	const {
-		themeVars: {
-			colors: {
-				palette: {
-					primary: { default: PRIMARY_MAIN, dark: PRIMARY_DARK, darker: PRIMARY_DARKER },
-				},
-			},
-		},
-	} = useTheme();
-	return (
-		<svg
-			className="MuiBox-root css-olkjfu"
-			viewBox="0 0 480 360"
-			xmlns="http://www.w3.org/2000/svg"
-			aria-label="Upload Illustration"
-		>
-			<title>Upload Illustration</title>
-			<defs>
-				<linearGradient id="BG" x1="19.496%" x2="77.479%" y1="71.822%" y2="16.69%">
-					<stop offset="0%" stopColor={PRIMARY_MAIN} />
-					<stop offset="100%" stopColor={PRIMARY_MAIN} stopOpacity="0" />
-				</linearGradient>
-			</defs>
-			<path
-				fill="url(#BG)"
-				fillRule="nonzero"
-				d="M0 198.78c0 41.458 14.945 79.236 39.539 107.786 28.214 32.765 69.128 53.365 114.734 53.434a148.44 148.44 0 0056.495-11.036c9.051-3.699 19.182-3.274 27.948 1.107a75.779 75.779 0 0033.957 8.01c5.023 0 9.942-.494 14.7-1.433 13.58-2.67 25.94-8.99 36.09-17.94 6.378-5.627 14.547-8.456 22.897-8.446h.142c27.589 0 53.215-8.732 74.492-23.696 19.021-13.36 34.554-31.696 44.904-53.224C474.92 234.58 480 213.388 480 190.958c0-76.93-59.774-139.305-133.498-139.305-7.516 0-14.88.663-22.063 1.899C305.418 21.42 271.355 0 232.499 0a103.651 103.651 0 00-45.88 10.661c-13.24 6.487-25.011 15.705-34.64 26.939-32.698.544-62.931 11.69-87.676 30.291C25.351 97.155 0 144.882 0 198.781z"
-				opacity="0.2"
-			/>
-			<defs>
-				<linearGradient id="linearGradient-2" x1="30.113%" x2="30.113%" y1="0%" y2="100%">
-					<stop offset="0%" stopOpacity="0" />
-					<stop offset="100%" />
-				</linearGradient>
-			</defs>
-			<path
-				fill="#FF4842"
-				fillRule="nonzero"
-				d="M293.006 206.192c-2.248 2.672-4.676 2.628-6.123 2.251l.054-1.384s5.979-2.733 5.827-2.159c-.057.215.057.733.242 1.292zM309.393 209.217c-3.818 2.206-6.058-.38-6.578-1.112.32-.84.801-2.204.703-2.592-.144-.577 5.827 2.155 5.827 2.155l.048 1.549zM305.303 187.204s3.632-2.093 4.425-1.151c.792.942-4.425 1.151-4.425 1.151zM307.497 188.355s-.598.299.721.681c1.318.383-.721-.681-.721-.681zM285.532 182.599c-.123.036 3.022 2.123 5.862.395-.012 0-5.154-.61-5.862-.395zM291.239 186.591s-.861 1.692-2.625 1.943c-1.764.251 2.625-1.943 2.625-1.943zM302.785 190.262s4.138 6.578 3.346 8.129c-.793 1.552-3.346-8.129-3.346-8.129zM294.62 216.416c0 .368 0 .712-.036.996-.107 1.33-3.381.828-3.381.828-2.99.416-4.066-1.019-4.422-2.392a5.444 5.444 0 01-.164-1.363 5.606 5.606 0 01.054-.819v-.272a1.998 1.998 0 011.859.379c1.331 1.08 3.095.252 3.095.252s1.294.107 1.793.538c.281.242.745-.341 1.098-.897.021.359.054 1.007.074 1.701.024.356.03.714.03 1.049z"
-				opacity="0.05"
-			/>
-			<path
-				fill="#FF4842"
-				fillRule="nonzero"
-				d="M289.37 216.339s-.897.879-2.589-.479a5.444 5.444 0 01-.164-1.363c.864.658 2.466 1.842 2.753 1.842zM294.62 216.416a.67.67 0 01-.519.132.672.672 0 01-.452-.287c.348-.266.669-.566.956-.897.009.359.015.717.015 1.052zM301.637 217.032c0 .371 0 .715.033.996.108 1.333 3.382.828 3.382.828 2.989.419 4.066-1.017 4.421-2.392a5.728 5.728 0 00.111-2.182v-.272a2.024 2.024 0 00-1.862.379c-1.331 1.08-3.092.251-3.092.251s-1.294.111-1.794.542c-.281.242-.744-.341-1.094-.897a61.426 61.426 0 00-.078 1.701c-.024.35-.03.708-.027 1.046z"
-				opacity="0.05"
-			/>
-			<path
-				fill="#FF4842"
-				fillRule="nonzero"
-				d="M306.887 216.949s.897.879 2.587-.479c.112-.444.169-.901.167-1.36-.867.655-2.47 1.839-2.754 1.839zM301.637 217.032a.67.67 0 00.969-.155 6.35 6.35 0 01-.957-.897c-.009.356-.014.714-.012 1.052zM319.614 106.269c.063-.138.123-.275.18-.41.057-.134.077-.209.11-.317-.11.237-.206.48-.29.727z"
-				opacity="0.05"
-			/>
-			<path
-				fill={PRIMARY_DARKER}
-				fillRule="nonzero"
-				d="M157.592 279.461a4.114 4.114 0 01-.917-3.131l13.196-107.979 1.094-8.97a4.142 4.142 0 014.078-3.635l38.654-.257a4.135 4.135 0 004.108-4.249l-.015-.409a4.14 4.14 0 014.135-4.279h40.619a4.134 4.134 0 014.098 4.682 4.137 4.137 0 003.995 4.682l39.01.996a4.13 4.13 0 013.97 4.831l-19.502 113.909-.368 2.152a4.042 4.042 0 01-.598 1.543l-135.557.114z"
-			/>
-			<path
-				fill="#FFF"
-				fillRule="nonzero"
-				d="M251.256817 123.296578L274.098317 123.296578 274.098317 200.823078 251.256817 200.823078z"
-				transform="rotate(34.64 262.678 162.06)"
-			/>
-			<path
-				fill="#FFC107"
-				fillRule="nonzero"
-				d="M270.624591 129.857671L286.750291 129.857671 286.750291 145.983371 270.624591 145.983371z"
-				transform="rotate(34.804 278.687 137.92)"
-			/>
-			<path
-				fill="#FFC107"
-				fillRule="nonzero"
-				d="M260.275579 145.813111L276.423079 145.813111 276.423079 161.960611 260.275579 161.960611z"
-				opacity="0.5"
-				transform="rotate(34.64 268.35 153.887)"
-			/>
-			<path
-				fill="#FFC107"
-				fillRule="nonzero"
-				d="M249.191579 161.852111L265.339079 161.852111 265.339079 177.999611 249.191579 177.999611z"
-				opacity="0.3"
-				transform="rotate(34.64 257.265 169.926)"
-			/>
-			<path
-				fill="#FFF"
-				fillRule="nonzero"
-				d="M237.472537 121.334214L260.314037 121.334214 260.314037 198.567714 237.472537 198.567714z"
-				transform="rotate(16.29 248.893 159.951)"
-			/>
-			<path
-				fill="#FF4842"
-				fillRule="nonzero"
-				d="M249.053192 123.761554L265.200692 123.761554 265.200692 139.909054 249.053192 139.909054z"
-				transform="rotate(16.29 257.127 131.835)"
-			/>
-			<path
-				fill="#FF4842"
-				fillRule="nonzero"
-				d="M243.584192 142.473554L259.731692 142.473554 259.731692 158.621053 243.584192 158.621053z"
-				opacity="0.5"
-				transform="rotate(16.29 251.658 150.547)"
-			/>
-			<path
-				fill="#FF4842"
-				fillRule="nonzero"
-				d="M238.116192 161.182554L254.263692 161.182554 254.263692 177.330054 238.116192 177.330054z"
-				opacity="0.3"
-				transform="rotate(16.29 246.19 169.256)"
-			/>
-			<path
-				fill="#FFF"
-				fillRule="nonzero"
-				d="M230.099192 121.641542L252.940692 121.641542 252.940692 198.875042 230.099192 198.875042z"
-				transform="rotate(4.6 241.52 160.258)"
-			/>
-			<path
-				fill="#1890FF"
-				fillRule="nonzero"
-				d="M235.800489 122.985499L251.947989 122.985499 251.947989 139.132999 235.800489 139.132999z"
-				transform="rotate(4.6 243.874 131.06)"
-			/>
-			<path
-				fill="#1890FF"
-				fillRule="nonzero"
-				d="M234.234488 142.413498L250.381988 142.413498 250.381988 158.560998 234.234488 158.560998z"
-				opacity="0.5"
-				transform="rotate(4.6 242.308 150.487)"
-			/>
-			<path
-				fill="#1890FF"
-				fillRule="nonzero"
-				d="M232.672488 161.846499L248.819988 161.846499 248.819988 177.993999 232.672488 177.993999z"
-				opacity="0.3"
-				transform="rotate(4.6 240.746 169.92)"
-			/>
-			<path
-				fill="#FFF"
-				fillRule="nonzero"
-				d="M224.736657 123.384871L247.578157 123.384871 247.578157 200.618371 224.736657 200.618371z"
-				transform="rotate(-2.61 236.157 162.002)"
-			/>
-			<path
-				fill={PRIMARY_MAIN}
-				fillRule="nonzero"
-				d="M226.751283 124.659968L242.898783 124.659968 242.898783 140.807468 226.751283 140.807468z"
-				transform="rotate(-2.61 234.825 132.734)"
-			/>
-			<path
-				fill={PRIMARY_MAIN}
-				fillRule="nonzero"
-				d="M228.305601 143.479224L244.473301 143.479224 244.473301 159.646924 228.305601 159.646924z"
-				opacity="0.5"
-				transform="rotate(-2.862 236.39 151.563)"
-			/>
-			<path
-				fill={PRIMARY_MAIN}
-				fillRule="nonzero"
-				d="M228.525282 163.608968L244.672782 163.608968 244.672782 179.756468 228.525282 179.756468z"
-				opacity="0.3"
-				transform="rotate(-2.61 236.599 171.683)"
-			/>
-			<path
-				fill="#F4F6F8"
-				fillRule="nonzero"
-				d="M232.679 225.726l-20.294 7.851-29.661 11.466c-1.121-2.093-2.771-4.921-4.813-8.297-7.026-11.642-18.65-29.75-29.001-45.665-11.66-17.938-21.696-33.075-21.696-33.075l7.265-2.093 59.346-17.23 38.854 87.043z"
-			/>
-			<path
-				fill="#000"
-				fillRule="nonzero"
-				d="M232.679 225.726l-20.294 7.851a152.535 152.535 0 01-34.484 3.169c-7.026-11.642-18.65-29.75-29-45.665l-14.42-35.18 59.343-17.218 38.855 87.043z"
-				opacity="0.1"
-			/>
-			<path
-				fill={PRIMARY_MAIN}
-				fillRule="nonzero"
-				d="M247.777 220.841s-31.526 18.65-78.596 14.432l-37.525-91.486 17.984-3.091 62.168-10.677 35.279 89.043.69 1.779z"
-			/>
-			<path
-				fill="#FF4842"
-				fillRule="nonzero"
-				d="M247.075 219.074c-7.424.365-16.013.329-24.654-.702-9.351-1.101-18.757-3.367-26.82-7.523-6.742-3.471-12.506-7.247-17.532-11.776-13.074-11.759-21.164-28.579-28.429-58.365l62.168-10.677 35.267 89.043z"
-				opacity="0.1"
-			/>
-			<path
-				fill="#FFF"
-				fillRule="nonzero"
-				d="M270.203 213.959s-23.239 4.55-46.894 1.749c-9.351-1.1-18.757-3.367-26.82-7.522-6.742-3.471-12.503-7.247-17.532-11.777-16.181-14.557-24.725-36.866-33.536-81.918 0 0 38.633 9.325 76.836-11.101-.012.015 12.865 80.373 47.946 110.569z"
-			/>
-			<path
-				fill={PRIMARY_MAIN}
-				fillRule="nonzero"
-				d="M165.405 131.03s31.416-1.776 40.187-8.548l1.331 4.276s-20.758 9.157-41.518 9.268v-4.996zM165.405 152.137s30.417-1.665 53.399-12.766l.777 1.776s-18.237 10.647-54.176 13.433v-2.443zM167.853 160.362s30.418-1.665 53.397-12.766l.777 1.776s-18.237 10.643-54.174 13.433v-2.443zM170.299 168.599s30.417-1.666 53.399-12.766l.774 1.775s-18.237 10.647-54.173 13.433v-2.442zM172.748 176.835s30.417-1.665 53.396-12.766l.777 1.776s-18.237 10.647-54.173 13.433v-2.443zM175.193 185.075s30.417-1.665 53.399-12.766l.774 1.776s-18.237 10.643-54.173 13.433v-2.443z"
-				opacity="0.3"
-			/>
-			<path
-				fill={PRIMARY_MAIN}
-				fillRule="nonzero"
-				d="M389.069 287.344s-12.641-.762-11.152 8.097c0 0-.299 1.563 1.124 2.275 0 0 .021-.658 1.295-.434.454.077.916.099 1.375.066a2.832 2.832 0 001.677-.694c.468-.409 3.555-1.468 4.936-7.274 0 0 1.023-1.267.981-1.593l-2.132.897s.73 1.54.156 2.816c0 0-.069-2.759-.479-2.691-.083 0-1.109.533-1.109.533s1.253 2.69.299 4.628c0 0 .359-3.304-.699-4.434l-1.495.876s1.465 2.768.472 5.029c0 0 .254-3.465-.789-4.817l-1.361 1.062s1.379 2.729.539 4.604c0 0-.111-4.036-.835-4.341 0 0-1.195 1.049-1.369 1.494 0 0 .942 1.98.356 3.026 0 0-.359-2.691-.652-2.691 0 0-1.196 1.794-1.309 2.99 0 0 .051-1.818 1.022-3.172a3.593 3.593 0 00-1.818.942s.186-1.262 2.111-1.37c0 0 .981-1.351 1.241-1.435 0 0-1.914-.158-3.074.356 0 0 1.023-1.196 3.427-.649l1.342-1.094s-2.52-.347-3.588.036c0 0 1.229-1.052 3.95-.299l1.462-.873s-2.147-.463-3.426-.299c0 0 1.348-.729 3.856.06l1.044-.47s-1.573-.299-2.033-.358c-.461-.06-.488-.174-.488-.174a5.426 5.426 0 012.957.329s2.222-.813 2.186-.954z"
-			/>
-			<ellipse cx="380.363" cy="298.487" fill={PRIMARY_MAIN} fillRule="nonzero" opacity="0.1" rx="8.945" ry="1.513" />
-			<path
-				fill={PRIMARY_MAIN}
-				fillRule="nonzero"
-				d="M394.956 258.102s-7.125-.43-6.279 4.565a1.218 1.218 0 00.631 1.283s.015-.371.733-.245c.255.042.515.054.774.036.349-.023.681-.159.945-.389.264-.23 2.003-.828 2.783-4.102 0 0 .577-.714.553-.897l-1.196.511s.41.87.087 1.591c0 0-.039-1.558-.269-1.522-.048 0-.625.299-.625.299s.706 1.495.173 2.61c0 0 .204-1.862-.394-2.502l-.846.496s.825 1.561.266 2.834c0 0 .143-1.955-.446-2.714l-.765.598s.774 1.539.299 2.595c0 0-.063-2.275-.469-2.446a4.75 4.75 0 00-.775.834s.532 1.118.204 1.707c0 0-.204-1.515-.368-1.521 0 0-.67 1.001-.739 1.689a3.671 3.671 0 01.577-1.794 2.007 2.007 0 00-1.025.532s.104-.711 1.196-.771c0 0 .553-.763.699-.81 0 0-1.079-.09-1.734.2 0 0 .577-.67 1.932-.365l.759-.619s-1.423-.194-2.024.021c0 0 .694-.598 2.227-.161l.826-.494a7.445 7.445 0 00-1.935-.164s.763-.413 2.174.033l.598-.263s-.897-.177-1.148-.203c-.252-.027-.275-.099-.275-.099a3.078 3.078 0 011.668.185s1.232-.46 1.208-.538z"
-			/>
-			<ellipse cx="390.052" cy="264.383" fill={PRIMARY_MAIN} fillRule="nonzero" opacity="0.1" rx="5.044" ry="1" />
-			<path
-				fill={PRIMARY_MAIN}
-				fillRule="nonzero"
-				d="M352.896 262.437s-8.604-.52-7.588 5.51a1.47 1.47 0 00.763 1.549s0-.449.897-.299c.309.052.623.067.935.045a1.923 1.923 0 001.142-.473c.318-.279 2.419-.998 3.361-4.951 0 0 .697-.861.667-1.085l-1.468.622s.496 1.046.104 1.916c0 0-.048-1.877-.326-1.835-.056 0-.753.364-.753.364s.852 1.824.209 3.152c0 0 .245-2.249-.475-3.02l-1.023.598s.996 1.886.32 3.423c0 0 .174-2.359-.535-3.289l-.927.724s.939 1.857.365 3.133c0 0-.072-2.747-.565-2.954 0 0-.81.715-.933 1.008 0 0 .64 1.348.242 2.06 0 0-.245-1.83-.445-1.839 0 0-.804 1.214-.897 2.042.04-.768.28-1.512.697-2.159a2.427 2.427 0 00-1.235.643s.125-.858 1.435-.933c0 0 .667-.92.846-.974 0 0-1.303-.111-2.093.239 0 0 .694-.807 2.329-.44l.915-.747s-1.713-.233-2.442.024c0 0 .837-.715 2.69-.191l.996-.598s-1.462-.314-2.335-.201c0 0 .921-.496 2.625.042l.711-.32s-1.07-.209-1.384-.242c-.314-.033-.329-.119-.329-.119a3.69 3.69 0 012.012.221s1.519-.553 1.492-.646z"
-			/>
-			<ellipse cx="346.97" cy="270.022" fill={PRIMARY_MAIN} fillRule="nonzero" opacity="0.1" rx="6.09" ry="1.028" />
-			<rect
-				width="51.026"
-				height="91.312"
-				x="303.926"
-				y="69.211"
-				fill="#FFF"
-				fillRule="nonzero"
-				rx="4.737"
-				transform="rotate(-71.99 329.44 114.867)"
-			/>
-			<circle cx="303.613" cy="103.507" r="9.376" fill={PRIMARY_MAIN} fillRule="nonzero" opacity="0.2" />
-			<path
-				fill={PRIMARY_MAIN}
-				fillRule="nonzero"
-				d="M344.582495 86.4883769L347.150665 86.4883769 347.150665 127.336977 344.582495 127.336977z"
-				opacity="0.2"
-				transform="rotate(-71.99 345.867 106.913)"
-			/>
-			<path
-				fill={PRIMARY_MAIN}
-				fillRule="nonzero"
-				d="M333.028401 99.6373982L335.596571 99.6373982 335.596571 120.445898 333.028401 120.445898z"
-				opacity="0.2"
-				transform="rotate(-71.99 334.312 110.042)"
-			/>
-			<path
-				fill={PRIMARY_MAIN}
-				fillRule="nonzero"
-				d="M337.109071 101.431004L339.677241 101.431004 339.677241 135.086304 337.109071 135.086304z"
-				opacity="0.2"
-				transform="rotate(-71.99 338.393 118.259)"
-			/>
-			<path
-				fill={PRIMARY_MAIN}
-				fillRule="nonzero"
-				d="M338.505494 105.174377L341.073664 105.174377 341.073664 146.022978 338.505494 146.022978z"
-				opacity="0.2"
-				transform="rotate(-71.99 339.79 125.599)"
-			/>
-			<path
-				fill={PRIMARY_MAIN}
-				fillRule="nonzero"
-				d="M348.060839 134.648161L353.026769 134.648161 353.026769 145.952261 348.060839 145.952261z"
-				opacity="0.5"
-				transform="rotate(-71.99 350.544 140.3)"
-			/>
-			<rect
-				width="50.3"
-				height="78.305"
-				x="291.549"
-				y="174.486"
-				fill="#FFF"
-				fillRule="nonzero"
-				rx="4"
-				transform="rotate(-57.265 316.7 213.638)"
-			/>
-			<path
-				fill="#DFE3E8"
-				fillRule="nonzero"
-				d="M278.038439 203.88874L292.269539 203.88874 292.269539 207.23723 278.038439 207.23723z"
-				transform="rotate(-57.57 285.154 205.563)"
-			/>
-			<path
-				fill="#DFE3E8"
-				fillRule="nonzero"
-				d="M283.007935 199.201889L314.262535 199.201889 314.262535 202.272339 283.007935 202.272339z"
-				transform="rotate(-57.57 298.635 200.737)"
-			/>
-			<path
-				fill="#DFE3E8"
-				fillRule="nonzero"
-				d="M288.543935 202.715889L319.798535 202.715889 319.798535 205.786339 288.543935 205.786339z"
-				transform="rotate(-57.57 304.171 204.251)"
-			/>
-			<path
-				fill="#DFE3E8"
-				fillRule="nonzero"
-				d="M294.078936 206.235889L325.333536 206.235889 325.333536 209.306339 294.078936 209.306339z"
-				transform="rotate(-57.57 309.706 207.771)"
-			/>
-			<path
-				fill="#DFE3E8"
-				fillRule="nonzero"
-				d="M299.879919 211.40702L331.090719 211.40702 331.090719 214.47316 299.879919 214.47316z"
-				transform="rotate(-57.265 315.485 212.94)"
-			/>
-			<path
-				fill="#F4F6F8"
-				fillRule="nonzero"
-				d="M305.149936 213.268889L336.404536 213.268889 336.404536 216.339339 305.149936 216.339339z"
-				transform="rotate(-57.57 320.777 214.804)"
-			/>
-			<path
-				fill="#F4F6F8"
-				fillRule="nonzero"
-				d="M310.685935 216.782889L341.940535 216.782889 341.940535 219.853339 310.685935 219.853339z"
-				transform="rotate(-57.57 326.313 218.318)"
-			/>
-			<path
-				fill="#DFE3E8"
-				fillRule="nonzero"
-				d="M341.417983 210.406958L349.511163 210.406958 349.511163 218.500138 341.417983 218.500138z"
-				transform="rotate(-57.57 345.465 214.454)"
-			/>
-			<path
-				fill={PRIMARY_MAIN}
-				fillRule="nonzero"
-				d="M269.277 168.76l-45.767.493a3.127 3.127 0 00-3.094 3.125v3.782a3.127 3.127 0 01-3.05 3.124l-33.024.792a3.124 3.124 0 01-3.095-2.308l-1.551-5.701a3.124 3.124 0 00-3.053-2.308l-44.113.475a3.125 3.125 0 00-3.05 3.648l17.747 104.449a3.129 3.129 0 003.071 2.604l139.363.598a3.129 3.129 0 003.082-3.714l-20.366-106.521a3.125 3.125 0 00-3.1-2.538z"
-			/>
-			<path
-				fill={PRIMARY_DARK}
-				fillRule="nonzero"
-				d="M269.277 168.76l-45.767.493a3.127 3.127 0 00-3.094 3.125v3.782a3.127 3.127 0 01-3.05 3.124l-33.024.792a3.124 3.124 0 01-3.095-2.308l-1.551-5.701a3.124 3.124 0 00-3.053-2.308l-44.113.475a3.125 3.125 0 00-3.05 3.648l17.747 104.449a3.129 3.129 0 003.071 2.604l139.363.598a3.129 3.129 0 003.082-3.714l-20.366-106.521a3.125 3.125 0 00-3.1-2.538z"
-				opacity="0.243"
-			/>
-			<path
-				fill="url(#linearGradient-2)"
-				fillRule="nonzero"
-				d="M269.277 168.76l-45.767.493a3.127 3.127 0 00-3.094 3.125v3.782a3.127 3.127 0 01-3.05 3.124l-33.024.792a3.124 3.124 0 01-3.095-2.308l-1.551-5.701a3.124 3.124 0 00-3.053-2.308l-44.113.475a3.125 3.125 0 00-3.05 3.648l17.747 104.449a3.129 3.129 0 003.071 2.604l139.363.598a3.129 3.129 0 003.082-3.714l-20.366-106.521a3.125 3.125 0 00-3.1-2.538z"
-				opacity="0.32"
-			/>
-			<ellipse cx="119.593" cy="258.664" fill={PRIMARY_MAIN} fillRule="nonzero" opacity="0.1" rx="4.846" ry="1" />
-			<ellipse cx="101.03" cy="260.545" fill={PRIMARY_MAIN} fillRule="nonzero" opacity="0.1" rx="4.846" ry="1" />
-			<ellipse cx="108.459" cy="265.905" fill={PRIMARY_MAIN} fillRule="nonzero" opacity="0.1" rx="3.444" ry="1" />
-			<ellipse cx="89.193" cy="265.433" fill={PRIMARY_MAIN} fillRule="nonzero" opacity="0.1" rx="3.444" ry="1" />
-			<path
-				fill={PRIMARY_MAIN}
-				fillRule="nonzero"
-				d="M100.925 260.398s6.796-22.279-1.064-31.204c-5.881-6.676-12.557-5.877-15.547-5.052a5.528 5.528 0 00-3.564 2.963c-1.046 2.254-.858 5.913 6.521 10.186 12.35 7.151 13.119 16.96 13.119 16.96l.535 6.147z"
-			/>
-			<path
-				fill={PRIMARY_MAIN}
-				fillRule="nonzero"
-				d="M101.2 260.575s-7.961-16.193-10.147-15.846c-1.037.17-1.516 1.196-1.734 2.218a6.516 6.516 0 00.434 3.941c1.13 2.601 4.165 7.519 11.447 9.687z"
-			/>
-			<path
-				stroke={PRIMARY_DARKER}
-				strokeLinecap="round"
-				strokeWidth="0.5"
-				d="M91.113 247.808s8.353 12.115 9.968 12.647"
-			/>
-			<path stroke={PRIMARY_DARKER} strokeLinecap="round" strokeWidth="0.5" d="M90.344 249.701L92.293 249.701" />
-			<path stroke={PRIMARY_DARKER} strokeLinecap="round" strokeWidth="0.5" d="M92.68 254.132L95.738 254.263" />
-			<path stroke={PRIMARY_DARKER} strokeLinecap="round" strokeWidth="0.5" d="M94.542 250.586L94.21 252.179" />
-			<path stroke={PRIMARY_DARKER} strokeLinecap="round" strokeWidth="0.5" d="M97.792 255.432L97.732 256.879" />
-			<path
-				fill={PRIMARY_MAIN}
-				fillRule="nonzero"
-				d="M100.964 260.575s7.959-16.193 10.144-15.846c1.041.17 1.519 1.196 1.734 2.218a6.514 6.514 0 01-.433 3.941c-1.13 2.601-4.168 7.519-11.445 9.687z"
-			/>
-			<path
-				stroke={PRIMARY_DARKER}
-				strokeLinecap="round"
-				strokeWidth="0.5"
-				d="M111.051 247.808s-8.371 12.115-9.97 12.647"
-			/>
-			<path stroke={PRIMARY_DARKER} strokeLinecap="round" strokeWidth="0.5" d="M111.82 249.701L109.871 249.701" />
-			<path stroke={PRIMARY_DARKER} strokeLinecap="round" strokeWidth="0.5" d="M109.485 254.132L106.426 254.263" />
-			<path stroke={PRIMARY_DARKER} strokeLinecap="round" strokeWidth="0.5" d="M107.622 250.586L107.954 252.179" />
-			<path stroke={PRIMARY_DARKER} strokeLinecap="round" strokeWidth="0.5" d="M104.372 255.432L104.432 256.879" />
-			<path
-				fill={PRIMARY_MAIN}
-				fillRule="nonzero"
-				d="M87.295 264.357a1.375 1.375 0 01-.452-.685.599.599 0 01.317-.697c.3-.11.598.09.837.299.24.21.512.431.81.38a1.237 1.237 0 01-.382-1.16.47.47 0 01.105-.236c.161-.174.454-.099.648.039.598.43.787 1.264.79 2.018.03-.277.03-.555 0-.831a.853.853 0 01.334-.727.938.938 0 01.476-.114.988.988 0 01.774.222.767.767 0 01-.03.947c-.229.257-.5.474-.801.64a1.805 1.805 0 00-.571.547.399.399 0 00-.042.096h-1.74a4.826 4.826 0 01-1.073-.738zM117.626 257.833a1.355 1.355 0 01-.452-.682.599.599 0 01.314-.696c.299-.111.598.089.837.299.239.209.509.436.817.391a1.236 1.236 0 01-.386-1.157.472.472 0 01.107-.236c.162-.173.455-.099.649.036.613.433.787 1.268.79 2.021a4.008 4.008 0 000-.834.852.852 0 01.299-.736.955.955 0 01.475-.11.998.998 0 01.774.218.768.768 0 01-.033.951 2.996 2.996 0 01-.798.64 1.767 1.767 0 00-.571.544.499.499 0 00-.042.098h-1.701a4.78 4.78 0 01-1.079-.747zM107.518 264.357a1.406 1.406 0 01-.455-.685.6.6 0 01.317-.697c.299-.11.598.09.837.299.239.21.505.437.816.395a1.247 1.247 0 01-.385-1.16.472.472 0 01.107-.236c.162-.174.455-.099.649.039.613.43.783 1.264.789 2.018.03-.277.03-.555 0-.832a.855.855 0 01.314-.735.93.93 0 01.476-.114.988.988 0 01.774.222.764.764 0 01-.033.947 3.028 3.028 0 01-.798.64 1.798 1.798 0 00-.571.547.411.411 0 00-.042.096h-1.734c-.385-.2-.742-.45-1.061-.744z"
-			/>
-			<circle cx="84.467" cy="87.003" r="6.467" fill={PRIMARY_MAIN} fillRule="nonzero" opacity="0.1" />
-			<circle cx="395.425" cy="138.681" r="6.467" fill={PRIMARY_MAIN} fillRule="nonzero" opacity="0.1" />
-			<circle cx="279.178" cy="66.467" r="6.467" fill={PRIMARY_MAIN} fillRule="nonzero" opacity="0.1" />
-			<circle cx="97.4" cy="122.68" r="10.838" fill={PRIMARY_MAIN} fillRule="nonzero" opacity="0.1" />
-			<path
-				fill={PRIMARY_DARK}
-				fillRule="nonzero"
-				d="M206.029 209.911c-7.975 0-14.44 6.465-14.44 14.44s6.465 14.44 14.44 14.44 14.44-6.465 14.44-14.44-6.465-14.44-14.44-14.44z"
-			/>
-			<path
-				fill={PRIMARY_MAIN}
-				fillRule="nonzero"
-				d="M206.029 211.59c7.047 0 12.761 5.714 12.761 12.761 0 7.048-5.714 12.761-12.761 12.761-7.048 0-12.761-5.713-12.761-12.761.006-7.045 5.716-12.754 12.761-12.761"
-				opacity="0.72"
-			/>
-			<path
-				fill={PRIMARY_DARK}
-				fillRule="nonzero"
-				d="M200.058 225.406l-.699-.681a.721.721 0 010-1.065l6.127-5.984a.753.753 0 01.546-.231c.206 0 .404.083.545.231l6.127 5.978a.733.733 0 010 1.065l-.699.682a.768.768 0 01-1.091 0l-3.622-3.727v8.843a.725.725 0 01-.219.523.76.76 0 01-.534.217h-1.009a.75.75 0 01-.759-.74v-8.832l-3.622 3.726a.768.768 0 01-1.091-.005z"
-			/>
-		</svg>
-	);
+  const {
+    themeVars: {
+      colors: {
+        palette: {
+          primary: { default: PRIMARY_MAIN, dark: PRIMARY_DARK, darker: PRIMARY_DARKER }
+        }
+      }
+    }
+  } = useTheme()
+  return (
+    <svg
+      className='MuiBox-root css-olkjfu'
+      viewBox='0 0 480 360'
+      xmlns='http://www.w3.org/2000/svg'
+      aria-label='Upload Illustration'>
+      <title>Upload Illustration</title>
+      <defs>
+        <linearGradient id='BG' x1='19.496%' x2='77.479%' y1='71.822%' y2='16.69%'>
+          <stop offset='0%' stopColor={PRIMARY_MAIN} />
+          <stop offset='100%' stopColor={PRIMARY_MAIN} stopOpacity='0' />
+        </linearGradient>
+      </defs>
+      <path
+        fill='url(#BG)'
+        fillRule='nonzero'
+        d='M0 198.78c0 41.458 14.945 79.236 39.539 107.786 28.214 32.765 69.128 53.365 114.734 53.434a148.44 148.44 0 0056.495-11.036c9.051-3.699 19.182-3.274 27.948 1.107a75.779 75.779 0 0033.957 8.01c5.023 0 9.942-.494 14.7-1.433 13.58-2.67 25.94-8.99 36.09-17.94 6.378-5.627 14.547-8.456 22.897-8.446h.142c27.589 0 53.215-8.732 74.492-23.696 19.021-13.36 34.554-31.696 44.904-53.224C474.92 234.58 480 213.388 480 190.958c0-76.93-59.774-139.305-133.498-139.305-7.516 0-14.88.663-22.063 1.899C305.418 21.42 271.355 0 232.499 0a103.651 103.651 0 00-45.88 10.661c-13.24 6.487-25.011 15.705-34.64 26.939-32.698.544-62.931 11.69-87.676 30.291C25.351 97.155 0 144.882 0 198.781z'
+        opacity='0.2'
+      />
+      <defs>
+        <linearGradient id='linearGradient-2' x1='30.113%' x2='30.113%' y1='0%' y2='100%'>
+          <stop offset='0%' stopOpacity='0' />
+          <stop offset='100%' />
+        </linearGradient>
+      </defs>
+      <path
+        fill='#FF4842'
+        fillRule='nonzero'
+        d='M293.006 206.192c-2.248 2.672-4.676 2.628-6.123 2.251l.054-1.384s5.979-2.733 5.827-2.159c-.057.215.057.733.242 1.292zM309.393 209.217c-3.818 2.206-6.058-.38-6.578-1.112.32-.84.801-2.204.703-2.592-.144-.577 5.827 2.155 5.827 2.155l.048 1.549zM305.303 187.204s3.632-2.093 4.425-1.151c.792.942-4.425 1.151-4.425 1.151zM307.497 188.355s-.598.299.721.681c1.318.383-.721-.681-.721-.681zM285.532 182.599c-.123.036 3.022 2.123 5.862.395-.012 0-5.154-.61-5.862-.395zM291.239 186.591s-.861 1.692-2.625 1.943c-1.764.251 2.625-1.943 2.625-1.943zM302.785 190.262s4.138 6.578 3.346 8.129c-.793 1.552-3.346-8.129-3.346-8.129zM294.62 216.416c0 .368 0 .712-.036.996-.107 1.33-3.381.828-3.381.828-2.99.416-4.066-1.019-4.422-2.392a5.444 5.444 0 01-.164-1.363 5.606 5.606 0 01.054-.819v-.272a1.998 1.998 0 011.859.379c1.331 1.08 3.095.252 3.095.252s1.294.107 1.793.538c.281.242.745-.341 1.098-.897.021.359.054 1.007.074 1.701.024.356.03.714.03 1.049z'
+        opacity='0.05'
+      />
+      <path
+        fill='#FF4842'
+        fillRule='nonzero'
+        d='M289.37 216.339s-.897.879-2.589-.479a5.444 5.444 0 01-.164-1.363c.864.658 2.466 1.842 2.753 1.842zM294.62 216.416a.67.67 0 01-.519.132.672.672 0 01-.452-.287c.348-.266.669-.566.956-.897.009.359.015.717.015 1.052zM301.637 217.032c0 .371 0 .715.033.996.108 1.333 3.382.828 3.382.828 2.989.419 4.066-1.017 4.421-2.392a5.728 5.728 0 00.111-2.182v-.272a2.024 2.024 0 00-1.862.379c-1.331 1.08-3.092.251-3.092.251s-1.294.111-1.794.542c-.281.242-.744-.341-1.094-.897a61.426 61.426 0 00-.078 1.701c-.024.35-.03.708-.027 1.046z'
+        opacity='0.05'
+      />
+      <path
+        fill='#FF4842'
+        fillRule='nonzero'
+        d='M306.887 216.949s.897.879 2.587-.479c.112-.444.169-.901.167-1.36-.867.655-2.47 1.839-2.754 1.839zM301.637 217.032a.67.67 0 00.969-.155 6.35 6.35 0 01-.957-.897c-.009.356-.014.714-.012 1.052zM319.614 106.269c.063-.138.123-.275.18-.41.057-.134.077-.209.11-.317-.11.237-.206.48-.29.727z'
+        opacity='0.05'
+      />
+      <path
+        fill={PRIMARY_DARKER}
+        fillRule='nonzero'
+        d='M157.592 279.461a4.114 4.114 0 01-.917-3.131l13.196-107.979 1.094-8.97a4.142 4.142 0 014.078-3.635l38.654-.257a4.135 4.135 0 004.108-4.249l-.015-.409a4.14 4.14 0 014.135-4.279h40.619a4.134 4.134 0 014.098 4.682 4.137 4.137 0 003.995 4.682l39.01.996a4.13 4.13 0 013.97 4.831l-19.502 113.909-.368 2.152a4.042 4.042 0 01-.598 1.543l-135.557.114z'
+      />
+      <path
+        fill='#FFF'
+        fillRule='nonzero'
+        d='M251.256817 123.296578L274.098317 123.296578 274.098317 200.823078 251.256817 200.823078z'
+        transform='rotate(34.64 262.678 162.06)'
+      />
+      <path
+        fill='#FFC107'
+        fillRule='nonzero'
+        d='M270.624591 129.857671L286.750291 129.857671 286.750291 145.983371 270.624591 145.983371z'
+        transform='rotate(34.804 278.687 137.92)'
+      />
+      <path
+        fill='#FFC107'
+        fillRule='nonzero'
+        d='M260.275579 145.813111L276.423079 145.813111 276.423079 161.960611 260.275579 161.960611z'
+        opacity='0.5'
+        transform='rotate(34.64 268.35 153.887)'
+      />
+      <path
+        fill='#FFC107'
+        fillRule='nonzero'
+        d='M249.191579 161.852111L265.339079 161.852111 265.339079 177.999611 249.191579 177.999611z'
+        opacity='0.3'
+        transform='rotate(34.64 257.265 169.926)'
+      />
+      <path
+        fill='#FFF'
+        fillRule='nonzero'
+        d='M237.472537 121.334214L260.314037 121.334214 260.314037 198.567714 237.472537 198.567714z'
+        transform='rotate(16.29 248.893 159.951)'
+      />
+      <path
+        fill='#FF4842'
+        fillRule='nonzero'
+        d='M249.053192 123.761554L265.200692 123.761554 265.200692 139.909054 249.053192 139.909054z'
+        transform='rotate(16.29 257.127 131.835)'
+      />
+      <path
+        fill='#FF4842'
+        fillRule='nonzero'
+        d='M243.584192 142.473554L259.731692 142.473554 259.731692 158.621053 243.584192 158.621053z'
+        opacity='0.5'
+        transform='rotate(16.29 251.658 150.547)'
+      />
+      <path
+        fill='#FF4842'
+        fillRule='nonzero'
+        d='M238.116192 161.182554L254.263692 161.182554 254.263692 177.330054 238.116192 177.330054z'
+        opacity='0.3'
+        transform='rotate(16.29 246.19 169.256)'
+      />
+      <path
+        fill='#FFF'
+        fillRule='nonzero'
+        d='M230.099192 121.641542L252.940692 121.641542 252.940692 198.875042 230.099192 198.875042z'
+        transform='rotate(4.6 241.52 160.258)'
+      />
+      <path
+        fill='#1890FF'
+        fillRule='nonzero'
+        d='M235.800489 122.985499L251.947989 122.985499 251.947989 139.132999 235.800489 139.132999z'
+        transform='rotate(4.6 243.874 131.06)'
+      />
+      <path
+        fill='#1890FF'
+        fillRule='nonzero'
+        d='M234.234488 142.413498L250.381988 142.413498 250.381988 158.560998 234.234488 158.560998z'
+        opacity='0.5'
+        transform='rotate(4.6 242.308 150.487)'
+      />
+      <path
+        fill='#1890FF'
+        fillRule='nonzero'
+        d='M232.672488 161.846499L248.819988 161.846499 248.819988 177.993999 232.672488 177.993999z'
+        opacity='0.3'
+        transform='rotate(4.6 240.746 169.92)'
+      />
+      <path
+        fill='#FFF'
+        fillRule='nonzero'
+        d='M224.736657 123.384871L247.578157 123.384871 247.578157 200.618371 224.736657 200.618371z'
+        transform='rotate(-2.61 236.157 162.002)'
+      />
+      <path
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        d='M226.751283 124.659968L242.898783 124.659968 242.898783 140.807468 226.751283 140.807468z'
+        transform='rotate(-2.61 234.825 132.734)'
+      />
+      <path
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        d='M228.305601 143.479224L244.473301 143.479224 244.473301 159.646924 228.305601 159.646924z'
+        opacity='0.5'
+        transform='rotate(-2.862 236.39 151.563)'
+      />
+      <path
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        d='M228.525282 163.608968L244.672782 163.608968 244.672782 179.756468 228.525282 179.756468z'
+        opacity='0.3'
+        transform='rotate(-2.61 236.599 171.683)'
+      />
+      <path
+        fill='#F4F6F8'
+        fillRule='nonzero'
+        d='M232.679 225.726l-20.294 7.851-29.661 11.466c-1.121-2.093-2.771-4.921-4.813-8.297-7.026-11.642-18.65-29.75-29.001-45.665-11.66-17.938-21.696-33.075-21.696-33.075l7.265-2.093 59.346-17.23 38.854 87.043z'
+      />
+      <path
+        fill='#000'
+        fillRule='nonzero'
+        d='M232.679 225.726l-20.294 7.851a152.535 152.535 0 01-34.484 3.169c-7.026-11.642-18.65-29.75-29-45.665l-14.42-35.18 59.343-17.218 38.855 87.043z'
+        opacity='0.1'
+      />
+      <path
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        d='M247.777 220.841s-31.526 18.65-78.596 14.432l-37.525-91.486 17.984-3.091 62.168-10.677 35.279 89.043.69 1.779z'
+      />
+      <path
+        fill='#FF4842'
+        fillRule='nonzero'
+        d='M247.075 219.074c-7.424.365-16.013.329-24.654-.702-9.351-1.101-18.757-3.367-26.82-7.523-6.742-3.471-12.506-7.247-17.532-11.776-13.074-11.759-21.164-28.579-28.429-58.365l62.168-10.677 35.267 89.043z'
+        opacity='0.1'
+      />
+      <path
+        fill='#FFF'
+        fillRule='nonzero'
+        d='M270.203 213.959s-23.239 4.55-46.894 1.749c-9.351-1.1-18.757-3.367-26.82-7.522-6.742-3.471-12.503-7.247-17.532-11.777-16.181-14.557-24.725-36.866-33.536-81.918 0 0 38.633 9.325 76.836-11.101-.012.015 12.865 80.373 47.946 110.569z'
+      />
+      <path
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        d='M165.405 131.03s31.416-1.776 40.187-8.548l1.331 4.276s-20.758 9.157-41.518 9.268v-4.996zM165.405 152.137s30.417-1.665 53.399-12.766l.777 1.776s-18.237 10.647-54.176 13.433v-2.443zM167.853 160.362s30.418-1.665 53.397-12.766l.777 1.776s-18.237 10.643-54.174 13.433v-2.443zM170.299 168.599s30.417-1.666 53.399-12.766l.774 1.775s-18.237 10.647-54.173 13.433v-2.442zM172.748 176.835s30.417-1.665 53.396-12.766l.777 1.776s-18.237 10.647-54.173 13.433v-2.443zM175.193 185.075s30.417-1.665 53.399-12.766l.774 1.776s-18.237 10.643-54.173 13.433v-2.443z'
+        opacity='0.3'
+      />
+      <path
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        d='M389.069 287.344s-12.641-.762-11.152 8.097c0 0-.299 1.563 1.124 2.275 0 0 .021-.658 1.295-.434.454.077.916.099 1.375.066a2.832 2.832 0 001.677-.694c.468-.409 3.555-1.468 4.936-7.274 0 0 1.023-1.267.981-1.593l-2.132.897s.73 1.54.156 2.816c0 0-.069-2.759-.479-2.691-.083 0-1.109.533-1.109.533s1.253 2.69.299 4.628c0 0 .359-3.304-.699-4.434l-1.495.876s1.465 2.768.472 5.029c0 0 .254-3.465-.789-4.817l-1.361 1.062s1.379 2.729.539 4.604c0 0-.111-4.036-.835-4.341 0 0-1.195 1.049-1.369 1.494 0 0 .942 1.98.356 3.026 0 0-.359-2.691-.652-2.691 0 0-1.196 1.794-1.309 2.99 0 0 .051-1.818 1.022-3.172a3.593 3.593 0 00-1.818.942s.186-1.262 2.111-1.37c0 0 .981-1.351 1.241-1.435 0 0-1.914-.158-3.074.356 0 0 1.023-1.196 3.427-.649l1.342-1.094s-2.52-.347-3.588.036c0 0 1.229-1.052 3.95-.299l1.462-.873s-2.147-.463-3.426-.299c0 0 1.348-.729 3.856.06l1.044-.47s-1.573-.299-2.033-.358c-.461-.06-.488-.174-.488-.174a5.426 5.426 0 012.957.329s2.222-.813 2.186-.954z'
+      />
+      <ellipse
+        cx='380.363'
+        cy='298.487'
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        opacity='0.1'
+        rx='8.945'
+        ry='1.513'
+      />
+      <path
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        d='M394.956 258.102s-7.125-.43-6.279 4.565a1.218 1.218 0 00.631 1.283s.015-.371.733-.245c.255.042.515.054.774.036.349-.023.681-.159.945-.389.264-.23 2.003-.828 2.783-4.102 0 0 .577-.714.553-.897l-1.196.511s.41.87.087 1.591c0 0-.039-1.558-.269-1.522-.048 0-.625.299-.625.299s.706 1.495.173 2.61c0 0 .204-1.862-.394-2.502l-.846.496s.825 1.561.266 2.834c0 0 .143-1.955-.446-2.714l-.765.598s.774 1.539.299 2.595c0 0-.063-2.275-.469-2.446a4.75 4.75 0 00-.775.834s.532 1.118.204 1.707c0 0-.204-1.515-.368-1.521 0 0-.67 1.001-.739 1.689a3.671 3.671 0 01.577-1.794 2.007 2.007 0 00-1.025.532s.104-.711 1.196-.771c0 0 .553-.763.699-.81 0 0-1.079-.09-1.734.2 0 0 .577-.67 1.932-.365l.759-.619s-1.423-.194-2.024.021c0 0 .694-.598 2.227-.161l.826-.494a7.445 7.445 0 00-1.935-.164s.763-.413 2.174.033l.598-.263s-.897-.177-1.148-.203c-.252-.027-.275-.099-.275-.099a3.078 3.078 0 011.668.185s1.232-.46 1.208-.538z'
+      />
+      <ellipse
+        cx='390.052'
+        cy='264.383'
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        opacity='0.1'
+        rx='5.044'
+        ry='1'
+      />
+      <path
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        d='M352.896 262.437s-8.604-.52-7.588 5.51a1.47 1.47 0 00.763 1.549s0-.449.897-.299c.309.052.623.067.935.045a1.923 1.923 0 001.142-.473c.318-.279 2.419-.998 3.361-4.951 0 0 .697-.861.667-1.085l-1.468.622s.496 1.046.104 1.916c0 0-.048-1.877-.326-1.835-.056 0-.753.364-.753.364s.852 1.824.209 3.152c0 0 .245-2.249-.475-3.02l-1.023.598s.996 1.886.32 3.423c0 0 .174-2.359-.535-3.289l-.927.724s.939 1.857.365 3.133c0 0-.072-2.747-.565-2.954 0 0-.81.715-.933 1.008 0 0 .64 1.348.242 2.06 0 0-.245-1.83-.445-1.839 0 0-.804 1.214-.897 2.042.04-.768.28-1.512.697-2.159a2.427 2.427 0 00-1.235.643s.125-.858 1.435-.933c0 0 .667-.92.846-.974 0 0-1.303-.111-2.093.239 0 0 .694-.807 2.329-.44l.915-.747s-1.713-.233-2.442.024c0 0 .837-.715 2.69-.191l.996-.598s-1.462-.314-2.335-.201c0 0 .921-.496 2.625.042l.711-.32s-1.07-.209-1.384-.242c-.314-.033-.329-.119-.329-.119a3.69 3.69 0 012.012.221s1.519-.553 1.492-.646z'
+      />
+      <ellipse
+        cx='346.97'
+        cy='270.022'
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        opacity='0.1'
+        rx='6.09'
+        ry='1.028'
+      />
+      <rect
+        width='51.026'
+        height='91.312'
+        x='303.926'
+        y='69.211'
+        fill='#FFF'
+        fillRule='nonzero'
+        rx='4.737'
+        transform='rotate(-71.99 329.44 114.867)'
+      />
+      <circle
+        cx='303.613'
+        cy='103.507'
+        r='9.376'
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        opacity='0.2'
+      />
+      <path
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        d='M344.582495 86.4883769L347.150665 86.4883769 347.150665 127.336977 344.582495 127.336977z'
+        opacity='0.2'
+        transform='rotate(-71.99 345.867 106.913)'
+      />
+      <path
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        d='M333.028401 99.6373982L335.596571 99.6373982 335.596571 120.445898 333.028401 120.445898z'
+        opacity='0.2'
+        transform='rotate(-71.99 334.312 110.042)'
+      />
+      <path
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        d='M337.109071 101.431004L339.677241 101.431004 339.677241 135.086304 337.109071 135.086304z'
+        opacity='0.2'
+        transform='rotate(-71.99 338.393 118.259)'
+      />
+      <path
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        d='M338.505494 105.174377L341.073664 105.174377 341.073664 146.022978 338.505494 146.022978z'
+        opacity='0.2'
+        transform='rotate(-71.99 339.79 125.599)'
+      />
+      <path
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        d='M348.060839 134.648161L353.026769 134.648161 353.026769 145.952261 348.060839 145.952261z'
+        opacity='0.5'
+        transform='rotate(-71.99 350.544 140.3)'
+      />
+      <rect
+        width='50.3'
+        height='78.305'
+        x='291.549'
+        y='174.486'
+        fill='#FFF'
+        fillRule='nonzero'
+        rx='4'
+        transform='rotate(-57.265 316.7 213.638)'
+      />
+      <path
+        fill='#DFE3E8'
+        fillRule='nonzero'
+        d='M278.038439 203.88874L292.269539 203.88874 292.269539 207.23723 278.038439 207.23723z'
+        transform='rotate(-57.57 285.154 205.563)'
+      />
+      <path
+        fill='#DFE3E8'
+        fillRule='nonzero'
+        d='M283.007935 199.201889L314.262535 199.201889 314.262535 202.272339 283.007935 202.272339z'
+        transform='rotate(-57.57 298.635 200.737)'
+      />
+      <path
+        fill='#DFE3E8'
+        fillRule='nonzero'
+        d='M288.543935 202.715889L319.798535 202.715889 319.798535 205.786339 288.543935 205.786339z'
+        transform='rotate(-57.57 304.171 204.251)'
+      />
+      <path
+        fill='#DFE3E8'
+        fillRule='nonzero'
+        d='M294.078936 206.235889L325.333536 206.235889 325.333536 209.306339 294.078936 209.306339z'
+        transform='rotate(-57.57 309.706 207.771)'
+      />
+      <path
+        fill='#DFE3E8'
+        fillRule='nonzero'
+        d='M299.879919 211.40702L331.090719 211.40702 331.090719 214.47316 299.879919 214.47316z'
+        transform='rotate(-57.265 315.485 212.94)'
+      />
+      <path
+        fill='#F4F6F8'
+        fillRule='nonzero'
+        d='M305.149936 213.268889L336.404536 213.268889 336.404536 216.339339 305.149936 216.339339z'
+        transform='rotate(-57.57 320.777 214.804)'
+      />
+      <path
+        fill='#F4F6F8'
+        fillRule='nonzero'
+        d='M310.685935 216.782889L341.940535 216.782889 341.940535 219.853339 310.685935 219.853339z'
+        transform='rotate(-57.57 326.313 218.318)'
+      />
+      <path
+        fill='#DFE3E8'
+        fillRule='nonzero'
+        d='M341.417983 210.406958L349.511163 210.406958 349.511163 218.500138 341.417983 218.500138z'
+        transform='rotate(-57.57 345.465 214.454)'
+      />
+      <path
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        d='M269.277 168.76l-45.767.493a3.127 3.127 0 00-3.094 3.125v3.782a3.127 3.127 0 01-3.05 3.124l-33.024.792a3.124 3.124 0 01-3.095-2.308l-1.551-5.701a3.124 3.124 0 00-3.053-2.308l-44.113.475a3.125 3.125 0 00-3.05 3.648l17.747 104.449a3.129 3.129 0 003.071 2.604l139.363.598a3.129 3.129 0 003.082-3.714l-20.366-106.521a3.125 3.125 0 00-3.1-2.538z'
+      />
+      <path
+        fill={PRIMARY_DARK}
+        fillRule='nonzero'
+        d='M269.277 168.76l-45.767.493a3.127 3.127 0 00-3.094 3.125v3.782a3.127 3.127 0 01-3.05 3.124l-33.024.792a3.124 3.124 0 01-3.095-2.308l-1.551-5.701a3.124 3.124 0 00-3.053-2.308l-44.113.475a3.125 3.125 0 00-3.05 3.648l17.747 104.449a3.129 3.129 0 003.071 2.604l139.363.598a3.129 3.129 0 003.082-3.714l-20.366-106.521a3.125 3.125 0 00-3.1-2.538z'
+        opacity='0.243'
+      />
+      <path
+        fill='url(#linearGradient-2)'
+        fillRule='nonzero'
+        d='M269.277 168.76l-45.767.493a3.127 3.127 0 00-3.094 3.125v3.782a3.127 3.127 0 01-3.05 3.124l-33.024.792a3.124 3.124 0 01-3.095-2.308l-1.551-5.701a3.124 3.124 0 00-3.053-2.308l-44.113.475a3.125 3.125 0 00-3.05 3.648l17.747 104.449a3.129 3.129 0 003.071 2.604l139.363.598a3.129 3.129 0 003.082-3.714l-20.366-106.521a3.125 3.125 0 00-3.1-2.538z'
+        opacity='0.32'
+      />
+      <ellipse
+        cx='119.593'
+        cy='258.664'
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        opacity='0.1'
+        rx='4.846'
+        ry='1'
+      />
+      <ellipse
+        cx='101.03'
+        cy='260.545'
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        opacity='0.1'
+        rx='4.846'
+        ry='1'
+      />
+      <ellipse
+        cx='108.459'
+        cy='265.905'
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        opacity='0.1'
+        rx='3.444'
+        ry='1'
+      />
+      <ellipse
+        cx='89.193'
+        cy='265.433'
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        opacity='0.1'
+        rx='3.444'
+        ry='1'
+      />
+      <path
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        d='M100.925 260.398s6.796-22.279-1.064-31.204c-5.881-6.676-12.557-5.877-15.547-5.052a5.528 5.528 0 00-3.564 2.963c-1.046 2.254-.858 5.913 6.521 10.186 12.35 7.151 13.119 16.96 13.119 16.96l.535 6.147z'
+      />
+      <path
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        d='M101.2 260.575s-7.961-16.193-10.147-15.846c-1.037.17-1.516 1.196-1.734 2.218a6.516 6.516 0 00.434 3.941c1.13 2.601 4.165 7.519 11.447 9.687z'
+      />
+      <path
+        stroke={PRIMARY_DARKER}
+        strokeLinecap='round'
+        strokeWidth='0.5'
+        d='M91.113 247.808s8.353 12.115 9.968 12.647'
+      />
+      <path
+        stroke={PRIMARY_DARKER}
+        strokeLinecap='round'
+        strokeWidth='0.5'
+        d='M90.344 249.701L92.293 249.701'
+      />
+      <path
+        stroke={PRIMARY_DARKER}
+        strokeLinecap='round'
+        strokeWidth='0.5'
+        d='M92.68 254.132L95.738 254.263'
+      />
+      <path
+        stroke={PRIMARY_DARKER}
+        strokeLinecap='round'
+        strokeWidth='0.5'
+        d='M94.542 250.586L94.21 252.179'
+      />
+      <path
+        stroke={PRIMARY_DARKER}
+        strokeLinecap='round'
+        strokeWidth='0.5'
+        d='M97.792 255.432L97.732 256.879'
+      />
+      <path
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        d='M100.964 260.575s7.959-16.193 10.144-15.846c1.041.17 1.519 1.196 1.734 2.218a6.514 6.514 0 01-.433 3.941c-1.13 2.601-4.168 7.519-11.445 9.687z'
+      />
+      <path
+        stroke={PRIMARY_DARKER}
+        strokeLinecap='round'
+        strokeWidth='0.5'
+        d='M111.051 247.808s-8.371 12.115-9.97 12.647'
+      />
+      <path
+        stroke={PRIMARY_DARKER}
+        strokeLinecap='round'
+        strokeWidth='0.5'
+        d='M111.82 249.701L109.871 249.701'
+      />
+      <path
+        stroke={PRIMARY_DARKER}
+        strokeLinecap='round'
+        strokeWidth='0.5'
+        d='M109.485 254.132L106.426 254.263'
+      />
+      <path
+        stroke={PRIMARY_DARKER}
+        strokeLinecap='round'
+        strokeWidth='0.5'
+        d='M107.622 250.586L107.954 252.179'
+      />
+      <path
+        stroke={PRIMARY_DARKER}
+        strokeLinecap='round'
+        strokeWidth='0.5'
+        d='M104.372 255.432L104.432 256.879'
+      />
+      <path
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        d='M87.295 264.357a1.375 1.375 0 01-.452-.685.599.599 0 01.317-.697c.3-.11.598.09.837.299.24.21.512.431.81.38a1.237 1.237 0 01-.382-1.16.47.47 0 01.105-.236c.161-.174.454-.099.648.039.598.43.787 1.264.79 2.018.03-.277.03-.555 0-.831a.853.853 0 01.334-.727.938.938 0 01.476-.114.988.988 0 01.774.222.767.767 0 01-.03.947c-.229.257-.5.474-.801.64a1.805 1.805 0 00-.571.547.399.399 0 00-.042.096h-1.74a4.826 4.826 0 01-1.073-.738zM117.626 257.833a1.355 1.355 0 01-.452-.682.599.599 0 01.314-.696c.299-.111.598.089.837.299.239.209.509.436.817.391a1.236 1.236 0 01-.386-1.157.472.472 0 01.107-.236c.162-.173.455-.099.649.036.613.433.787 1.268.79 2.021a4.008 4.008 0 000-.834.852.852 0 01.299-.736.955.955 0 01.475-.11.998.998 0 01.774.218.768.768 0 01-.033.951 2.996 2.996 0 01-.798.64 1.767 1.767 0 00-.571.544.499.499 0 00-.042.098h-1.701a4.78 4.78 0 01-1.079-.747zM107.518 264.357a1.406 1.406 0 01-.455-.685.6.6 0 01.317-.697c.299-.11.598.09.837.299.239.21.505.437.816.395a1.247 1.247 0 01-.385-1.16.472.472 0 01.107-.236c.162-.174.455-.099.649.039.613.43.783 1.264.789 2.018.03-.277.03-.555 0-.832a.855.855 0 01.314-.735.93.93 0 01.476-.114.988.988 0 01.774.222.764.764 0 01-.033.947 3.028 3.028 0 01-.798.64 1.798 1.798 0 00-.571.547.411.411 0 00-.042.096h-1.734c-.385-.2-.742-.45-1.061-.744z'
+      />
+      <circle
+        cx='84.467'
+        cy='87.003'
+        r='6.467'
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        opacity='0.1'
+      />
+      <circle
+        cx='395.425'
+        cy='138.681'
+        r='6.467'
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        opacity='0.1'
+      />
+      <circle
+        cx='279.178'
+        cy='66.467'
+        r='6.467'
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        opacity='0.1'
+      />
+      <circle
+        cx='97.4'
+        cy='122.68'
+        r='10.838'
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        opacity='0.1'
+      />
+      <path
+        fill={PRIMARY_DARK}
+        fillRule='nonzero'
+        d='M206.029 209.911c-7.975 0-14.44 6.465-14.44 14.44s6.465 14.44 14.44 14.44 14.44-6.465 14.44-14.44-6.465-14.44-14.44-14.44z'
+      />
+      <path
+        fill={PRIMARY_MAIN}
+        fillRule='nonzero'
+        d='M206.029 211.59c7.047 0 12.761 5.714 12.761 12.761 0 7.048-5.714 12.761-12.761 12.761-7.048 0-12.761-5.713-12.761-12.761.006-7.045 5.716-12.754 12.761-12.761'
+        opacity='0.72'
+      />
+      <path
+        fill={PRIMARY_DARK}
+        fillRule='nonzero'
+        d='M200.058 225.406l-.699-.681a.721.721 0 010-1.065l6.127-5.984a.753.753 0 01.546-.231c.206 0 .404.083.545.231l6.127 5.978a.733.733 0 010 1.065l-.699.682a.768.768 0 01-1.091 0l-3.622-3.727v8.843a.725.725 0 01-.219.523.76.76 0 01-.534.217h-1.009a.75.75 0 01-.759-.74v-8.832l-3.622 3.726a.768.768 0 01-1.091-.005z'
+      />
+    </svg>
+  )
 }

+ 76 - 96
src/components/upload/upload-list-item.tsx

@@ -1,103 +1,83 @@
-import { Card, Image, Tooltip, Typography } from "antd";
-import type { ItemRender } from "antd/es/upload/interface";
-import { m } from "framer-motion";
-import { useEffect, useState } from "react";
-
-import { varFade } from "@/components/animate/variants";
-import { Iconify, SvgIcon } from "@/components/icon";
-import { fBytes } from "@/utils/format-number";
-
-import { getBlobUrl, getFileFormat, getFileThumb } from "./utils";
+import { useEffect, useState } from 'react'
+import { m } from 'framer-motion'
+import { fBytes } from '@/utils/format-number'
+import { Card, Image, Tooltip, Typography } from 'antd'
+import type { ItemRender } from 'antd/es/upload/interface'
+import { varFade } from '@/components/animate/variants'
+import { Iconify, SvgIcon } from '@/components/icon'
+import { getBlobUrl, getFileFormat, getFileThumb } from './utils'
 
 type Props = {
-	file: Parameters<ItemRender>["1"];
-	actions: Parameters<ItemRender>["3"];
-	thumbnail?: boolean;
-};
+  file: Parameters<ItemRender>['1']
+  actions: Parameters<ItemRender>['3']
+  thumbnail?: boolean
+}
 
-export default function UploadListItem({
-	file,
-	actions,
-	thumbnail = false,
-}: Props) {
-	const { name, size } = file;
-	const thumb = getFileThumb(name);
-	const format = getFileFormat(name);
-	const [imgThumbUrl, setImgThumbUrl] = useState("");
+export default function UploadListItem({ file, actions, thumbnail = false }: Props) {
+  const { name, size } = file
+  const thumb = getFileThumb(name)
+  const format = getFileFormat(name)
+  const [imgThumbUrl, setImgThumbUrl] = useState('')
 
-	useEffect(() => {
-		// TODO: mock upload sucess, you should delete 'error' in the production environment
-		if (
-			file.status &&
-			["done", "error"].includes(file.status) &&
-			format === "img"
-		) {
-			if (file.originFileObj) {
-				setImgThumbUrl(getBlobUrl(file.originFileObj));
-			}
-		}
-	}, [file, format]);
+  useEffect(() => {
+    // TODO: mock upload sucess, you should delete 'error' in the production environment
+    if (file.status && ['done', 'error'].includes(file.status) && format === 'img') {
+      if (file.originFileObj) {
+        setImgThumbUrl(getBlobUrl(file.originFileObj))
+      }
+    }
+  }, [file, format])
 
-	const closeButton = (
-		<button
-			type="button"
-			className="ml-auto h-6 w-6 cursor-pointer rounded-full text-center hover:bg-gray-400 hover:bg-opacity-20"
-			onClick={actions.remove}
-		>
-			<Iconify icon="mingcute:close-line" size={14} className="text-gray-600" />
-		</button>
-	);
+  const closeButton = (
+    <button
+      type='button'
+      className='ml-auto h-6 w-6 cursor-pointer rounded-full text-center hover:bg-gray-400 hover:bg-opacity-20'
+      onClick={actions.remove}>
+      <Iconify icon='mingcute:close-line' size={14} className='text-gray-600' />
+    </button>
+  )
 
-	const thumbList = (
-		<Card
-			className="relative flex items-center justify-center"
-			style={{ width: 80, height: 80, marginTop: "8px", marginRight: "8px" }}
-		>
-			<Tooltip title={name}>
-				{format === "img" ? (
-					<Image src={imgThumbUrl} preview={false} width={40} height={40} />
-				) : (
-					<SvgIcon icon={thumb} size={40} />
-				)}
-			</Tooltip>
-			<div className="absolute right-0 top-0">{closeButton}</div>
-		</Card>
-	);
-	const cardList = (
-		<Card
-			styles={{
-				body: {
-					display: "flex",
-					alignItems: "center",
-					padding: "8px 12px",
-				},
-			}}
-			style={{ marginTop: "8px" }}
-		>
-			{format === "img" ? (
-				<Image src={imgThumbUrl} preview={false} width={32} height={32} />
-			) : (
-				<SvgIcon icon={thumb} size={32} />
-			)}
-			<div className="ml-4 flex flex-col">
-				<Typography.Text className="!text-sm !font-medium">
-					{name}
-				</Typography.Text>
-				<Typography.Text type="secondary" className="!text-xs">
-					{fBytes(size)}
-				</Typography.Text>
-			</div>
-			{closeButton}
-		</Card>
-	);
-	return (
-		<m.div
-			initial="initial"
-			animate="animate"
-			exit="exit"
-			variants={varFade().inUp}
-		>
-			{thumbnail ? thumbList : cardList}
-		</m.div>
-	);
+  const thumbList = (
+    <Card
+      className='relative flex items-center justify-center'
+      style={{ width: 80, height: 80, marginTop: '8px', marginRight: '8px' }}>
+      <Tooltip title={name}>
+        {format === 'img' ? (
+          <Image src={imgThumbUrl} preview={false} width={40} height={40} />
+        ) : (
+          <SvgIcon icon={thumb} size={40} />
+        )}
+      </Tooltip>
+      <div className='absolute right-0 top-0'>{closeButton}</div>
+    </Card>
+  )
+  const cardList = (
+    <Card
+      styles={{
+        body: {
+          display: 'flex',
+          alignItems: 'center',
+          padding: '8px 12px'
+        }
+      }}
+      style={{ marginTop: '8px' }}>
+      {format === 'img' ? (
+        <Image src={imgThumbUrl} preview={false} width={32} height={32} />
+      ) : (
+        <SvgIcon icon={thumb} size={32} />
+      )}
+      <div className='ml-4 flex flex-col'>
+        <Typography.Text className='!text-sm !font-medium'>{name}</Typography.Text>
+        <Typography.Text type='secondary' className='!text-xs'>
+          {fBytes(size)}
+        </Typography.Text>
+      </div>
+      {closeButton}
+    </Card>
+  )
+  return (
+    <m.div initial='initial' animate='animate' exit='exit' variants={varFade().inUp}>
+      {thumbnail ? thumbList : cardList}
+    </m.div>
+  )
 }

+ 37 - 38
src/components/upload/upload.tsx

@@ -1,46 +1,45 @@
-import { Upload as AntdUpload, Typography } from "antd";
-import type { ItemRender } from "antd/es/upload/interface";
-import { StyledUpload } from "./styles";
-import UploadIllustration from "./upload-illustration";
-import UploadListItem from "./upload-list-item";
+import { Upload as AntdUpload, Typography } from 'antd'
+import type { UploadProps } from 'antd'
+import type { ItemRender } from 'antd/es/upload/interface'
+import { StyledUpload } from './styles'
+import UploadIllustration from './upload-illustration'
+import UploadListItem from './upload-list-item'
 
-import type { UploadProps } from "antd";
-
-const { Dragger } = AntdUpload;
-const { Text, Title } = Typography;
+const { Dragger } = AntdUpload
+const { Text, Title } = Typography
 
 interface Props extends UploadProps {
-	thumbnail?: boolean;
+  thumbnail?: boolean
 }
 
 const itemRender: (thumbnail: boolean) => ItemRender = (thumbnail) => {
-	return function temp(...args) {
-		const [, file, , actions] = args;
-		return <UploadListItem file={file} actions={actions} thumbnail={thumbnail} />;
-	};
-};
+  return function temp(...args) {
+    const [, file, , actions] = args
+    return <UploadListItem file={file} actions={actions} thumbnail={thumbnail} />
+  }
+}
 export function Upload({ thumbnail = false, ...other }: Props) {
-	return (
-		<StyledUpload $thumbnail={thumbnail}>
-			<Dragger {...other} itemRender={itemRender(thumbnail)}>
-				<div className="opacity-100 hover:opacity-80">
-					<p className="m-auto max-w-[200px]">
-						<UploadIllustration />
-					</p>
-					<Typography>
-						<Title level={5} className="mt-4">
-							Drop or Select file
-						</Title>
-						<Text type="secondary">
-							Drop files here or click
-							<Text className="mx-2 !text-primary" underline>
-								browse
-							</Text>
-							thorough your machine
-						</Text>
-					</Typography>
-				</div>
-			</Dragger>
-		</StyledUpload>
-	);
+  return (
+    <StyledUpload $thumbnail={thumbnail}>
+      <Dragger {...other} itemRender={itemRender(thumbnail)}>
+        <div className='opacity-100 hover:opacity-80'>
+          <p className='m-auto max-w-[200px]'>
+            <UploadIllustration />
+          </p>
+          <Typography>
+            <Title level={5} className='mt-4'>
+              Drop or Select file
+            </Title>
+            <Text type='secondary'>
+              Drop files here or click
+              <Text className='mx-2 !text-primary' underline>
+                browse
+              </Text>
+              thorough your machine
+            </Text>
+          </Typography>
+        </div>
+      </Dragger>
+    </StyledUpload>
+  )
 }

+ 116 - 116
src/components/upload/utils.ts

@@ -1,63 +1,63 @@
-import type { RcFile } from "antd/es/upload";
-import { toast } from "sonner";
+import { toast } from 'sonner'
+import type { RcFile } from 'antd/es/upload'
 
 // Define more types here
-const FORMAT_PDF = ["pdf"];
-const FORMAT_TEXT = ["txt"];
-const FORMAT_PHOTOSHOP = ["psd"];
-const FORMAT_WORD = ["doc", "docx"];
-const FORMAT_EXCEL = ["xls", "xlsx"];
-const FORMAT_ZIP = ["zip", "rar", "iso"];
-const FORMAT_ILLUSTRATOR = ["ai", "esp"];
-const FORMAT_POWERPOINT = ["ppt", "pptx"];
-const FORMAT_AUDIO = ["wav", "aif", "mp3", "aac"];
-const FORMAT_IMG = ["jpg", "jpeg", "gif", "bmp", "png", "svg"];
-const FORMAT_VIDEO = ["m4v", "avi", "mpg", "mp4", "webm"];
+const FORMAT_PDF = ['pdf']
+const FORMAT_TEXT = ['txt']
+const FORMAT_PHOTOSHOP = ['psd']
+const FORMAT_WORD = ['doc', 'docx']
+const FORMAT_EXCEL = ['xls', 'xlsx']
+const FORMAT_ZIP = ['zip', 'rar', 'iso']
+const FORMAT_ILLUSTRATOR = ['ai', 'esp']
+const FORMAT_POWERPOINT = ['ppt', 'pptx']
+const FORMAT_AUDIO = ['wav', 'aif', 'mp3', 'aac']
+const FORMAT_IMG = ['jpg', 'jpeg', 'gif', 'bmp', 'png', 'svg']
+const FORMAT_VIDEO = ['m4v', 'avi', 'mpg', 'mp4', 'webm']
 
 /**
  * 获取文件格式
  * @param fileName
  */
 export function getFileFormat(fileName: string | undefined) {
-	let format: string | undefined;
-	switch (true) {
-		case FORMAT_PDF.includes(fileTypeByName(fileName)):
-			format = "pdf";
-			break;
-		case FORMAT_TEXT.includes(fileTypeByName(fileName)):
-			format = "txt";
-			break;
-		case FORMAT_PHOTOSHOP.includes(fileTypeByName(fileName)):
-			format = "psd";
-			break;
-		case FORMAT_WORD.includes(fileTypeByName(fileName)):
-			format = "word";
-			break;
-		case FORMAT_EXCEL.includes(fileTypeByName(fileName)):
-			format = "excel";
-			break;
-		case FORMAT_ZIP.includes(fileTypeByName(fileName)):
-			format = "zip";
-			break;
-		case FORMAT_ILLUSTRATOR.includes(fileTypeByName(fileName)):
-			format = "ai";
-			break;
-		case FORMAT_POWERPOINT.includes(fileTypeByName(fileName)):
-			format = "ppt";
-			break;
-		case FORMAT_AUDIO.includes(fileTypeByName(fileName)):
-			format = "audio";
-			break;
-		case FORMAT_IMG.includes(fileTypeByName(fileName)):
-			format = "img";
-			break;
-		case FORMAT_VIDEO.includes(fileTypeByName(fileName)):
-			format = "video";
-			break;
-		default:
-			format = fileTypeByName(fileName);
-	}
-	return format;
+  let format: string | undefined
+  switch (true) {
+    case FORMAT_PDF.includes(fileTypeByName(fileName)):
+      format = 'pdf'
+      break
+    case FORMAT_TEXT.includes(fileTypeByName(fileName)):
+      format = 'txt'
+      break
+    case FORMAT_PHOTOSHOP.includes(fileTypeByName(fileName)):
+      format = 'psd'
+      break
+    case FORMAT_WORD.includes(fileTypeByName(fileName)):
+      format = 'word'
+      break
+    case FORMAT_EXCEL.includes(fileTypeByName(fileName)):
+      format = 'excel'
+      break
+    case FORMAT_ZIP.includes(fileTypeByName(fileName)):
+      format = 'zip'
+      break
+    case FORMAT_ILLUSTRATOR.includes(fileTypeByName(fileName)):
+      format = 'ai'
+      break
+    case FORMAT_POWERPOINT.includes(fileTypeByName(fileName)):
+      format = 'ppt'
+      break
+    case FORMAT_AUDIO.includes(fileTypeByName(fileName)):
+      format = 'audio'
+      break
+    case FORMAT_IMG.includes(fileTypeByName(fileName)):
+      format = 'img'
+      break
+    case FORMAT_VIDEO.includes(fileTypeByName(fileName)):
+      format = 'video'
+      break
+    default:
+      format = fileTypeByName(fileName)
+  }
+  return format
 }
 
 /**
@@ -65,79 +65,79 @@ export function getFileFormat(fileName: string | undefined) {
  * @param fileName
  */
 export function getFileThumb(fileName: string | undefined) {
-	let thumb: string | undefined;
-	const format = getFileFormat(fileName);
-	switch (format) {
-		case "txt":
-			thumb = "ic_file_txt";
-			break;
-		case "zip":
-			thumb = "ic_file_zip";
-			break;
-		case "audio":
-			thumb = "ic_file_audio";
-			break;
-		case "video":
-			thumb = "ic_file_video";
-			break;
-		case "word":
-			thumb = "ic_file_word";
-			break;
-		case "excel":
-			thumb = "ic_file_excel";
-			break;
-		case "ppt":
-			thumb = "ic_file_ppt";
-			break;
-		case "pdf":
-			thumb = "ic_file_pdf";
-			break;
-		case "psd":
-			thumb = "ic_file_psd";
-			break;
-		case "ai":
-			thumb = "ic_file_ai";
-			break;
-		case "img":
-			thumb = "ic_file_img";
-			break;
-		case "folder":
-			thumb = "ic_folder";
-			break;
-		default:
-			thumb = "ic_file";
-	}
-	return thumb;
+  let thumb: string | undefined
+  const format = getFileFormat(fileName)
+  switch (format) {
+    case 'txt':
+      thumb = 'ic_file_txt'
+      break
+    case 'zip':
+      thumb = 'ic_file_zip'
+      break
+    case 'audio':
+      thumb = 'ic_file_audio'
+      break
+    case 'video':
+      thumb = 'ic_file_video'
+      break
+    case 'word':
+      thumb = 'ic_file_word'
+      break
+    case 'excel':
+      thumb = 'ic_file_excel'
+      break
+    case 'ppt':
+      thumb = 'ic_file_ppt'
+      break
+    case 'pdf':
+      thumb = 'ic_file_pdf'
+      break
+    case 'psd':
+      thumb = 'ic_file_psd'
+      break
+    case 'ai':
+      thumb = 'ic_file_ai'
+      break
+    case 'img':
+      thumb = 'ic_file_img'
+      break
+    case 'folder':
+      thumb = 'ic_folder'
+      break
+    default:
+      thumb = 'ic_file'
+  }
+  return thumb
 }
 
-export function fileTypeByName(fileName = "") {
-	return fileName?.split(".").pop() || "folder";
+export function fileTypeByName(fileName = '') {
+  return fileName?.split('.').pop() || 'folder'
 }
 
 export function beforeAvatarUpload(file: RcFile) {
-	const isJpgOrPng = file.type === "image/jpeg" || file.type === "image/png";
-	if (!isJpgOrPng) {
-		toast.error("You can only upload JPG/PNG file!", {
-			position: "top-center",
-		});
-	}
-	const isLt2M = file.size / 1024 / 1024 < 2;
-	if (!isLt2M) {
-		toast.error("Image must smaller than 2MB!", {
-			position: "top-center",
-		});
-	}
-	return isJpgOrPng && isLt2M;
+  const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'
+  if (!isJpgOrPng) {
+    toast.error('You can only upload JPG/PNG file!', {
+      position: 'top-center'
+    })
+  }
+  const isLt2M = file.size / 1024 / 1024 < 2
+  if (!isLt2M) {
+    toast.error('Image must smaller than 2MB!', {
+      position: 'top-center'
+    })
+  }
+  return isJpgOrPng && isLt2M
 }
 
 export function getBase64(img: RcFile, callback: (url: string) => void) {
-	const reader = new FileReader();
-	reader.addEventListener("load", () => callback(reader.result as string));
-	reader.readAsDataURL(img);
+  const reader = new FileReader()
+  reader.addEventListener('load', () => callback(reader.result as string))
+  reader.readAsDataURL(img)
 }
 
 export function getBlobUrl(imgFile: RcFile) {
-	const fileBlob = new Blob([imgFile]);
-	const thumbnailUrl = URL.createObjectURL(fileBlob);
-	return thumbnailUrl;
+  const fileBlob = new Blob([imgFile])
+  const thumbnailUrl = URL.createObjectURL(fileBlob)
+  return thumbnailUrl
 }

+ 7 - 7
src/components/verifition/Verify/VerifyPoints/index.jsx

@@ -1,12 +1,12 @@
+import React, { forwardRef, memo } from 'react'
 import PropTypes from 'prop-types'
-import React, { memo, forwardRef } from 'react'
 
-const VerifyPoints = memo(forwardRef((props, ref) => {
-  return (
-    <div>VerifyPoints</div>
-  )
-}))
+const VerifyPoints = memo(
+  forwardRef((props, ref) => {
+    return <div>VerifyPoints</div>
+  })
+)
 
 VerifyPoints.propTypes = {}
 
-export default VerifyPoints
+export default VerifyPoints

+ 6 - 2
src/components/verifition/utils/ase.js

@@ -1,11 +1,15 @@
 import CryptoJS from 'crypto-js'
+
 /**
  * @word 要加密的内容
  * @keyWord String  服务器随机返回的关键字
  *  */
-export function aesEncrypt (word, keyWord = 'XwKsGlMcdPMEhR1B') {
+export function aesEncrypt(word, keyWord = 'XwKsGlMcdPMEhR1B') {
   var key = CryptoJS.enc.Utf8.parse(keyWord)
   var srcs = CryptoJS.enc.Utf8.parse(word)
-  var encrypted = CryptoJS.AES.encrypt(srcs, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 })
+  var encrypted = CryptoJS.AES.encrypt(srcs, key, {
+    mode: CryptoJS.mode.ECB,
+    padding: CryptoJS.pad.Pkcs7
+  })
   return encrypted.toString()
 }

+ 4 - 5
src/components/verifition/utils/axios.js

@@ -10,21 +10,20 @@ const service = axios.create({
   }
 })
 service.interceptors.request.use(
-  config => {
+  (config) => {
     return config
   },
-  error => {
+  (error) => {
     Promise.reject(error)
   }
 )
 
 // response interceptor
 service.interceptors.response.use(
-  response => {
+  (response) => {
     const res = response.data
     return res
   },
-  error => {
-  }
+  (error) => {}
 )
 export default service

+ 69 - 7
src/components/verifition/utils/util.js

@@ -1,28 +1,28 @@
-export function resetSize (vm, props) {
-  var img_width, img_height, bar_width, bar_height	// 图片的宽度、高度,移动条的宽度、高度
+export function resetSize(vm, props) {
+  var img_width, img_height, bar_width, bar_height // 图片的宽度、高度,移动条的宽度、高度
   var parentWidth = vm.parentNode.offsetWidth || window.offsetWidth
   var parentHeight = vm.parentNode.offsetHeight || window.offsetHeight
 
   if (props.imgSize.width.indexOf('%') != -1) {
-    img_width = parseInt(props.imgSize.width) / 100 * parentWidth + 'px'
+    img_width = (parseInt(props.imgSize.width) / 100) * parentWidth + 'px'
   } else {
     img_width = props.imgSize.width
   }
 
   if (props.imgSize.height.indexOf('%') != -1) {
-    img_height = parseInt(props.imgSize.height) / 100 * parentHeight + 'px'
+    img_height = (parseInt(props.imgSize.height) / 100) * parentHeight + 'px'
   } else {
     img_height = props.imgSize.height
   }
 
   if (props.barSize.width.indexOf('%') != -1) {
-    bar_width = parseInt(props.barSize.width) / 100 * parentWidth + 'px'
+    bar_width = (parseInt(props.barSize.width) / 100) * parentWidth + 'px'
   } else {
     bar_width = props.barSize.width
   }
 
   if (props.barSize.height.indexOf('%') != -1) {
-    bar_height = parseInt(props.barSize.height) / 100 * parentHeight + 'px'
+    bar_height = (parseInt(props.barSize.height) / 100) * parentHeight + 'px'
   } else {
     bar_height = props.barSize.height
   }
@@ -30,6 +30,68 @@ export function resetSize (vm, props) {
   return { imgWidth: img_width, imgHeight: img_height, barWidth: bar_width, barHeight: bar_height }
 }
 
-export const _code_chars = [1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
+export const _code_chars = [
+  1,
+  2,
+  3,
+  4,
+  5,
+  6,
+  7,
+  8,
+  9,
+  'a',
+  'b',
+  'c',
+  'd',
+  'e',
+  'f',
+  'g',
+  'h',
+  'i',
+  'j',
+  'k',
+  'l',
+  'm',
+  'n',
+  'o',
+  'p',
+  'q',
+  'r',
+  's',
+  't',
+  'u',
+  'v',
+  'w',
+  'x',
+  'y',
+  'z',
+  'A',
+  'B',
+  'C',
+  'D',
+  'E',
+  'F',
+  'G',
+  'H',
+  'I',
+  'J',
+  'K',
+  'L',
+  'M',
+  'N',
+  'O',
+  'P',
+  'Q',
+  'R',
+  'S',
+  'T',
+  'U',
+  'V',
+  'W',
+  'X',
+  'Y',
+  'Z'
+]
 export const _code_color1 = ['#fffff0', '#f0ffff', '#f0fff0', '#fff0f0']
 export const _code_color2 = ['#FF0033', '#006699', '#993366', '#FF9900', '#66CC66', '#FF33CC']

+ 29 - 29
src/hooks/event/use-copy-to-clipboard.ts

@@ -1,38 +1,38 @@
-import { useState } from 'react';
-import { toast } from 'sonner';
+import { useState } from 'react'
+import { toast } from 'sonner'
 
 // ----------------------------------------------------------------------
 
-type CopiedValue = string | null;
+type CopiedValue = string | null
 
-type CopyFn = (text: string) => Promise<boolean>;
+type CopyFn = (text: string) => Promise<boolean>
 
 type ReturnType = {
-	copyFn: CopyFn;
-	copiedText: CopiedValue;
-};
+  copyFn: CopyFn
+  copiedText: CopiedValue
+}
 
 export default function useCopyToClipboard(): ReturnType {
-	const [copiedText, setCopiedText] = useState<CopiedValue>(null);
-
-	const copyFn: CopyFn = async (text) => {
-		if (!navigator?.clipboard) {
-			console.warn('Clipboard not supported');
-			return false;
-		}
-
-		// Try to save to clipboard then save it in the state if worked
-		try {
-			await navigator.clipboard.writeText(text);
-			setCopiedText(text);
-			toast.success('Copied!');
-			return true;
-		} catch (error) {
-			console.warn('Copy failed', error);
-			setCopiedText(null);
-			return false;
-		}
-	};
-
-	return { copiedText, copyFn };
+  const [copiedText, setCopiedText] = useState<CopiedValue>(null)
+
+  const copyFn: CopyFn = async (text) => {
+    if (!navigator?.clipboard) {
+      console.warn('Clipboard not supported')
+      return false
+    }
+
+    // Try to save to clipboard then save it in the state if worked
+    try {
+      await navigator.clipboard.writeText(text)
+      setCopiedText(text)
+      toast.success('Copied!')
+      return true
+    } catch (error) {
+      console.warn('Copy failed', error)
+      setCopiedText(null)
+      return false
+    }
+  }
+
+  return { copiedText, copyFn }
 }

+ 2 - 2
src/hooks/index.ts

@@ -1,2 +1,2 @@
-export { default as useCopyToClipboard } from './event/use-copy-to-clipboard';
-export * from './web/use-media-query';
+export { default as useCopyToClipboard } from './event/use-copy-to-clipboard'
+export * from './web/use-media-query'

+ 66 - 64
src/hooks/web/use-media-query.ts

@@ -1,36 +1,38 @@
-import { breakpointsTokens } from '@/theme/tokens/breakpoints';
-import { removePx } from '@/utils/theme';
-import { useEffect, useMemo, useState } from 'react';
+import { useEffect, useMemo, useState } from 'react'
+import { removePx } from '@/utils/theme'
+import { breakpointsTokens } from '@/theme/tokens/breakpoints'
 
 type MediaQueryConfig = {
-	minWidth?: number;
-	maxWidth?: number;
-	minHeight?: number;
-	maxHeight?: number;
-	orientation?: 'portrait' | 'landscape';
-	prefersColorScheme?: 'dark' | 'light';
-	prefersReducedMotion?: boolean;
-	devicePixelRatio?: number;
-	pointerType?: 'coarse' | 'fine';
-};
+  minWidth?: number
+  maxWidth?: number
+  minHeight?: number
+  maxHeight?: number
+  orientation?: 'portrait' | 'landscape'
+  prefersColorScheme?: 'dark' | 'light'
+  prefersReducedMotion?: boolean
+  devicePixelRatio?: number
+  pointerType?: 'coarse' | 'fine'
+}
 
 const buildMediaQuery = (config: MediaQueryConfig | string): string => {
-	if (typeof config === 'string') return config;
+  if (typeof config === 'string') return config
 
-	const conditions: string[] = [];
+  const conditions: string[] = []
 
-	if (config.minWidth) conditions.push(`(min-width: ${config.minWidth}px)`);
-	if (config.maxWidth) conditions.push(`(max-width: ${config.maxWidth}px)`);
-	if (config.minHeight) conditions.push(`(min-height: ${config.minHeight}px)`);
-	if (config.maxHeight) conditions.push(`(max-height: ${config.maxHeight}px)`);
-	if (config.orientation) conditions.push(`(orientation: ${config.orientation})`);
-	if (config.prefersColorScheme) conditions.push(`(prefers-color-scheme: ${config.prefersColorScheme})`);
-	if (config.prefersReducedMotion) conditions.push('(prefers-reduced-motion: reduce)');
-	if (config.devicePixelRatio) conditions.push(`(-webkit-min-device-pixel-ratio: ${config.devicePixelRatio})`);
-	if (config.pointerType) conditions.push(`(pointer: ${config.pointerType})`);
+  if (config.minWidth) conditions.push(`(min-width: ${config.minWidth}px)`)
+  if (config.maxWidth) conditions.push(`(max-width: ${config.maxWidth}px)`)
+  if (config.minHeight) conditions.push(`(min-height: ${config.minHeight}px)`)
+  if (config.maxHeight) conditions.push(`(max-height: ${config.maxHeight}px)`)
+  if (config.orientation) conditions.push(`(orientation: ${config.orientation})`)
+  if (config.prefersColorScheme)
+    conditions.push(`(prefers-color-scheme: ${config.prefersColorScheme})`)
+  if (config.prefersReducedMotion) conditions.push('(prefers-reduced-motion: reduce)')
+  if (config.devicePixelRatio)
+    conditions.push(`(-webkit-min-device-pixel-ratio: ${config.devicePixelRatio})`)
+  if (config.pointerType) conditions.push(`(pointer: ${config.pointerType})`)
 
-	return conditions.join(' and ');
-};
+  return conditions.join(' and ')
+}
 
 /**
  * React hook for handling media queries
@@ -75,54 +77,54 @@ const buildMediaQuery = (config: MediaQueryConfig | string): string => {
  * @see {@link MediaQueryConfig} for all supported configuration options
  */
 export const useMediaQuery = (config: MediaQueryConfig | string) => {
-	// 服务器端渲染时默认为 false
-	const [matches, setMatches] = useState(false);
+  // 服务器端渲染时默认为 false
+  const [matches, setMatches] = useState(false)
 
-	// 将 config 转换为 mediaQuery 字符串
-	const mediaQueryString = useMemo(() => buildMediaQuery(config), [config]);
+  // 将 config 转换为 mediaQuery 字符串
+  const mediaQueryString = useMemo(() => buildMediaQuery(config), [config])
 
-	useEffect(() => {
-		// 客户端渲染时立即检查当前状态
-		const mediaQuery = window.matchMedia(mediaQueryString);
-		setMatches(mediaQuery.matches);
+  useEffect(() => {
+    // 客户端渲染时立即检查当前状态
+    const mediaQuery = window.matchMedia(mediaQueryString)
+    setMatches(mediaQuery.matches)
 
-		// 监听变化
-		const handler = (e: MediaQueryListEvent) => setMatches(e.matches);
+    // 监听变化
+    const handler = (e: MediaQueryListEvent) => setMatches(e.matches)
 
-		// 使用新旧两种 API 以确保最大兼容性
-		if (mediaQuery.addEventListener) {
-			mediaQuery.addEventListener('change', handler);
-		} else {
-			// 兼容旧版浏览器
-			mediaQuery.addListener(handler);
-		}
+    // 使用新旧两种 API 以确保最大兼容性
+    if (mediaQuery.addEventListener) {
+      mediaQuery.addEventListener('change', handler)
+    } else {
+      // 兼容旧版浏览器
+      mediaQuery.addListener(handler)
+    }
 
-		// 清理函数
-		return () => {
-			if (mediaQuery.removeEventListener) {
-				mediaQuery.removeEventListener('change', handler);
-			} else {
-				// 兼容旧版浏览器
-				mediaQuery.removeListener(handler);
-			}
-		};
-	}, [mediaQueryString]);
+    // 清理函数
+    return () => {
+      if (mediaQuery.removeEventListener) {
+        mediaQuery.removeEventListener('change', handler)
+      } else {
+        // 兼容旧版浏览器
+        mediaQuery.removeListener(handler)
+      }
+    }
+  }, [mediaQueryString])
 
-	return matches;
-};
+  return matches
+}
 
-type Breakpoints = typeof breakpointsTokens;
-type BreakpointsKeys = keyof Breakpoints;
+type Breakpoints = typeof breakpointsTokens
+type BreakpointsKeys = keyof Breakpoints
 // 辅助函数
 export const up = (key: BreakpointsKeys) => ({
-	minWidth: removePx(breakpointsTokens[key])
-});
+  minWidth: removePx(breakpointsTokens[key])
+})
 
 export const down = (key: BreakpointsKeys) => ({
-	maxWidth: removePx(breakpointsTokens[key]) - 0.05 // 减去0.05px避免断点重叠
-});
+  maxWidth: removePx(breakpointsTokens[key]) - 0.05 // 减去0.05px避免断点重叠
+})
 
 export const between = (start: BreakpointsKeys, end: BreakpointsKeys) => ({
-	minWidth: removePx(breakpointsTokens[start]),
-	maxWidth: removePx(breakpointsTokens[end]) - 0.05
-});
+  minWidth: removePx(breakpointsTokens[start]),
+  maxWidth: removePx(breakpointsTokens[end]) - 0.05
+})

+ 52 - 47
src/layouts/components/bread-crumb.tsx

@@ -1,54 +1,59 @@
-import { Breadcrumb, type BreadcrumbProps, type GetProp } from 'antd';
-import { useMemo } from 'react';
-import { useTranslation } from 'react-i18next';
-import { Link, useMatches } from 'react-router';
+import { useMemo } from 'react'
+import { useTranslation } from 'react-i18next'
+import { Link, useMatches } from 'react-router'
+import { useFlattenedRoutes, usePermissionRoutes } from '@/router/hooks'
+import { menuFilter } from '@/router/utils'
+import { Breadcrumb, type BreadcrumbProps, type GetProp } from 'antd'
+import { Iconify } from '@/components/icon'
 
-import { Iconify } from '@/components/icon';
-import { useFlattenedRoutes, usePermissionRoutes } from '@/router/hooks';
-import { menuFilter } from '@/router/utils';
-
-type MenuItem = GetProp<BreadcrumbProps, 'items'>[number];
+type MenuItem = GetProp<BreadcrumbProps, 'items'>[number]
 
 /**
  * 动态面包屑解决方案:https://github.com/MinjieChang/myblog/issues/29
  */
 export default function BreadCrumb() {
-	const { t } = useTranslation();
-	const matches = useMatches();
-	const flattenedRoutes = useFlattenedRoutes();
-	const permissionRoutes = usePermissionRoutes();
-
-	const breadCrumbs = useMemo(() => {
-		const menuRoutes = menuFilter(permissionRoutes);
-		const paths = matches.filter((item) => item.pathname !== '/').map((item) => item.pathname);
-
-		const pathRouteMetas = flattenedRoutes.filter((item) => paths.includes(item.key));
-
-		let currentMenuItems = [...menuRoutes];
-
-		return pathRouteMetas.map((routeMeta): MenuItem => {
-			const { key, label } = routeMeta;
-
-			// Find current level menu items
-			const currentRoute = currentMenuItems.find((item) => item.meta?.key === key);
-
-			// Update menu items for next level
-			currentMenuItems = currentRoute?.children?.filter((item) => !item.meta?.hideMenu) ?? [];
-
-			return {
-				key,
-				title: t(label),
-				...(currentMenuItems.length > 0 && {
-					menu: {
-						items: currentMenuItems.map((item) => ({
-							key: item.meta?.key,
-							label: item.meta?.key ? <Link to={item.meta.key}>{t(item.meta.label)}</Link> : null
-						}))
-					}
-				})
-			};
-		});
-	}, [matches, flattenedRoutes, t, permissionRoutes]);
-
-	return <Breadcrumb items={breadCrumbs} className="!text-sm" separator={<Iconify icon="ph:dot-duotone" />} />;
+  const { t } = useTranslation()
+  const matches = useMatches()
+  const flattenedRoutes = useFlattenedRoutes()
+  const permissionRoutes = usePermissionRoutes()
+
+  const breadCrumbs = useMemo(() => {
+    const menuRoutes = menuFilter(permissionRoutes)
+    const paths = matches.filter((item) => item.pathname !== '/').map((item) => item.pathname)
+
+    const pathRouteMetas = flattenedRoutes.filter((item) => paths.includes(item.key))
+
+    let currentMenuItems = [...menuRoutes]
+
+    return pathRouteMetas.map((routeMeta): MenuItem => {
+      const { key, label } = routeMeta
+
+      // Find current level menu items
+      const currentRoute = currentMenuItems.find((item) => item.meta?.key === key)
+
+      // Update menu items for next level
+      currentMenuItems = currentRoute?.children?.filter((item) => !item.meta?.hideMenu) ?? []
+
+      return {
+        key,
+        title: t(label),
+        ...(currentMenuItems.length > 0 && {
+          menu: {
+            items: currentMenuItems.map((item) => ({
+              key: item.meta?.key,
+              label: item.meta?.key ? <Link to={item.meta.key}>{t(item.meta.label)}</Link> : null
+            }))
+          }
+        })
+      }
+    })
+  }, [matches, flattenedRoutes, t, permissionRoutes])
+
+  return (
+    <Breadcrumb
+      items={breadCrumbs}
+      className='!text-sm'
+      separator={<Iconify icon='ph:dot-duotone' />}
+    />
+  )
 }

+ 8 - 9
src/layouts/components/header-simple.tsx

@@ -1,12 +1,11 @@
-import Logo from '@/components/logo';
-
-import SettingButton from './setting-button';
+import Logo from '@/components/logo'
+import SettingButton from './setting-button'
 
 export default function HeaderSimple() {
-	return (
-		<header className="flex h-16 w-full items-center justify-between px-6">
-			<Logo size={30} />
-			<SettingButton />
-		</header>
-	);
+  return (
+    <header className='flex h-16 w-full items-center justify-between px-6'>
+      <Logo />
+      <SettingButton />
+    </header>
+  )
 }

+ 237 - 242
src/layouts/components/notice.tsx

@@ -1,258 +1,253 @@
-import { faker } from '@faker-js/faker';
-import { Badge, Button, Drawer, Space, Tabs, type TabsProps, Tag } from 'antd';
-import { type CSSProperties, type ReactNode, useState } from 'react';
-
-import CyanBlur from '@/assets/images/background/cyan-blur.png';
-import RedBlur from '@/assets/images/background/red-blur.png';
-import { IconButton, Iconify, SvgIcon } from '@/components/icon';
-import { themeVars } from '@/theme/theme.css';
+import { type CSSProperties, type ReactNode, useState } from 'react'
+import CyanBlur from '@/assets/images/background/cyan-blur.png'
+import RedBlur from '@/assets/images/background/red-blur.png'
+import { faker } from '@faker-js/faker'
+import { Badge, Button, Drawer, Space, Tabs, type TabsProps, Tag } from 'antd'
+import { themeVars } from '@/theme/theme.css'
+import { IconButton, Iconify, SvgIcon } from '@/components/icon'
 
 export default function NoticeButton() {
-	const [drawerOpen, setDrawerOpen] = useState(false);
-	const [count, setCount] = useState(4);
+  const [drawerOpen, setDrawerOpen] = useState(false)
+  const [count, setCount] = useState(4)
 
-	const style: CSSProperties = {
-		backdropFilter: 'blur(20px)',
-		backgroundImage: `url("${CyanBlur}"), url("${RedBlur}")`,
-		backgroundRepeat: 'no-repeat, no-repeat',
-		backgroundColor: `rgba(${themeVars.colors.background.paperChannel}, 0.9)`,
-		backgroundPosition: 'right top, left bottom',
-		backgroundSize: '50, 50%'
-	};
+  const style: CSSProperties = {
+    backdropFilter: 'blur(20px)',
+    backgroundImage: `url("${CyanBlur}"), url("${RedBlur}")`,
+    backgroundRepeat: 'no-repeat, no-repeat',
+    backgroundColor: `rgba(${themeVars.colors.background.paperChannel}, 0.9)`,
+    backgroundPosition: 'right top, left bottom',
+    backgroundSize: '50, 50%'
+  }
 
-	return (
-		<div>
-			<IconButton onClick={() => setDrawerOpen(true)}>
-				<Badge
-					count={count}
-					styles={{
-						root: { color: 'inherit' },
-						indicator: { color: themeVars.colors.common.white }
-					}}
-				>
-					<Iconify icon="solar:bell-bing-bold-duotone" size={24} />
-				</Badge>
-			</IconButton>
-			<Drawer
-				placement="right"
-				title="Notifications"
-				onClose={() => setDrawerOpen(false)}
-				open={drawerOpen}
-				closable={false}
-				width={420}
-				styles={{
-					body: { padding: 0 },
-					mask: { backgroundColor: 'transparent' }
-				}}
-				style={style}
-				extra={
-					<IconButton
-						style={{ color: themeVars.colors.palette.primary.default }}
-						onClick={() => {
-							setCount(0);
-							setDrawerOpen(false);
-						}}
-					>
-						<Iconify icon="solar:check-read-broken" size={20} />
-					</IconButton>
-				}
-				footer={
-					<div
-						style={{ color: themeVars.colors.text.primary }}
-						className="flex h-10 w-full items-center justify-center font-semibold"
-					>
-						View All
-					</div>
-				}
-			>
-				<NoticeTab />
-			</Drawer>
-		</div>
-	);
+  return (
+    <div>
+      <IconButton onClick={() => setDrawerOpen(true)}>
+        <Badge
+          count={count}
+          styles={{
+            root: { color: 'inherit' },
+            indicator: { color: themeVars.colors.common.white }
+          }}>
+          <Iconify icon='solar:bell-bing-bold-duotone' size={24} />
+        </Badge>
+      </IconButton>
+      <Drawer
+        placement='right'
+        title='Notifications'
+        onClose={() => setDrawerOpen(false)}
+        open={drawerOpen}
+        closable={false}
+        width={420}
+        styles={{
+          body: { padding: 0 },
+          mask: { backgroundColor: 'transparent' }
+        }}
+        style={style}
+        extra={
+          <IconButton
+            style={{ color: themeVars.colors.palette.primary.default }}
+            onClick={() => {
+              setCount(0)
+              setDrawerOpen(false)
+            }}>
+            <Iconify icon='solar:check-read-broken' size={20} />
+          </IconButton>
+        }
+        footer={
+          <div
+            style={{ color: themeVars.colors.text.primary }}
+            className='flex h-10 w-full items-center justify-center font-semibold'>
+            View All
+          </div>
+        }>
+        <NoticeTab />
+      </Drawer>
+    </div>
+  )
 }
 
 function NoticeTab() {
-	const tabChildren: ReactNode = (
-		<div className="text-sm">
-			<div className="flex">
-				<img className="h-10 w-10 rounded-full" src={faker.image.avatarGitHub()} alt="" />
-				<div className="ml-2">
-					<div>
-						<span className="font-medium">{faker.person.fullName()}</span>
-						<span className="text-xs font-light"> sent you a frind request</span>
-					</div>
-					<span className="text-xs font-light opacity-60">about 1 hour ago</span>
-					<div className="mt-2">
-						<Space>
-							<Button type="primary">Accept</Button>
-							<Button>Refuse</Button>
-						</Space>
-					</div>
-				</div>
-			</div>
+  const tabChildren: ReactNode = (
+    <div className='text-sm'>
+      <div className='flex'>
+        <img className='h-10 w-10 rounded-full' src={faker.image.avatarGitHub()} alt='' />
+        <div className='ml-2'>
+          <div>
+            <span className='font-medium'>{faker.person.fullName()}</span>
+            <span className='text-xs font-light'> sent you a frind request</span>
+          </div>
+          <span className='text-xs font-light opacity-60'>about 1 hour ago</span>
+          <div className='mt-2'>
+            <Space>
+              <Button type='primary'>Accept</Button>
+              <Button>Refuse</Button>
+            </Space>
+          </div>
+        </div>
+      </div>
 
-			<div className="mt-8 flex">
-				<img className="h-10 w-10 rounded-full" src={faker.image.avatarGitHub()} alt="" />
-				<div className="ml-2">
-					<div>
-						<span className="font-medium">{faker.person.fullName()}</span>
-						<span className="text-xs font-light"> added file to </span>
-						<span className="font-medium">File Manager</span>
-					</div>
-					<span className="text-xs font-light opacity-60">5 hour ago</span>
-					<div className="mt-2 flex items-center rounded-lg bg-bg-neutral p-4">
-						<div className="ml-2 flex flex-col text-gray">
-							<span className="font-medium">@{faker.person.fullName()}</span>
-							<span className="text-xs">{faker.lorem.lines(2)}</span>
-						</div>
-					</div>
-					<div className="mt-2">
-						<Space>
-							<Button type="primary">Reply</Button>
-						</Space>
-					</div>
-				</div>
-			</div>
+      <div className='mt-8 flex'>
+        <img className='h-10 w-10 rounded-full' src={faker.image.avatarGitHub()} alt='' />
+        <div className='ml-2'>
+          <div>
+            <span className='font-medium'>{faker.person.fullName()}</span>
+            <span className='text-xs font-light'> added file to </span>
+            <span className='font-medium'>File Manager</span>
+          </div>
+          <span className='text-xs font-light opacity-60'>5 hour ago</span>
+          <div className='mt-2 flex items-center rounded-lg bg-bg-neutral p-4'>
+            <div className='ml-2 flex flex-col text-gray'>
+              <span className='font-medium'>@{faker.person.fullName()}</span>
+              <span className='text-xs'>{faker.lorem.lines(2)}</span>
+            </div>
+          </div>
+          <div className='mt-2'>
+            <Space>
+              <Button type='primary'>Reply</Button>
+            </Space>
+          </div>
+        </div>
+      </div>
 
-			<div className="mt-8 flex">
-				<img className="h-10 w-10 rounded-full" src={faker.image.avatarGitHub()} alt="" />
-				<div className="ml-2">
-					<div>
-						<span className="font-medium">{faker.person.fullName()}</span>
-						<span className="text-xs font-light"> mentioned you in</span>
-						<span className="font-medium">Slash Admin</span>
-					</div>
-					<span className="text-xs font-light opacity-60">1 days ago</span>
-					<div className="mt-2">
-						<Space>
-							<Button type="primary">Reply</Button>
-						</Space>
-					</div>
-				</div>
-			</div>
+      <div className='mt-8 flex'>
+        <img className='h-10 w-10 rounded-full' src={faker.image.avatarGitHub()} alt='' />
+        <div className='ml-2'>
+          <div>
+            <span className='font-medium'>{faker.person.fullName()}</span>
+            <span className='text-xs font-light'> mentioned you in</span>
+            <span className='font-medium'>Slash Admin</span>
+          </div>
+          <span className='text-xs font-light opacity-60'>1 days ago</span>
+          <div className='mt-2'>
+            <Space>
+              <Button type='primary'>Reply</Button>
+            </Space>
+          </div>
+        </div>
+      </div>
 
-			<div className="mt-8 flex">
-				<img className="h-10 w-10 rounded-full" src={faker.image.avatarGitHub()} alt="" />
-				<div className="ml-2">
-					<div>
-						<span className="font-medium">{faker.person.fullName()}</span>
-						<span className="text-xs font-light"> added file to </span>
-						<span className="font-medium">File Manager</span>
-					</div>
-					<span className="text-xs font-light opacity-60">2 days ago</span>
-					<div className="mt-2 flex items-center rounded-lg bg-bg-neutral p-4">
-						<SvgIcon icon="ic_file_audio" size={48} />
-						<div className="ml-2 flex flex-col text-gray">
-							<span className="font-medium">Witout Me</span>
-							<span className="text-xs">1.2GB·30 min ago </span>
-						</div>
-						<Button className="ml-4">Download</Button>
-					</div>
-				</div>
-			</div>
+      <div className='mt-8 flex'>
+        <img className='h-10 w-10 rounded-full' src={faker.image.avatarGitHub()} alt='' />
+        <div className='ml-2'>
+          <div>
+            <span className='font-medium'>{faker.person.fullName()}</span>
+            <span className='text-xs font-light'> added file to </span>
+            <span className='font-medium'>File Manager</span>
+          </div>
+          <span className='text-xs font-light opacity-60'>2 days ago</span>
+          <div className='mt-2 flex items-center rounded-lg bg-bg-neutral p-4'>
+            <SvgIcon icon='ic_file_audio' size={48} />
+            <div className='ml-2 flex flex-col text-gray'>
+              <span className='font-medium'>Witout Me</span>
+              <span className='text-xs'>1.2GB·30 min ago </span>
+            </div>
+            <Button className='ml-4'>Download</Button>
+          </div>
+        </div>
+      </div>
 
-			<div className="mt-8 flex">
-				<img className="h-10 w-10 rounded-full" src={faker.image.avatarGitHub()} alt="" />
-				<div className="ml-2">
-					<div>
-						<span className="font-medium">{faker.person.fullName()}</span>
-						<span className="text-xs font-light"> request a payment of </span>
-						<span className="font-medium">$3000</span>
-					</div>
-					<span className="text-xs font-light opacity-60">4 days ago</span>
-					<div className="mt-2">
-						<Space>
-							<Button type="primary">Pay</Button>
-							<Button>Refuse</Button>
-						</Space>
-					</div>
-				</div>
-			</div>
+      <div className='mt-8 flex'>
+        <img className='h-10 w-10 rounded-full' src={faker.image.avatarGitHub()} alt='' />
+        <div className='ml-2'>
+          <div>
+            <span className='font-medium'>{faker.person.fullName()}</span>
+            <span className='text-xs font-light'> request a payment of </span>
+            <span className='font-medium'>$3000</span>
+          </div>
+          <span className='text-xs font-light opacity-60'>4 days ago</span>
+          <div className='mt-2'>
+            <Space>
+              <Button type='primary'>Pay</Button>
+              <Button>Refuse</Button>
+            </Space>
+          </div>
+        </div>
+      </div>
 
-			<div className="mt-8 flex">
-				<IconButton>
-					<SvgIcon icon="ic_order" size={30} />
-				</IconButton>
-				<div className="ml-2">
-					<div>
-						<span className="font-light">Your order is placed waiting for shipping</span>
-					</div>
-					<span className="text-xs font-light opacity-60">4 days ago</span>{' '}
-				</div>
-			</div>
+      <div className='mt-8 flex'>
+        <IconButton>
+          <SvgIcon icon='ic_order' size={30} />
+        </IconButton>
+        <div className='ml-2'>
+          <div>
+            <span className='font-light'>Your order is placed waiting for shipping</span>
+          </div>
+          <span className='text-xs font-light opacity-60'>4 days ago</span>{' '}
+        </div>
+      </div>
 
-			<div className="mt-8 flex">
-				<IconButton>
-					<SvgIcon icon="ic_mail" size={30} />
-				</IconButton>
-				<div className="ml-2">
-					<div>
-						<span className="font-light">You have new mail</span>
-					</div>
-					<span className="text-xs font-light opacity-60">5 days ago</span>{' '}
-				</div>
-			</div>
+      <div className='mt-8 flex'>
+        <IconButton>
+          <SvgIcon icon='ic_mail' size={30} />
+        </IconButton>
+        <div className='ml-2'>
+          <div>
+            <span className='font-light'>You have new mail</span>
+          </div>
+          <span className='text-xs font-light opacity-60'>5 days ago</span>{' '}
+        </div>
+      </div>
 
-			<div className="mt-8 flex">
-				<IconButton>
-					<SvgIcon icon="ic_chat" size={30} />
-				</IconButton>
-				<div className="ml-2">
-					<div>
-						<span className="font-light">You have new message 5 unread message</span>
-					</div>
-					<span className="text-xs font-light opacity-60">7 days ago</span>
-				</div>
-			</div>
+      <div className='mt-8 flex'>
+        <IconButton>
+          <SvgIcon icon='ic_chat' size={30} />
+        </IconButton>
+        <div className='ml-2'>
+          <div>
+            <span className='font-light'>You have new message 5 unread message</span>
+          </div>
+          <span className='text-xs font-light opacity-60'>7 days ago</span>
+        </div>
+      </div>
 
-			<div className="mt-8 flex">
-				<IconButton>
-					<SvgIcon icon="ic_delivery" size={30} />
-				</IconButton>
-				<div className="ml-2">
-					<div>
-						<span className="font-light">Delivery processing your order is being shipped</span>
-					</div>
-					<span className="text-xs font-light opacity-60">8 days ago</span>{' '}
-				</div>
-			</div>
-		</div>
-	);
-	const items: TabsProps['items'] = [
-		{
-			key: '1',
-			label: (
-				<div className="flex">
-					<span>All</span>
-					<Tag color="processing">22</Tag>
-				</div>
-			),
-			children: tabChildren
-		},
-		{
-			key: '2',
-			label: (
-				<div className="flex">
-					<span>Unread</span>
-					<Tag color="error">12</Tag>
-				</div>
-			),
-			children: tabChildren
-		},
-		{
-			key: '3',
-			label: (
-				<div className="flex">
-					<span>Archived</span>
-					<Tag color="green">10</Tag>
-				</div>
-			),
-			children: tabChildren
-		}
-	];
-	return (
-		<div className="flex flex-col px-6">
-			<Tabs defaultActiveKey="1" items={items} />
-		</div>
-	);
+      <div className='mt-8 flex'>
+        <IconButton>
+          <SvgIcon icon='ic_delivery' size={30} />
+        </IconButton>
+        <div className='ml-2'>
+          <div>
+            <span className='font-light'>Delivery processing your order is being shipped</span>
+          </div>
+          <span className='text-xs font-light opacity-60'>8 days ago</span>{' '}
+        </div>
+      </div>
+    </div>
+  )
+  const items: TabsProps['items'] = [
+    {
+      key: '1',
+      label: (
+        <div className='flex'>
+          <span>All</span>
+          <Tag color='processing'>22</Tag>
+        </div>
+      ),
+      children: tabChildren
+    },
+    {
+      key: '2',
+      label: (
+        <div className='flex'>
+          <span>Unread</span>
+          <Tag color='error'>12</Tag>
+        </div>
+      ),
+      children: tabChildren
+    },
+    {
+      key: '3',
+      label: (
+        <div className='flex'>
+          <span>Archived</span>
+          <Tag color='green'>10</Tag>
+        </div>
+      ),
+      children: tabChildren
+    }
+  ]
+  return (
+    <div className='flex flex-col px-6'>
+      <Tabs defaultActiveKey='1' items={items} />
+    </div>
+  )
 }

+ 236 - 241
src/layouts/components/search-bar.tsx

@@ -1,245 +1,240 @@
-import { Empty, Input, type InputRef, Modal, Tag } from 'antd';
-import match from 'autosuggest-highlight/match';
-import parse from 'autosuggest-highlight/parse';
-import { type CSSProperties, useEffect, useMemo, useRef, useState } from 'react';
-import { useTranslation } from 'react-i18next';
-import { useBoolean, useEvent, useKeyPressEvent } from 'react-use';
-import styled from 'styled-components';
-
-import { IconButton, SvgIcon } from '@/components/icon';
-import Scrollbar from '@/components/scrollbar';
-import { useFlattenedRoutes, useRouter } from '@/router/hooks';
-import { themeVars } from '@/theme/theme.css';
-import { rgbAlpha } from '@/utils/theme';
+import { type CSSProperties, useEffect, useMemo, useRef, useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import { useBoolean, useEvent, useKeyPressEvent } from 'react-use'
+import { useFlattenedRoutes, useRouter } from '@/router/hooks'
+import match from 'autosuggest-highlight/match'
+import parse from 'autosuggest-highlight/parse'
+import styled from 'styled-components'
+import { rgbAlpha } from '@/utils/theme'
+import { Empty, Input, type InputRef, Modal, Tag } from 'antd'
+import { themeVars } from '@/theme/theme.css'
+import { IconButton, SvgIcon } from '@/components/icon'
+import Scrollbar from '@/components/scrollbar'
 
 export default function SearchBar() {
-	const { t } = useTranslation();
-	const { replace } = useRouter();
-	const inputRef = useRef<InputRef>(null);
-	const listRef = useRef<HTMLDivElement>(null);
-
-	const [search, toggle] = useBoolean(false);
-
-	const flattenedRoutes = useFlattenedRoutes();
-
-	const activeStyle: CSSProperties = {
-		border: `1px dashed ${themeVars.colors.palette.primary.default}`,
-		backgroundColor: rgbAlpha(themeVars.colors.palette.primary.defaultChannel, 0.1)
-	};
-
-	const [searchQuery, setSearchQuery] = useState('');
-	const [selectedItemIndex, setSelectedItemIndex] = useState(0);
-
-	const searchResult = useMemo(() => {
-		return flattenedRoutes.filter(
-			(item) =>
-				t(item.label).toLowerCase().includes(searchQuery.toLowerCase()) ||
-				item.key.toLowerCase().includes(searchQuery.toLowerCase())
-		);
-	}, [searchQuery, t, flattenedRoutes]);
-
-	// biome-ignore lint/correctness/useExhaustiveDependencies:  在搜索结果变化时重置选中索引
-	useEffect(() => {
-		setSelectedItemIndex(0);
-	}, [searchResult.length]);
-
-	const handleMetaK = (event: KeyboardEvent) => {
-		if (event.metaKey && event.key === 'k') {
-			// https://developer.mozilla.org/zh-CN/docs/Web/API/KeyboardEvent/metaKey
-			handleOpen();
-		}
-	};
-	useEvent('keydown', handleMetaK);
-
-	useKeyPressEvent('ArrowUp', (event) => {
-		if (!search) return;
-		event.preventDefault();
-		let nextIndex = selectedItemIndex - 1;
-		if (nextIndex < 0) {
-			nextIndex = searchResult.length - 1;
-		}
-		setSelectedItemIndex(nextIndex);
-		scrollSelectedItemIntoView(nextIndex);
-	});
-
-	useKeyPressEvent('ArrowDown', (event) => {
-		if (!search) return;
-		event.preventDefault();
-		let nextIndex = selectedItemIndex + 1;
-		if (nextIndex > searchResult.length - 1) {
-			nextIndex = 0;
-		}
-		setSelectedItemIndex(nextIndex);
-		scrollSelectedItemIntoView(nextIndex);
-	});
-
-	useKeyPressEvent('Enter', (event) => {
-		if (!search || searchResult.length === 0) return;
-		event.preventDefault();
-		const selectItem = searchResult[selectedItemIndex].key;
-		if (selectItem) {
-			handleSelect(selectItem);
-			toggle(false);
-		}
-	});
-
-	useKeyPressEvent('Escape', () => {
-		handleCancel();
-	});
-
-	const handleOpen = () => {
-		toggle(true);
-		setSearchQuery('');
-		setSelectedItemIndex(0);
-	};
-	const handleCancel = () => {
-		toggle(false);
-	};
-	const handleAfterOpenChange = (open: boolean) => {
-		if (open) {
-			// auto focus
-			inputRef.current?.focus();
-		}
-	};
-
-	const scrollSelectedItemIntoView = (index: number) => {
-		if (listRef.current) {
-			const selectedItem = listRef.current.children[index];
-			selectedItem.scrollIntoView({
-				behavior: 'smooth',
-				block: 'center'
-			});
-		}
-	};
-
-	const handleHover = (index: number) => {
-		if (index === selectedItemIndex) return;
-		setSelectedItemIndex(index);
-	};
-
-	const handleSelect = (key: string) => {
-		replace(key);
-		handleCancel();
-	};
-
-	return (
-		<>
-			<div className="flex items-center justify-center">
-				<IconButton className="h-8 rounded-xl bg-hover py-2 text-xs font-bold" onClick={handleOpen}>
-					<div className="flex items-center justify-center gap-2">
-						<SvgIcon icon="ic-search" size="20" />
-						<span className="flex h-6 items-center justify-center rounded-md bg-common-white px-1.5 font-bold text-gray-800">
-							{' '}
-							⌘K{' '}
-						</span>
-					</div>
-				</IconButton>
-			</div>
-			<Modal
-				centered
-				keyboard
-				open={search}
-				onCancel={handleCancel}
-				closeIcon={false}
-				afterOpenChange={handleAfterOpenChange}
-				styles={{
-					body: {
-						height: '400px',
-						display: 'flex',
-						flexDirection: 'column',
-						justifyContent: 'center'
-					}
-				}}
-				title={
-					<Input
-						ref={inputRef}
-						value={searchQuery}
-						onChange={(e) => setSearchQuery(e.target.value)}
-						placeholder="Search..."
-						variant="borderless"
-						autoFocus
-						prefix={<SvgIcon icon="ic-search" size="20" />}
-						suffix={
-							<IconButton className="h-6 rounded-md bg-hover text-xs" onClick={handleCancel}>
-								Esc
-							</IconButton>
-						}
-					/>
-				}
-				footer={
-					<div className="flex flex-wrap">
-						<div className="flex">
-							<Tag color="cyan">↑</Tag>
-							<Tag color="cyan">↓</Tag>
-							<span>to navigate</span>
-						</div>
-						<div className="flex">
-							<Tag color="cyan">↵</Tag>
-							<span>to select</span>
-						</div>
-						<div className="flex">
-							<Tag color="cyan">ESC</Tag>
-							<span>to close</span>
-						</div>
-					</div>
-				}
-			>
-				{searchResult.length === 0 ? (
-					<Empty />
-				) : (
-					<Scrollbar>
-						<div ref={listRef} className="py-2">
-							{searchResult.map(({ key, label }, index) => {
-								const partsTitle = parse(t(label), match(t(label), searchQuery));
-								const partsKey = parse(key, match(key, searchQuery));
-								return (
-									<StyledListItemButton
-										key={key}
-										style={index === selectedItemIndex ? activeStyle : {}}
-										onClick={() => handleSelect(key)}
-										onMouseMove={() => handleHover(index)}
-									>
-										<div className="flex justify-between">
-											<div>
-												<div className="font-medium">
-													{partsTitle.map((item) => (
-														<span
-															key={item.text}
-															style={{
-																color: item.highlight
-																	? themeVars.colors.palette.primary.default
-																	: themeVars.colors.text.primary
-															}}
-														>
-															{item.text}
-														</span>
-													))}
-												</div>
-												<div className="text-xs">
-													{partsKey.map((item) => (
-														<span
-															key={item.text}
-															style={{
-																color: item.highlight
-																	? themeVars.colors.palette.primary.default
-																	: themeVars.colors.text.secondary
-															}}
-														>
-															{item.text}
-														</span>
-													))}
-												</div>
-											</div>
-										</div>
-									</StyledListItemButton>
-								);
-							})}
-						</div>
-					</Scrollbar>
-				)}
-			</Modal>
-		</>
-	);
+  const { t } = useTranslation()
+  const { replace } = useRouter()
+  const inputRef = useRef<InputRef>(null)
+  const listRef = useRef<HTMLDivElement>(null)
+
+  const [search, toggle] = useBoolean(false)
+
+  const flattenedRoutes = useFlattenedRoutes()
+
+  const activeStyle: CSSProperties = {
+    border: `1px dashed ${themeVars.colors.palette.primary.default}`,
+    backgroundColor: rgbAlpha(themeVars.colors.palette.primary.defaultChannel, 0.1)
+  }
+
+  const [searchQuery, setSearchQuery] = useState('')
+  const [selectedItemIndex, setSelectedItemIndex] = useState(0)
+
+  const searchResult = useMemo(() => {
+    return flattenedRoutes.filter(
+      (item) =>
+        t(item.label).toLowerCase().includes(searchQuery.toLowerCase()) ||
+        item.key.toLowerCase().includes(searchQuery.toLowerCase())
+    )
+  }, [searchQuery, t, flattenedRoutes])
+
+  // biome-ignore lint/correctness/useExhaustiveDependencies:  在搜索结果变化时重置选中索引
+  useEffect(() => {
+    setSelectedItemIndex(0)
+  }, [searchResult.length])
+
+  const handleMetaK = (event: KeyboardEvent) => {
+    if (event.metaKey && event.key === 'k') {
+      // https://developer.mozilla.org/zh-CN/docs/Web/API/KeyboardEvent/metaKey
+      handleOpen()
+    }
+  }
+  useEvent('keydown', handleMetaK)
+
+  useKeyPressEvent('ArrowUp', (event) => {
+    if (!search) return
+    event.preventDefault()
+    let nextIndex = selectedItemIndex - 1
+    if (nextIndex < 0) {
+      nextIndex = searchResult.length - 1
+    }
+    setSelectedItemIndex(nextIndex)
+    scrollSelectedItemIntoView(nextIndex)
+  })
+
+  useKeyPressEvent('ArrowDown', (event) => {
+    if (!search) return
+    event.preventDefault()
+    let nextIndex = selectedItemIndex + 1
+    if (nextIndex > searchResult.length - 1) {
+      nextIndex = 0
+    }
+    setSelectedItemIndex(nextIndex)
+    scrollSelectedItemIntoView(nextIndex)
+  })
+
+  useKeyPressEvent('Enter', (event) => {
+    if (!search || searchResult.length === 0) return
+    event.preventDefault()
+    const selectItem = searchResult[selectedItemIndex].key
+    if (selectItem) {
+      handleSelect(selectItem)
+      toggle(false)
+    }
+  })
+
+  useKeyPressEvent('Escape', () => {
+    handleCancel()
+  })
+
+  const handleOpen = () => {
+    toggle(true)
+    setSearchQuery('')
+    setSelectedItemIndex(0)
+  }
+  const handleCancel = () => {
+    toggle(false)
+  }
+  const handleAfterOpenChange = (open: boolean) => {
+    if (open) {
+      // auto focus
+      inputRef.current?.focus()
+    }
+  }
+
+  const scrollSelectedItemIntoView = (index: number) => {
+    if (listRef.current) {
+      const selectedItem = listRef.current.children[index]
+      selectedItem.scrollIntoView({
+        behavior: 'smooth',
+        block: 'center'
+      })
+    }
+  }
+
+  const handleHover = (index: number) => {
+    if (index === selectedItemIndex) return
+    setSelectedItemIndex(index)
+  }
+
+  const handleSelect = (key: string) => {
+    replace(key)
+    handleCancel()
+  }
+
+  return (
+    <>
+      <div className='flex items-center justify-center'>
+        <IconButton className='h-8 rounded-xl bg-hover py-2 text-xs font-bold' onClick={handleOpen}>
+          <div className='flex items-center justify-center gap-2'>
+            <SvgIcon icon='ic-search' size='20' />
+            <span className='flex h-6 items-center justify-center rounded-md bg-common-white px-1.5 font-bold text-gray-800'>
+              {' '}
+              ⌘K{' '}
+            </span>
+          </div>
+        </IconButton>
+      </div>
+      <Modal
+        centered
+        keyboard
+        open={search}
+        onCancel={handleCancel}
+        closeIcon={false}
+        afterOpenChange={handleAfterOpenChange}
+        styles={{
+          body: {
+            height: '400px',
+            display: 'flex',
+            flexDirection: 'column',
+            justifyContent: 'center'
+          }
+        }}
+        title={
+          <Input
+            ref={inputRef}
+            value={searchQuery}
+            onChange={(e) => setSearchQuery(e.target.value)}
+            placeholder='Search...'
+            variant='borderless'
+            autoFocus
+            prefix={<SvgIcon icon='ic-search' size='20' />}
+            suffix={
+              <IconButton className='h-6 rounded-md bg-hover text-xs' onClick={handleCancel}>
+                Esc
+              </IconButton>
+            }
+          />
+        }
+        footer={
+          <div className='flex flex-wrap'>
+            <div className='flex'>
+              <Tag color='cyan'>↑</Tag>
+              <Tag color='cyan'>↓</Tag>
+              <span>to navigate</span>
+            </div>
+            <div className='flex'>
+              <Tag color='cyan'>↵</Tag>
+              <span>to select</span>
+            </div>
+            <div className='flex'>
+              <Tag color='cyan'>ESC</Tag>
+              <span>to close</span>
+            </div>
+          </div>
+        }>
+        {searchResult.length === 0 ? (
+          <Empty />
+        ) : (
+          <Scrollbar>
+            <div ref={listRef} className='py-2'>
+              {searchResult.map(({ key, label }, index) => {
+                const partsTitle = parse(t(label), match(t(label), searchQuery))
+                const partsKey = parse(key, match(key, searchQuery))
+                return (
+                  <StyledListItemButton
+                    key={key}
+                    style={index === selectedItemIndex ? activeStyle : {}}
+                    onClick={() => handleSelect(key)}
+                    onMouseMove={() => handleHover(index)}>
+                    <div className='flex justify-between'>
+                      <div>
+                        <div className='font-medium'>
+                          {partsTitle.map((item) => (
+                            <span
+                              key={item.text}
+                              style={{
+                                color: item.highlight
+                                  ? themeVars.colors.palette.primary.default
+                                  : themeVars.colors.text.primary
+                              }}>
+                              {item.text}
+                            </span>
+                          ))}
+                        </div>
+                        <div className='text-xs'>
+                          {partsKey.map((item) => (
+                            <span
+                              key={item.text}
+                              style={{
+                                color: item.highlight
+                                  ? themeVars.colors.palette.primary.default
+                                  : themeVars.colors.text.secondary
+                              }}>
+                              {item.text}
+                            </span>
+                          ))}
+                        </div>
+                      </div>
+                    </div>
+                  </StyledListItemButton>
+                )
+              })}
+            </div>
+          </Scrollbar>
+        )}
+      </Modal>
+    </>
+  )
 }
 
 const StyledListItemButton = styled.div`
@@ -250,4 +245,4 @@ const StyledListItemButton = styled.div`
   padding: 8px 16px;
   border-radius: 8px;
   color: ${themeVars.colors.text.secondary};
-`;
+`

+ 5 - 5
src/layouts/dashboard/config.ts

@@ -1,6 +1,6 @@
-export const NAV_WIDTH = 260;
-export const NAV_COLLAPSED_WIDTH = 80;
-export const NAV_HORIZONTAL_HEIGHT = 48;
+export const NAV_WIDTH = 260
+export const NAV_COLLAPSED_WIDTH = 80
+export const NAV_HORIZONTAL_HEIGHT = 48
 
-export const HEADER_HEIGHT = 64;
-export const MULTI_TABS_HEIGHT = 32;
+export const HEADER_HEIGHT = 64
+export const MULTI_TABS_HEIGHT = 32

+ 47 - 47
src/layouts/dashboard/index.tsx

@@ -1,50 +1,50 @@
-import { Layout } from 'antd';
-import { type CSSProperties, Suspense, useMemo } from 'react';
-
-import { CircleLoading } from '@/components/loading';
-import { useSettings } from '@/store/settingStore';
-import { cn } from '@/utils';
-
-import Header from './header';
-import Main from './main';
-import Nav from './nav';
-
-import { down, useMediaQuery } from '@/hooks';
-import { ThemeLayout } from '#/enum';
-import { NAV_COLLAPSED_WIDTH, NAV_WIDTH } from './config';
+import { type CSSProperties, Suspense, useMemo } from 'react'
+import { useSettings } from '@/store/settingStore'
+import { ThemeLayout } from '#/enum'
+import { down, useMediaQuery } from '@/hooks'
+import { cn } from '@/utils'
+import { Layout } from 'antd'
+import { CircleLoading } from '@/components/loading'
+import { NAV_COLLAPSED_WIDTH, NAV_WIDTH } from './config'
+import Header from './header'
+import Main from './main'
+import Nav from './nav'
 
 function DashboardLayout() {
-	const { themeLayout } = useSettings();
-
-	const mobileOrTablet = useMediaQuery(down('md'));
-
-	const layoutClassName = useMemo(() => {
-		return cn('flex h-screen overflow-hidden', themeLayout === ThemeLayout.Horizontal ? 'flex-col' : 'flex-row');
-	}, [themeLayout]);
-
-	const secondLayoutStyle: CSSProperties = {
-		display: 'flex',
-		flexDirection: 'column',
-		transition: 'all 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
-		paddingLeft: mobileOrTablet
-			? 0
-			: themeLayout === ThemeLayout.Horizontal
-				? 0
-				: themeLayout === ThemeLayout.Mini
-					? NAV_COLLAPSED_WIDTH
-					: NAV_WIDTH
-	};
-
-	return (
-		<Layout className={layoutClassName}>
-			<Suspense fallback={<CircleLoading />}>
-				<Layout style={secondLayoutStyle}>
-					<Header />
-					<Nav />
-					<Main />
-				</Layout>
-			</Suspense>
-		</Layout>
-	);
+  const { themeLayout } = useSettings()
+
+  const mobileOrTablet = useMediaQuery(down('md'))
+
+  const layoutClassName = useMemo(() => {
+    return cn(
+      'flex h-screen overflow-hidden',
+      themeLayout === ThemeLayout.Horizontal ? 'flex-col' : 'flex-row'
+    )
+  }, [themeLayout])
+
+  const secondLayoutStyle: CSSProperties = {
+    display: 'flex',
+    flexDirection: 'column',
+    transition: 'all 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
+    paddingLeft: mobileOrTablet
+      ? 0
+      : themeLayout === ThemeLayout.Horizontal
+        ? 0
+        : themeLayout === ThemeLayout.Mini
+          ? NAV_COLLAPSED_WIDTH
+          : NAV_WIDTH
+  }
+
+  return (
+    <Layout className={layoutClassName}>
+      <Suspense fallback={<CircleLoading />}>
+        <Layout style={secondLayoutStyle}>
+          <Header />
+          <Nav />
+          <Main />
+        </Layout>
+      </Suspense>
+    </Layout>
+  )
 }
-export default DashboardLayout;
+export default DashboardLayout

+ 39 - 40
src/layouts/dashboard/main.tsx

@@ -1,45 +1,44 @@
-import { useSettings } from '@/store/settingStore';
-import { themeVars } from '@/theme/theme.css';
-import { cn } from '@/utils';
-import { Content } from 'antd/es/layout/layout';
-import type { CSSProperties } from 'react';
-import { Outlet } from 'react-router';
-import { ThemeLayout } from '#/enum';
-import { MULTI_TABS_HEIGHT } from './config';
-import MultiTabs from './multi-tabs';
-import { MultiTabsProvider } from './multi-tabs/providers/multi-tabs-provider';
+import type { CSSProperties } from 'react'
+import { Outlet } from 'react-router'
+import { useSettings } from '@/store/settingStore'
+import { ThemeLayout } from '#/enum'
+import { cn } from '@/utils'
+import { Content } from 'antd/es/layout/layout'
+import { themeVars } from '@/theme/theme.css'
+import { MULTI_TABS_HEIGHT } from './config'
+import MultiTabs from './multi-tabs'
+import { MultiTabsProvider } from './multi-tabs/providers/multi-tabs-provider'
 
 const Main = () => {
-	const { themeStretch, themeLayout, multiTab } = useSettings();
+  const { themeStretch, themeLayout, multiTab } = useSettings()
 
-	const mainStyle: CSSProperties = {
-		paddingTop: multiTab ? MULTI_TABS_HEIGHT : 0,
-		background: themeVars.colors.background.default,
-		transition: 'all 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
-		width: '100%'
-	};
+  const mainStyle: CSSProperties = {
+    paddingTop: multiTab ? MULTI_TABS_HEIGHT : 0,
+    background: themeVars.colors.background.default,
+    transition: 'all 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
+    width: '100%'
+  }
 
-	return (
-		<Content style={mainStyle} className="flex">
-			<div className="flex-grow overflow-auto size-full">
-				<div
-					className={cn(
-						'm-auto size-full flex-grow sm:p-2',
-						themeStretch ? '' : 'xl:max-w-screen-xl',
-						themeLayout === ThemeLayout.Horizontal ? 'flex-col' : 'flex-row'
-					)}
-				>
-					{multiTab ? (
-						<MultiTabsProvider>
-							<MultiTabs />
-						</MultiTabsProvider>
-					) : (
-						<Outlet />
-					)}
-				</div>
-			</div>
-		</Content>
-	);
-};
+  return (
+    <Content style={mainStyle} className='flex'>
+      <div className='flex-grow overflow-auto size-full'>
+        <div
+          className={cn(
+            'm-auto size-full flex-grow sm:p-2',
+            themeStretch ? '' : 'xl:max-w-screen-xl',
+            themeLayout === ThemeLayout.Horizontal ? 'flex-col' : 'flex-row'
+          )}>
+          {multiTab ? (
+            <MultiTabsProvider>
+              <MultiTabs />
+            </MultiTabsProvider>
+          ) : (
+            <Outlet />
+          )}
+        </div>
+      </div>
+    </Content>
+  )
+}
 
-export default Main;
+export default Main

+ 80 - 75
src/layouts/dashboard/multi-tabs/components/sortable-container.tsx

@@ -1,88 +1,93 @@
+import React from 'react'
 import {
-	DndContext,
-	type DragEndEvent,
-	DragOverlay,
-	type DragStartEvent,
-	KeyboardSensor,
-	MeasuringStrategy,
-	PointerSensor,
-	TouchSensor,
-	closestCenter,
-	defaultDropAnimationSideEffects,
-	useSensor,
-	useSensors
-} from '@dnd-kit/core';
-import { SortableContext, horizontalListSortingStrategy } from '@dnd-kit/sortable';
-import React from 'react';
+  closestCenter,
+  defaultDropAnimationSideEffects,
+  DndContext,
+  type DragEndEvent,
+  DragOverlay,
+  type DragStartEvent,
+  KeyboardSensor,
+  MeasuringStrategy,
+  PointerSensor,
+  TouchSensor,
+  useSensor,
+  useSensors
+} from '@dnd-kit/core'
+import { horizontalListSortingStrategy, SortableContext } from '@dnd-kit/sortable'
 
 interface SortableContainerProps {
-	items: any[];
-	onSortEnd?: (oldIndex: number, newIndex: number) => void;
-	children: React.ReactNode;
-	renderOverlay?: (activeId: string | number) => React.ReactNode;
+  items: any[]
+  onSortEnd?: (oldIndex: number, newIndex: number) => void
+  children: React.ReactNode
+  renderOverlay?: (activeId: string | number) => React.ReactNode
 }
 
-const SortableContainer: React.FC<SortableContainerProps> = ({ items, onSortEnd, children, renderOverlay }) => {
-	const [activeId, setActiveId] = React.useState<string | number | null>(null);
+const SortableContainer: React.FC<SortableContainerProps> = ({
+  items,
+  onSortEnd,
+  children,
+  renderOverlay
+}) => {
+  const [activeId, setActiveId] = React.useState<string | number | null>(null)
 
-	// 配置拖拽传感器
-	const sensors = useSensors(
-		useSensor(PointerSensor, {
-			activationConstraint: {
-				distance: 8 // 8px 的移动距离后才触发拖拽
-			}
-		}),
-		useSensor(TouchSensor),
-		useSensor(KeyboardSensor)
-	);
+  // 配置拖拽传感器
+  const sensors = useSensors(
+    useSensor(PointerSensor, {
+      activationConstraint: {
+        distance: 8 // 8px 的移动距离后才触发拖拽
+      }
+    }),
+    useSensor(TouchSensor),
+    useSensor(KeyboardSensor)
+  )
 
-	// 开始拖拽时的处理
-	const handleDragStart = (event: DragStartEvent) => {
-		setActiveId(event.active.id);
-	};
+  // 开始拖拽时的处理
+  const handleDragStart = (event: DragStartEvent) => {
+    setActiveId(event.active.id)
+  }
 
-	// 结束拖拽时的处理
-	const handleDragEnd = (event: DragEndEvent) => {
-		const { active, over } = event;
-		setActiveId(null);
+  // 结束拖拽时的处理
+  const handleDragEnd = (event: DragEndEvent) => {
+    const { active, over } = event
+    setActiveId(null)
 
-		if (over && active.id !== over.id) {
-			const oldIndex = items.findIndex((item) => item.key === active.id);
-			const newIndex = items.findIndex((item) => item.key === over.id);
+    if (over && active.id !== over.id) {
+      const oldIndex = items.findIndex((item) => item.key === active.id)
+      const newIndex = items.findIndex((item) => item.key === over.id)
 
-			if (oldIndex !== -1 && newIndex !== -1) {
-				onSortEnd?.(oldIndex, newIndex);
-			}
-		}
-	};
+      if (oldIndex !== -1 && newIndex !== -1) {
+        onSortEnd?.(oldIndex, newIndex)
+      }
+    }
+  }
 
-	return (
-		<DndContext
-			sensors={sensors}
-			collisionDetection={closestCenter}
-			onDragStart={handleDragStart}
-			onDragEnd={handleDragEnd}
-			measuring={{ droppable: { strategy: MeasuringStrategy.Always } }}
-		>
-			<SortableContext items={items.map((item) => item.key)} strategy={horizontalListSortingStrategy}>
-				{children}
-			</SortableContext>
+  return (
+    <DndContext
+      sensors={sensors}
+      collisionDetection={closestCenter}
+      onDragStart={handleDragStart}
+      onDragEnd={handleDragEnd}
+      measuring={{ droppable: { strategy: MeasuringStrategy.Always } }}>
+      <SortableContext
+        items={items.map((item) => item.key)}
+        strategy={horizontalListSortingStrategy}>
+        {children}
+      </SortableContext>
 
-			<DragOverlay
-				dropAnimation={{
-					sideEffects: defaultDropAnimationSideEffects({
-						styles: {
-							active: {
-								opacity: '0.5'
-							}
-						}
-					})
-				}}
-			>
-				{activeId && renderOverlay ? renderOverlay(activeId) : null}
-			</DragOverlay>
-		</DndContext>
-	);
-};
+      <DragOverlay
+        dropAnimation={{
+          sideEffects: defaultDropAnimationSideEffects({
+            styles: {
+              active: {
+                opacity: '0.5'
+              }
+            }
+          })
+        }}>
+        {activeId && renderOverlay ? renderOverlay(activeId) : null}
+      </DragOverlay>
+    </DndContext>
+  )
+}
 
-export default SortableContainer;
+export default SortableContainer

+ 35 - 36
src/layouts/dashboard/multi-tabs/components/sortable-item.tsx

@@ -1,42 +1,41 @@
-import { cn } from '@/utils';
-import { useSortable } from '@dnd-kit/sortable';
-import { CSS } from '@dnd-kit/utilities';
-import { useMultiTabsContext } from '../providers/multi-tabs-provider';
-import type { KeepAliveTab } from '../types';
-import { TabItem } from './tab-item';
+import { cn } from '@/utils'
+import { useSortable } from '@dnd-kit/sortable'
+import { CSS } from '@dnd-kit/utilities'
+import { useMultiTabsContext } from '../providers/multi-tabs-provider'
+import type { KeepAliveTab } from '../types'
+import { TabItem } from './tab-item'
 
 type Props = {
-	tab: KeepAliveTab;
-	onClick: () => void;
-};
+  tab: KeepAliveTab
+  onClick: () => void
+}
 
 export const SortableItem = ({ tab, onClick }: Props) => {
-	const { activeTabRoutePath, closeTab } = useMultiTabsContext();
-	const isActive = tab.key === activeTabRoutePath;
-	const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
-		id: tab.key,
-		data: {
-			type: 'tab',
-			tab
-		}
-	});
+  const { activeTabRoutePath, closeTab } = useMultiTabsContext()
+  const isActive = tab.key === activeTabRoutePath
+  const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
+    id: tab.key,
+    data: {
+      type: 'tab',
+      tab
+    }
+  })
 
-	const style = {
-		transform: CSS.Translate.toString(transform),
-		transition
-	};
+  const style = {
+    transform: CSS.Translate.toString(transform),
+    transition
+  }
 
-	return (
-		<li
-			ref={setNodeRef}
-			style={style}
-			{...attributes}
-			{...listeners}
-			id={`tab${tab.key.split('/').join('-')}`}
-			onClick={onClick}
-			className={cn('flex-shrink-0 rounded-t-lg border border-border', isActive && 'text-primary')}
-		>
-			<TabItem tab={tab} onClose={() => closeTab(tab.key)} />
-		</li>
-	);
-};
+  return (
+    <li
+      ref={setNodeRef}
+      style={style}
+      {...attributes}
+      {...listeners}
+      id={`tab${tab.key.split('/').join('-')}`}
+      onClick={onClick}
+      className={cn('flex-shrink-0 rounded-t-lg border border-border', isActive && 'text-primary')}>
+      <TabItem tab={tab} onClose={() => closeTab(tab.key)} />
+    </li>
+  )
+}

+ 104 - 102
src/layouts/dashboard/multi-tabs/components/tab-item.tsx

@@ -1,109 +1,111 @@
-import { Iconify } from '@/components/icon';
-import { Dropdown, type MenuProps } from 'antd';
-import { useTranslation } from 'react-i18next';
-import { MultiTabOperation } from '#/enum';
-import { useTabLabelRender } from '../hooks/use-tab-label-render';
-import { useMultiTabsContext } from '../providers/multi-tabs-provider';
-import type { TabItemProps } from '../types';
+import { useTranslation } from 'react-i18next'
+import { MultiTabOperation } from '#/enum'
+import { useTabLabelRender } from '../hooks/use-tab-label-render'
+import { Dropdown, type MenuProps } from 'antd'
+import { Iconify } from '@/components/icon'
+import { useMultiTabsContext } from '../providers/multi-tabs-provider'
+import type { TabItemProps } from '../types'
 
 export function TabItem({ tab, style, onClose }: TabItemProps) {
-	const { t } = useTranslation();
-	const { tabs, refreshTab, closeTab, closeOthersTab, closeLeft, closeRight, closeAll } = useMultiTabsContext();
+  const { t } = useTranslation()
+  const { tabs, refreshTab, closeTab, closeOthersTab, closeLeft, closeRight, closeAll } =
+    useMultiTabsContext()
 
-	const renderTabLabel = useTabLabelRender();
-	const menuItems: MenuProps['items'] = [
-		{
-			label: t(`sys.tab.${MultiTabOperation.REFRESH}`),
-			key: MultiTabOperation.REFRESH,
-			icon: <Iconify icon="mdi:reload" size={18} />
-		},
-		{
-			label: t(`sys.tab.${MultiTabOperation.CLOSE}`),
-			key: MultiTabOperation.CLOSE,
-			icon: <Iconify icon="material-symbols:close" size={18} />,
-			disabled: tabs.length === 1
-		},
-		{
-			type: 'divider'
-		},
-		{
-			label: t(`sys.tab.${MultiTabOperation.CLOSELEFT}`),
-			key: MultiTabOperation.CLOSELEFT,
-			icon: <Iconify icon="material-symbols:tab-close-right-outline" size={18} className="rotate-180" />,
-			disabled: tabs.findIndex((t) => t.key === tab.key) === 0
-		},
-		{
-			label: t(`sys.tab.${MultiTabOperation.CLOSERIGHT}`),
-			key: MultiTabOperation.CLOSERIGHT,
-			icon: <Iconify icon="material-symbols:tab-close-right-outline" size={18} />,
-			disabled: tabs.findIndex((t) => t.key === tab.key) === tabs.length - 1
-		},
-		{
-			type: 'divider'
-		},
-		{
-			label: t(`sys.tab.${MultiTabOperation.CLOSEOTHERS}`),
-			key: MultiTabOperation.CLOSEOTHERS,
-			icon: <Iconify icon="material-symbols:tab-close-outline" size={18} />,
-			disabled: tabs.length === 1
-		},
-		{
-			label: t(`sys.tab.${MultiTabOperation.CLOSEALL}`),
-			key: MultiTabOperation.CLOSEALL,
-			icon: <Iconify icon="mdi:collapse-all-outline" size={18} />
-		}
-	];
+  const renderTabLabel = useTabLabelRender()
+  const menuItems: MenuProps['items'] = [
+    {
+      label: t(`sys.tab.${MultiTabOperation.REFRESH}`),
+      key: MultiTabOperation.REFRESH,
+      icon: <Iconify icon='mdi:reload' size={18} />
+    },
+    {
+      label: t(`sys.tab.${MultiTabOperation.CLOSE}`),
+      key: MultiTabOperation.CLOSE,
+      icon: <Iconify icon='material-symbols:close' size={18} />,
+      disabled: tabs.length === 1
+    },
+    {
+      type: 'divider'
+    },
+    {
+      label: t(`sys.tab.${MultiTabOperation.CLOSELEFT}`),
+      key: MultiTabOperation.CLOSELEFT,
+      icon: (
+        <Iconify icon='material-symbols:tab-close-right-outline' size={18} className='rotate-180' />
+      ),
+      disabled: tabs.findIndex((t) => t.key === tab.key) === 0
+    },
+    {
+      label: t(`sys.tab.${MultiTabOperation.CLOSERIGHT}`),
+      key: MultiTabOperation.CLOSERIGHT,
+      icon: <Iconify icon='material-symbols:tab-close-right-outline' size={18} />,
+      disabled: tabs.findIndex((t) => t.key === tab.key) === tabs.length - 1
+    },
+    {
+      type: 'divider'
+    },
+    {
+      label: t(`sys.tab.${MultiTabOperation.CLOSEOTHERS}`),
+      key: MultiTabOperation.CLOSEOTHERS,
+      icon: <Iconify icon='material-symbols:tab-close-outline' size={18} />,
+      disabled: tabs.length === 1
+    },
+    {
+      label: t(`sys.tab.${MultiTabOperation.CLOSEALL}`),
+      key: MultiTabOperation.CLOSEALL,
+      icon: <Iconify icon='mdi:collapse-all-outline' size={18} />
+    }
+  ]
 
-	const menuClick = (menuInfo: any) => {
-		const { key, domEvent } = menuInfo;
-		domEvent.stopPropagation();
+  const menuClick = (menuInfo: any) => {
+    const { key, domEvent } = menuInfo
+    domEvent.stopPropagation()
 
-		switch (key) {
-			case MultiTabOperation.REFRESH:
-				refreshTab(tab.key);
-				break;
-			case MultiTabOperation.CLOSE:
-				closeTab(tab.key);
-				break;
-			case MultiTabOperation.CLOSEOTHERS:
-				closeOthersTab(tab.key);
-				break;
-			case MultiTabOperation.CLOSELEFT:
-				closeLeft(tab.key);
-				break;
-			case MultiTabOperation.CLOSERIGHT:
-				closeRight(tab.key);
-				break;
-			case MultiTabOperation.CLOSEALL:
-				closeAll();
-				break;
-			default:
-				break;
-		}
-	};
+    switch (key) {
+      case MultiTabOperation.REFRESH:
+        refreshTab(tab.key)
+        break
+      case MultiTabOperation.CLOSE:
+        closeTab(tab.key)
+        break
+      case MultiTabOperation.CLOSEOTHERS:
+        closeOthersTab(tab.key)
+        break
+      case MultiTabOperation.CLOSELEFT:
+        closeLeft(tab.key)
+        break
+      case MultiTabOperation.CLOSERIGHT:
+        closeRight(tab.key)
+        break
+      case MultiTabOperation.CLOSEALL:
+        closeAll()
+        break
+      default:
+        break
+    }
+  }
 
-	return (
-		<Dropdown
-			trigger={['contextMenu']}
-			menu={{
-				items: menuItems,
-				onClick: menuClick
-			}}
-		>
-			<div className="relative flex select-none items-center px-4 py-1" style={style}>
-				<div>{renderTabLabel(tab)}</div>
-				{!tab.hideTab && (
-					<Iconify
-						icon="ion:close-outline"
-						size={18}
-						className="ml-2 cursor-pointer opacity-50"
-						onClick={(e) => {
-							e.stopPropagation();
-							onClose?.();
-						}}
-					/>
-				)}
-			</div>
-		</Dropdown>
-	);
+  return (
+    <Dropdown
+      trigger={['contextMenu']}
+      menu={{
+        items: menuItems,
+        onClick: menuClick
+      }}>
+      <div className='relative flex select-none items-center px-4 py-1' style={style}>
+        <div>{renderTabLabel(tab)}</div>
+        {!tab.hideTab && (
+          <Iconify
+            icon='ion:close-outline'
+            size={18}
+            className='ml-2 cursor-pointer opacity-50'
+            onClick={(e) => {
+              e.stopPropagation()
+              onClose?.()
+            }}
+          />
+        )}
+      </div>
+    </Dropdown>
+  )
 }

+ 27 - 29
src/layouts/dashboard/multi-tabs/hooks/use-tab-label-render.tsx

@@ -1,35 +1,33 @@
-import { useMemo } from 'react';
-import { useTranslation } from 'react-i18next';
-import { USER_LIST } from '@/_mock/assets';
-import type { KeepAliveTab } from '../types';
+import { useMemo } from 'react'
+import { useTranslation } from 'react-i18next'
+import { USER_LIST } from '@/_mock/assets'
+import type { KeepAliveTab } from '../types'
 
 export function useTabLabelRender() {
-	const { t } = useTranslation();
+  const { t } = useTranslation()
 
-	const specialTabRenderMap = useMemo<
-		Record<string, (tab: KeepAliveTab) => React.ReactNode>
-	>(
-		() => ({
-			'sys.menu.system.user_detail': (tab: KeepAliveTab) => {
-				const userId = tab.params?.id;
-				const defaultLabel = t(tab.label);
-				if (userId) {
-					const user = USER_LIST.find((item) => item.id === userId);
-					return `${user?.username}-${defaultLabel}`;
-				}
-				return defaultLabel;
-			}
-		}),
-		[t]
-	);
+  const specialTabRenderMap = useMemo<Record<string, (tab: KeepAliveTab) => React.ReactNode>>(
+    () => ({
+      'sys.menu.system.user_detail': (tab: KeepAliveTab) => {
+        const userId = tab.params?.id
+        const defaultLabel = t(tab.label)
+        if (userId) {
+          const user = USER_LIST.find((item) => item.id === userId)
+          return `${user?.username}-${defaultLabel}`
+        }
+        return defaultLabel
+      }
+    }),
+    [t]
+  )
 
-	const renderTabLabel = (tab: KeepAliveTab) => {
-		const specialRender = specialTabRenderMap[tab.label];
-		if (specialRender) {
-			return specialRender(tab);
-		}
-		return t(tab.label);
-	};
+  const renderTabLabel = (tab: KeepAliveTab) => {
+    const specialRender = specialTabRenderMap[tab.label]
+    if (specialRender) {
+      return specialRender(tab)
+    }
+    return t(tab.label)
+  }
 
-	return renderTabLabel;
+  return renderTabLabel
 }

+ 79 - 79
src/layouts/dashboard/multi-tabs/hooks/use-tab-operations.ts

@@ -1,94 +1,94 @@
-import { useRouter } from '@/router/hooks';
-import { type Dispatch, type SetStateAction, useCallback } from 'react';
-import type { KeepAliveTab } from '../types';
+import { type Dispatch, type SetStateAction, useCallback } from 'react'
+import { useRouter } from '@/router/hooks'
+import type { KeepAliveTab } from '../types'
 
-const { VITE_APP_HOMEPAGE: HOMEPAGE } = import.meta.env;
+const { VITE_APP_HOMEPAGE: HOMEPAGE } = import.meta.env
 
 export function useTabOperations(
-	tabs: KeepAliveTab[],
-	setTabs: Dispatch<SetStateAction<KeepAliveTab[]>>,
-	activeTabRoutePath: string
+  tabs: KeepAliveTab[],
+  setTabs: Dispatch<SetStateAction<KeepAliveTab[]>>,
+  activeTabRoutePath: string
 ) {
-	const { push } = useRouter();
+  const { push } = useRouter()
 
-	const closeTab = useCallback(
-		(path = activeTabRoutePath) => {
-			const tempTabs = [...tabs];
-			if (tempTabs.length === 1) return;
+  const closeTab = useCallback(
+    (path = activeTabRoutePath) => {
+      const tempTabs = [...tabs]
+      if (tempTabs.length === 1) return
 
-			const deleteTabIndex = tempTabs.findIndex((item) => item.key === path);
-			if (deleteTabIndex === -1) return;
+      const deleteTabIndex = tempTabs.findIndex((item) => item.key === path)
+      if (deleteTabIndex === -1) return
 
-			if (deleteTabIndex > 0) {
-				push(tempTabs[deleteTabIndex - 1].key);
-			} else {
-				push(tempTabs[deleteTabIndex + 1].key);
-			}
+      if (deleteTabIndex > 0) {
+        push(tempTabs[deleteTabIndex - 1].key)
+      } else {
+        push(tempTabs[deleteTabIndex + 1].key)
+      }
 
-			tempTabs.splice(deleteTabIndex, 1);
-			setTabs(tempTabs);
-		},
-		[activeTabRoutePath, push, tabs, setTabs]
-	);
+      tempTabs.splice(deleteTabIndex, 1)
+      setTabs(tempTabs)
+    },
+    [activeTabRoutePath, push, tabs, setTabs]
+  )
 
-	const closeOthersTab = useCallback(
-		(path = activeTabRoutePath) => {
-			setTabs((prev) => prev.filter((item) => item.key === path));
-			if (path !== activeTabRoutePath) {
-				push(path);
-			}
-		},
-		[activeTabRoutePath, push, setTabs]
-	);
+  const closeOthersTab = useCallback(
+    (path = activeTabRoutePath) => {
+      setTabs((prev) => prev.filter((item) => item.key === path))
+      if (path !== activeTabRoutePath) {
+        push(path)
+      }
+    },
+    [activeTabRoutePath, push, setTabs]
+  )
 
-	const closeAll = useCallback(() => {
-		setTabs([]);
-		push(HOMEPAGE);
-	}, [push, setTabs]);
+  const closeAll = useCallback(() => {
+    setTabs([])
+    push(HOMEPAGE)
+  }, [push, setTabs])
 
-	const closeLeft = useCallback(
-		(path: string) => {
-			const currentTabIndex = tabs.findIndex((item) => item.key === path);
-			const newTabs = tabs.slice(currentTabIndex);
-			setTabs(newTabs);
-			push(path);
-		},
-		[push, tabs, setTabs]
-	);
+  const closeLeft = useCallback(
+    (path: string) => {
+      const currentTabIndex = tabs.findIndex((item) => item.key === path)
+      const newTabs = tabs.slice(currentTabIndex)
+      setTabs(newTabs)
+      push(path)
+    },
+    [push, tabs, setTabs]
+  )
 
-	const closeRight = useCallback(
-		(path: string) => {
-			const currentTabIndex = tabs.findIndex((item) => item.key === path);
-			const newTabs = tabs.slice(0, currentTabIndex + 1);
-			setTabs(newTabs);
-			push(path);
-		},
-		[push, tabs, setTabs]
-	);
+  const closeRight = useCallback(
+    (path: string) => {
+      const currentTabIndex = tabs.findIndex((item) => item.key === path)
+      const newTabs = tabs.slice(0, currentTabIndex + 1)
+      setTabs(newTabs)
+      push(path)
+    },
+    [push, tabs, setTabs]
+  )
 
-	const refreshTab = useCallback(
-		(path = activeTabRoutePath) => {
-			setTabs((prev) => {
-				const newTabs = [...prev];
-				const index = newTabs.findIndex((item) => item.key === path);
-				if (index >= 0) {
-					newTabs[index] = {
-						...newTabs[index],
-						timeStamp: new Date().getTime().toString()
-					};
-				}
-				return newTabs;
-			});
-		},
-		[activeTabRoutePath, setTabs]
-	);
+  const refreshTab = useCallback(
+    (path = activeTabRoutePath) => {
+      setTabs((prev) => {
+        const newTabs = [...prev]
+        const index = newTabs.findIndex((item) => item.key === path)
+        if (index >= 0) {
+          newTabs[index] = {
+            ...newTabs[index],
+            timeStamp: new Date().getTime().toString()
+          }
+        }
+        return newTabs
+      })
+    },
+    [activeTabRoutePath, setTabs]
+  )
 
-	return {
-		closeTab,
-		closeOthersTab,
-		closeAll,
-		closeLeft,
-		closeRight,
-		refreshTab
-	};
+  return {
+    closeTab,
+    closeOthersTab,
+    closeAll,
+    closeLeft,
+    closeRight,
+    refreshTab
+  }
 }

+ 32 - 27
src/layouts/dashboard/multi-tabs/hooks/use-tab-style.ts

@@ -1,33 +1,38 @@
-import { up, useMediaQuery } from '@/hooks';
-import { useSettings } from '@/store/settingStore';
-import { themeVars } from '@/theme/theme.css';
-import { rgbAlpha } from '@/utils/theme';
-import { type CSSProperties, useMemo } from 'react';
-import { ThemeLayout } from '#/enum';
-
-import { HEADER_HEIGHT, MULTI_TABS_HEIGHT, NAV_COLLAPSED_WIDTH, NAV_HORIZONTAL_HEIGHT, NAV_WIDTH } from '../../config';
+import { type CSSProperties, useMemo } from 'react'
+import { useSettings } from '@/store/settingStore'
+import { ThemeLayout } from '#/enum'
+import { up, useMediaQuery } from '@/hooks'
+import { rgbAlpha } from '@/utils/theme'
+import { themeVars } from '@/theme/theme.css'
+import {
+  HEADER_HEIGHT,
+  MULTI_TABS_HEIGHT,
+  NAV_COLLAPSED_WIDTH,
+  NAV_HORIZONTAL_HEIGHT,
+  NAV_WIDTH
+} from '../../config'
 
 export function useMultiTabsStyle() {
-	const { themeLayout } = useSettings();
-	const isPc = useMediaQuery(up('md'));
+  const { themeLayout } = useSettings()
+  const isPc = useMediaQuery(up('md'))
 
-	return useMemo(() => {
-		const style: CSSProperties = {
-			position: 'fixed',
-			top: HEADER_HEIGHT,
-			right: 0,
-			height: MULTI_TABS_HEIGHT,
-			backgroundColor: rgbAlpha(themeVars.colors.background.defaultChannel, 0.9),
-			transition: 'all 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
-			width: '100%'
-		};
+  return useMemo(() => {
+    const style: CSSProperties = {
+      position: 'fixed',
+      top: HEADER_HEIGHT,
+      right: 0,
+      height: MULTI_TABS_HEIGHT,
+      backgroundColor: rgbAlpha(themeVars.colors.background.defaultChannel, 0.9),
+      transition: 'all 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms',
+      width: '100%'
+    }
 
-		if (themeLayout === ThemeLayout.Horizontal) {
-			style.top = HEADER_HEIGHT + NAV_HORIZONTAL_HEIGHT - 2;
-		} else if (isPc) {
-			style.width = `calc(100% - ${themeLayout === ThemeLayout.Vertical ? NAV_WIDTH : NAV_COLLAPSED_WIDTH}px`;
-		}
+    if (themeLayout === ThemeLayout.Horizontal) {
+      style.top = HEADER_HEIGHT + NAV_HORIZONTAL_HEIGHT - 2
+    } else if (isPc) {
+      style.width = `calc(100% - ${themeLayout === ThemeLayout.Vertical ? NAV_WIDTH : NAV_COLLAPSED_WIDTH}px`
+    }
 
-		return style;
-	}, [themeLayout, isPc]);
+    return style
+  }, [themeLayout, isPc])
 }

+ 109 - 102
src/layouts/dashboard/multi-tabs/index.tsx

@@ -1,113 +1,120 @@
-import { useRouter } from '@/router/hooks';
-import { replaceDynamicParams } from '@/router/hooks/use-current-route-meta';
-import { Tabs } from 'antd';
-import { useEffect, useRef } from 'react';
-import styled from 'styled-components';
-import SortableContainer from './components/sortable-container';
-import { SortableItem } from './components/sortable-item';
-import { TabItem } from './components/tab-item';
-import { useMultiTabsStyle } from './hooks/use-tab-style';
-import { useMultiTabsContext } from './providers/multi-tabs-provider';
-import type { KeepAliveTab } from './types';
+import { useEffect, useRef } from 'react'
+import { useRouter } from '@/router/hooks'
+import { replaceDynamicParams } from '@/router/hooks/use-current-route-meta'
+import styled from 'styled-components'
+import { useMultiTabsStyle } from './hooks/use-tab-style'
+import { Tabs } from 'antd'
+import SortableContainer from './components/sortable-container'
+import { SortableItem } from './components/sortable-item'
+import { TabItem } from './components/tab-item'
+import { useMultiTabsContext } from './providers/multi-tabs-provider'
+import type { KeepAliveTab } from './types'
 
 export default function MultiTabs() {
-	const scrollContainer = useRef<HTMLUListElement>(null);
-
-	const { tabs, activeTabRoutePath, setTabs } = useMultiTabsContext();
-	const style = useMultiTabsStyle();
-	const { push } = useRouter();
-
-	const handleTabClick = ({ key, params = {} }: KeepAliveTab) => {
-		console.log('handleTabClick', key, params);
-		const tabKey = replaceDynamicParams(key, params);
-		push(tabKey);
-	};
-
-	useEffect(() => {
-		if (!scrollContainer.current) return;
-		const tab = tabs.find((item) => item.key === activeTabRoutePath);
-		const currentTabElement = scrollContainer.current.querySelector(`#tab${tab?.key.split('/').join('-')}`);
-		if (currentTabElement) {
-			currentTabElement.scrollIntoView({
-				block: 'nearest',
-				behavior: 'smooth'
-			});
-		}
-	}, [tabs, activeTabRoutePath]);
-
-	useEffect(() => {
-		const container = scrollContainer.current;
-		if (!container) return;
-
-		const handleWheel = (e: WheelEvent) => {
-			e.preventDefault();
-			container.scrollLeft += e.deltaY;
-		};
-
-		container.addEventListener('mouseenter', () => {
-			container.addEventListener('wheel', handleWheel);
-		});
-
-		container.addEventListener('mouseleave', () => {
-			container.removeEventListener('wheel', handleWheel);
-		});
-
-		return () => {
-			container.removeEventListener('wheel', handleWheel);
-		};
-	}, []);
-
-	const handleDragEnd = (oldIndex: number, newIndex: number) => {
-		const newTabs = Array.from(tabs);
-		const [movedTab] = newTabs.splice(oldIndex, 1);
-		newTabs.splice(newIndex, 0, movedTab);
-
-		setTabs([...newTabs]);
-	};
-
-	const renderOverlay = (id: string | number) => {
-		const tab = tabs.find((tab) => tab.key === id);
-		if (!tab) return null;
-		return <TabItem tab={tab} />;
-	};
-
-	return (
-		<StyledMultiTabs>
-			<Tabs
-				size="small"
-				type="card"
-				tabBarGutter={4}
-				activeKey={activeTabRoutePath}
-				items={tabs.map((tab) => ({
-					...tab,
-					children: <div key={tab.timeStamp}>{tab.children}</div>
-				}))}
-				renderTabBar={() => {
-					return (
-						<div style={style}>
-							<SortableContainer items={tabs} onSortEnd={handleDragEnd} renderOverlay={renderOverlay}>
-								<ul ref={scrollContainer} className="flex overflow-x-auto w-full px-2 h-[32px] hide-scrollbar">
-									{tabs.map((tab) => (
-										<SortableItem tab={tab} key={tab.key} onClick={() => handleTabClick(tab)} />
-									))}
-								</ul>
-							</SortableContainer>
-						</div>
-					);
-				}}
-			/>
-		</StyledMultiTabs>
-	);
+  const scrollContainer = useRef<HTMLUListElement>(null)
+
+  const { tabs, activeTabRoutePath, setTabs } = useMultiTabsContext()
+  const style = useMultiTabsStyle()
+  const { push } = useRouter()
+
+  const handleTabClick = ({ key, params = {} }: KeepAliveTab) => {
+    console.log('handleTabClick', key, params)
+    const tabKey = replaceDynamicParams(key, params)
+    push(tabKey)
+  }
+
+  useEffect(() => {
+    if (!scrollContainer.current) return
+    const tab = tabs.find((item) => item.key === activeTabRoutePath)
+    const currentTabElement = scrollContainer.current.querySelector(
+      `#tab${tab?.key.split('/').join('-')}`
+    )
+    if (currentTabElement) {
+      currentTabElement.scrollIntoView({
+        block: 'nearest',
+        behavior: 'smooth'
+      })
+    }
+  }, [tabs, activeTabRoutePath])
+
+  useEffect(() => {
+    const container = scrollContainer.current
+    if (!container) return
+
+    const handleWheel = (e: WheelEvent) => {
+      e.preventDefault()
+      container.scrollLeft += e.deltaY
+    }
+
+    container.addEventListener('mouseenter', () => {
+      container.addEventListener('wheel', handleWheel)
+    })
+
+    container.addEventListener('mouseleave', () => {
+      container.removeEventListener('wheel', handleWheel)
+    })
+
+    return () => {
+      container.removeEventListener('wheel', handleWheel)
+    }
+  }, [])
+
+  const handleDragEnd = (oldIndex: number, newIndex: number) => {
+    const newTabs = Array.from(tabs)
+    const [movedTab] = newTabs.splice(oldIndex, 1)
+    newTabs.splice(newIndex, 0, movedTab)
+
+    setTabs([...newTabs])
+  }
+
+  const renderOverlay = (id: string | number) => {
+    const tab = tabs.find((tab) => tab.key === id)
+    if (!tab) return null
+    return <TabItem tab={tab} />
+  }
+
+  return (
+    <StyledMultiTabs>
+      <Tabs
+        size='small'
+        type='card'
+        tabBarGutter={4}
+        activeKey={activeTabRoutePath}
+        items={tabs.map((tab) => ({
+          ...tab,
+          children: <div key={tab.timeStamp}>{tab.children}</div>
+        }))}
+        renderTabBar={() => {
+          return (
+            <div style={style}>
+              <SortableContainer
+                items={tabs}
+                onSortEnd={handleDragEnd}
+                renderOverlay={renderOverlay}>
+                <ul
+                  ref={scrollContainer}
+                  className='flex overflow-x-auto w-full px-2 h-[32px] hide-scrollbar'>
+                  {tabs.map((tab) => (
+                    <SortableItem tab={tab} key={tab.key} onClick={() => handleTabClick(tab)} />
+                  ))}
+                </ul>
+              </SortableContainer>
+            </div>
+          )
+        }}
+      />
+    </StyledMultiTabs>
+  )
 }
 
 const StyledMultiTabs = styled.div`
   height: 100%;
   margin-top: 2px;
-  
+
   .anticon {
     margin: 0px !important;
   }
-  
+
   .ant-tabs {
     height: 100%;
     .ant-tabs-content {
@@ -126,9 +133,9 @@ const StyledMultiTabs = styled.div`
     scrollbar-width: none;
     -ms-overflow-style: none;
     will-change: transform;
- 
+
     &::-webkit-scrollbar {
       display: none;
     }
   }
-`;
+`

+ 59 - 59
src/layouts/dashboard/multi-tabs/providers/multi-tabs-provider.tsx

@@ -1,77 +1,77 @@
-import { useCurrentRouteMeta } from '@/router/hooks';
-import { replaceDynamicParams } from '@/router/hooks/use-current-route-meta';
-import { isEmpty } from 'ramda';
-import { createContext, useContext, useEffect, useMemo, useState } from 'react';
-import { useTabOperations } from '../hooks/use-tab-operations';
-import type { KeepAliveTab, MultiTabsContextType } from '../types';
+import { createContext, useContext, useEffect, useMemo, useState } from 'react'
+import { useCurrentRouteMeta } from '@/router/hooks'
+import { replaceDynamicParams } from '@/router/hooks/use-current-route-meta'
+import { isEmpty } from 'ramda'
+import { useTabOperations } from '../hooks/use-tab-operations'
+import type { KeepAliveTab, MultiTabsContextType } from '../types'
 
 const MultiTabsContext = createContext<MultiTabsContextType>({
-	tabs: [],
-	activeTabRoutePath: '',
-	setTabs: () => {},
-	closeTab: () => {},
-	closeOthersTab: () => {},
-	closeAll: () => {},
-	closeLeft: () => {},
-	closeRight: () => {},
-	refreshTab: () => {}
-});
+  tabs: [],
+  activeTabRoutePath: '',
+  setTabs: () => {},
+  closeTab: () => {},
+  closeOthersTab: () => {},
+  closeAll: () => {},
+  closeLeft: () => {},
+  closeRight: () => {},
+  refreshTab: () => {}
+})
 
 export function MultiTabsProvider({ children }: { children: React.ReactNode }) {
-	const [tabs, setTabs] = useState<KeepAliveTab[]>([]);
-	const currentRouteMeta = useCurrentRouteMeta();
+  const [tabs, setTabs] = useState<KeepAliveTab[]>([])
+  const currentRouteMeta = useCurrentRouteMeta()
 
-	const activeTabRoutePath = useMemo(() => {
-		if (!currentRouteMeta) return '';
-		const { key, params = {} } = currentRouteMeta;
-		return isEmpty(params) ? key : replaceDynamicParams(key, params);
-	}, [currentRouteMeta]);
+  const activeTabRoutePath = useMemo(() => {
+    if (!currentRouteMeta) return ''
+    const { key, params = {} } = currentRouteMeta
+    return isEmpty(params) ? key : replaceDynamicParams(key, params)
+  }, [currentRouteMeta])
 
-	const operations = useTabOperations(tabs, setTabs, activeTabRoutePath);
+  const operations = useTabOperations(tabs, setTabs, activeTabRoutePath)
 
-	useEffect(() => {
-		if (!currentRouteMeta) return;
+  useEffect(() => {
+    if (!currentRouteMeta) return
 
-		setTabs((prev) => {
-			const filtered = prev.filter((item) => !item.hideTab);
+    setTabs((prev) => {
+      const filtered = prev.filter((item) => !item.hideTab)
 
-			let { key } = currentRouteMeta;
-			const { outlet: children, params = {} } = currentRouteMeta;
+      let { key } = currentRouteMeta
+      const { outlet: children, params = {} } = currentRouteMeta
 
-			if (!isEmpty(params)) {
-				key = replaceDynamicParams(key, params);
-			}
+      if (!isEmpty(params)) {
+        key = replaceDynamicParams(key, params)
+      }
 
-			const isExisted = filtered.find((item) => item.key === key);
-			if (!isExisted) {
-				return [
-					...filtered,
-					{
-						...currentRouteMeta,
-						key,
-						children,
-						timeStamp: new Date().getTime().toString()
-					}
-				];
-			}
+      const isExisted = filtered.find((item) => item.key === key)
+      if (!isExisted) {
+        return [
+          ...filtered,
+          {
+            ...currentRouteMeta,
+            key,
+            children,
+            timeStamp: new Date().getTime().toString()
+          }
+        ]
+      }
 
-			return filtered;
-		});
-	}, [currentRouteMeta]);
+      return filtered
+    })
+  }, [currentRouteMeta])
 
-	const contextValue = useMemo(
-		() => ({
-			tabs,
-			activeTabRoutePath,
-			setTabs,
-			...operations
-		}),
-		[tabs, activeTabRoutePath, operations]
-	);
+  const contextValue = useMemo(
+    () => ({
+      tabs,
+      activeTabRoutePath,
+      setTabs,
+      ...operations
+    }),
+    [tabs, activeTabRoutePath, operations]
+  )
 
-	return <MultiTabsContext.Provider value={contextValue}>{children}</MultiTabsContext.Provider>;
+  return <MultiTabsContext.Provider value={contextValue}>{children}</MultiTabsContext.Provider>
 }
 
 export function useMultiTabsContext() {
-	return useContext(MultiTabsContext);
+  return useContext(MultiTabsContext)
 }

+ 24 - 24
src/layouts/dashboard/multi-tabs/types.ts

@@ -1,33 +1,33 @@
-import type { MenuProps } from "antd";
-import type { CSSProperties, ReactNode } from "react";
+import type { CSSProperties, ReactNode } from 'react'
 // types.ts
-import type { RouteMeta } from "#/router";
+import type { RouteMeta } from '#/router'
+import type { MenuProps } from 'antd'
 
 export type KeepAliveTab = RouteMeta & {
-	children: ReactNode;
-	timeStamp?: string;
-};
+  children: ReactNode
+  timeStamp?: string
+}
 
 export type MultiTabsContextType = {
-	tabs: KeepAliveTab[];
-	activeTabRoutePath?: string;
-	setTabs: (tabs: KeepAliveTab[]) => void;
-	closeTab: (path?: string) => void;
-	closeOthersTab: (path?: string) => void;
-	closeAll: () => void;
-	closeLeft: (path: string) => void;
-	closeRight: (path: string) => void;
-	refreshTab: (path: string) => void;
-};
+  tabs: KeepAliveTab[]
+  activeTabRoutePath?: string
+  setTabs: (tabs: KeepAliveTab[]) => void
+  closeTab: (path?: string) => void
+  closeOthersTab: (path?: string) => void
+  closeAll: () => void
+  closeLeft: (path: string) => void
+  closeRight: (path: string) => void
+  refreshTab: (path: string) => void
+}
 
 export type TabItemProps = {
-	tab: KeepAliveTab;
-	style?: CSSProperties;
-	className?: string;
-	onClose?: () => void;
-};
+  tab: KeepAliveTab
+  style?: CSSProperties
+  className?: string
+  onClose?: () => void
+}
 
 export type TabDropdownProps = {
-	menuItems: MenuProps["items"];
-	menuClick: (menuInfo: any, tab: KeepAliveTab) => void;
-};
+  menuItems: MenuProps['items']
+  menuClick: (menuInfo: any, tab: KeepAliveTab) => void
+}

+ 51 - 48
src/layouts/dashboard/nav/nav-horizontal.tsx

@@ -1,51 +1,54 @@
-import { Menu, type MenuProps } from 'antd';
-import { useMemo } from 'react';
-import { useNavigate } from 'react-router';
-
-import { useFlattenedRoutes, usePathname, usePermissionRoutes, useRouteToMenuFn } from '@/router/hooks';
-import { menuFilter } from '@/router/utils';
-
-import { themeVars } from '@/theme/theme.css';
-import { NAV_HORIZONTAL_HEIGHT } from '../config';
+import { useMemo } from 'react'
+import { useNavigate } from 'react-router'
+import {
+  useFlattenedRoutes,
+  usePathname,
+  usePermissionRoutes,
+  useRouteToMenuFn
+} from '@/router/hooks'
+import { menuFilter } from '@/router/utils'
+import { Menu, type MenuProps } from 'antd'
+import { themeVars } from '@/theme/theme.css'
+import { NAV_HORIZONTAL_HEIGHT } from '../config'
 
 export default function NavHorizontal() {
-	const navigate = useNavigate();
-	const pathname = usePathname();
-
-	const routeToMenuFn = useRouteToMenuFn();
-	const permissionRoutes = usePermissionRoutes();
-	const flattenedRoutes = useFlattenedRoutes();
-
-	const menuList = useMemo(() => {
-		const menuRoutes = menuFilter(permissionRoutes);
-		return routeToMenuFn(menuRoutes);
-	}, [routeToMenuFn, permissionRoutes]);
-
-	const selectedKeys = useMemo(() => [pathname], [pathname]);
-
-	const onClick: MenuProps['onClick'] = ({ key }) => {
-		const nextLink = flattenedRoutes?.find((el) => el.key === key);
-		// Handle special case for external links in menu items
-		// For external links: skip internal routing, avoid adding new tab in current project,
-		// prevent selecting current route, and open link in new browser tab
-		if (nextLink?.hideTab && nextLink?.frameSrc) {
-			window.open(nextLink?.frameSrc, '_blank');
-			return;
-		}
-		navigate(key);
-	};
-
-	return (
-		<div className="w-screen" style={{ height: NAV_HORIZONTAL_HEIGHT }}>
-			<Menu
-				mode="horizontal"
-				items={menuList}
-				defaultOpenKeys={[]}
-				selectedKeys={selectedKeys}
-				onClick={onClick}
-				className="!border-none"
-				style={{ background: themeVars.colors.background.default }}
-			/>
-		</div>
-	);
+  const navigate = useNavigate()
+  const pathname = usePathname()
+
+  const routeToMenuFn = useRouteToMenuFn()
+  const permissionRoutes = usePermissionRoutes()
+  const flattenedRoutes = useFlattenedRoutes()
+
+  const menuList = useMemo(() => {
+    const menuRoutes = menuFilter(permissionRoutes)
+    return routeToMenuFn(menuRoutes)
+  }, [routeToMenuFn, permissionRoutes])
+
+  const selectedKeys = useMemo(() => [pathname], [pathname])
+
+  const onClick: MenuProps['onClick'] = ({ key }) => {
+    const nextLink = flattenedRoutes?.find((el) => el.key === key)
+    // Handle special case for external links in menu items
+    // For external links: skip internal routing, avoid adding new tab in current project,
+    // prevent selecting current route, and open link in new browser tab
+    if (nextLink?.hideTab && nextLink?.frameSrc) {
+      window.open(nextLink?.frameSrc, '_blank')
+      return
+    }
+    navigate(key)
+  }
+
+  return (
+    <div className='w-screen' style={{ height: NAV_HORIZONTAL_HEIGHT }}>
+      <Menu
+        mode='horizontal'
+        items={menuList}
+        defaultOpenKeys={[]}
+        selectedKeys={selectedKeys}
+        onClick={onClick}
+        className='!border-none'
+        style={{ background: themeVars.colors.background.default }}
+      />
+    </div>
+  )
 }

+ 10 - 10
src/layouts/simple/index.tsx

@@ -1,14 +1,14 @@
-import type React from 'react';
-import HeaderSimple from '../components/header-simple';
+import type React from 'react'
+import HeaderSimple from '../components/header-simple'
 
 type Props = {
-	children: React.ReactNode;
-};
+  children: React.ReactNode
+}
 export default function SimpleLayout({ children }: Props) {
-	return (
-		<div className="flex h-screen w-full flex-col text-text-base bg-bg">
-			<HeaderSimple />
-			{children}
-		</div>
-	);
+  return (
+    <div className='flex h-screen w-full flex-col text-text-base bg-bg'>
+      <HeaderSimple />
+      {children}
+    </div>
+  )
 }

+ 17 - 17
src/locales/lang/en_US/common.json

@@ -1,22 +1,22 @@
 {
-	"common": {
-		"okText": "OK",
-		"closeText": "Close",
-		"cancelText": "Cancel",
-		"loadingText": "Loading...",
-		"saveText": "Save",
-		"delText": "Delete",
-		"resetText": "Reset",
-		"searchText": "Search",
-		"queryText": "Search",
+  "common": {
+    "okText": "OK",
+    "closeText": "Close",
+    "cancelText": "Cancel",
+    "loadingText": "Loading...",
+    "saveText": "Save",
+    "delText": "Delete",
+    "resetText": "Reset",
+    "searchText": "Search",
+    "queryText": "Search",
 
-		"inputText": "Please enter",
-		"chooseText": "Please choose",
+    "inputText": "Please enter",
+    "chooseText": "Please choose",
 
-		"redo": "Refresh",
-		"back": "Back",
+    "redo": "Refresh",
+    "back": "Back",
 
-		"light": "Light",
-		"dark": "Dark"
-	}
+    "light": "Light",
+    "dark": "Dark"
+  }
 }

+ 5 - 5
src/locales/lang/en_US/index.ts

@@ -1,7 +1,7 @@
-import common from './common.json';
-import sys from './sys.json';
+import common from './common.json'
+import sys from './sys.json'
 
 export default {
-	...common,
-	...sys
-};
+  ...common,
+  ...sys
+}

+ 138 - 138
src/locales/lang/en_US/sys.json

@@ -1,147 +1,147 @@
 {
-	"sys": {
-		"api": {
-			"operationSuccess": "Operation Success",
-			"operationFailed": "Operation failed",
-			"errorTip": "Error Tip",
-			"successTip": "Success Tip",
-			"errorMessage": "The operation failed, the system is abnormal!",
-			"timeoutMessage": "Login timed out, please log in again!",
-			"apiTimeoutMessage": "The interface request timed out, please refresh the page and try again!",
-			"apiRequestFailed": "The interface request failed, please try again later!",
-			"networkException": "network anomaly",
-			"networkExceptionMsg": "Please check if your network connection is normal! The network is abnormal",
+  "sys": {
+    "api": {
+      "operationSuccess": "Operation Success",
+      "operationFailed": "Operation failed",
+      "errorTip": "Error Tip",
+      "successTip": "Success Tip",
+      "errorMessage": "The operation failed, the system is abnormal!",
+      "timeoutMessage": "Login timed out, please log in again!",
+      "apiTimeoutMessage": "The interface request timed out, please refresh the page and try again!",
+      "apiRequestFailed": "The interface request failed, please try again later!",
+      "networkException": "network anomaly",
+      "networkExceptionMsg": "Please check if your network connection is normal! The network is abnormal",
 
-			"errMsg401": "The user does not have permission (token, user name, password error)!",
-			"errMsg403": "The user is authorized, but access is forbidden!",
-			"errMsg404": "Network request error, the resource was not found!",
-			"errMsg405": "Network request error, request method not allowed!",
-			"errMsg408": "Network request timed out!",
-			"errMsg500": "Server error, please contact the administrator!",
-			"errMsg501": "The network is not implemented!",
-			"errMsg502": "Network Error!",
-			"errMsg503": "The service is unavailable, the server is temporarily overloaded or maintained!",
-			"errMsg504": "Network timeout!",
-			"errMsg505": "The http version does not support the request!"
-		},
-		"login": {
-			"backSignIn": "Back sign in",
-			"mobileSignInFormTitle": "Mobile sign in",
-			"qrSignInFormTitle": "Qr code sign in",
-			"signInFormTitle": "Sign in",
-			"signUpFormTitle": "Sign up",
-			"forgetFormTitle": "Reset password",
+      "errMsg401": "The user does not have permission (token, user name, password error)!",
+      "errMsg403": "The user is authorized, but access is forbidden!",
+      "errMsg404": "Network request error, the resource was not found!",
+      "errMsg405": "Network request error, request method not allowed!",
+      "errMsg408": "Network request timed out!",
+      "errMsg500": "Server error, please contact the administrator!",
+      "errMsg501": "The network is not implemented!",
+      "errMsg502": "Network Error!",
+      "errMsg503": "The service is unavailable, the server is temporarily overloaded or maintained!",
+      "errMsg504": "Network timeout!",
+      "errMsg505": "The http version does not support the request!"
+    },
+    "login": {
+      "backSignIn": "Back sign in",
+      "mobileSignInFormTitle": "Mobile sign in",
+      "qrSignInFormTitle": "Qr code sign in",
+      "signInFormTitle": "Sign in",
+      "signUpFormTitle": "Sign up",
+      "forgetFormTitle": "Reset password",
 
-			"signInPrimaryTitle": "Hi, Welcome Back",
-			"signInSecondTitle": "Backstage management system",
-			"signInTitle": "",
-			"signInDesc": "Enter your personal details and get started!",
-			"policy": "I agree to the xxx Privacy Policy",
-			"scanSign": "scanning the code to complete the login",
-			"forgetFormSecondTitle": "Please enter the email address associated with your account and We will email you a link to reset your password.",
+      "signInPrimaryTitle": "Hi, Welcome Back",
+      "signInSecondTitle": "Backstage management system",
+      "signInTitle": "",
+      "signInDesc": "Enter your personal details and get started!",
+      "policy": "I agree to the xxx Privacy Policy",
+      "scanSign": "scanning the code to complete the login",
+      "forgetFormSecondTitle": "Please enter the email address associated with your account and We will email you a link to reset your password.",
 
-			"loginButton": "Sign in",
-			"registerButton": "Sign up",
-			"rememberMe": "Remember me",
-			"forgetPassword": "Forget Password?",
-			"otherSignIn": "Sign in with",
-			"sendSmsButton": "Send SMS code",
-			"sendSmsText": "Reacquire in {{second}}s",
-			"sendEmailButton": "Send Email",
+      "loginButton": "Sign in",
+      "registerButton": "Sign up",
+      "rememberMe": "Remember me",
+      "forgetPassword": "Forget Password?",
+      "otherSignIn": "Sign in with",
+      "sendSmsButton": "Send SMS code",
+      "sendSmsText": "Reacquire in {{second}}s",
+      "sendEmailButton": "Send Email",
 
-			"loginSuccessTitle": "Login successful",
-			"loginSuccessDesc": "Welcome back",
+      "loginSuccessTitle": "Login successful",
+      "loginSuccessDesc": "Welcome back",
 
-			"accountPlaceholder": "Please input username",
-			"passwordPlaceholder": "Please input password",
-			"confirmPasswordPlaceholder": "Please input confirm password",
-			"emaildPlaceholder": "Please input email",
-			"smsPlaceholder": "Please input sms code",
-			"mobilePlaceholder": "Please input mobile",
-			"policyPlaceholder": "Register after checking",
-			"diffPwd": "The two passwords are inconsistent",
+      "accountPlaceholder": "Please input username",
+      "passwordPlaceholder": "Please input password",
+      "confirmPasswordPlaceholder": "Please input confirm password",
+      "emaildPlaceholder": "Please input email",
+      "smsPlaceholder": "Please input sms code",
+      "mobilePlaceholder": "Please input mobile",
+      "policyPlaceholder": "Register after checking",
+      "diffPwd": "The two passwords are inconsistent",
 
-			"userName": "Username",
-			"password": "Password",
-			"confirmPassword": "Confirm Password",
-			"email": "Email",
-			"smsCode": "SMS code",
-			"mobile": "Mobile",
+      "userName": "Username",
+      "password": "Password",
+      "confirmPassword": "Confirm Password",
+      "email": "Email",
+      "smsCode": "SMS code",
+      "mobile": "Mobile",
 
-			"registerAndAgree": "By signing up, I agree to",
-			"termsOfService": " Terms of service ",
-			"privacyPolicy": " Privacy policy ",
-			"logout": "Logout"
-		},
-		"tab": {
-			"fullscreen": "FullScreen",
-			"refresh": "Refresh",
-			"close": "Close",
-			"closeOthers": "Close Others",
-			"closeAll": "Close All",
-			"closeLeft": "Close Left",
-			"closeRight": "Close Right"
-		},
-		"menu": {
-			"dashboard": "Dashboard",
-			"workbench": "Workbench",
-			"analysis": "Analysis",
-			"management": "Management",
-			"user": {
-				"index": "User",
-				"profile": "Profile",
-				"account": "Account"
-			},
-			"system": {
-				"index": "System",
-				"organization": "Organization",
-				"permission": "Permission",
-				"role": "Role",
-				"user": "User",
-				"user_detail": "User Detail"
-			},
-			"blog": "Blog",
-			"components": "Components",
-			"icon": "Icon",
-			"animate": "Animate",
-			"scroll": "Scroll",
-			"markdown": "Markdown",
-			"editor": "Editor",
-			"i18n": "Multi Language",
-			"upload": "Upload",
-			"chart": "Chart",
-			"toast": "Toast",
-			"functions": "Functions",
-			"clipboard": "Clipboard",
-			"token_expired": "Token Expired",
-			"menulevel": {
-				"index": "Menu Level",
-				"1a": "Menu Level 1a",
-				"1b": {
-					"index": "Menu Level 1b",
-					"2a": "Menu Level 2a",
-					"2b": {
-						"index": "Menu Level 2b",
-						"3a": "Menu Level 3a",
-						"3b": "Menu Level 3b"
-					}
-				}
-			},
-			"disabled": "Item Disabled",
-			"label": "Item Label",
-			"frame": "Item External",
-			"external_link": "External Link",
-			"iframe": "Iframe",
-			"blank": "Blank",
-			"calendar": "Calendar",
-			"kanban": "Kanban",
-			"error": {
-				"index": "Error Page",
-				"403": "403",
-				"404": "404",
-				"500": "500"
-			}
-		},
-		"docs": "Document"
-	}
+      "registerAndAgree": "By signing up, I agree to",
+      "termsOfService": " Terms of service ",
+      "privacyPolicy": " Privacy policy ",
+      "logout": "Logout"
+    },
+    "tab": {
+      "fullscreen": "FullScreen",
+      "refresh": "Refresh",
+      "close": "Close",
+      "closeOthers": "Close Others",
+      "closeAll": "Close All",
+      "closeLeft": "Close Left",
+      "closeRight": "Close Right"
+    },
+    "menu": {
+      "dashboard": "Dashboard",
+      "workbench": "Workbench",
+      "analysis": "Analysis",
+      "management": "Management",
+      "user": {
+        "index": "User",
+        "profile": "Profile",
+        "account": "Account"
+      },
+      "system": {
+        "index": "System",
+        "organization": "Organization",
+        "permission": "Permission",
+        "role": "Role",
+        "user": "User",
+        "user_detail": "User Detail"
+      },
+      "blog": "Blog",
+      "components": "Components",
+      "icon": "Icon",
+      "animate": "Animate",
+      "scroll": "Scroll",
+      "markdown": "Markdown",
+      "editor": "Editor",
+      "i18n": "Multi Language",
+      "upload": "Upload",
+      "chart": "Chart",
+      "toast": "Toast",
+      "functions": "Functions",
+      "clipboard": "Clipboard",
+      "token_expired": "Token Expired",
+      "menulevel": {
+        "index": "Menu Level",
+        "1a": "Menu Level 1a",
+        "1b": {
+          "index": "Menu Level 1b",
+          "2a": "Menu Level 2a",
+          "2b": {
+            "index": "Menu Level 2b",
+            "3a": "Menu Level 3a",
+            "3b": "Menu Level 3b"
+          }
+        }
+      },
+      "disabled": "Item Disabled",
+      "label": "Item Label",
+      "frame": "Item External",
+      "external_link": "External Link",
+      "iframe": "Iframe",
+      "blank": "Blank",
+      "calendar": "Calendar",
+      "kanban": "Kanban",
+      "error": {
+        "index": "Error Page",
+        "403": "403",
+        "404": "404",
+        "500": "500"
+      }
+    },
+    "docs": "Document"
+  }
 }

+ 17 - 17
src/locales/lang/zh_CN/common.json

@@ -1,22 +1,22 @@
 {
-	"common": {
-		"okText": "确认",
-		"closeText": "关闭",
-		"cancelText": "取消",
-		"loadingText": "加载中...",
-		"saveText": "保存",
-		"delText": "删除",
-		"resetText": "重置",
-		"searchText": "搜索",
-		"queryText": "查询",
+  "common": {
+    "okText": "确认",
+    "closeText": "关闭",
+    "cancelText": "取消",
+    "loadingText": "加载中...",
+    "saveText": "保存",
+    "delText": "删除",
+    "resetText": "重置",
+    "searchText": "搜索",
+    "queryText": "查询",
 
-		"inputText": "请输入",
-		"chooseText": "请选择",
+    "inputText": "请输入",
+    "chooseText": "请选择",
 
-		"redo": "刷新",
-		"back": "返回",
+    "redo": "刷新",
+    "back": "返回",
 
-		"light": "亮色主题",
-		"dark": "黑暗主题"
-	}
+    "light": "亮色主题",
+    "dark": "黑暗主题"
+  }
 }

+ 5 - 5
src/locales/lang/zh_CN/index.ts

@@ -1,7 +1,7 @@
-import common from './common.json';
-import sys from './sys.json';
+import common from './common.json'
+import sys from './sys.json'
 
 export default {
-	...common,
-	...sys
-};
+  ...common,
+  ...sys
+}

+ 139 - 139
src/locales/lang/zh_CN/sys.json

@@ -1,148 +1,148 @@
 {
-	"sys": {
-		"api": {
-			"operationSuccess": "操作成功",
-			"operationFailed": "操作失败",
-			"errorTip": "错误提示",
-			"successTip": "成功提示",
-			"errorMessage": "操作失败,系统异常!",
-			"timeoutMessage": "登录超时,请重新登录!",
-			"apiTimeoutMessage": "接口请求超时,请刷新页面重试!",
-			"apiRequestFailed": "请求出错,请稍候重试",
-			"networkException": "网络异常",
-			"networkExceptionMsg": "网络异常,请检查您的网络连接是否正常!",
+  "sys": {
+    "api": {
+      "operationSuccess": "操作成功",
+      "operationFailed": "操作失败",
+      "errorTip": "错误提示",
+      "successTip": "成功提示",
+      "errorMessage": "操作失败,系统异常!",
+      "timeoutMessage": "登录超时,请重新登录!",
+      "apiTimeoutMessage": "接口请求超时,请刷新页面重试!",
+      "apiRequestFailed": "请求出错,请稍候重试",
+      "networkException": "网络异常",
+      "networkExceptionMsg": "网络异常,请检查您的网络连接是否正常!",
 
-			"errMsg401": "用户没有权限(令牌、用户名、密码错误)!",
-			"errMsg403": "用户得到授权,但是访问是被禁止的。!",
-			"errMsg404": "网络请求错误,未找到该资源!",
-			"errMsg405": "网络请求错误,请求方法未允许!",
-			"errMsg408": "网络请求超时!",
-			"errMsg500": "服务器错误,请联系管理员!",
-			"errMsg501": "网络未实现!",
-			"errMsg502": "网络错误!",
-			"errMsg503": "服务不可用,服务器暂时过载或维护!",
-			"errMsg504": "网络超时!",
-			"errMsg505": "http版本不支持该请求!"
-		},
-		"login": {
-			"backSignIn": "返回",
-			"signInFormTitle": "登录",
-			"mobileSignInFormTitle": "手机登录",
-			"qrSignInFormTitle": "二维码登录",
-			"signUpFormTitle": "注册",
-			"forgetFormTitle": "重置密码",
+      "errMsg401": "用户没有权限(令牌、用户名、密码错误)!",
+      "errMsg403": "用户得到授权,但是访问是被禁止的。!",
+      "errMsg404": "网络请求错误,未找到该资源!",
+      "errMsg405": "网络请求错误,请求方法未允许!",
+      "errMsg408": "网络请求超时!",
+      "errMsg500": "服务器错误,请联系管理员!",
+      "errMsg501": "网络未实现!",
+      "errMsg502": "网络错误!",
+      "errMsg503": "服务不可用,服务器暂时过载或维护!",
+      "errMsg504": "网络超时!",
+      "errMsg505": "http版本不支持该请求!"
+    },
+    "login": {
+      "backSignIn": "返回",
+      "signInFormTitle": "登录",
+      "mobileSignInFormTitle": "手机登录",
+      "qrSignInFormTitle": "二维码登录",
+      "signUpFormTitle": "注册",
+      "forgetFormTitle": "重置密码",
 
-			"signInPrimaryTitle": "欢迎回来",
-			"signInSecondTitle": "欢迎使用要易业务管理系统",
-			"signInDesc": "输入您的个人详细信息开始使用!",
-			"policy": "我同意xxx隐私政策",
-			"scanSign": "扫码后点击'确认',即可完成登录",
-			"forgetFormSecondTitle": "请输入与您的帐户关联的电子邮件地址,我们将通过电子邮件向您发送重置密码的链接。",
+      "signInPrimaryTitle": "欢迎回来",
+      "signInSecondTitle": "欢迎使用要易业务管理系统",
+      "signInDesc": "输入您的个人详细信息开始使用!",
+      "policy": "我同意xxx隐私政策",
+      "scanSign": "扫码后点击'确认',即可完成登录",
+      "forgetFormSecondTitle": "请输入与您的帐户关联的电子邮件地址,我们将通过电子邮件向您发送重置密码的链接。",
 
-			"loginButton": "登录",
-			"registerButton": "注册",
-			"rememberMe": "记住我",
-			"forgetPassword": "忘记密码?",
-			"otherSignIn": "其他登录方式",
-			"sendSmsButton": "发送验证码",
-			"sendSmsText": "{{second}}秒后重新获取",
-			"sendEmailButton": "发送邮件",
+      "loginButton": "登录",
+      "registerButton": "注册",
+      "rememberMe": "记住我",
+      "forgetPassword": "忘记密码?",
+      "otherSignIn": "其他登录方式",
+      "sendSmsButton": "发送验证码",
+      "sendSmsText": "{{second}}秒后重新获取",
+      "sendEmailButton": "发送邮件",
 
-			"loginSuccessTitle": "登录成功",
-			"loginSuccessDesc": "欢迎回来",
+      "loginSuccessTitle": "登录成功",
+      "loginSuccessDesc": "欢迎回来",
 
-			"tenantNamePlaceholder": "请输入企业名称",
-			"accountPlaceholder": "请输入账号",
-			"passwordPlaceholder": "请输入密码",
-			"confirmPasswordPlaceholder": "请输入确认密码",
-			"emaildPlaceholder": "请输入邮箱",
-			"smsPlaceholder": "请输入验证码",
-			"mobilePlaceholder": "请输入手机号码",
-			"policyPlaceholder": "勾选后才能注册",
-			"diffPwd": "两次输入密码不一致",
+      "tenantNamePlaceholder": "请输入企业名称",
+      "accountPlaceholder": "请输入账号",
+      "passwordPlaceholder": "请输入密码",
+      "confirmPasswordPlaceholder": "请输入确认密码",
+      "emaildPlaceholder": "请输入邮箱",
+      "smsPlaceholder": "请输入验证码",
+      "mobilePlaceholder": "请输入手机号码",
+      "policyPlaceholder": "勾选后才能注册",
+      "diffPwd": "两次输入密码不一致",
 
-			"tenantName": "企业名称",
-			"userName": "账号",
-			"password": "密码",
-			"confirmPassword": "确认密码",
-			"email": "邮箱",
-			"smsCode": "短信验证码",
-			"mobile": "手机号码",
+      "tenantName": "企业名称",
+      "userName": "账号",
+      "password": "密码",
+      "confirmPassword": "确认密码",
+      "email": "邮箱",
+      "smsCode": "短信验证码",
+      "mobile": "手机号码",
 
-			"registerAndAgree": "注册即我同意",
-			"termsOfService": " 服务条款 ",
-			"privacyPolicy": " 隐私政策 ",
-			"logout": "退出"
-		},
-		"tab": {
-			"fullscreen": "内容全屏",
-			"refresh": "刷新",
-			"close": "关闭标签页",
-			"closeOthers": "关闭其它标签页",
-			"closeAll": "关闭所有标签页",
-			"closeLeft": "关闭左侧标签页",
-			"closeRight": "关闭右侧标签页"
-		},
-		"menu": {
-			"dashboard": "仪表",
-			"workbench": "工作台",
-			"analysis": "分析",
-			"management": "管理",
-			"user": {
-				"index": "用户",
-				"profile": "个人资料",
-				"account": "账户"
-			},
-			"system": {
-				"index": "系统",
-				"organization": "组织",
-				"permission": "权限",
-				"role": "角色",
-				"user": "用户",
-				"user_detail": "用户详情"
-			},
-			"blog": "博客",
-			"components": "组件",
-			"icon": "图标",
-			"animate": "动画",
-			"scroll": "滚动",
-			"markdown": "Markdown",
-			"editor": "富文本",
-			"i18n": "多语言",
-			"upload": "上传",
-			"chart": "图表",
-			"toast": "Toast",
-			"functions": "功能",
-			"clipboard": "剪贴板",
-			"token_expired": "Token失效",
-			"menulevel": {
-				"index": "多级菜单",
-				"1a": "多级菜单 1a",
-				"1b": {
-					"index": "多级菜单 1b",
-					"2a": "多级菜单 2a",
-					"2b": {
-						"index": "多级菜单 2b",
-						"3a": "多级菜单 3a",
-						"3b": "多级菜单 3b"
-					}
-				}
-			},
-			"disabled": "项目禁用",
-			"label": "项目标签",
-			"frame": "项目外部链接",
-			"external_link": "外链",
-			"iframe": "内嵌",
-			"blank": "空白",
-			"calendar": "日历",
-			"kanban": "看板",
-			"error": {
-				"index": "异常页",
-				"403": "403",
-				"404": "404",
-				"500": "500"
-			}
-		},
-		"docs": "文档"
-	}
+      "registerAndAgree": "注册即我同意",
+      "termsOfService": " 服务条款 ",
+      "privacyPolicy": " 隐私政策 ",
+      "logout": "退出"
+    },
+    "tab": {
+      "fullscreen": "内容全屏",
+      "refresh": "刷新",
+      "close": "关闭标签页",
+      "closeOthers": "关闭其它标签页",
+      "closeAll": "关闭所有标签页",
+      "closeLeft": "关闭左侧标签页",
+      "closeRight": "关闭右侧标签页"
+    },
+    "menu": {
+      "dashboard": "仪表",
+      "workbench": "工作台",
+      "analysis": "分析",
+      "management": "管理",
+      "user": {
+        "index": "用户",
+        "profile": "个人资料",
+        "account": "账户"
+      },
+      "system": {
+        "index": "系统",
+        "organization": "组织",
+        "permission": "权限",
+        "role": "角色",
+        "user": "用户",
+        "user_detail": "用户详情"
+      },
+      "blog": "博客",
+      "components": "组件",
+      "icon": "图标",
+      "animate": "动画",
+      "scroll": "滚动",
+      "markdown": "Markdown",
+      "editor": "富文本",
+      "i18n": "多语言",
+      "upload": "上传",
+      "chart": "图表",
+      "toast": "Toast",
+      "functions": "功能",
+      "clipboard": "剪贴板",
+      "token_expired": "Token失效",
+      "menulevel": {
+        "index": "多级菜单",
+        "1a": "多级菜单 1a",
+        "1b": {
+          "index": "多级菜单 1b",
+          "2a": "多级菜单 2a",
+          "2b": {
+            "index": "多级菜单 2b",
+            "3a": "多级菜单 3a",
+            "3b": "多级菜单 3b"
+          }
+        }
+      },
+      "disabled": "项目禁用",
+      "label": "项目标签",
+      "frame": "项目外部链接",
+      "external_link": "外链",
+      "iframe": "内嵌",
+      "blank": "空白",
+      "calendar": "日历",
+      "kanban": "看板",
+      "error": {
+        "index": "异常页",
+        "403": "403",
+        "404": "404",
+        "500": "500"
+      }
+    },
+    "docs": "文档"
+  }
 }

+ 42 - 43
src/pages/components/animate/control-panel.tsx

@@ -1,47 +1,46 @@
-import { themeVars } from "@/theme/theme.css";
-import { Card } from "antd";
+import { Card } from 'antd'
+import { themeVars } from '@/theme/theme.css'
 
 type Props = {
-	variantKey: {
-		type: string;
-		values: string[];
-	}[];
-	selectedVariant: string;
-	onChangeVarient: (varient: string) => void;
-};
+  variantKey: {
+    type: string
+    values: string[]
+  }[]
+  selectedVariant: string
+  onChangeVarient: (varient: string) => void
+}
 export default function ControlPanel({ variantKey, selectedVariant, onChangeVarient }: Props) {
-	const selectedStyle = (variantKey: string) => {
-		return variantKey === selectedVariant
-			? {
-					backgroundColor: themeVars.colors.palette.primary.default,
-					color: themeVars.colors.text.primary,
-				}
-			: {};
-	};
-	return (
-		<Card className="h-[480px] overflow-y-scroll">
-			{variantKey.map((item) => (
-				<div key={item.type}>
-					<div className="text-xs font-medium">{item.type.toUpperCase()}</div>
-					<ul className="mb-4 ml-2 mt-2 text-gray-600">
-						{item.values.map((item) => (
-							<li
-								key={item}
-								className="m-2 cursor-pointer rounded-md p-2"
-								onClick={() => onChangeVarient(item)}
-								onKeyDown={(e) => {
-									if (e.key === "Enter") {
-										onChangeVarient(item);
-									}
-								}}
-								style={selectedStyle(item)}
-							>
-								{item}
-							</li>
-						))}
-					</ul>
-				</div>
-			))}
-		</Card>
-	);
+  const selectedStyle = (variantKey: string) => {
+    return variantKey === selectedVariant
+      ? {
+          backgroundColor: themeVars.colors.palette.primary.default,
+          color: themeVars.colors.text.primary
+        }
+      : {}
+  }
+  return (
+    <Card className='h-[480px] overflow-y-scroll'>
+      {variantKey.map((item) => (
+        <div key={item.type}>
+          <div className='text-xs font-medium'>{item.type.toUpperCase()}</div>
+          <ul className='mb-4 ml-2 mt-2 text-gray-600'>
+            {item.values.map((item) => (
+              <li
+                key={item}
+                className='m-2 cursor-pointer rounded-md p-2'
+                onClick={() => onChangeVarient(item)}
+                onKeyDown={(e) => {
+                  if (e.key === 'Enter') {
+                    onChangeVarient(item)
+                  }
+                }}
+                style={selectedStyle(item)}>
+                {item}
+              </li>
+            ))}
+          </ul>
+        </div>
+      ))}
+    </Card>
+  )
 }

+ 21 - 22
src/pages/components/animate/index.tsx

@@ -1,26 +1,25 @@
-import { themeVars } from "@/theme/theme.css";
-import { Tabs, type TabsProps, Typography } from "antd";
-import BackgroundView from "./views/background";
-import Inview from "./views/inview";
-import ScrollView from "./views/scroll";
+import { Tabs, type TabsProps, Typography } from 'antd'
+import { themeVars } from '@/theme/theme.css'
+import BackgroundView from './views/background'
+import Inview from './views/inview'
+import ScrollView from './views/scroll'
 
 export default function AnimatePage() {
-	const TABS: TabsProps["items"] = [
-		{ key: "inview", label: "In View", children: <Inview /> },
-		{ key: "scroll", label: "Scroll", children: <ScrollView /> },
-		{ key: "background", label: "Background", children: <BackgroundView /> },
-	];
+  const TABS: TabsProps['items'] = [
+    { key: 'inview', label: 'In View', children: <Inview /> },
+    { key: 'scroll', label: 'Scroll', children: <ScrollView /> },
+    { key: 'background', label: 'Background', children: <BackgroundView /> }
+  ]
 
-	return (
-		<>
-			<Typography.Link
-				href="https://www.framer.com/motion/"
-				style={{ color: themeVars.colors.palette.primary.default }}
-				className="mb-4 block"
-			>
-				https://www.framer.com/motion/
-			</Typography.Link>
-			<Tabs items={TABS} type="card" />
-		</>
-	);
+  return (
+    <>
+      <Typography.Link
+        href='https://www.framer.com/motion/'
+        style={{ color: themeVars.colors.palette.primary.default }}
+        className='mb-4 block'>
+        https://www.framer.com/motion/
+      </Typography.Link>
+      <Tabs items={TABS} type='card' />
+    </>
+  )
 }

+ 24 - 26
src/pages/components/animate/views/background/container.tsx

@@ -1,31 +1,29 @@
-import { m } from "framer-motion";
-import { useMemo } from "react";
-
-import Cover3 from "@/assets/images/cover/cover_3.jpg";
-import MotionContainer from "@/components/animate/motion-container";
-import { getVariant } from "@/components/animate/variants";
-import { themeVars } from "@/theme/theme.css";
+import { useMemo } from 'react'
+import Cover3 from '@/assets/images/cover/cover_3.jpg'
+import { m } from 'framer-motion'
+import { themeVars } from '@/theme/theme.css'
+import MotionContainer from '@/components/animate/motion-container'
+import { getVariant } from '@/components/animate/variants'
 
 type Props = {
-	variant: string;
-};
+  variant: string
+}
 export default function ContainerView({ variant }: Props) {
-	const varients = useMemo(() => getVariant(variant), [variant]);
-	const isKenburns = variant.includes("kenburns");
+  const varients = useMemo(() => getVariant(variant), [variant])
+  const isKenburns = variant.includes('kenburns')
 
-	return (
-		<div
-			key={variant}
-			className="h-[480px] overflow-hidden rounded-lg"
-			style={{ backgroundColor: themeVars.colors.background.neutral }}
-		>
-			<MotionContainer className="flex h-full w-full flex-col items-center gap-6">
-				{isKenburns ? (
-					<m.img src={Cover3} className="h-full w-full object-cover" variants={varients} />
-				) : (
-					<m.div {...varients} className="h-full w-full" />
-				)}
-			</MotionContainer>
-		</div>
-	);
+  return (
+    <div
+      key={variant}
+      className='h-[480px] overflow-hidden rounded-lg'
+      style={{ backgroundColor: themeVars.colors.background.neutral }}>
+      <MotionContainer className='flex h-full w-full flex-col items-center gap-6'>
+        {isKenburns ? (
+          <m.img src={Cover3} className='h-full w-full object-cover' variants={varients} />
+        ) : (
+          <m.div {...varients} className='h-full w-full' />
+        )}
+      </MotionContainer>
+    </div>
+  )
 }

+ 48 - 52
src/pages/components/animate/views/background/index.tsx

@@ -1,57 +1,53 @@
-import { Card, Col, Row } from "antd";
-import { useMemo, useState } from "react";
-
-import ControlPanel from "../../control-panel";
-
-import ContainerView from "./container";
-import Toolbar from "./toolbar";
+import { useMemo, useState } from 'react'
+import { Card, Col, Row } from 'antd'
+import ControlPanel from '../../control-panel'
+import ContainerView from './container'
+import Toolbar from './toolbar'
 
 export default function BackgroundView() {
-	const defaultValue = useMemo(() => {
-		return {
-			selectedVariant: "kenburnsTop",
-		};
-	}, []);
-	const [selectedVariant, setSelectedVariant] = useState(
-		defaultValue.selectedVariant,
-	);
+  const defaultValue = useMemo(() => {
+    return {
+      selectedVariant: 'kenburnsTop'
+    }
+  }, [])
+  const [selectedVariant, setSelectedVariant] = useState(defaultValue.selectedVariant)
 
-	const onRefresh = () => {
-		setSelectedVariant(defaultValue.selectedVariant);
-	};
-	return (
-		<Card>
-			<Row>
-				<Col xs={24} md={18}>
-					<Toolbar onRefresh={onRefresh} />
-				</Col>
-			</Row>
-			<Row justify="space-between">
-				<Col xs={24} md={18}>
-					<ContainerView variant={selectedVariant} />
-				</Col>
-				<Col xs={24} md={5}>
-					<ControlPanel
-						variantKey={variantKey}
-						selectedVariant={selectedVariant}
-						onChangeVarient={(varient) => setSelectedVariant(varient)}
-					/>
-				</Col>
-			</Row>
-		</Card>
-	);
+  const onRefresh = () => {
+    setSelectedVariant(defaultValue.selectedVariant)
+  }
+  return (
+    <Card>
+      <Row>
+        <Col xs={24} md={18}>
+          <Toolbar onRefresh={onRefresh} />
+        </Col>
+      </Row>
+      <Row justify='space-between'>
+        <Col xs={24} md={18}>
+          <ContainerView variant={selectedVariant} />
+        </Col>
+        <Col xs={24} md={5}>
+          <ControlPanel
+            variantKey={variantKey}
+            selectedVariant={selectedVariant}
+            onChangeVarient={(varient) => setSelectedVariant(varient)}
+          />
+        </Col>
+      </Row>
+    </Card>
+  )
 }
 const variantKey = [
-	{
-		type: "kenburns",
-		values: ["kenburnsTop", "kenburnsBottom", "kenburnsLeft", "kenburnsRight"],
-	},
-	{
-		type: "pan",
-		values: ["panTop", "panBottom", "panLeft", "panRight"],
-	},
-	{
-		type: "color change",
-		values: ["color2x", "color3x", "color4x", "color5x"],
-	},
-];
+  {
+    type: 'kenburns',
+    values: ['kenburnsTop', 'kenburnsBottom', 'kenburnsLeft', 'kenburnsRight']
+  },
+  {
+    type: 'pan',
+    values: ['panTop', 'panBottom', 'panLeft', 'panRight']
+  },
+  {
+    type: 'color change',
+    values: ['color2x', 'color3x', 'color4x', 'color5x']
+  }
+]

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor