ZaiZai %!s(int64=2) %!d(string=hai) anos
pai
achega
3badc35589
Modificáronse 100 ficheiros con 4756 adicións e 0 borrados
  1. 10 0
      .editorconfig
  2. 1 0
      .env
  3. 12 0
      .eslintignore
  4. 4 0
      .eslintrc.json
  5. 6 0
      .gitattributes
  6. 3 0
      .gitpod.yml
  7. 4 0
      .markdownlint.json
  8. 3 0
      .npmrc
  9. 1 0
      .nvmrc
  10. 6 0
      .prettierignore
  11. 12 0
      .prettierrc
  12. 9 0
      .vscode/extensions.json
  13. 35 0
      .vscode/settings.json
  14. 17 0
      breakings/2.2.0/button.yml
  15. 12 0
      breakings/2.2.1/button.yml
  16. 12 0
      breakings/2.2.1/input-number.yml
  17. 12 0
      breakings/2.2.3/image.yml
  18. 18 0
      breakings/breaking.yml.example
  19. 9 0
      codecov.yml
  20. 3 0
      commitlint.config.js
  21. 109 0
      commitlint.config.ts
  22. 9 0
      docs/.gitignore
  23. 29 0
      docs/.vitepress/build/crowdin-credentials.ts
  24. 113 0
      docs/.vitepress/build/crowdin-generate.ts
  25. 15 0
      docs/.vitepress/build/rebuild-pwa.ts
  26. 86 0
      docs/.vitepress/config.mts
  27. 15 0
      docs/.vitepress/config/analytics.ts
  28. 1 0
      docs/.vitepress/config/features.ts
  29. 144 0
      docs/.vitepress/config/head.ts
  30. 7 0
      docs/.vitepress/config/index.ts
  31. 23 0
      docs/.vitepress/config/nav.ts
  32. 67 0
      docs/.vitepress/config/plugins.ts
  33. 51 0
      docs/.vitepress/config/sidebars.ts
  34. 66 0
      docs/.vitepress/config/sponsors.ts
  35. 5 0
      docs/.vitepress/crowdin/en-US/component/changelog.json
  36. 12 0
      docs/.vitepress/crowdin/en-US/component/demo-block.json
  37. 4 0
      docs/.vitepress/crowdin/en-US/component/edit-link.json
  38. 6 0
      docs/.vitepress/crowdin/en-US/component/footer.json
  39. 4 0
      docs/.vitepress/crowdin/en-US/component/icons.json
  40. 3 0
      docs/.vitepress/crowdin/en-US/component/last-update-at.json
  41. 6 0
      docs/.vitepress/crowdin/en-US/component/pwa.json
  42. 5 0
      docs/.vitepress/crowdin/en-US/component/search.json
  43. 6 0
      docs/.vitepress/crowdin/en-US/component/sponsor.json
  44. 3 0
      docs/.vitepress/crowdin/en-US/component/translation.json
  45. 324 0
      docs/.vitepress/crowdin/en-US/pages/component.json
  46. 83 0
      docs/.vitepress/crowdin/en-US/pages/guide.json
  47. 28 0
      docs/.vitepress/crowdin/en-US/pages/home.json
  48. 5 0
      docs/.vitepress/crowdin/en-US/pages/not-found.json
  49. 12 0
      docs/.vitepress/crowdin/en-US/pages/resource.json
  50. 17 0
      docs/.vitepress/crowdin/en-US/pages/sidebar.json
  51. 10 0
      docs/.vitepress/dark-mode.js
  52. 3 0
      docs/.vitepress/env.d.ts
  53. 34 0
      docs/.vitepress/lang.js
  54. 40 0
      docs/.vitepress/plugins/api-table.ts
  55. 37 0
      docs/.vitepress/plugins/external-link-icon.ts
  56. 122 0
      docs/.vitepress/plugins/markdown-transform.ts
  57. 6 0
      docs/.vitepress/plugins/table-wrapper.ts
  58. 36 0
      docs/.vitepress/plugins/tag.ts
  59. 29 0
      docs/.vitepress/plugins/tooltip.ts
  60. 189 0
      docs/.vitepress/sw.ts
  61. 19 0
      docs/.vitepress/theme/index.ts
  62. 66 0
      docs/.vitepress/theme/style.css
  63. 56 0
      docs/.vitepress/utils/highlight.ts
  64. 10 0
      docs/.vitepress/utils/lang.ts
  65. 1 0
      docs/.vitepress/utils/types.ts
  66. 37 0
      docs/.vitepress/vitepress/components/common/vp-link.vue
  67. 36 0
      docs/.vitepress/vitepress/components/common/vp-markdown.vue
  68. 15 0
      docs/.vitepress/vitepress/components/common/vp-switch.vue
  69. 50 0
      docs/.vitepress/vitepress/components/common/vp-theme-toggler.vue
  70. 28 0
      docs/.vitepress/vitepress/components/demo/vp-example.vue
  71. 27 0
      docs/.vitepress/vitepress/components/demo/vp-source-code.vue
  72. 17 0
      docs/.vitepress/vitepress/components/dev/a11y-tag.vue
  73. 5 0
      docs/.vitepress/vitepress/components/dev/deprecated-tag.vue
  74. 11 0
      docs/.vitepress/vitepress/components/dev/version-tag.vue
  75. 34 0
      docs/.vitepress/vitepress/components/doc-content/vp-edit-link.vue
  76. 47 0
      docs/.vitepress/vitepress/components/doc-content/vp-last-updated-at.vue
  77. 35 0
      docs/.vitepress/vitepress/components/doc-content/vp-page-footer.vue
  78. 93 0
      docs/.vitepress/vitepress/components/doc-content/vp-page-nav.vue
  79. 65 0
      docs/.vitepress/vitepress/components/doc-content/vp-table-of-content.vue
  80. 40 0
      docs/.vitepress/vitepress/components/full-screen/vp-menu-link.vue
  81. 23 0
      docs/.vitepress/vitepress/components/full-screen/vp-menu.vue
  82. 24 0
      docs/.vitepress/vitepress/components/full-screen/vp-theme-toggler.vue
  83. 90 0
      docs/.vitepress/vitepress/components/full-screen/vp-translation.vue
  84. 38 0
      docs/.vitepress/vitepress/components/globals/contributors.vue
  85. 58 0
      docs/.vitepress/vitepress/components/globals/design-guide.vue
  86. 73 0
      docs/.vitepress/vitepress/components/globals/design/consistency-svg.vue
  87. 80 0
      docs/.vitepress/vitepress/components/globals/design/controllability-svg.vue
  88. 74 0
      docs/.vitepress/vitepress/components/globals/design/efficiency-svg.vue
  89. 52 0
      docs/.vitepress/vitepress/components/globals/design/feedback-svg.vue
  90. 332 0
      docs/.vitepress/vitepress/components/globals/icons-categories.json
  91. 151 0
      docs/.vitepress/vitepress/components/globals/icons.vue
  92. 33 0
      docs/.vitepress/vitepress/components/globals/main-color.vue
  93. 161 0
      docs/.vitepress/vitepress/components/globals/neutral-color.vue
  94. 400 0
      docs/.vitepress/vitepress/components/globals/parallax-home.vue
  95. 138 0
      docs/.vitepress/vitepress/components/globals/resource.vue
  96. 169 0
      docs/.vitepress/vitepress/components/globals/resources/axure-components-svg.vue
  97. 86 0
      docs/.vitepress/vitepress/components/globals/resources/figma-template-svg.vue
  98. 82 0
      docs/.vitepress/vitepress/components/globals/resources/sketch-template-svg.vue
  99. 40 0
      docs/.vitepress/vitepress/components/globals/secondary-colors.vue
  100. 7 0
      docs/.vitepress/vitepress/components/globals/vp-api-bool.vue

+ 10 - 0
.editorconfig

@@ -0,0 +1,10 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+quote_type = single

+ 1 - 0
.env

@@ -0,0 +1 @@
+E2E_TEST_PORT=5001

+ 12 - 0
.eslintignore

@@ -0,0 +1,12 @@
+node_modules
+dist
+pnpm-lock.yaml
+CHANGELOG.en-US.md
+docs/components.d.ts
+coverage
+play
+ssr-testing/cases/*
+docs/.vitepress/i18n/*
+docs/.vitepress/crowdin/*
+!docs/.vitepress/crowdin/en-US
+!.*

+ 4 - 0
.eslintrc.json

@@ -0,0 +1,4 @@
+{
+  "root": true,
+  "extends": ["@element-plus/eslint-config"]
+}

+ 6 - 0
.gitattributes

@@ -0,0 +1,6 @@
+* text=auto eol=lf
+*.ts linguist-detectable=false
+*.css linguist-detectable=false
+*.scss linguist-detectable=false
+*.js linguist-detectable=true
+*.vue linguist-detectable=true

+ 3 - 0
.gitpod.yml

@@ -0,0 +1,3 @@
+tasks:
+  - init: pnpm i
+    command: pnpm dev

+ 4 - 0
.markdownlint.json

@@ -0,0 +1,4 @@
+{
+  "MD033": false,
+  "MD013": false
+}

+ 3 - 0
.npmrc

@@ -0,0 +1,3 @@
+shamefully-hoist=true
+strict-peer-dependencies=false
+shell-emulator=true

+ 1 - 0
.nvmrc

@@ -0,0 +1 @@
+v16

+ 6 - 0
.prettierignore

@@ -0,0 +1,6 @@
+dist
+node_modules
+coverage
+CHANGELOG.en-US.md
+pnpm-lock.yaml
+docs/components.d.ts

+ 12 - 0
.prettierrc

@@ -0,0 +1,12 @@
+{
+  "semi": false,
+  "singleQuote": true,
+  "overrides": [
+    {
+      "files": ".prettierrc",
+      "options": {
+        "parser": "json"
+      }
+    }
+  ]
+}

+ 9 - 0
.vscode/extensions.json

@@ -0,0 +1,9 @@
+{
+  "recommendations": [
+    "vue.volar",
+    "dbaeumer.vscode-eslint",
+    "esbenp.prettier-vscode",
+    "antfu.vite",
+    "lokalise.i18n-ally"
+  ]
+}

+ 35 - 0
.vscode/settings.json

@@ -0,0 +1,35 @@
+{
+  "cSpell.words": ["Element Plus", "element-plus"],
+  "typescript.tsdk": "node_modules/typescript/lib",
+  "editor.formatOnSave": true,
+  "npm.packageManager": "pnpm",
+  "eslint.probe": [
+    "javascript",
+    "javascriptreact",
+    "typescript",
+    "typescriptreact",
+    "html",
+    "vue",
+    "markdown",
+    "json",
+    "jsonc"
+  ],
+  "eslint.validate": [
+    "javascript",
+    "javascriptreact",
+    "typescript",
+    "typescriptreact",
+    "html",
+    "vue",
+    "markdown",
+    "json",
+    "jsonc"
+  ],
+  "vite.devCommand": "pnpm run dev -- --",
+  "i18n-ally.localesPaths": "packages/locale/lang",
+  "i18n-ally.enabledParsers": ["ts"],
+  "i18n-ally.enabledFrameworks": ["vue", "vue-sfc"],
+  "i18n-ally.keystyle": "nested",
+  "iconify.includes": ["ep"],
+  "unocss.root": "./docs"
+}

+ 17 - 0
breakings/2.2.0/button.yml

@@ -0,0 +1,17 @@
+- scope: 'component'
+  name: 'el-button'
+  type: 'props'
+  version: '2.2.0'
+  commit_hash: 'a4aad5a'
+  description: |
+    We want to make text button more like a button, instead of like a text/link.
+    In addition with design changes, text button now looks more like a button.
+    Because the API `type` also represents how the button looks like, so we decided to add a
+    new API `text` to represent the text button.
+  props:
+    - api: 'type'
+      before: '"primary" | "success" | "warning" | "danger" | "info" | "text"'
+      after: '"primary" | "success" | "warning" | "danger" | "info"'
+    - api: 'text'
+      before: '-'
+      after: 'boolean'

+ 12 - 0
breakings/2.2.1/button.yml

@@ -0,0 +1,12 @@
+- scope: 'component'
+  name: 'el-button'
+  type: 'props'
+  version: '2.2.1'
+  commit_hash: 'ad3d998'
+  description: |
+    Per [Link Button Request](https://github.com/element-plus/element-plus/issues/7610), we need to
+    add a `link` like button back for those uses link button.
+  props:
+    - api: 'link'
+      before: ''
+      after: 'boolean'

+ 12 - 0
breakings/2.2.1/input-number.yml

@@ -0,0 +1,12 @@
+- scope: 'component'
+  name: 'el-input-number'
+  type: 'props'
+  version: '2.2.1'
+  commit_hash: '2577b06'
+  description: |
+    Add a default value setter on clear input number value. When clear event occurs, the value will be set to the
+    value from the new API.
+  props:
+    - api: 'value-on-clear'
+      before: ''
+      after: '"min" | "max" | null | number'

+ 12 - 0
breakings/2.2.3/image.yml

@@ -0,0 +1,12 @@
+- scope: 'component'
+  name: 'el-image'
+  type: 'props'
+  version: '2.2.3'
+  commit_hash: '7a48556'
+  description: |
+    Per [HTMLImageElement.loading Request](https://github.com/element-plus/element-plus/issues/7841),
+    Add [native loading](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attr-loading) support for Image components.
+  props:
+    - api: 'loading'
+      before: ''
+      after: '"eager" | "lazy"'

+ 18 - 0
breakings/breaking.yml.example

@@ -0,0 +1,18 @@
+- scope: 'component' # package name under packages/*
+  name: 'el-button' # the name of which file you changed
+  type: 'props' # which API you have changed, must match the field the same level with the detailed changes, in this case "props"
+  version: '2.2.0' # which version this breaking change is going to make to.
+  commit_hash: 'a4aad5a'
+  description:
+    | # detailed description for the reason of this breaking change, you may also include links. Markdown supported.
+    We want to make text button more like a button, instead of like a text/link.
+    In addition with design changes, text button now looks more like a button.
+    Because the API `type` also represents how the button looks like, so we decided to add a
+    new API `text` to represent the text button.
+  props: # this is used for building a table in the breaking change list.
+    - api: 'type' # API name
+      before: '"primary" | "success" | "warning" | "danger" | "info" | "text"' # before changed
+      after: '"primary" | "success" | "warning" | "danger" | "info"' # after changed
+    - api: 'text'
+      before: '-'
+      after: 'boolean'

+ 9 - 0
codecov.yml

@@ -0,0 +1,9 @@
+coverage:
+  status:
+    project:
+      default:
+        target: auto
+        threshold: 10%
+comment:
+  layout: 'reach, diff, flags, files'
+  require_changes: true

+ 3 - 0
commitlint.config.js

@@ -0,0 +1,3 @@
+// commitlint uses `ts-node` to load typescript config, it's too slow. So we replace it with `esbuild`.
+require('@esbuild-kit/cjs-loader')
+module.exports = require('./commitlint.config.ts').default

+ 109 - 0
commitlint.config.ts

@@ -0,0 +1,109 @@
+import { execSync } from 'child_process'
+import fg from 'fast-glob'
+
+const getPackages = (packagePath) =>
+  fg.sync('*', { cwd: packagePath, onlyDirectories: true })
+
+const scopes = [
+  ...getPackages('packages'),
+  ...getPackages('internal'),
+  'docs',
+  'play',
+  'project',
+  'core',
+  'style',
+  'ci',
+  'dev',
+  'deploy',
+  'other',
+  'typography',
+  'color',
+  'border',
+  'var',
+  'ssr',
+]
+
+const gitStatus = execSync('git status --porcelain || true')
+  .toString()
+  .trim()
+  .split('\n')
+
+const scopeComplete = gitStatus
+  .find((r) => ~r.indexOf('M  packages'))
+  ?.replace(/\//g, '%%')
+  ?.match(/packages%%((\w|-)*)/)?.[1]
+
+const subjectComplete = gitStatus
+  .find((r) => ~r.indexOf('M  packages/components'))
+  ?.replace(/\//g, '%%')
+  ?.match(/packages%%components%%((\w|-)*)/)?.[1]
+
+export default {
+  rules: {
+    /**
+     * type[scope]: [function] description
+     *      ^^^^^
+     */
+    'scope-enum': [2, 'always', scopes],
+    /**
+     * type[scope]: [function] description
+     *
+     * ^^^^^^^^^^^^^^ empty line.
+     * - Something here
+     */
+    'body-leading-blank': [1, 'always'],
+    /**
+     * type[scope]: [function] description
+     *
+     * - something here
+     *
+     * ^^^^^^^^^^^^^^
+     */
+    'footer-leading-blank': [1, 'always'],
+    /**
+     * type[scope]: [function] description [No more than 72 characters]
+     *      ^^^^^
+     */
+    'header-max-length': [2, 'always', 72],
+    'scope-case': [2, 'always', 'lower-case'],
+    'subject-case': [
+      1,
+      'never',
+      ['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
+    ],
+    'subject-empty': [2, 'never'],
+    'subject-full-stop': [2, 'never', '.'],
+    'type-case': [2, 'always', 'lower-case'],
+    'type-empty': [2, 'never'],
+    /**
+     * type[scope]: [function] description
+     * ^^^^
+     */
+    'type-enum': [
+      2,
+      'always',
+      [
+        'build',
+        'chore',
+        'ci',
+        'docs',
+        'feat',
+        'fix',
+        'perf',
+        'refactor',
+        'revert',
+        'release',
+        'style',
+        'test',
+        'improvement',
+      ],
+    ],
+  },
+  prompt: {
+    defaultScope: scopeComplete,
+    customScopesAlign: !scopeComplete ? 'top' : 'bottom',
+    defaultSubject: subjectComplete && `[${subjectComplete}] `,
+    allowCustomIssuePrefixs: false,
+    allowEmptyIssuePrefixs: false,
+  },
+}

+ 9 - 0
docs/.gitignore

@@ -0,0 +1,9 @@
+.vitepress/i18n/*
+.vitepress/crowdin/*
+!.vitepress/crowdin/en-US
+
+temp
+zh-CN
+fr-FR
+es-ES
+ja-JP

+ 29 - 0
docs/.vitepress/build/crowdin-credentials.ts

@@ -0,0 +1,29 @@
+import path from 'path'
+import fs from 'fs/promises'
+import chalk from 'chalk'
+import consola from 'consola'
+import { docRoot, errorAndExit } from '@element-plus/build-utils'
+
+const credentialPlaceholder = 'API_TOKEN_PLACEHOLDER'
+
+const CREDENTIAL = process.env.CROWDIN_TOKEN
+if (!CREDENTIAL) {
+  errorAndExit(new Error('Environment variable CROWDIN_TOKEN cannot be empty'))
+}
+
+;(async () => {
+  consola.debug(chalk.cyan('Fetching Crowdin credential'))
+  const configPath = path.resolve(docRoot, 'crowdin.yml')
+  try {
+    const file = await fs.readFile(configPath, {
+      encoding: 'utf-8',
+    })
+    await fs.writeFile(
+      configPath,
+      file.replace(credentialPlaceholder, CREDENTIAL)
+    )
+    consola.success(chalk.green('Crowdin credential update successfully'))
+  } catch (e: any) {
+    errorAndExit(e)
+  }
+})()

+ 113 - 0
docs/.vitepress/build/crowdin-generate.ts

@@ -0,0 +1,113 @@
+import fs from 'fs'
+import path from 'path'
+import chalk from 'chalk'
+import consola from 'consola'
+import { docRoot, errorAndExit } from '@element-plus/build-utils'
+
+// NB: this file is only for generating files that enables developers to develop the website.
+const componentLocaleRoot = path.resolve(docRoot, '.vitepress/crowdin')
+
+const exists = 'File already exists'
+
+async function main() {
+  const localeOutput = path.resolve(docRoot, '.vitepress/i18n')
+  if (fs.existsSync(localeOutput)) {
+    throw new Error(exists)
+  }
+
+  consola.trace(chalk.cyan('Starting for build doc for developing'))
+  // all language should be identical since it is mirrored from crowdin.
+  const dirs = await fs.promises.readdir(componentLocaleRoot, {
+    withFileTypes: true,
+  })
+  const languages = dirs.map((dir) => dir.name)
+  const langWithoutEn = languages.filter((l) => l !== 'en-US')
+
+  await fs.promises.mkdir(localeOutput)
+
+  // build lang.json for telling `header>language-select` how many languages are there
+  await fs.promises.writeFile(
+    path.resolve(localeOutput, 'lang.json'),
+    JSON.stringify(languages),
+    'utf-8'
+  )
+
+  // loop through en-US
+
+  const enUS = path.resolve(componentLocaleRoot, 'en-US')
+  // we do not include en-US since we are currently using it as template
+  const languagePaths = langWithoutEn.map((l) => {
+    return {
+      name: l,
+      pathname: path.resolve(componentLocaleRoot, l),
+    }
+  })
+
+  consola.debug(languagePaths)
+  await traverseDir(enUS, languagePaths, localeOutput)
+}
+
+async function traverseDir(
+  dir: string,
+  paths: { name: string; pathname: string }[],
+  targetPath: string
+) {
+  const contents = await fs.promises.readdir(dir, { withFileTypes: true })
+
+  await Promise.all(
+    contents.map(async (c) => {
+      if (c.isDirectory()) {
+        await fs.promises.mkdir(path.resolve(targetPath, c.name), {
+          recursive: true,
+        })
+
+        return traverseDir(
+          path.resolve(dir, c.name),
+          paths.map((p) => {
+            return {
+              ...p,
+              pathname: path.resolve(p.pathname, c.name),
+            }
+          }),
+          path.resolve(targetPath, c.name)
+        )
+      } else if (c.isFile()) {
+        // eslint-disable-next-line @typescript-eslint/no-var-requires
+        const content = require(path.resolve(dir, c.name))
+
+        const contentToWrite = {
+          'en-US': content,
+        }
+
+        await Promise.all(
+          paths.map(async (p) => {
+            // eslint-disable-next-line @typescript-eslint/no-var-requires
+            const content = require(path.resolve(p.pathname, c.name))
+
+            contentToWrite[p.name] = content
+          })
+        )
+
+        return fs.promises.writeFile(
+          path.resolve(targetPath, c.name),
+          JSON.stringify(contentToWrite, null, 2),
+          {
+            encoding: 'utf-8',
+          }
+        )
+      }
+    })
+  )
+}
+
+main()
+  .then(() => {
+    consola.success(chalk.green('Locale for website development generated'))
+  })
+  .catch((err) => {
+    if (err.message === exists) {
+      // do nothing
+    } else {
+      errorAndExit(err)
+    }
+  })

+ 15 - 0
docs/.vitepress/build/rebuild-pwa.ts

@@ -0,0 +1,15 @@
+import { resolveConfig } from 'vite'
+import type { VitePluginPWAAPI } from 'vite-plugin-pwa'
+
+const rebuildPwa = async () => {
+  const config = await resolveConfig({}, 'build', 'production')
+  const pwaPlugin: VitePluginPWAAPI = config.plugins.find((i) => {
+    return i.name === 'vite-plugin-pwa'
+  })?.api
+
+  if (pwaPlugin && pwaPlugin.generateSW && !pwaPlugin.disabled) {
+    await pwaPlugin.generateSW()
+  }
+}
+
+rebuildPwa()

+ 86 - 0
docs/.vitepress/config.mts

@@ -0,0 +1,86 @@
+import consola from 'consola'
+import { REPO_BRANCH, REPO_PATH } from '@element-plus/build-constants'
+import { docsDirName } from '@element-plus/build-utils'
+import { languages } from './utils/lang'
+import { features, head, mdPlugin, nav, sidebars } from './config'
+import type { UserConfig } from 'vitepress'
+
+const buildTransformers = () => {
+  const transformer = () => {
+    return {
+      props: [],
+      needRuntime: true,
+    }
+  }
+
+  const transformers = {}
+  const directives = [
+    'infinite-scroll',
+    'loading',
+    'popover',
+    'click-outside',
+    'repeat-click',
+    'trap-focus',
+    'mousewheel',
+    'resize',
+  ]
+  directives.forEach((k) => {
+    transformers[k] = transformer
+  })
+
+  return transformers
+}
+
+consola.debug(`DOC_ENV: ${process.env.DOC_ENV}`)
+
+const locales = {}
+languages.forEach((lang) => {
+  locales[`/${lang}`] = {
+    label: lang,
+    lang,
+  }
+})
+
+export const config: UserConfig = {
+  title: 'Element Plus',
+  description: 'a Vue 3 based component library for designers and developers',
+  lastUpdated: true,
+  head,
+  themeConfig: {
+    repo: REPO_PATH,
+    docsBranch: REPO_BRANCH,
+    docsDir: docsDirName,
+
+    editLinks: true,
+    editLinkText: 'Edit this page on GitHub',
+    lastUpdated: 'Last Updated',
+
+    logo: '/images/element-plus-logo.svg',
+    logoSmall: '/images/element-plus-logo-small.svg',
+    sidebars,
+    nav,
+    agolia: {
+      apiKey: '377f2b647a96d9b1d62e4780f2344da2',
+      appId: 'BH4D9OD16A',
+    },
+    features,
+    langs: languages,
+  },
+
+  locales,
+
+  markdown: {
+    config: (md) => mdPlugin(md),
+  },
+
+  vue: {
+    template: {
+      ssr: true,
+      compilerOptions: {
+        directiveTransforms: buildTransformers(),
+      },
+    },
+  },
+}
+
+export default config

+ 15 - 0
docs/.vitepress/config/analytics.ts

@@ -0,0 +1,15 @@
+export const sendEvent = (
+  action: string,
+  label: string,
+  value?: any,
+  category?: string
+): void => {
+  const gtag = (window as any).gtag
+  if (gtag) {
+    gtag('event', action, {
+      event_label: label,
+      event_value: value,
+      event_category: category,
+    })
+  }
+}

+ 1 - 0
docs/.vitepress/config/features.ts

@@ -0,0 +1 @@
+export const features = {}

+ 144 - 0
docs/.vitepress/config/head.ts

@@ -0,0 +1,144 @@
+import fs from 'fs'
+import path from 'path'
+import { vpRoot } from '@element-plus/build-utils'
+import { languages } from '../utils/lang'
+
+import type { HeadConfig } from 'vitepress'
+
+export const head: HeadConfig[] = [
+  [
+    'link',
+    {
+      rel: 'icon',
+      href: '/images/element-plus-logo-small.svg',
+      type: 'image/svg+xm',
+    },
+  ],
+  [
+    'link',
+    {
+      rel: 'apple-touch-icon',
+      href: '/apple-touch-icon.png',
+      sizes: '180x180',
+    },
+  ],
+  [
+    'link',
+    {
+      rel: 'mask-icon',
+      href: '/safari-pinned-tab.svg',
+      color: '#5bbad5',
+    },
+  ],
+  [
+    'meta',
+    {
+      name: 'theme-color',
+      content: '#ffffff',
+    },
+  ],
+  [
+    'meta',
+    {
+      name: 'msapplication-TileColor',
+      content: '#409eff',
+    },
+  ],
+  [
+    'meta',
+    {
+      name: 'msapplication-config',
+      content: '/browserconfig.xml',
+    },
+  ],
+  [
+    'script',
+    {},
+    `;(() => {
+      window.supportedLangs = ${JSON.stringify(languages)}
+    })()`,
+  ],
+
+  ['script', {}, fs.readFileSync(path.resolve(vpRoot, 'lang.js'), 'utf-8')],
+  [
+    'script',
+    {
+      async: 'true',
+      src: 'https://www.googletagmanager.com/gtag/js?id=UA-175337989-1',
+    },
+  ],
+  [
+    'script',
+    {},
+    `if ('serviceWorker' in navigator) {
+      navigator.serviceWorker
+        .register('/sw.js')
+        .then(function(registration) {
+          console.log(registration);
+        })
+        .catch(function(err) {
+          console.log(err);
+        });
+    }`,
+  ],
+  [
+    'script',
+    {
+      async: 'true',
+    },
+    `window.dataLayer = window.dataLayer || [];
+function gtag(){dataLayer.push(arguments);}
+gtag('js', new Date());
+gtag('config', 'UA-175337989-1');`,
+  ],
+  [
+    'script',
+    {
+      async: 'true',
+      src: 'https://www.googletagmanager.com/gtag/js?id=G-M74ZHEQ1M1',
+    },
+  ],
+  [
+    'script',
+    {},
+    `
+      window.dataLayer = window.dataLayer || [];
+      function gtag(){dataLayer.push(arguments);}
+      gtag('js', new Date());
+
+      gtag('config', 'G-M74ZHEQ1M1');
+    `,
+  ],
+  [
+    'script',
+    {},
+    `(function(h,o,t,j,a,r){
+      h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
+      h._hjSettings={hjid:2894908,hjsv:6};
+      a=o.getElementsByTagName('head')[0];
+      r=o.createElement('script');r.async=1;
+      r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
+      a.appendChild(r);
+  })(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');`,
+  ],
+  [
+    'script',
+    {
+      async: 'true',
+    },
+    `
+  var resource = document.createElement('link');
+  resource.setAttribute("rel", "stylesheet");
+  resource.setAttribute("href","//fonts.loli.net/css?family=Inter:300,400,500,600|Open+Sans:400,600;display=swap");
+  resource.setAttribute("type","text/css");
+  var head = document.querySelector('head');
+  head.appendChild(resource);
+    `,
+  ],
+]
+
+head.push([
+  'script',
+  {},
+  fs.readFileSync(path.resolve(vpRoot, 'dark-mode.js'), 'utf-8'),
+])

+ 7 - 0
docs/.vitepress/config/index.ts

@@ -0,0 +1,7 @@
+export * from './analytics'
+export * from './features'
+export * from './head'
+export * from './nav'
+export * from './plugins'
+export * from './sidebars'
+export * from './sponsors'

+ 23 - 0
docs/.vitepress/config/nav.ts

@@ -0,0 +1,23 @@
+import { ensureLang } from '../utils/lang'
+import navLocale from '../i18n/pages/sidebar.json'
+
+// Mapping the first sub link to the nav link to avoid 404 error.
+
+function getNav() {
+  return Object.fromEntries(
+    Object.entries(navLocale).map(([lang, locales]) => {
+      const item: {
+        link: string
+        text: string
+        activeMatch?: string
+      }[] = Object.values(locales).map((item) => ({
+        ...item,
+        link: `${ensureLang(lang)}${item.link}`,
+      }))
+
+      return [lang, item]
+    })
+  )
+}
+
+export const nav = getNav()

+ 67 - 0
docs/.vitepress/config/plugins.ts

@@ -0,0 +1,67 @@
+import path from 'path'
+import fs from 'fs'
+import MarkdownIt from 'markdown-it'
+import mdContainer from 'markdown-it-container'
+import { docRoot } from '@element-plus/build-utils'
+import externalLinkIcon from '../plugins/external-link-icon'
+import tableWrapper from '../plugins/table-wrapper'
+import tooltip from '../plugins/tooltip'
+import tag from '../plugins/tag'
+import { ApiTableContainer } from '../plugins/api-table'
+import { highlight } from '../utils/highlight'
+import type Token from 'markdown-it/lib/token'
+import type Renderer from 'markdown-it/lib/renderer'
+
+const localMd = MarkdownIt().use(tag)
+
+interface ContainerOpts {
+  marker?: string | undefined
+  validate?(params: string): boolean
+  render?(
+    tokens: Token[],
+    index: number,
+    options: any,
+    env: any,
+    self: Renderer
+  ): string
+}
+
+export const mdPlugin = (md: MarkdownIt) => {
+  md.use(externalLinkIcon)
+  md.use(tableWrapper)
+  md.use(tooltip)
+  md.use(tag)
+  md.use(mdContainer, 'demo', {
+    validate(params) {
+      return !!params.trim().match(/^demo\s*(.*)$/)
+    },
+
+    render(tokens, idx) {
+      const m = tokens[idx].info.trim().match(/^demo\s*(.*)$/)
+      if (tokens[idx].nesting === 1 /* means the tag is opening */) {
+        const description = m && m.length > 1 ? m[1] : ''
+        const sourceFileToken = tokens[idx + 2]
+        let source = ''
+        const sourceFile = sourceFileToken.children?.[0].content ?? ''
+
+        if (sourceFileToken.type === 'inline') {
+          source = fs.readFileSync(
+            path.resolve(docRoot, 'examples', `${sourceFile}.vue`),
+            'utf-8'
+          )
+        }
+        if (!source) throw new Error(`Incorrect source file: ${sourceFile}`)
+
+        return `<Demo :demos="demos" source="${encodeURIComponent(
+          highlight(source, 'vue')
+        )}" path="${sourceFile}" raw-source="${encodeURIComponent(
+          source
+        )}" description="${encodeURIComponent(localMd.render(description))}">`
+      } else {
+        return '</Demo>'
+      }
+    },
+  } as ContainerOpts)
+
+  md.use(ApiTableContainer)
+}

+ 51 - 0
docs/.vitepress/config/sidebars.ts

@@ -0,0 +1,51 @@
+import { ensureLang } from '../utils/lang'
+import guideLocale from '../i18n/pages/guide.json'
+import componentLocale from '../i18n/pages/component.json'
+
+function getGuideSidebar() {
+  return Object.fromEntries(
+    Object.entries(guideLocale).map(([lang, val]) => [
+      lang,
+      Object.values(val).map((item) => mapPrefix(item, lang)),
+    ])
+  )
+}
+
+function getComponentsSideBar() {
+  return Object.fromEntries(
+    Object.entries(componentLocale).map(([lang, val]) => [
+      lang,
+      Object.values(val).map((item) => mapPrefix(item, lang, '/component')),
+    ])
+  )
+}
+
+// return sidebar with language configs.
+// this might create duplicated data but the overhead is ignorable
+const getSidebars = () => {
+  return {
+    '/guide/': getGuideSidebar(),
+    '/component/': getComponentsSideBar(),
+  }
+}
+
+type Item = {
+  text: string
+  children?: Item[]
+  link?: string
+}
+
+function mapPrefix(item: Item, lang: string, prefix = '') {
+  if (item.children && item.children.length > 0) {
+    return {
+      ...item,
+      children: item.children.map((child) => mapPrefix(child, lang, prefix)),
+    }
+  }
+  return {
+    ...item,
+    link: `${ensureLang(lang)}${prefix}${item.link}`,
+  }
+}
+
+export const sidebars = getSidebars()

+ 66 - 0
docs/.vitepress/config/sponsors.ts

@@ -0,0 +1,66 @@
+export const rightRichTextSponsors = []
+
+export const rightLogoSmallSponsors = [
+  {
+    name: 'BuildAdmin',
+    img: '/images/sponsors/buildadmin.png',
+    imgL: '/images/sponsors/buildadmin-l.png',
+    url: 'https://wonderful-code.gitee.io/?from=element-plus',
+    slogan: 'Vue3 opensource admin system',
+    slogan_cn: 'Vue3企业级开源后台管理系统',
+  },
+  {
+    name: 'bit',
+    img: '/images/bit.svg',
+    imgL: '/images/bit-l.png',
+    url: 'https://bit.dev/?from=element-ui',
+    slogan: 'Share Code',
+    isDark: true, // dark theme
+  },
+]
+
+export const leftCustomImgSponsors = [
+  {
+    name: 'JSDesign',
+    name_cn: '即时设计',
+    img: '/images/js-design.png',
+    url: 'https://js.design?source=element-plus',
+    slogan: 'Professional online UI design tool',
+    slogan_cn: '专业在线UI设计工具',
+    banner_img: '/images/js-design-banner.jpg',
+  },
+  {
+    name: 'VForm',
+    img: '/images/vform.png',
+    url: 'https://vform666.com/vform3.html?from=element_plus',
+    slogan: 'Vue 2/3 Visual/Low-Code Forms',
+    slogan_cn: 'Vue 2/3 可视化低代码表单',
+    banner_img: '/images/vform-banner.png',
+  },
+  {
+    name: 'JNPF',
+    img: '/images/jnpf_index.png',
+    url: 'https://www.jnpfsoft.com/index.html?from=elementUI',
+    slogan: 'JNPF low code development platform to develop simple!',
+    slogan_cn: 'JNPF 低代码开发平台,让开发变得简单!',
+    className: 'jnpf',
+    banner_img: '/images/jnpfsoft.jpg',
+  },
+  {
+    name: 'JeePlus',
+    img: '/images/sponsors/jeeplus.png',
+    url: 'http://www.jeeplus.org/#/demo?from=ele',
+    slogan: 'JeePlus development platform',
+    slogan_cn: 'JeePlus 快速开发平台',
+    banner_img: '/images/sponsors/jeeplus_banner.jpg',
+  },
+]
+
+export const platinumSponsors = [
+  ...leftCustomImgSponsors,
+  ...rightRichTextSponsors,
+]
+
+export const leftLogoSponsors = []
+
+export const goldSponsors = [...rightLogoSmallSponsors, ...leftLogoSponsors]

+ 5 - 0
docs/.vitepress/crowdin/en-US/component/changelog.json

@@ -0,0 +1,5 @@
+{
+  "published-by": "Published by",
+  "open-link": "Open link",
+  "select-version": "Select a version"
+}

+ 12 - 0
docs/.vitepress/crowdin/en-US/component/demo-block.json

@@ -0,0 +1,12 @@
+{
+  "view-source": "View source",
+  "hide-source": "Hide source",
+  "edit-in-editor": "Edit in Playground",
+  "edit-on-github": "Edit on GitHub",
+  "edit-in-codepen": "Edit in Codepen.io",
+  "copy-code": "Copy code",
+  "switch-button-option-text": "Switch to Options API",
+  "switch-button-setup-text": "Switch to Composition API",
+  "copy-success": "Copied!",
+  "copy-error": "This browser does not support automatic copy!"
+}

+ 4 - 0
docs/.vitepress/crowdin/en-US/component/edit-link.json

@@ -0,0 +1,4 @@
+{
+  "edit-on-github": "Edit this page on GitHub",
+  "edit-on-crowdin": "Edit this page on Crowdin"
+}

+ 6 - 0
docs/.vitepress/crowdin/en-US/component/footer.json

@@ -0,0 +1,6 @@
+{
+  "source": "Source",
+  "contributors": "Contributors",
+  "component": "Component",
+  "docs": "Docs"
+}

+ 4 - 0
docs/.vitepress/crowdin/en-US/component/icons.json

@@ -0,0 +1,4 @@
+{
+  "copy-success": "Copied!",
+  "copy-error": "Your browser does not support copy :("
+}

+ 3 - 0
docs/.vitepress/crowdin/en-US/component/last-update-at.json

@@ -0,0 +1,3 @@
+{
+  "title": "Last Updated"
+}

+ 6 - 0
docs/.vitepress/crowdin/en-US/component/pwa.json

@@ -0,0 +1,6 @@
+{
+  "message": "New content available, click on refresh button to update.",
+  "refresh": "Refresh",
+  "always-refresh": "Always Refresh",
+  "close": "Close"
+}

+ 5 - 0
docs/.vitepress/crowdin/en-US/component/search.json

@@ -0,0 +1,5 @@
+{
+  "search": "Search",
+  "empty": "No results",
+  "index": "en"
+}

+ 6 - 0
docs/.vitepress/crowdin/en-US/component/sponsor.json

@@ -0,0 +1,6 @@
+{
+  "title": "Sponsors",
+  "sponsoredBy": "Sponsored by",
+  "platinumSponsor": "Platinum Sponsors",
+  "goldSponsor": "Gold Sponsors"
+}

+ 3 - 0
docs/.vitepress/crowdin/en-US/component/translation.json

@@ -0,0 +1,3 @@
+{
+  "help": "Help Translate 😉"
+}

+ 324 - 0
docs/.vitepress/crowdin/en-US/pages/component.json

@@ -0,0 +1,324 @@
+{
+  "basic": {
+    "text": "Basic",
+    "children": [
+      {
+        "link": "/button",
+        "text": "Button"
+      },
+      {
+        "link": "/border",
+        "text": "Border"
+      },
+      {
+        "link": "/color",
+        "text": "Color"
+      },
+      {
+        "link": "/container",
+        "text": "Layout Container"
+      },
+      {
+        "link": "/icon",
+        "text": "Icon"
+      },
+      {
+        "link": "/layout",
+        "text": "Layout"
+      },
+      {
+        "link": "/link",
+        "text": "Link"
+      },
+      {
+        "link": "/scrollbar",
+        "text": "Scrollbar"
+      },
+      {
+        "link": "/space",
+        "text": "Space"
+      },
+      {
+        "link": "/typography",
+        "text": "Typography"
+      }
+    ]
+  },
+  "configuration": {
+    "text": "Configuration",
+    "children": [
+      {
+        "link": "/config-provider",
+        "text": "Config Provider"
+      }
+    ]
+  },
+  "form": {
+    "text": "Form",
+    "children": [
+      {
+        "link": "/autocomplete",
+        "text": "Autocomplete"
+      },
+      {
+        "link": "/cascader",
+        "text": "Cascader"
+      },
+      {
+        "link": "/checkbox",
+        "text": "Checkbox"
+      },
+      {
+        "link": "/color-picker",
+        "text": "Color Picker"
+      },
+      {
+        "link": "/date-picker",
+        "text": "Date Picker"
+      },
+      {
+        "link": "/datetime-picker",
+        "text": "DateTime Picker"
+      },
+      {
+        "link": "/form",
+        "text": "Form"
+      },
+      {
+        "link": "/input",
+        "text": "Input"
+      },
+      {
+        "link": "/input-number",
+        "text": "Input Number"
+      },
+      {
+        "link": "/radio",
+        "text": "Radio"
+      },
+      {
+        "link": "/rate",
+        "text": "Rate"
+      },
+      {
+        "link": "/select",
+        "text": "Select"
+      },
+      {
+        "link": "/select-v2",
+        "text": "Virtualized Select"
+      },
+      {
+        "link": "/slider",
+        "text": "Slider"
+      },
+      {
+        "link": "/switch",
+        "text": "Switch"
+      },
+      {
+        "link": "/time-picker",
+        "text": "Time Picker"
+      },
+      {
+        "link": "/time-select",
+        "text": "Time Select"
+      },
+      {
+        "link": "/transfer",
+        "text": "Transfer"
+      },
+      {
+        "link": "/upload",
+        "text": "Upload"
+      }
+    ]
+  },
+  "data": {
+    "text": "Data",
+    "children": [
+      {
+        "link": "/avatar",
+        "text": "Avatar"
+      },
+      {
+        "link": "/badge",
+        "text": "Badge"
+      },
+      {
+        "link": "/calendar",
+        "text": "Calendar"
+      },
+      {
+        "link": "/card",
+        "text": "Card"
+      },
+      {
+        "link": "/carousel",
+        "text": "Carousel"
+      },
+      {
+        "link": "/collapse",
+        "text": "Collapse"
+      },
+      {
+        "link": "/descriptions",
+        "text": "Descriptions"
+      },
+      {
+        "link": "/empty",
+        "text": "Empty"
+      },
+      {
+        "link": "/image",
+        "text": "Image"
+      },
+      {
+        "link": "/infinite-scroll",
+        "text": "Infinite Scroll"
+      },
+      {
+        "link": "/pagination",
+        "text": "Pagination"
+      },
+      {
+        "link": "/progress",
+        "text": "Progress"
+      },
+      {
+        "link": "/result",
+        "text": "Result"
+      },
+      {
+        "link": "/skeleton",
+        "text": "Skeleton"
+      },
+      {
+        "link": "/table",
+        "text": "Table"
+      },
+      {
+        "link": "/table-v2",
+        "text": "Virtualized Table",
+        "promotion": "2.2.0"
+      },
+      {
+        "link": "/tag",
+        "text": "Tag"
+      },
+      {
+        "link": "/timeline",
+        "text": "Timeline"
+      },
+      {
+        "link": "/tree",
+        "text": "Tree"
+      },
+      {
+        "link": "/tree-select",
+        "text": "TreeSelect",
+        "promotion": "2.1.8"
+      },
+      {
+        "link": "/tree-v2",
+        "text": "Virtualized Tree"
+      },
+      {
+        "link": "/statistic",
+        "text": "Statistic",
+        "promotion": "2.2.30"
+      }
+    ]
+  },
+  "navigation": {
+    "text": "Navigation",
+    "children": [
+      {
+        "link": "/affix",
+        "text": "Affix"
+      },
+      {
+        "link": "/backtop",
+        "text": "Backtop"
+      },
+      {
+        "link": "/breadcrumb",
+        "text": "Breadcrumb"
+      },
+      {
+        "link": "/dropdown",
+        "text": "Dropdown"
+      },
+      {
+        "link": "/menu",
+        "text": "Menu"
+      },
+      {
+        "link": "/page-header",
+        "text": "Page Header"
+      },
+      {
+        "link": "/steps",
+        "text": "Steps"
+      },
+      {
+        "link": "/tabs",
+        "text": "Tabs"
+      }
+    ]
+  },
+  "feedback": {
+    "text": "Feedback",
+    "children": [
+      {
+        "link": "/alert",
+        "text": "Alert"
+      },
+      {
+        "link": "/dialog",
+        "text": "Dialog"
+      },
+      {
+        "link": "/drawer",
+        "text": "Drawer"
+      },
+      {
+        "link": "/loading",
+        "text": "Loading"
+      },
+      {
+        "link": "/message",
+        "text": "Message"
+      },
+      {
+        "link": "/message-box",
+        "text": "Message Box"
+      },
+      {
+        "link": "/notification",
+        "text": "Notification"
+      },
+      {
+        "link": "/popconfirm",
+        "text": "Popconfirm"
+      },
+      {
+        "link": "/popover",
+        "text": "Popover"
+      },
+      {
+        "link": "/tooltip",
+        "text": "Tooltip"
+      }
+    ]
+  },
+  "others": {
+    "text": "Others",
+    "children": [
+      {
+        "link": "/divider",
+        "text": "Divider"
+      }
+    ]
+  }
+}

+ 83 - 0
docs/.vitepress/crowdin/en-US/pages/guide.json

@@ -0,0 +1,83 @@
+{
+  "intro": {
+    "text": "Basics",
+    "children": [
+      {
+        "text": "Design",
+        "link": "/guide/design"
+      },
+      {
+        "text": "Navigation",
+        "link": "/guide/nav"
+      },
+      {
+        "text": "Installation",
+        "link": "/guide/installation"
+      },
+      {
+        "text": "Quick Start",
+        "link": "/guide/quickstart"
+      }
+    ]
+  },
+  "advanced": {
+    "text": "Advanced",
+    "children": [
+      {
+        "text": "i18n",
+        "link": "/guide/i18n"
+      },
+      {
+        "text": "Migration from ElementUI",
+        "link": "/guide/migration"
+      },
+      {
+        "text": "Theming",
+        "link": "/guide/theming"
+      },
+      {
+        "text": "Dark Mode",
+        "link": "/guide/dark-mode",
+        "promotion": "2.2.0"
+      },
+      {
+        "text": "Custom Namespace",
+        "link": "/guide/namespace",
+        "promotion": "2.2.0"
+      },
+      {
+        "text": "SSR",
+        "link": "/guide/ssr"
+      },
+      {
+        "text": "Built-in Transitions",
+        "link": "/guide/transitions"
+      },
+      {
+        "text": "Changelog",
+        "link": "/guide/changelog"
+      }
+    ]
+  },
+  "development": {
+    "text": "Development",
+    "children": [
+      {
+        "text": "Development Guide",
+        "link": "/guide/dev-guide"
+      },
+      {
+        "text": "Development FAQ",
+        "link": "/guide/dev-faq"
+      },
+      {
+        "text": "Commit Examples",
+        "link": "/guide/commit-examples"
+      },
+      {
+        "text": "Translation",
+        "link": "/guide/translation"
+      }
+    ]
+  }
+}

+ 28 - 0
docs/.vitepress/crowdin/en-US/pages/home.json

@@ -0,0 +1,28 @@
+{
+  "1": "",
+  "2": "",
+  "3": "Guide",
+  "4": "Understand the design guidelines, helping designers build product that's logically sound, reasonably structured and easy to use.",
+  "5": "View Detail",
+  "6": "Component",
+  "7": "Experience interaction details by strolling through component demos. Use encapsulated code to improve developing efficiency.",
+  "8": "Resource",
+  "9": "Download relevant design resources for shaping page prototype or visual draft, increasing design efficiency.",
+  "10": "Links",
+  "11": "GitHub",
+  "12": "Changelog",
+  "13": "Element UI for Vue 2",
+  "14": "Online Theme Roller",
+  "15": "Gitter",
+  "16": "Feedback",
+  "17": "Contribution",
+  "18": "SegmentFault",
+  "19": "Community",
+  "20": "Become a Sponsor!",
+  "21": "Please contact us via",
+  "title_release": "Element Plus stable release is coming",
+  "title": "Element Plus",
+  "title_sub": "a Vue 3 based component library for designers and developers",
+  "china_mirror": "China Mirror 🇨🇳",
+  "discord": "Discord"
+}

+ 5 - 0
docs/.vitepress/crowdin/en-US/pages/not-found.json

@@ -0,0 +1,5 @@
+{
+  "title": "Resource not found",
+  "button-title": "Back to Home",
+  "desc": "The page you requested does not exist"
+}

+ 12 - 0
docs/.vitepress/crowdin/en-US/pages/resource.json

@@ -0,0 +1,12 @@
+{
+  "title": "Resources",
+  "lineOne": "More resources are still under development. ",
+  "lineTwo": "If you are interested in participating in the design of Element Plus, be our guest to contact us via <span><a href=\"mailto:element-plus@outlook.com\" target=\"_blank\">element-plus@outlook.com</a></span>.",
+  "download": "Download",
+  "axure": "Axure Components",
+  "axureIntro": "By importing Element Plus in Axure, you can easily apply all the components we provide during interaction design.",
+  "sketch": "Sketch Template",
+  "sketchIntro": "Newly designed Sketch component library in 2022 with full components for Element Plus to improve design efficiency while keeping a unified visual style.",
+  "figma": "Figma Template",
+  "figmaIntro": "Newly designed Figma component library in 2022 with new features such as Auto-layout and Variants."
+}

+ 17 - 0
docs/.vitepress/crowdin/en-US/pages/sidebar.json

@@ -0,0 +1,17 @@
+[
+  {
+    "text": "Guide",
+    "link": "/guide/design",
+    "activeMatch": "/guide/"
+  },
+  {
+    "text": "Component",
+    "link": "/component/button",
+    "activeMatch": "/component/"
+  },
+  {
+    "text": "Resource",
+    "link": "/resource/index",
+    "activeMatch": "/resource/"
+  }
+]

+ 10 - 0
docs/.vitepress/dark-mode.js

@@ -0,0 +1,10 @@
+;(() => {
+  const saved = localStorage.getItem('el-theme-appearance')
+  if (
+    saved === 'auto'
+      ? window.matchMedia(`(prefers-color-scheme: dark)`).matches
+      : saved === 'dark'
+  ) {
+    document.documentElement.classList.add('dark')
+  }
+})()

+ 3 - 0
docs/.vitepress/env.d.ts

@@ -0,0 +1,3 @@
+/// <reference types="vite/client" />
+
+export {}

+ 34 - 0
docs/.vitepress/lang.js

@@ -0,0 +1,34 @@
+;(() => {
+  const supportedLangs = window.supportedLangs
+  const cacheKey = 'preferred_lang'
+  const defaultLang = 'en-US'
+  // docs supported languages
+  const langAlias = {
+    en: 'en-US',
+    fr: 'fr-FR',
+    es: 'es-ES',
+  }
+  let userPreferredLang = localStorage.getItem(cacheKey) || navigator.language
+  const language =
+    langAlias[userPreferredLang] ||
+    (supportedLangs.includes(userPreferredLang)
+      ? userPreferredLang
+      : defaultLang)
+  localStorage.setItem(cacheKey, language)
+  userPreferredLang = language
+  if (!location.pathname.startsWith(`/${userPreferredLang}`)) {
+    const toPath = [`/${userPreferredLang}`]
+      .concat(location.pathname.split('/').slice(2))
+      .join('/')
+    location.pathname =
+      toPath.endsWith('.html') || toPath.endsWith('/')
+        ? toPath
+        : toPath.concat('/')
+  }
+  if (navigator && navigator.serviceWorker.controller) {
+    navigator.serviceWorker.controller.postMessage({
+      type: 'LANG',
+      lang: userPreferredLang,
+    })
+  }
+})()

+ 40 - 0
docs/.vitepress/plugins/api-table.ts

@@ -0,0 +1,40 @@
+import markdown from 'markdown-it'
+
+import type MarkdownIt from 'markdown-it'
+
+const ApiMd = new markdown()
+
+export const ApiTableContainer = (md: MarkdownIt) => {
+  const fence = md.renderer.rules.fence!
+
+  ApiMd.renderer.rules = md.renderer.rules
+  md.renderer.rules.fence = (...args) => {
+    const [tokens, idx, ...rest] = args
+    const [options, env] = rest
+    const token = tokens[idx]
+    if (token.info === 'api') {
+      const newTokens = md.parse(token.content, env)
+
+      let result = ''
+      const { rules } = md.renderer
+      newTokens.forEach((newToken, idx) => {
+        const { type } = newToken
+        if (type === 'inline') {
+          result += md.renderer.renderInline(newToken.children!, options, env)
+        } else if (typeof rules[type] !== 'undefined') {
+          result += rules[newToken.type]!(
+            newTokens,
+            idx,
+            options,
+            env,
+            md.renderer
+          )
+        } else {
+          result += md.renderer.renderToken(newTokens, idx, options)
+        }
+      })
+      return result
+    }
+    return fence.call(md, ...args)
+  }
+}

+ 37 - 0
docs/.vitepress/plugins/external-link-icon.ts

@@ -0,0 +1,37 @@
+import type MarkdownIt from 'markdown-it'
+import type Renderer from 'markdown-it/lib/renderer'
+
+export default (md: MarkdownIt): void => {
+  const renderToken: Renderer.RenderRule = (tokens, idx, options, env, self) =>
+    self.renderToken(tokens, idx, options)
+  const defaultLinkOpenRenderer = md.renderer.rules.link_open || renderToken
+  const defaultLinkCloseRenderer = md.renderer.rules.link_close || renderToken
+  let isExternalLink = false
+
+  md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
+    const token = tokens[idx]
+    const href = token.attrGet('href')
+
+    if (href) {
+      token.attrJoin('class', 'vp-link')
+      if (/^((ht|f)tps?):\/\/?/.test(href)) {
+        isExternalLink = true
+      }
+    }
+
+    return defaultLinkOpenRenderer(tokens, idx, options, env, self)
+  }
+
+  md.renderer.rules.link_close = (tokens, idx, options, env, self) => {
+    if (isExternalLink) {
+      isExternalLink = false
+      return `<i-ri-external-link-line class="link-icon" />${self.renderToken(
+        tokens,
+        idx,
+        options
+      )}`
+    }
+
+    return defaultLinkCloseRenderer(tokens, idx, options, env, self)
+  }
+}

+ 122 - 0
docs/.vitepress/plugins/markdown-transform.ts

@@ -0,0 +1,122 @@
+import fs from 'fs'
+import path from 'path'
+import glob from 'fast-glob'
+import { docRoot, docsDirName, projRoot } from '@element-plus/build-utils'
+import { REPO_BRANCH, REPO_PATH } from '@element-plus/build-constants'
+import { getLang, languages } from '../utils/lang'
+import footerLocale from '../i18n/component/footer.json'
+
+import type { Plugin } from 'vite'
+
+type Append = Record<'headers' | 'footers' | 'scriptSetups', string[]>
+
+export function MarkdownTransform(): Plugin {
+  return {
+    name: 'element-plus-md-transform',
+    enforce: 'pre',
+    async transform(code, id) {
+      if (!id.endsWith('.md')) return
+
+      const componentId = path.basename(id, '.md')
+      const append: Append = {
+        headers: [],
+        footers: [],
+        scriptSetups: [
+          `const demos = import.meta.globEager('../../examples/${componentId}/*.vue')`,
+        ],
+      }
+
+      code = transformVpScriptSetup(code, append)
+
+      const pattern = `{${[...languages, languages[0]].join(',')}}/component`
+      const compPaths = await glob(pattern, {
+        cwd: docRoot,
+        absolute: true,
+        onlyDirectories: true,
+      })
+      if (compPaths.some((compPath) => id.startsWith(compPath))) {
+        code = transformComponentMarkdown(id, componentId, code, append)
+      }
+
+      return combineMarkdown(
+        code,
+        [combineScriptSetup(append.scriptSetups), ...append.headers],
+        append.footers
+      )
+    },
+  }
+}
+
+const combineScriptSetup = (codes: string[]) =>
+  `\n<script setup>
+${codes.join('\n')}
+</script>
+`
+
+const combineMarkdown = (
+  code: string,
+  headers: string[],
+  footers: string[]
+) => {
+  const frontmatterEnds = code.indexOf('---\n\n') + 4
+  const firstSubheader = code.search(/\n## \w/)
+  const sliceIndex = firstSubheader < 0 ? frontmatterEnds : firstSubheader
+
+  if (headers.length > 0)
+    code =
+      code.slice(0, sliceIndex) + headers.join('\n') + code.slice(sliceIndex)
+  code += footers.join('\n')
+
+  return `${code}\n`
+}
+
+const vpScriptSetupRE = /<vp-script\s(.*\s)?setup(\s.*)?>([\s\S]*)<\/vp-script>/
+
+const transformVpScriptSetup = (code: string, append: Append) => {
+  const matches = code.match(vpScriptSetupRE)
+  if (matches) code = code.replace(matches[0], '')
+  const scriptSetup = matches?.[3] ?? ''
+  if (scriptSetup) append.scriptSetups.push(scriptSetup)
+  return code
+}
+
+const GITHUB_BLOB_URL = `https://github.com/${REPO_PATH}/blob/${REPO_BRANCH}`
+const GITHUB_TREE_URL = `https://github.com/${REPO_PATH}/tree/${REPO_BRANCH}`
+const transformComponentMarkdown = (
+  id: string,
+  componentId: string,
+  code: string,
+  append: Append
+) => {
+  const lang = getLang(id)
+  const docUrl = `${GITHUB_BLOB_URL}/${docsDirName}/en-US/component/${componentId}.md`
+  const componentUrl = `${GITHUB_TREE_URL}/packages/components/${componentId}`
+  const componentPath = path.resolve(
+    projRoot,
+    `packages/components/${componentId}`
+  )
+  const isComponent = fs.existsSync(componentPath)
+
+  const links = [[footerLocale[lang].docs, docUrl]]
+  if (isComponent) links.unshift([footerLocale[lang].component, componentUrl])
+  const linksText = links
+    .filter((i) => i)
+    .map(([text, link]) => `[${text}](${link})`)
+    .join(' • ')
+
+  const sourceSection = `
+## ${footerLocale[lang].source}
+
+${linksText}
+`
+
+  const contributorsSection = `
+## ${footerLocale[lang].contributors}
+
+<Contributors id="${componentId}" />
+`
+
+  append.footers.push(sourceSection, isComponent ? contributorsSection : '')
+
+  return code
+}

+ 6 - 0
docs/.vitepress/plugins/table-wrapper.ts

@@ -0,0 +1,6 @@
+import type MarkdownIt from 'markdown-it'
+
+export default (md: MarkdownIt): void => {
+  md.renderer.rules.table_open = () => '<div class="vp-table"><table>'
+  md.renderer.rules.table_close = () => '</table></div>'
+}

+ 36 - 0
docs/.vitepress/plugins/tag.ts

@@ -0,0 +1,36 @@
+import type MarkdownIt from 'markdown-it'
+
+export default (md: MarkdownIt): void => {
+  /**
+   * To enable the plugin to be parsed in the demo description, the content is rendered as span instead of ElTag.
+   */
+  md.renderer.rules.tag = (tokens, idx) => {
+    const token = tokens[idx]
+    const value = token.content
+    /**
+     * Add styles for some special tags
+     * vitepress/styles/content/tag-mark-content.scss
+     */
+    const tagClass = ['beta', 'deprecated', 'a11y'].includes(value) ? value : ''
+    return `<span class="vp-tag ${tagClass}">${value}</span>`
+  }
+
+  md.inline.ruler.before('emphasis', 'tag', (state, silent) => {
+    const tagRegExp = /^\^\(([^)]*)\)/
+    const str = state.src.slice(state.pos, state.posMax)
+
+    if (!tagRegExp.test(str)) return false
+    if (silent) return true
+
+    const result = str.match(tagRegExp)
+
+    if (!result) return false
+
+    const token = state.push('tag', 'tag', 0)
+    token.content = result[1].trim()
+    token.level = state.level
+    state.pos += result[0].length
+
+    return true
+  })
+}

+ 29 - 0
docs/.vitepress/plugins/tooltip.ts

@@ -0,0 +1,29 @@
+import type MarkdownIt from 'markdown-it'
+
+export default (md: MarkdownIt): void => {
+  md.renderer.rules.tooltip = (tokens, idx) => {
+    const token = tokens[idx]
+
+    return `<api-typing type="${token.content}" details="${token.info}" />`
+  }
+
+  md.inline.ruler.before('emphasis', 'tooltip', (state, silent) => {
+    const tooltipRegExp = /^\^\[([^\]]*)\](`[^`]*`)?/
+    const str = state.src.slice(state.pos, state.posMax)
+
+    if (!tooltipRegExp.test(str)) return false
+    if (silent) return true
+
+    const result = str.match(tooltipRegExp)
+
+    if (!result) return false
+
+    const token = state.push('tooltip', 'tooltip', 0)
+    token.content = result[1].replace(/\\\|/g, '|')
+    token.info = (result[2] || '').replace(/^`(.*)`$/, '$1')
+    token.level = state.level
+    state.pos += result[0].length
+
+    return true
+  })
+}

+ 189 - 0
docs/.vitepress/sw.ts

@@ -0,0 +1,189 @@
+import { cacheNames, clientsClaim } from 'workbox-core'
+import type { ManifestEntry } from 'workbox-build'
+
+declare let self: ServiceWorkerGlobalScope & {
+  __WB_MANIFEST: ManifestEntry[]
+}
+const manifest = self.__WB_MANIFEST
+const cacheName = cacheNames.runtime
+const defaultLang = manifest.some((item) => {
+  return item.url.includes(navigator.language)
+})
+  ? navigator.language
+  : 'en-US'
+
+let userPreferredLang = ''
+let cacheEntries: RequestInfo[] = []
+let cacheManifestURLs: string[] = []
+let manifestURLs: string[] = []
+
+class LangDB {
+  private db: IDBDatabase | undefined
+  private databaseName = 'PWA_DB'
+  private version = 1
+  private storeNames = 'lang'
+
+  constructor() {
+    this.initDB()
+  }
+
+  private initDB() {
+    return new Promise<boolean>((resolve) => {
+      const request = indexedDB.open(this.databaseName, this.version)
+
+      request.onsuccess = (event) => {
+        this.db = (event.target as IDBOpenDBRequest).result
+        resolve(true)
+      }
+
+      request.onupgradeneeded = (event) => {
+        this.db = (event.target as IDBOpenDBRequest).result
+
+        if (!this.db.objectStoreNames.contains(this.storeNames)) {
+          this.db.createObjectStore(this.storeNames, { keyPath: 'id' })
+        }
+      }
+    })
+  }
+
+  private async initLang() {
+    this.db!.transaction(this.storeNames, 'readwrite')
+      .objectStore(this.storeNames)
+      .add({ id: 1, lang: defaultLang })
+  }
+
+  async getLang() {
+    if (!this.db) await this.initDB()
+
+    return new Promise<string>((resolve) => {
+      const request = this.db!.transaction(this.storeNames)
+        .objectStore(this.storeNames)
+        .get(1)
+
+      request.onsuccess = () => {
+        if (request.result) {
+          resolve(request.result.lang)
+        } else {
+          this.initLang()
+          resolve(defaultLang)
+        }
+      }
+
+      request.onerror = () => {
+        resolve(defaultLang)
+      }
+    })
+  }
+
+  async setLang(lang: string) {
+    if (userPreferredLang !== lang) {
+      userPreferredLang = lang
+      cacheEntries = []
+      cacheManifestURLs = []
+      manifestURLs = []
+
+      if (!this.db) await this.initDB()
+
+      this.db!.transaction(this.storeNames, 'readwrite')
+        .objectStore(this.storeNames)
+        .put({ id: 1, lang })
+    }
+  }
+}
+
+async function initManifest() {
+  userPreferredLang = userPreferredLang || (await langDB.getLang())
+  // match the data that needs to be cached
+  // NOTE: When the structure of the document dist files changes, it needs to be changed here
+  const cacheList = [
+    userPreferredLang,
+    `assets/(${userPreferredLang}|app|index|style|chunks)`,
+    'images',
+    'android-chrome',
+    'apple-touch-icon',
+    'manifest.webmanifest',
+  ]
+  const regExp = new RegExp(`^(${cacheList.join('|')})`)
+
+  for (const item of manifest) {
+    const url = new URL(item.url, self.location.origin)
+    manifestURLs.push(url.href)
+
+    if (regExp.test(item.url) || /^\/$/.test(item.url)) {
+      const request = new Request(url.href, { credentials: 'same-origin' })
+      cacheEntries.push(request)
+      cacheManifestURLs.push(url.href)
+    }
+  }
+}
+
+const langDB = new LangDB()
+
+self.addEventListener('install', (event) => {
+  event.waitUntil(
+    caches.open(cacheName).then(async (cache) => {
+      if (!cacheEntries.length) await initManifest()
+
+      return cache.addAll(cacheEntries)
+    })
+  )
+})
+
+self.addEventListener('activate', (event: ExtendableEvent) => {
+  // clean up outdated runtime cache
+  event.waitUntil(
+    caches.open(cacheName).then(async (cache) => {
+      if (!cacheManifestURLs.length) await initManifest()
+
+      cache.keys().then((keys) => {
+        keys.forEach((request) => {
+          // clean up those who are not listed in cacheManifestURLs
+          !cacheManifestURLs.includes(request.url) && cache.delete(request)
+        })
+      })
+    })
+  )
+})
+
+self.addEventListener('fetch', (event) => {
+  event.respondWith(
+    caches.match(event.request).then(async (response) => {
+      // when the cache is hit, it returns directly to the cache
+      if (response) return response
+      if (!manifestURLs.length) await initManifest()
+      const requestClone = event.request.clone()
+
+      // otherwise create a new fetch request
+      return fetch(requestClone)
+        .then((response) => {
+          const responseClone = response.clone()
+
+          if (response.type !== 'basic' && response.status !== 200) {
+            return response
+          }
+
+          // cache the data contained in the manifestURLs list
+          manifestURLs.includes(requestClone.url) &&
+            caches.open(cacheName).then((cache) => {
+              cache.put(requestClone, responseClone)
+            })
+          return response
+        })
+        .catch((err) => {
+          throw new Error(`Failed to load resource ${requestClone.url}, ${err}`)
+        })
+    })
+  )
+})
+
+self.addEventListener('message', (event) => {
+  if (event.data) {
+    if (event.data.type === 'SKIP_WAITING') {
+      self.skipWaiting()
+    } else if (event.data.type === 'LANG') {
+      langDB.setLang(event.data.lang)
+    }
+  }
+})
+
+clientsClaim()

+ 19 - 0
docs/.vitepress/theme/index.ts

@@ -0,0 +1,19 @@
+import ElementPlus from 'element-plus'
+
+import VPApp, { NotFound, globals } from '../vitepress'
+import { define } from '../utils/types'
+import 'uno.css'
+import './style.css'
+import type { Theme } from 'vitepress'
+
+export default define<Theme>({
+  NotFound,
+  Layout: VPApp,
+  enhanceApp: ({ app }) => {
+    app.use(ElementPlus)
+
+    globals.forEach(([name, Comp]) => {
+      app.component(name, Comp)
+    })
+  },
+})

+ 66 - 0
docs/.vitepress/theme/style.css

@@ -0,0 +1,66 @@
+#nprogress {
+  pointer-events: none;
+}
+#nprogress .bar {
+  background: #29d;
+  position: fixed;
+  z-index: 1031;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 2px;
+}
+#nprogress .peg {
+  display: block;
+  position: absolute;
+  right: 0;
+  width: 100px;
+  height: 100%;
+  box-shadow: 0 0 10px #29d, 0 0 5px #29d;
+  opacity: 1;
+  -webkit-transform: rotate(3deg) translateY(-4px);
+  -ms-transform: rotate(3deg) translateY(-4px);
+  transform: rotate(3deg) translateY(-4px);
+}
+#nprogress .spinner {
+  display: block;
+  position: fixed;
+  z-index: 1031;
+  top: 15px;
+  right: 15px;
+}
+#nprogress .spinner-icon {
+  width: 18px;
+  height: 18px;
+  box-sizing: border-box;
+  border-color: #29d transparent transparent #29d;
+  border-style: solid;
+  border-width: 2px;
+  border-radius: 50%;
+  -webkit-animation: nprogress-spinner 0.4s linear infinite;
+  animation: nprogress-spinner 0.4s linear infinite;
+}
+.nprogress-custom-parent {
+  overflow: hidden;
+  position: relative;
+}
+.nprogress-custom-parent #nprogress .bar,
+.nprogress-custom-parent #nprogress .spinner {
+  position: absolute;
+}
+@-webkit-keyframes nprogress-spinner {
+  0% {
+    -webkit-transform: rotate(0deg);
+  }
+  to {
+    -webkit-transform: rotate(1turn);
+  }
+}
+@keyframes nprogress-spinner {
+  0% {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(1turn);
+  }
+}

+ 56 - 0
docs/.vitepress/utils/highlight.ts

@@ -0,0 +1,56 @@
+// ref https://github.com/vuejs/vitepress/blob/main/src/node/markdown/plugins/highlight.ts
+import chalk from 'chalk'
+import escapeHtml from 'escape-html'
+import prism from 'prismjs'
+import consola from 'consola'
+
+// prism is listed as actual dep so it's ok to require
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const loadLanguages = require('prismjs/components/index')
+
+// required to make embedded highlighting work...
+loadLanguages(['markup', 'css', 'javascript'])
+
+function wrap(code: string, lang: string): string {
+  if (lang === 'text') {
+    code = escapeHtml(code)
+  }
+  return `<pre v-pre><code>${code}</code></pre>`
+}
+
+export const highlight = (str: string, lang: string) => {
+  if (!lang) {
+    return wrap(str, 'text')
+  }
+  lang = lang.toLowerCase()
+  const rawLang = lang
+  if (lang === 'vue' || lang === 'html') {
+    lang = 'markup'
+  }
+  if (lang === 'md') {
+    lang = 'markdown'
+  }
+  if (lang === 'ts') {
+    lang = 'typescript'
+  }
+  if (lang === 'py') {
+    lang = 'python'
+  }
+  if (!prism.languages[lang]) {
+    try {
+      loadLanguages([lang])
+    } catch {
+      // eslint-disable-next-line no-console
+      consola.warn(
+        chalk.yellow(
+          `[vitepress] Syntax highlight for language "${lang}" is not supported.`
+        )
+      )
+    }
+  }
+  if (prism.languages[lang]) {
+    const code = prism.highlight(str, prism.languages[lang], lang)
+    return wrap(code, rawLang)
+  }
+  return wrap(str, 'text')
+}

+ 10 - 0
docs/.vitepress/utils/lang.ts

@@ -0,0 +1,10 @@
+import fs from 'fs'
+import path from 'path'
+import { docRoot } from '@element-plus/build-utils'
+
+export const languages = fs.readdirSync(path.resolve(__dirname, '../crowdin'))
+
+export const ensureLang = (lang: string) => `/${lang}`
+
+export const getLang = (id: string) =>
+  path.relative(docRoot, id).split(path.sep)[0]

+ 1 - 0
docs/.vitepress/utils/types.ts

@@ -0,0 +1 @@
+export const define = <T>(value: T): T => value

+ 37 - 0
docs/.vitepress/vitepress/components/common/vp-link.vue

@@ -0,0 +1,37 @@
+<script setup lang="ts">
+import { computed } from 'vue'
+
+const props = defineProps<{
+  href?: string
+  noIcon?: boolean
+}>()
+
+const isExternal = computed(() => props.href && /^[a-z]+:/i.test(props.href))
+</script>
+
+<template>
+  <component
+    :is="href ? 'a' : 'span'"
+    class="link-item"
+    :class="{ link: href }"
+    :href="href"
+    :target="isExternal ? '_blank' : undefined"
+    :rel="isExternal ? 'noopener noreferrer' : undefined"
+  >
+    <slot />
+    <ElIcon v-if="isExternal && !noIcon">
+      <i-ri-external-link-line class="link-icon" />
+    </ElIcon>
+  </component>
+</template>
+
+<style scoped>
+.link-item {
+  display: flex;
+  align-items: center;
+}
+
+.el-icon {
+  margin-left: 4px;
+}
+</style>

+ 36 - 0
docs/.vitepress/vitepress/components/common/vp-markdown.vue

@@ -0,0 +1,36 @@
+<script lang="ts" setup>
+import { computed } from 'vue'
+import MarkdownIt from 'markdown-it'
+
+const md = new MarkdownIt()
+
+const props = defineProps({
+  content: { type: String, required: true },
+})
+
+const attr = 'rel="noreferrer noopenner" target="_blank"'
+
+const parsed = computed(() => {
+  // Note this is relatively arbitrary so that this could be buggy.
+  return md
+    .render(props.content)
+    .replace(
+      /#([0-9]+) by/g,
+      `<a href="https://github.com/element-plus/element-plus/pull/$1" ${attr}>#$1</a> by`
+    )
+    .replace(
+      /@([A-Za-z0-9_-]+)/g,
+      `<a href="https://github.com/$1" ${attr}>@$1</a>`
+    )
+})
+</script>
+
+<template>
+  <div class="markdown-wrapper" v-html="parsed" />
+</template>
+
+<style>
+.markdown-wrapper h3 {
+  margin-top: 1rem;
+}
+</style>

+ 15 - 0
docs/.vitepress/vitepress/components/common/vp-switch.vue

@@ -0,0 +1,15 @@
+<script setup lang="ts">
+// for now el-switch does not support customized icon in the dot
+// we will implement a simple version of el-switch then update the switch
+// component for this feature
+</script>
+
+<template>
+  <div class="switch" role="switch">
+    <div class="switch__action">
+      <div class="switch__icon">
+        <slot />
+      </div>
+    </div>
+  </div>
+</template>

+ 50 - 0
docs/.vitepress/vitepress/components/common/vp-theme-toggler.vue

@@ -0,0 +1,50 @@
+<script setup lang="ts">
+import DarkIcon from '../icons/dark.vue'
+import LightIcon from '../icons/light.vue'
+import VPSwitch from './vp-switch.vue'
+</script>
+
+<template>
+  <VPSwitch>
+    <ElIcon :size="13">
+      <DarkIcon class="dark-icon" />
+      <LightIcon class="light-icon" />
+    </ElIcon>
+  </VPSwitch>
+</template>
+
+<style lang="scss" scoped>
+.el-icon {
+  cursor: pointer;
+}
+
+.dark-icon,
+.light-icon {
+  transition: color var(--el-transition-duration),
+    opacity var(--el-transition-duration);
+}
+
+.light-icon {
+  opacity: 1;
+  position: absolute;
+  top: 0;
+  left: 0;
+}
+
+.dark-icon {
+  opacity: 0;
+  position: absolute;
+  top: 0;
+  left: 0;
+}
+
+@at-root .dark {
+  .dark-icon {
+    opacity: 1;
+  }
+
+  .light-icon {
+    opacity: 0;
+  }
+}
+</style>

+ 28 - 0
docs/.vitepress/vitepress/components/demo/vp-example.vue

@@ -0,0 +1,28 @@
+<script setup lang="ts">
+defineProps({
+  file: {
+    type: String,
+    required: true,
+  },
+  demo: {
+    type: Object,
+    required: true,
+  },
+})
+</script>
+
+<template>
+  <div class="example-showcase">
+    <ClientOnly>
+      <component :is="demo" v-if="demo" v-bind="$attrs" />
+    </ClientOnly>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.example-showcase {
+  padding: 1.5rem;
+  margin: 0.5px;
+  background-color: var(--bg-color);
+}
+</style>

+ 27 - 0
docs/.vitepress/vitepress/components/demo/vp-source-code.vue

@@ -0,0 +1,27 @@
+<script setup lang="ts">
+import { computed } from 'vue'
+
+const props = defineProps({
+  source: {
+    type: String,
+    required: true,
+  },
+})
+
+const decoded = computed(() => {
+  return decodeURIComponent(props.source)
+})
+</script>
+
+<template>
+  <div class="example-source-wrapper">
+    <div class="example-source language-vue" v-html="decoded" />
+  </div>
+</template>
+
+<style scoped lang="scss">
+.language-vue {
+  margin: 0;
+  border-radius: 0;
+}
+</style>

+ 17 - 0
docs/.vitepress/vitepress/components/dev/a11y-tag.vue

@@ -0,0 +1,17 @@
+<template>
+  <el-tag size="small" effect="plain" hit round class="ml-2">a11y</el-tag>
+</template>
+<style scoped>
+.el-tag {
+  color: #6222c2;
+}
+.el-tag.is-hit {
+  border-color: #9065db;
+}
+.dark .el-tag {
+  color: #9065db;
+}
+.dark .el-tag.is-hit {
+  border-color: #6222c2;
+}
+</style>

+ 5 - 0
docs/.vitepress/vitepress/components/dev/deprecated-tag.vue

@@ -0,0 +1,5 @@
+<template>
+  <el-tag size="small" type="warning" effect="plain" hit round class="ml-2"
+    >deprecated</el-tag
+  >
+</template>

+ 11 - 0
docs/.vitepress/vitepress/components/dev/version-tag.vue

@@ -0,0 +1,11 @@
+<script lang="ts" setup>
+defineProps<{
+  version: string
+}>()
+</script>
+
+<template>
+  <el-tag size="small" effect="plain" hit round class="ml-2">
+    {{ version }}
+  </el-tag>
+</template>

+ 34 - 0
docs/.vitepress/vitepress/components/doc-content/vp-edit-link.vue

@@ -0,0 +1,34 @@
+<script setup lang="ts">
+import { useEditLink } from '../../composables/edit-link'
+
+const { url, text } = useEditLink()
+</script>
+
+<template>
+  <div class="edit-link">
+    <a
+      v-if="url"
+      class="link text-sm"
+      :href="url"
+      target="_blank"
+      rel="noopener noreferrer"
+    >
+      {{ text }}
+      <ElIcon :size="16" style="vertical-align: text-top; line-height: 24px">
+        <i-ri-external-link-line />
+      </ElIcon>
+    </a>
+  </div>
+</template>
+
+<style scoped>
+.link {
+  display: inline-block;
+  font-weight: 500;
+}
+
+.link:hover {
+  text-decoration: none;
+  color: var(--brand-color);
+}
+</style>

+ 47 - 0
docs/.vitepress/vitepress/components/doc-content/vp-last-updated-at.vue

@@ -0,0 +1,47 @@
+<script setup lang="ts">
+import { computed, onMounted, ref } from 'vue'
+import { useData } from 'vitepress'
+import { useLang } from '../../composables/lang'
+import localeData from '../../../i18n/component/last-update-at.json'
+
+const { page } = useData()
+const lang = useLang()
+
+const prefix = computed(() => {
+  return localeData[lang.value].title
+})
+
+const datetime = ref('')
+onMounted(() => {
+  datetime.value = new Date(page.value.lastUpdated).toLocaleString(lang.value)
+})
+</script>
+
+<template>
+  <p class="last-updated text-sm">
+    <span class="prefix">{{ prefix }}:</span>
+    <span class="datetime">{{ datetime }}</span>
+  </p>
+</template>
+
+<style scoped lang="scss">
+@use '../../styles/mixins' as *;
+
+.last-updated {
+  display: inline-block;
+  margin: 0;
+  line-height: 1.4;
+  color: var(--text-color-light);
+
+  .prefix {
+    display: inline-block;
+    font-weight: 500;
+  }
+
+  .datetime {
+    display: inline-block;
+    margin-left: 6px;
+    font-weight: 400;
+  }
+}
+</style>

+ 35 - 0
docs/.vitepress/vitepress/components/doc-content/vp-page-footer.vue

@@ -0,0 +1,35 @@
+<script setup lang="ts">
+import VPEditLink from './vp-edit-link.vue'
+</script>
+
+<template>
+  <footer class="page-footer">
+    <div class="edit">
+      <VPEditLink />
+    </div>
+  </footer>
+</template>
+
+<style scoped lang="scss">
+@use '../../styles/mixins' as *;
+.page-footer {
+  padding-top: 1rem;
+  padding-bottom: 1rem;
+  overflow: auto;
+
+  .updated {
+    padding-top: 4px;
+  }
+}
+
+@include respond-to('lg') {
+  .page-footer {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+  .updated {
+    padding-top: 0;
+  }
+}
+</style>

+ 93 - 0
docs/.vitepress/vitepress/components/doc-content/vp-page-nav.vue

@@ -0,0 +1,93 @@
+<script setup lang="ts">
+import { withBase } from 'vitepress'
+import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue'
+import { usePageNav } from '../../composables/page-nav'
+
+const { hasLinks, prev, next } = usePageNav()
+</script>
+
+<template>
+  <div v-if="hasLinks" class="next-and-prev-link">
+    <div class="container">
+      <div class="prev">
+        <a v-if="prev" class="link" :href="withBase(prev.link)">
+          <ElIcon class="mr-1">
+            <ArrowLeft />
+          </ElIcon>
+          <span class="text">{{ prev.text }}</span>
+        </a>
+      </div>
+      <div class="next">
+        <a v-if="next" class="link" :href="withBase(next.link)">
+          <span class="text">{{ next.text }}</span>
+          <ElIcon class="ml-1">
+            <ArrowRight />
+          </ElIcon>
+        </a>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.next-and-prev-link {
+  padding-top: 1rem;
+}
+
+.container {
+  display: flex;
+  justify-content: space-between;
+  border-top: 1px solid var(--border-color);
+  padding-top: 1rem;
+}
+
+.prev,
+.next {
+  display: flex;
+  flex-shrink: 0;
+  width: 50%;
+}
+
+.prev {
+  justify-content: flex-start;
+  padding-right: 12px;
+}
+
+.next {
+  justify-content: flex-end;
+  padding-left: 12px;
+}
+
+.link {
+  display: inline-flex;
+  justify-content: center;
+  align-items: center;
+
+  max-width: 100%;
+  height: 24px;
+  font-size: 14px;
+  font-weight: 500;
+}
+
+.text {
+  display: inline-flex;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+
+.el-icon {
+  display: inline-flex;
+  flex-shrink: 0;
+  font-size: 12px;
+  color: var(--text-color);
+  transform: translateY(1px);
+}
+
+.icon-prev {
+  margin-right: 8px;
+}
+.icon-next {
+  margin-left: 8px;
+}
+</style>

+ 65 - 0
docs/.vitepress/vitepress/components/doc-content/vp-table-of-content.vue

@@ -0,0 +1,65 @@
+<script setup lang="ts">
+import { computed, ref } from 'vue'
+import { useToc } from '../../composables/use-toc'
+import { useActiveSidebarLinks } from '../../composables/active-bar'
+
+import sponsorLocale from '../../../i18n/component/sponsor.json'
+import { useLang } from '../../composables/lang'
+import SponsorsButton from '../sponsors/sponsors-button.vue'
+import SponsorRightTextList from '../sponsors/right-richtext-list.vue'
+import SponsorRightLogoSmallList from '../sponsors/right-logo-small-list.vue'
+// import SponsorLarge from '../vp-sponsor-large.vue'
+
+const headers = useToc()
+const marker = ref()
+const container = ref()
+useActiveSidebarLinks(container, marker)
+const lang = useLang()
+const sponsor = computed(() => sponsorLocale[lang.value])
+</script>
+
+<template>
+  <aside ref="container" class="toc-wrapper">
+    <nav class="toc-content">
+      <h3 class="toc-content__heading">Contents</h3>
+      <ul class="toc-items">
+        <li
+          v-for="{ link, text, children } in headers"
+          :key="link"
+          class="toc-item"
+        >
+          <a class="toc-link" :href="link" :title="text">{{ text }}</a>
+          <ul v-if="children">
+            <li
+              v-for="{ link: childLink, text: childText } in children"
+              :key="childLink"
+              class="toc-item"
+            >
+              <a class="toc-link subitem" :href="childLink" :title="text">{{
+                childText
+              }}</a>
+            </li>
+          </ul>
+        </li>
+      </ul>
+      <div ref="marker" class="toc-marker" />
+      <!-- <SponsorLarge
+        class="mt-8 toc-ads flex flex-col"
+        item-style="width: 180px; height: 55px;"
+      /> -->
+      <p class="text-14px font-300 color-$text-color-secondary">
+        {{ sponsor.sponsoredBy }}
+      </p>
+      <sponsors-button class="sponsors-button mt-4 w-100%" />
+      <sponsor-right-logo-small-list />
+      <sponsor-right-text-list />
+    </nav>
+  </aside>
+</template>
+<style scoped lang="scss">
+.sponsors-button:deep {
+  button {
+    width: 100%;
+  }
+}
+</style>

+ 40 - 0
docs/.vitepress/vitepress/components/full-screen/vp-menu-link.vue

@@ -0,0 +1,40 @@
+<script lang="ts" setup>
+import VPLink from '../common/vp-link.vue'
+
+import type { Link } from '../../types'
+
+defineProps<{
+  item: Link
+}>()
+</script>
+
+<template>
+  <VPLink
+    :class="{
+      'is-menu-link': true,
+    }"
+    :href="item.link"
+    :no-icon="true"
+  >
+    {{ item.text }}
+  </VPLink>
+</template>
+
+<style scoped lang="scss">
+.is-menu-link {
+  display: block;
+  font-size: 13px;
+  font-weight: 500;
+  line-height: 24px;
+  color: var(--text-color);
+  transition: color var(--el-transition-duration);
+
+  &.active {
+    border-bottom: 2px solid var(--brand-color);
+  }
+
+  &:hover {
+    color: var(--brand-color);
+  }
+}
+</style>

+ 23 - 0
docs/.vitepress/vitepress/components/full-screen/vp-menu.vue

@@ -0,0 +1,23 @@
+<script setup lang="ts">
+import { useNav } from '../../composables/nav'
+import VPMenuLink from './vp-menu-link.vue'
+
+const navs = useNav()
+
+defineEmits(['close'])
+</script>
+
+<template>
+  <nav v-if="navs" class="full-screen-menu">
+    <div v-for="(item, key) in navs" :key="key" class="full-screen-menu__item">
+      <VPMenuLink :item="item" @click="$emit('close')" />
+    </div>
+  </nav>
+</template>
+
+<style lang="scss" scoped>
+.full-screen-menu__item {
+  padding: 12px 0;
+  border-bottom: 1px solid var(--border-color);
+}
+</style>

+ 24 - 0
docs/.vitepress/vitepress/components/full-screen/vp-theme-toggler.vue

@@ -0,0 +1,24 @@
+<script setup lang="ts">
+import CommonThemeToggler from '../common/vp-theme-toggler.vue'
+import { toggleDark } from '../../composables/dark'
+</script>
+
+<template>
+  <div class="full-screen-theme-toggler">
+    <span>Theme</span>
+    <CommonThemeToggler @click="toggleDark()" />
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.full-screen-theme-toggler {
+  display: flex;
+  padding: 12px 14px;
+  align-items: center;
+  justify-content: space-between;
+  margin-top: 16px;
+  font-size: 13px;
+  background-color: var(--bg-color-soft);
+  border-radius: 8px;
+}
+</style>

+ 90 - 0
docs/.vitepress/vitepress/components/full-screen/vp-translation.vue

@@ -0,0 +1,90 @@
+<script setup lang="ts">
+import { useToggle } from '@vueuse/core'
+import VPLink from '../common/vp-link.vue'
+import { useTranslation } from '../../composables/translation'
+import ExpandIcon from '../icons/expand.vue'
+
+const emit = defineEmits(['close'])
+
+const { languageMap, langs, lang, switchLang, helpTranslate } = useTranslation()
+
+const [show, toggle] = useToggle()
+
+const onSwitchLang = (lang: string) => {
+  switchLang(lang)
+  emit('close')
+}
+</script>
+
+<template>
+  <div class="full-screen-translation">
+    <ElButton
+      style="width: 100%; color: var(--text-color)"
+      text
+      @click="toggle"
+    >
+      <div class="translation-toggler">
+        <span> Translations </span>
+        <ElIcon :size="14">
+          <ExpandIcon class="toggle-icon" :class="{ expanded: show }" />
+        </ElIcon>
+      </div>
+    </ElButton>
+    <div v-show="show" class="translation-items">
+      <p
+        v-for="l in langs"
+        :key="l"
+        :class="{ active: l === lang }"
+        class="translation-item"
+        @click="onSwitchLang(l)"
+      >
+        {{ languageMap[l] }}
+      </p>
+      <p class="translation-item">
+        <VPLink :href="`/${lang}/guide/translation`">
+          {{ helpTranslate }}
+        </VPLink>
+      </p>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.full-screen-translation {
+  border-bottom: 1px solid var(--border-color);
+}
+.translation-toggler {
+  width: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  line-height: 24px;
+  .toggle-icon {
+    transition: transform var(--el-transition-duration);
+    transform: rotate(180deg);
+
+    &.expanded {
+      transform: rotate(0deg);
+    }
+  }
+}
+
+.translation-items {
+  padding-bottom: 12px;
+  .translation-item {
+    cursor: pointer;
+    margin: 0;
+    font-size: 14px;
+    line-height: 32px;
+
+    &.active {
+      font-weight: 500;
+      color: var(--brand-color);
+    }
+
+    .link-item {
+      font-weight: 500;
+    }
+  }
+}
+</style>

+ 38 - 0
docs/.vitepress/vitepress/components/globals/contributors.vue

@@ -0,0 +1,38 @@
+<script setup lang="ts">
+import { computed } from 'vue'
+import _contributors from '@element-plus/metadata/dist/contributors.json'
+import VpLink from '../common/vp-link.vue'
+
+const props = defineProps<{ id: string }>()
+
+const contributors = computed(() =>
+  _contributors[props.id]?.filter((c) => c.login !== 'renovate[bot]')
+)
+</script>
+
+<template>
+  <div class="mb-4">
+    <div class="flex flex-wrap gap-4 pt-2">
+      <div v-for="c of contributors" :key="c.hash">
+        <vp-link
+          :href="`https://github.com/${c.login}`"
+          class="flex gap-2 items-center link"
+          no-icon
+        >
+          <img :src="c.avatar" class="w-8 h-8 rounded-full" />
+          {{ c.name }}
+        </vp-link>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.link {
+  color: var(--text-color-light);
+
+  &:hover {
+    color: var(--brand-color);
+  }
+}
+</style>

+ 58 - 0
docs/.vitepress/vitepress/components/globals/design-guide.vue

@@ -0,0 +1,58 @@
+<template>
+  <div class="guide-design">
+    <div class="el-row cards" style="margin-left: -7px; margin-right: -7px">
+      <div class="el-col el-col-24 el-col-xs-12 el-col-sm-6 is-guttered">
+        <div class="card">
+          <consistency-svg m="4" w="20" alt="Consistency" />
+          <p>Consistency</p>
+        </div>
+      </div>
+      <div class="el-col el-col-24 el-col-xs-12 el-col-sm-6 is-guttered">
+        <div class="card">
+          <feedback-svg m="4" w="20" alt="Feedback" />
+          <p>Feedback</p>
+        </div>
+      </div>
+      <div class="el-col el-col-24 el-col-xs-12 el-col-sm-6 is-guttered">
+        <div class="card">
+          <efficiency-svg m="4" w="20" alt="Efficiency" />
+          <p>Efficiency</p>
+        </div>
+      </div>
+      <div class="el-col el-col-24 el-col-xs-12 el-col-sm-6 is-guttered">
+        <div class="card">
+          <controllability-svg m="4" w="20" alt="Controllability" />
+          <p>Controllability</p>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.el-col {
+  padding: 0 7px;
+}
+.card {
+  background: var(--el-fill-color-lighter);
+  height: 204px;
+  text-align: center;
+
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  flex-direction: column;
+
+  img {
+    margin: 1rem;
+    width: 5rem;
+    height: 5rem;
+  }
+}
+
+@media screen and (max-width: 767px) {
+  .el-col {
+    padding-bottom: 8px;
+  }
+}
+</style>

+ 73 - 0
docs/.vitepress/vitepress/components/globals/design/consistency-svg.vue

@@ -0,0 +1,73 @@
+<template>
+  <!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+  <svg
+    id="图层_1"
+    version="1.1"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    x="0px"
+    y="0px"
+    viewBox="0 0 79 79"
+    style="enable-background: new 0 0 79 79"
+    xml:space="preserve"
+  >
+    <circle class="st0" cx="39.5" cy="39.5" r="39.5" />
+    <path
+      class="st1"
+      d="M23.2,42c-0.5,0-1,0.4-1,1v17.8c0,0.5,0.4,1,1,1h3.5c0.5,0,1-0.4,1-1V43c0-0.5-0.4-1-1-1H23.2z"
+    />
+    <path
+      class="st1"
+      d="M32.6,42c-0.5,0-1,0.4-1,1v17.8c0,0.5,0.4,1,1,1H36c0.5,0,1-0.4,1-1V43c0-0.5-0.4-1-1-1H32.6z"
+    />
+    <path
+      class="st1"
+      d="M42,43c0-0.5,0.4-1,1-1h3.5c0.5,0,1,0.4,1,1v17.8c0,0.5-0.4,1-1,1H43c-0.5,0-1-0.4-1-1V43z"
+    />
+    <path
+      class="st1"
+      d="M52.3,42c-0.5,0-1,0.4-1,1v17.8c0,0.5,0.4,1,1,1h3.5c0.5,0,1-0.4,1-1V43c0-0.5-0.4-1-1-1H52.3z"
+    />
+    <path
+      class="st2"
+      d="M22.2,43c0-0.5,0.4-1,1-1h3.5c0.5,0,1,0.4,1,1v15.8c0,0.5-0.4,1-1,1h-3.5c-0.5,0-1-0.4-1-1V43z"
+    />
+    <path
+      class="st2"
+      d="M31.6,43c0-0.5,0.4-1,1-1H36c0.5,0,1,0.4,1,1v15.8c0,0.5-0.4,1-1,1h-3.5c-0.5,0-1-0.4-1-1V43z"
+    />
+    <path
+      class="st2"
+      d="M42,43c0-0.5,0.4-1,1-1h3.5c0.5,0,1,0.4,1,1v15.8c0,0.5-0.4,1-1,1H43c-0.5,0-1-0.4-1-1V43z"
+    />
+    <path
+      class="st2"
+      d="M51.3,43c0-0.5,0.4-1,1-1h3.5c0.5,0,1,0.4,1,1v15.8c0,0.5-0.4,1-1,1h-3.5c-0.5,0-1-0.4-1-1V43z"
+    />
+    <path
+      class="st3"
+      d="M57.3,28.1l-5.9-5.9v4.9H22.7c-0.5,0-1,0.4-1,1c0,0.5,0.4,1,1,1h28.6v4.9L57.3,28.1z"
+    />
+  </svg>
+</template>
+
+<style scoped lang="scss">
+.st0 {
+  fill: #eff5fd;
+}
+.st1 {
+  fill: #0077ce;
+}
+.st2 {
+  fill: #20a0ff;
+}
+.st3 {
+  fill: #7383bf;
+}
+
+.dark {
+  .st0 {
+    fill: #36393d;
+  }
+}
+</style>

+ 80 - 0
docs/.vitepress/vitepress/components/globals/design/controllability-svg.vue

@@ -0,0 +1,80 @@
+<template>
+  <svg
+    id="图层_1"
+    version="1.1"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    x="0px"
+    y="0px"
+    viewBox="0 0 79 79"
+    style="enable-background: new 0 0 79 79"
+    xml:space="preserve"
+  >
+    <circle class="st0" cx="39.5" cy="39.5" r="39.5" />
+    <g>
+      <defs>
+        <rect id="SVGID_1_" x="15.8" y="17.3" width="47.4" height="43.9" />
+      </defs>
+      <clipPath id="SVGID_00000103965616648291865560000002216192073450902938_">
+        <use xlink:href="#SVGID_1_" style="overflow: visible" />
+      </clipPath>
+      <g
+        style="
+          clip-path: url(#SVGID_00000103965616648291865560000002216192073450902938_);
+        "
+      >
+        <path
+          class="st2"
+          d="M57.3,23.2L57.3,23.2c0.5,0,1,0.4,1,1v36c0,0.5-0.4,1-1,1l0,0c-0.5,0-1-0.4-1-1v-36
+        C56.3,23.6,56.7,23.2,57.3,23.2z"
+        />
+        <ellipse class="st3" cx="57.3" cy="24.7" rx="5.9" ry="5.9" />
+        <ellipse class="st4" cx="57.3" cy="23.2" rx="5.9" ry="5.9" />
+        <path
+          class="st2"
+          d="M21.7,17.3L21.7,17.3c0.5,0,1,0.4,1,1v36c0,0.5-0.4,1-1,1h0c-0.5,0-1-0.4-1-1v-36
+        C20.7,17.7,21.2,17.3,21.7,17.3z"
+        />
+        <ellipse class="st3" cx="21.7" cy="55.3" rx="5.9" ry="5.9" />
+        <ellipse class="st4" cx="21.7" cy="53.8" rx="5.9" ry="5.9" />
+        <path
+          class="st2"
+          d="M39.5,17.3L39.5,17.3c0.5,0,1,0.4,1,1v42c0,0.5-0.4,1-1,1l0,0c-0.5,0-1-0.4-1-1v-42
+        C38.5,17.7,39,17.3,39.5,17.3z"
+        />
+        <ellipse class="st5" cx="39.5" cy="39" rx="5.9" ry="5.9" />
+        <ellipse class="st6" cx="39.5" cy="37.5" rx="5.9" ry="5.9" />
+      </g>
+    </g>
+  </svg>
+</template>
+
+<style scoped lang="scss">
+.st0 {
+  fill: #eff5fd;
+}
+.st1 {
+  clip-path: url(#SVGID_00000172405038201111576250000009038874290854515645_);
+}
+.st2 {
+  fill: #afcaf1;
+}
+.st3 {
+  fill: #afb6bb;
+}
+.st4 {
+  fill: #e7eced;
+}
+.st5 {
+  fill: #0077ce;
+}
+.st6 {
+  fill: #20a0ff;
+}
+
+.dark {
+  .st0 {
+    fill: #36393d;
+  }
+}
+</style>

+ 74 - 0
docs/.vitepress/vitepress/components/globals/design/efficiency-svg.vue

@@ -0,0 +1,74 @@
+<template>
+  <svg
+    id="图层_1"
+    version="1.1"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    x="0px"
+    y="0px"
+    viewBox="0 0 79 79"
+    style="enable-background: new 0 0 79 79"
+    xml:space="preserve"
+  >
+    <circle class="st0" cx="39.5" cy="39.5" r="39.5" />
+    <path
+      class="st1"
+      d="M37,16.8c-1.6,0-3,1.3-3,3V22c-3,0.9-5.7,2.5-7.9,4.5l-2-1.1c-1.4-0.8-3.2-0.3-4,1.1l-2.5,4.3
+      c-0.8,1.4-0.3,3.2,1.1,4l2,1.1c-0.4,1.5-0.5,3-0.5,4.5c0,1.6,0.2,3.1,0.5,4.5l-2,1.1c-1.4,0.8-1.9,2.6-1.1,4l2.5,4.3
+      c0.8,1.4,2.6,1.9,4,1.1l2-1.1c2.2,2.1,4.9,3.7,7.9,4.5v2.3c0,1.6,1.3,3,3,3h4.9c1.6,0,3-1.3,3-3V59c3-0.9,5.7-2.5,7.9-4.5l2,1.1
+      c1.4,0.8,3.2,0.3,4-1.1l2.5-4.3c0.8-1.4,0.3-3.2-1.1-4l-2-1.1c0.4-1.5,0.5-3,0.5-4.5c0-1.6-0.2-3.1-0.5-4.5l2-1.1
+      c1.4-0.8,1.9-2.6,1.1-4l-2.5-4.3c-0.8-1.4-2.6-1.9-4-1.1l-2,1.1c-2.2-2.1-4.9-3.7-7.9-4.5v-2.3c0-1.6-1.3-3-3-3H37z"
+    />
+    <path
+      class="st2"
+      d="M37,14.8c-1.6,0-3,1.3-3,3V20c-3,0.9-5.7,2.5-7.9,4.5l-2-1.1c-1.4-0.8-3.2-0.3-4,1.1l-2.5,4.3
+      c-0.8,1.4-0.3,3.2,1.1,4l2,1.1c-0.4,1.5-0.5,3-0.5,4.5s0.2,3.1,0.5,4.5l-2,1.1c-1.4,0.8-1.9,2.6-1.1,4l2.5,4.3
+      c0.8,1.4,2.6,1.9,4,1.1l2-1.1c2.2,2.1,4.9,3.7,7.9,4.5v2.3c0,1.6,1.3,3,3,3h4.9c1.6,0,3-1.3,3-3V57c3-0.9,5.7-2.5,7.9-4.5l2,1.1
+      c1.4,0.8,3.2,0.3,4-1.1l2.5-4.3c0.8-1.4,0.3-3.2-1.1-4l-2-1.1c0.4-1.5,0.5-3,0.5-4.5s-0.2-3.1-0.5-4.5l2-1.1c1.4-0.8,1.9-2.6,1.1-4
+      l-2.5-4.3c-0.8-1.4-2.6-1.9-4-1.1l-2,1.1c-2.2-2.1-4.9-3.7-7.9-4.5v-2.3c0-1.6-1.3-3-3-3H37z"
+    />
+    <ellipse
+      transform="matrix(1 -2.392332e-03 2.392332e-03 1 -9.202202e-02 9.445851e-02)"
+      class="st3"
+      cx="39.4"
+      cy="38.5"
+      rx="15.3"
+      ry="15.3"
+    />
+    <path
+      class="st4"
+      d="M38.5,30.6c0-0.5,0.4-1,1-1c0.5,0,1,0.4,1,1v10.9h-2V30.6z"
+    />
+    <path
+      class="st5"
+      d="M47.3,39.5c0.5,0,1,0.4,1,1c0,0.5-0.4,1-1,1h-8.9v-2H47.3z"
+    />
+  </svg>
+</template>
+
+<style scoped lang="scss">
+.st0 {
+  fill: #eff5fd;
+}
+.st1 {
+  fill: #afb6bb;
+}
+.st2 {
+  fill: #e7eced;
+}
+.st3 {
+  fill: #ffffff;
+}
+.st4 {
+  fill: #0077ce;
+}
+.st5 {
+  fill: #20a0ff;
+}
+
+.dark {
+  .st0 {
+    fill: #36393d;
+  }
+}
+</style>

+ 52 - 0
docs/.vitepress/vitepress/components/globals/design/feedback-svg.vue

@@ -0,0 +1,52 @@
+<template>
+  <svg
+    id="图层_1"
+    version="1.1"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    x="0px"
+    y="0px"
+    viewBox="0 0 79 79"
+    style="enable-background: new 0 0 79 79"
+    xml:space="preserve"
+  >
+    <circle class="st0" cx="39.5" cy="39.5" r="39.5" />
+    <path
+      class="st1"
+      d="M19.8,26.2c0-3.3,2.7-5.9,5.9-5.9h27.7c3.3,0,5.9,2.7,5.9,5.9v23.7c0,3.3-2.7,5.9-5.9,5.9H25.9l-6.2,6.4V26.2z"
+    />
+    <path
+      class="st2"
+      d="M19.8,24.2c0-3.3,2.7-5.9,5.9-5.9h27.7c3.3,0,5.9,2.7,5.9,5.9v23.7c0,3.3-2.7,5.9-5.9,5.9H25.9l-6.2,6.4V24.2z"
+    />
+    <path
+      class="st3"
+      d="M37.5,35.5c0-1.1,0.9-2,2-2s2,0.9,2,2v7.9c0,1.1-0.9,2-2,2s-2-0.9-2-2V35.5z"
+    />
+    <path
+      class="st3"
+      d="M37.5,29.1c0-1.1,0.9-2,2-2s2,0.9,2,2c0,1.1-0.9,2-2,2S37.5,30.2,37.5,29.1z"
+    />
+  </svg>
+</template>
+
+<style scoped lang="scss">
+.st0 {
+  fill: #eff5fd;
+}
+.st1 {
+  fill: #0077ce;
+}
+.st2 {
+  fill: #20a0ff;
+}
+.st3 {
+  fill: #ffffff;
+}
+
+.dark {
+  .st0 {
+    fill: #36393d;
+  }
+}
+</style>

+ 332 - 0
docs/.vitepress/vitepress/components/globals/icons-categories.json

@@ -0,0 +1,332 @@
+{
+  "categories": [
+    {
+      "name": "System",
+      "items": [
+        "Plus",
+        "Minus",
+        "CirclePlus",
+        "Search",
+        "Female",
+        "Male",
+        "Aim",
+        "House",
+        "FullScreen",
+        "Loading",
+        "Link",
+        "Service",
+        "Pointer",
+        "Star",
+        "Notification",
+        "Connection",
+        "ChatDotRound",
+        "Setting",
+        "Clock",
+        "Position",
+        "Discount",
+        "Odometer",
+        "ChatSquare",
+        "ChatRound",
+        "ChatLineRound",
+        "ChatLineSquare",
+        "ChatDotSquare",
+        "View",
+        "Hide",
+        "Unlock",
+        "Lock",
+        "RefreshRight",
+        "RefreshLeft",
+        "Refresh",
+        "Bell",
+        "MuteNotification",
+        "User",
+        "Check",
+        "CircleCheck",
+        "Warning",
+        "CircleClose",
+        "Close",
+        "PieChart",
+        "More",
+        "Compass",
+        "Filter",
+        "Switch",
+        "Select",
+        "SemiSelect",
+        "CloseBold",
+        "EditPen",
+        "Edit",
+        "Message",
+        "MessageBox",
+        "TurnOff",
+        "Finished",
+        "Delete",
+        "Crop",
+        "SwitchButton",
+        "Operation",
+        "Open",
+        "Remove",
+        "ZoomOut",
+        "ZoomIn",
+        "InfoFilled",
+        "CircleCheckFilled",
+        "SuccessFilled",
+        "WarningFilled",
+        "CircleCloseFilled",
+        "QuestionFilled",
+        "WarnTriangleFilled",
+        "UserFilled",
+        "MoreFilled",
+        "Tools",
+        "HomeFilled",
+        "Menu",
+        "UploadFilled",
+        "Avatar",
+        "HelpFilled",
+        "Share",
+        "StarFilled",
+        "Comment",
+        "Histogram",
+        "Grid",
+        "Promotion",
+        "DeleteFilled",
+        "RemoveFilled",
+        "CirclePlusFilled"
+      ]
+    },
+    {
+      "name": "Arrow",
+      "items": [
+        "ArrowLeft",
+        "ArrowUp",
+        "ArrowRight",
+        "ArrowDown",
+        "ArrowLeftBold",
+        "ArrowUpBold",
+        "ArrowRightBold",
+        "ArrowDownBold",
+        "DArrowRight",
+        "DArrowLeft",
+        "Download",
+        "Upload",
+        "Top",
+        "Bottom",
+        "Back",
+        "Right",
+        "TopRight",
+        "TopLeft",
+        "BottomRight",
+        "BottomLeft",
+        "Sort",
+        "SortUp",
+        "SortDown",
+        "Rank",
+        "CaretLeft",
+        "CaretTop",
+        "CaretRight",
+        "CaretBottom",
+        "DCaret",
+        "Expand",
+        "Fold"
+      ]
+    },
+    {
+      "name": "Document",
+      "items": [
+        "DocumentAdd",
+        "Document",
+        "Notebook",
+        "Tickets",
+        "Memo",
+        "Collection",
+        "Postcard",
+        "ScaleToOriginal",
+        "SetUp",
+        "DocumentDelete",
+        "DocumentChecked",
+        "DataBoard",
+        "DataAnalysis",
+        "CopyDocument",
+        "FolderChecked",
+        "Files",
+        "Folder",
+        "FolderDelete",
+        "FolderRemove",
+        "FolderOpened",
+        "DocumentCopy",
+        "DocumentRemove",
+        "FolderAdd",
+        "FirstAidKit",
+        "Reading",
+        "DataLine",
+        "Management",
+        "Checked",
+        "Ticket",
+        "Failed",
+        "TrendCharts",
+        "List"
+      ]
+    },
+    {
+      "name": "Media",
+      "items": [
+        "Microphone",
+        "Mute",
+        "Mic",
+        "VideoPause",
+        "VideoCamera",
+        "VideoPlay",
+        "Headset",
+        "Monitor",
+        "Film",
+        "Camera",
+        "Picture",
+        "PictureRounded",
+        "Iphone",
+        "Cellphone",
+        "VideoCameraFilled",
+        "PictureFilled",
+        "Platform",
+        "CameraFilled",
+        "BellFilled"
+      ]
+    },
+    {
+      "name": "Traffic",
+      "items": [
+        "Location",
+        "LocationInformation",
+        "DeleteLocation",
+        "Coordinate",
+        "Bicycle",
+        "OfficeBuilding",
+        "School",
+        "Guide",
+        "AddLocation",
+        "MapLocation",
+        "Place",
+        "LocationFilled",
+        "Van"
+      ]
+    },
+    {
+      "name": "Food",
+      "items": [
+        "Watermelon",
+        "Pear",
+        "NoSmoking",
+        "Smoking",
+        "Mug",
+        "GobletSquareFull",
+        "GobletFull",
+        "KnifeFork",
+        "Sugar",
+        "Bowl",
+        "MilkTea",
+        "Lollipop",
+        "Coffee",
+        "Chicken",
+        "Dish",
+        "IceTea",
+        "ColdDrink",
+        "CoffeeCup",
+        "DishDot",
+        "IceDrink",
+        "IceCream",
+        "Dessert",
+        "IceCreamSquare",
+        "ForkSpoon",
+        "IceCreamRound",
+        "Food",
+        "HotWater",
+        "Grape",
+        "Fries",
+        "Apple",
+        "Burger",
+        "Goblet",
+        "GobletSquare",
+        "Orange",
+        "Cherry"
+      ]
+    },
+    {
+      "name": "Items",
+      "items": [
+        "Printer",
+        "Calendar",
+        "CreditCard",
+        "Box",
+        "Money",
+        "Refrigerator",
+        "Cpu",
+        "Football",
+        "Brush",
+        "Suitcase",
+        "SuitcaseLine",
+        "Umbrella",
+        "AlarmClock",
+        "Medal",
+        "GoldMedal",
+        "Present",
+        "Mouse",
+        "Watch",
+        "QuartzWatch",
+        "Magnet",
+        "Help",
+        "Soccer",
+        "ToiletPaper",
+        "ReadingLamp",
+        "Paperclip",
+        "MagicStick",
+        "Basketball",
+        "Baseball",
+        "Coin",
+        "Goods",
+        "Sell",
+        "SoldOut",
+        "Key",
+        "ShoppingCart",
+        "ShoppingCartFull",
+        "ShoppingTrolley",
+        "Phone",
+        "Scissor",
+        "Handbag",
+        "ShoppingBag",
+        "Trophy",
+        "TrophyBase",
+        "Stopwatch",
+        "Timer",
+        "CollectionTag",
+        "Discount",
+        "TakeawayBox",
+        "PriceTag",
+        "Wallet",
+        "Opportunity",
+        "PhoneFilled",
+        "WalletFilled",
+        "GoodsFilled",
+        "Flag",
+        "BrushFilled",
+        "Briefcase",
+        "Stamp"
+      ]
+    },
+    {
+      "name": "Weather",
+      "items": [
+        "Sunrise",
+        "Sunny",
+        "Ship",
+        "MostlyCloudy",
+        "PartlyCloudy",
+        "Sunset",
+        "Sunrise",
+        "Drizzling",
+        "Pouring",
+        "Cloudy",
+        "Moon",
+        "MoonNight",
+        "Lightning"
+      ]
+    }
+  ]
+}

+ 151 - 0
docs/.vitepress/vitepress/components/globals/icons.vue

@@ -0,0 +1,151 @@
+<script setup lang="ts">
+import { computed, ref, shallowRef } from 'vue'
+import clipboardCopy from 'clipboard-copy'
+import { ElMessage } from 'element-plus'
+import * as Icons from '@element-plus/icons-vue'
+import { useLang } from '../../composables/lang'
+import localeData from '../../../i18n/component/icons.json'
+import IconCategories from './icons-categories.json'
+import type { DefineComponent } from 'vue'
+
+type CategoriesItem = {
+  name: string
+  icons: DefineComponent[]
+}
+
+const lang = useLang()
+const locale = computed(() => localeData[lang.value])
+const copyIcon = ref(true)
+
+const copyContent = async (content) => {
+  try {
+    await clipboardCopy(content)
+
+    ElMessage({
+      showClose: true,
+      message: locale.value['copy-success'],
+      type: 'success',
+    })
+  } catch {
+    ElMessage({
+      showClose: true,
+      message: locale.value['copy-error'],
+      type: 'error',
+    })
+  }
+}
+
+const copySvgIcon = async (name, refs) => {
+  if (copyIcon.value) {
+    await copyContent(`<el-icon><${name} /></el-icon>`)
+  } else {
+    const content = refs[name]?.[0].querySelector('svg')?.outerHTML ?? ''
+    await copyContent(content)
+  }
+}
+
+const categories = shallowRef<CategoriesItem[]>([])
+const iconMap = new Map(Object.entries(Icons))
+
+IconCategories.categories.forEach((o) => {
+  const result: CategoriesItem = {
+    name: o.name,
+    icons: [],
+  }
+  o.items.forEach((i) => {
+    const icon = iconMap.get(i)
+    if (icon) {
+      result.icons.push(icon)
+      iconMap.delete(i)
+    }
+  })
+  categories.value.push(result)
+})
+
+categories.value.push({ name: 'Other', icons: Array.from(iconMap.values()) })
+</script>
+
+<template>
+  <div style="text-align: right">
+    <el-switch
+      v-model="copyIcon"
+      active-text="Copy icon code"
+      inactive-text="Copy SVG content"
+    />
+  </div>
+  <div v-for="item in categories" :key="item.name" class="demo-icon-item">
+    <div class="demo-icon-title">{{ item.name }}</div>
+    <ul class="demo-icon-list">
+      <li
+        v-for="component in item.icons"
+        :key="component.name"
+        :ref="component.name"
+        class="icon-item"
+        @click="copySvgIcon(component.name, $refs)"
+      >
+        <span class="demo-svg-icon">
+          <ElIcon :size="20">
+            <component :is="component" />
+          </ElIcon>
+          <span class="icon-name">{{ component.name }}</span>
+        </span>
+      </li>
+    </ul>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.demo-icon {
+  &-item {
+    margin-top: 24px;
+    &:first-child {
+      margin-top: 0;
+    }
+  }
+  &-title {
+    font-weight: 400;
+    font-size: 18px;
+    line-height: 26px;
+  }
+  &-list {
+    overflow: hidden;
+    list-style: none;
+    padding: 0 !important;
+    border-top: 1px solid var(--el-border-color);
+    border-left: 1px solid var(--el-border-color);
+    border-radius: 4px;
+    display: grid;
+    grid-template-columns: repeat(7, 1fr);
+
+    .icon-item {
+      text-align: center;
+      color: var(--el-text-color-regular);
+      height: 90px;
+      font-size: 13px;
+      border-right: 1px solid var(--el-border-color);
+      border-bottom: 1px solid var(--el-border-color);
+      transition: background-color var(--el-transition-duration);
+      &:hover {
+        background-color: var(--el-border-color-extra-light);
+        .el-icon {
+          color: var(--brand-color-light);
+        }
+        color: var(--brand-color-light);
+      }
+
+      .demo-svg-icon {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        height: 100%;
+        cursor: pointer;
+
+        .icon-name {
+          margin-top: 8px;
+        }
+      }
+    }
+  }
+}
+</style>

+ 33 - 0
docs/.vitepress/vitepress/components/globals/main-color.vue

@@ -0,0 +1,33 @@
+<script lang="ts" setup>
+import { useCssVar } from '@vueuse/core'
+import { useCopyColor } from '../../utils'
+
+const primary = useCssVar('--el-color-primary')
+const colorLevel = [3, 5, 7, 8, 9].map((i) => `light-${i}`)
+colorLevel.unshift('dark-2')
+
+const { copyColor } = useCopyColor()
+</script>
+
+<template>
+  <el-row :gutter="12">
+    <el-col :span="10" :xs="{ span: 12 }">
+      <div class="demo-color-box" :style="{ background: primary }">
+        Brand Color
+        <div class="value" text="xs">{{ primary.toUpperCase() }}</div>
+        <div class="bg-color-sub" :style="{ background: primary }">
+          <div
+            v-for="level in colorLevel"
+            :key="level"
+            class="bg-blue-sub-item cursor-pointer hover:shadow"
+            :style="{
+              width: `${100 / 6}%`,
+              background: 'var(--el-color-primary-' + level + ')',
+            }"
+            @click="copyColor('primary-' + level)"
+          />
+        </div>
+      </div>
+    </el-col>
+  </el-row>
+</template>

+ 161 - 0
docs/.vitepress/vitepress/components/globals/neutral-color.vue

@@ -0,0 +1,161 @@
+<template>
+  <el-row :gutter="12">
+    <el-col :span="6" :xs="{ span: 12 }">
+      <div class="demo-color-box-group">
+        <div
+          v-for="(text, i) in textColors"
+          :key="i"
+          class="demo-color-box demo-color-box-other"
+          :style="{
+            color: 'var(--el-bg-color)',
+            background: text.var.value,
+          }"
+        >
+          {{ text.name }}
+          <div class="value" text="xs">
+            {{ text.var.value.toUpperCase() }}
+          </div>
+        </div>
+      </div>
+    </el-col>
+
+    <el-col :span="6" :xs="{ span: 12 }">
+      <div class="demo-color-box-group">
+        <div
+          v-for="(border, i) in borderColors"
+          :key="i"
+          class="demo-color-box demo-color-box-other demo-color-box-lite"
+          :style="{ background: border.var.value }"
+        >
+          {{ border.name }}
+          <div class="value" text="xs">
+            {{ border.var.value.toUpperCase() }}
+          </div>
+        </div>
+      </div>
+    </el-col>
+
+    <el-col :span="6" :xs="{ span: 12 }">
+      <div class="demo-color-box-group">
+        <div
+          v-for="(fill, i) in fillColors"
+          :key="i"
+          class="demo-color-box demo-color-box-other demo-color-box-lite"
+          :style="{
+            background: fill.var.value,
+            border: `1px solid ${
+              fill.name === 'Blank Fill'
+                ? 'var(--el-border-color-light)'
+                : 'transparent'
+            }`,
+          }"
+        >
+          {{ fill.name }}
+          <div class="value" text="xs">
+            {{ fill.var.value.toUpperCase() }}
+          </div>
+        </div>
+      </div>
+    </el-col>
+
+    <el-col :span="6" :xs="{ span: 12 }">
+      <div class="demo-color-box-group">
+        <div
+          class="demo-color-box demo-color-box-other"
+          :style="{ background: black }"
+        >
+          Basic Black
+          <div class="value" text="xs">{{ black }}</div>
+        </div>
+        <div
+          class="demo-color-box demo-color-box-other"
+          :style="{
+            background: white,
+            color: '#303133',
+            border: '1px solid #eee',
+          }"
+        >
+          Basic White
+          <div class="value" text="xs">{{ white }}</div>
+        </div>
+        <div
+          class="demo-color-box demo-color-box-other demo-color-box-lite bg-transparent"
+        >
+          Transparent
+          <div class="value" text="xs">Transparent</div>
+        </div>
+
+        <div
+          v-for="(bg, i) in backgroundColors"
+          :key="i"
+          class="demo-color-box demo-color-box-other demo-color-box-lite"
+          :style="{
+            background: bg.var.value,
+            border:
+              '1px solid ' +
+              (!isDark || bg.name === 'Base Background'
+                ? 'var(--el-border-color-light)'
+                : 'transparent'),
+          }"
+        >
+          {{ bg.name }}
+          <div class="value" text="xs">
+            {{ bg.var.value.toUpperCase() }}
+          </div>
+        </div>
+      </div>
+    </el-col>
+  </el-row>
+</template>
+
+<script lang="ts" setup>
+import { isDark } from '~/composables/dark'
+import { getCssVarName, getCssVarValue } from '~/utils/colors'
+
+const backgroundTypes = ['page', '', 'overlay']
+const backgroundColors = backgroundTypes.map((type) => {
+  return {
+    name: type
+      ? `${type[0].toUpperCase() + type.slice(1)} Background`
+      : 'Base Background',
+    var: getCssVarValue(getCssVarName('bg-color', type)),
+  }
+})
+
+const borderTypes = ['darker', 'dark', '', 'light', 'lighter', 'extra-light']
+const borderColors = borderTypes.map((type) => {
+  return {
+    name: type
+      ? `${type[0].toUpperCase() + type.slice(1)} Border`
+      : 'Base Border',
+    var: getCssVarValue(getCssVarName('border-color', type)),
+  }
+})
+
+const fillTypes = [
+  'darker',
+  'dark',
+  '',
+  'light',
+  'lighter',
+  'extra-light',
+  'blank',
+]
+const fillColors = fillTypes.map((type) => {
+  return {
+    name: type ? `${type[0].toUpperCase() + type.slice(1)} Fill` : 'Base Fill',
+    var: getCssVarValue(getCssVarName('fill-color', type)),
+  }
+})
+
+const textTypes = ['primary', 'regular', 'secondary', 'placeholder', 'disabled']
+const textColors = textTypes.map((type) => {
+  return {
+    name: `${type[0].toUpperCase() + type.slice(1)} Text`,
+    var: getCssVarValue(getCssVarName('text-color', type)),
+  }
+})
+
+const black = '#000000'
+const white = '#FFFFFF'
+</script>

+ 400 - 0
docs/.vitepress/vitepress/components/globals/parallax-home.vue

@@ -0,0 +1,400 @@
+<script setup lang="ts">
+import { computed, reactive, ref } from 'vue'
+import { useEventListener, useParallax, useThrottleFn } from '@vueuse/core'
+import { useLang } from '../../composables/lang'
+import homeLocale from '../../../i18n/pages/home.json'
+import HomeSponsors from '../home/home-sponsors.vue'
+import HomeCards from '../home/home-cards.vue'
+import HomeFooter from './vp-footer.vue'
+import type { CSSProperties } from 'vue'
+const target = ref<HTMLElement | null>(null)
+const parallax = reactive(useParallax(target))
+const jumbotronRedOffset = ref(0)
+const jumbotronRef = ref<HTMLElement | null>(null)
+const lang = useLang()
+const homeLang = computed(() => homeLocale[lang.value])
+
+function jumpTo(path: string) {
+  // vitepress has not router
+  location.href = `/${lang.value}/${path}`
+}
+
+const containerStyle: CSSProperties = {
+  display: 'flex',
+  flexDirection: 'column',
+  justifyContent: 'center',
+  alignItems: 'center',
+  transition: '.3s ease-out all',
+
+  position: 'relative',
+
+  perspective: '300px',
+}
+
+const cardStyle = computed(() => ({
+  height: '30rem',
+  width: '100%',
+  transition: '.3s ease-out all',
+  transform: `rotateX(${parallax.roll}deg) rotateY(${parallax.tilt}deg)`,
+}))
+
+const layerBase: CSSProperties = {
+  position: 'absolute',
+  width: '100%',
+  height: '100%',
+  transition: '.3s ease-out all',
+}
+
+const screenLayer = computed(() => ({
+  ...layerBase,
+  width: '80%',
+  height: '80%',
+  transform: `translateX(${parallax.tilt * 10 + 80}px) translateY(${
+    parallax.roll * 10 + 50
+  }px)`,
+}))
+
+const peopleLayer = computed(() => ({
+  ...layerBase,
+  width: '30%',
+  height: '30%',
+  right: 0,
+  bottom: 0,
+  transform: `translateX(${parallax.tilt * 25 + 25}px) translateY(${
+    parallax.roll * 25
+  }px) scale(1)`,
+}))
+
+// center layer
+const leftLayer = computed(() => ({
+  ...layerBase,
+  width: '20%',
+  height: '20%',
+  transform: `translateX(${parallax.tilt * 12 + 205}px) translateY(${
+    parallax.roll * 12 + 210
+  }px)`,
+}))
+
+const leftBottomLayer = computed(() => ({
+  ...layerBase,
+  width: '30%',
+  height: '30%',
+  left: 0,
+  bottom: 0,
+  transform: `translateX(${parallax.tilt * 30 - 10}px) translateY(${
+    parallax.roll * 30
+  }px)`,
+}))
+
+const rightLayer = computed(() => ({
+  ...layerBase,
+  width: '33%',
+  height: '33%',
+  top: 0,
+  right: 0,
+  transform: `translateX(${parallax.tilt * 25 + 5}px) translateY(${
+    parallax.roll * 25
+  }px)`,
+}))
+
+const handleScroll = useThrottleFn(() => {
+  const ele = jumbotronRef.value
+  if (ele) {
+    const rect = ele.getBoundingClientRect()
+    const eleHeight = ele.clientHeight
+    let calHeight = (180 - rect.top) * 2
+    if (calHeight < 0) calHeight = 0
+    if (calHeight > eleHeight) calHeight = eleHeight
+    jumbotronRedOffset.value = calHeight
+  }
+}, 10)
+
+useEventListener(window, 'scroll', handleScroll)
+</script>
+
+<template>
+  <div ref="target" class="home-page">
+    <div class="banner" text="center">
+      <div class="banner-desc" m="t-4">
+        <h1>{{ homeLang['title'] }}</h1>
+        <p m="t-2">{{ homeLang['title_sub'] }}</p>
+      </div>
+    </div>
+    <div ref="jumbotronRef" class="jumbotron">
+      <div class="parallax-container" :style="containerStyle">
+        <div :style="cardStyle">
+          <screen-svg :style="screenLayer" alt="banner" />
+          <people-svg
+            :style="peopleLayer"
+            alt="banner"
+            class="cursor-pointer"
+            @click="jumpTo('guide/quickstart.html')"
+          />
+          <left-layer-svg :style="leftLayer" alt="banner" />
+          <left-bottom-layer-svg :style="leftBottomLayer" alt="banner" />
+          <right-layer-svg :style="rightLayer" alt="banner" />
+        </div>
+      </div>
+    </div>
+    <img
+      src="/images/theme-index-blue.png"
+      alt="banner"
+      class="mobile-banner"
+    />
+    <HomeSponsors />
+    <HomeCards />
+  </div>
+  <HomeFooter :is-home="true" />
+</template>
+
+<style lang="scss">
+@use '../../styles/mixins' as *;
+
+.home-page {
+  .mobile-banner {
+    display: none;
+  }
+
+  .banner-dot h1 span {
+    position: relative;
+    &::after {
+      content: '';
+      position: absolute;
+      right: -12px;
+      bottom: 8px;
+      background: var(--el-color-primary);
+      height: 8px;
+      width: 8px;
+      border-radius: 100%;
+    }
+  }
+  .banner-desc {
+    h1 {
+      font-size: 34px;
+      margin: 0;
+      line-height: 48px;
+      color: var(--text-color);
+    }
+
+    p {
+      font-size: 18px;
+      color: var(--text-color-light);
+    }
+  }
+
+  .count-down {
+    .cd-main {
+      background: #f1f6fe;
+      border-radius: 10px;
+      width: 50%;
+      margin: 60px auto 120px;
+      padding: 30px 0;
+      font-size: 24px;
+      color: #666;
+      text-align: center;
+      font-weight: 600;
+    }
+    .cd-date {
+      font-size: 28px;
+    }
+    .cd-time {
+      display: flex;
+      justify-content: space-between;
+      width: 80%;
+      margin: 10px auto 0;
+    }
+    .cd-num {
+      color: var(--el-color-primary);
+      font-size: 78px;
+      font-weight: bold;
+    }
+    .cd-num span {
+      width: 50%;
+      display: inline-block;
+    }
+    .cd-str {
+      font-size: 22px;
+      margin-top: -5px;
+    }
+  }
+
+  .jumbotron {
+    width: 800px;
+    margin: 20px auto;
+    position: relative;
+
+    img {
+      width: 100%;
+    }
+
+    .parallax-container {
+      width: 800px;
+    }
+  }
+
+  @media screen and (max-width: 959px) {
+    .jumbotron {
+      display: none !important;
+    }
+
+    .mobile-banner {
+      display: inline-block;
+    }
+  }
+
+  @media (max-width: 768px) {
+    .jumbotron {
+      width: 50%;
+      display: flex;
+      margin: auto;
+      justify-content: center;
+      align-items: center;
+
+      .parallax-container {
+        width: 100%;
+      }
+    }
+  }
+
+  @media (max-width: 768px) {
+    .banner-desc {
+      padding-top: 0px;
+    }
+    .cards {
+      li {
+        width: 80%;
+        margin: 0 auto 20px;
+        float: none;
+      }
+      .card {
+        height: auto;
+        padding-bottom: 54px;
+      }
+    }
+    .banner-stars {
+      display: none;
+    }
+    .banner-desc {
+      h1 {
+        font-size: 22px;
+      }
+      #line2 {
+        display: none;
+      }
+      h2 {
+        font-size: 32px;
+      }
+      p {
+        width: auto;
+      }
+    }
+    .banner-dot h1 span {
+      &::after {
+        right: -8px;
+        bottom: 2px;
+        height: 6px;
+        width: 6px;
+      }
+    }
+    .count-down {
+      .cd-main {
+        width: 90%;
+        margin: 40px auto 40px;
+        padding: 20px 0;
+      }
+      .cd-date {
+        font-size: 22px;
+      }
+      .cd-num {
+        font-size: 38px;
+      }
+      .cd-str {
+        font-size: 12px;
+        margin-top: 0px;
+      }
+    }
+    .sponsors-list {
+      display: flex;
+      flex-direction: column;
+      align-content: center;
+      .sponsor {
+        justify-content: left;
+      }
+    }
+  }
+  .theme-intro-b {
+    position: fixed;
+    left: 0;
+    right: 0;
+    top: 0;
+    bottom: 0;
+    z-index: 200;
+    .intro-banner {
+      position: absolute;
+    }
+    img {
+      width: 300px;
+    }
+    .title {
+      position: absolute;
+      top: 0;
+      bottom: 0;
+      left: 0;
+      right: 0;
+      color: #fff;
+      text-align: center;
+      font-weight: bold;
+      font-size: 20px;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      p {
+        padding: 0;
+        margin: 10px 0;
+      }
+    }
+  }
+  .theme-intro-a {
+    position: fixed;
+    left: 0;
+    right: 0;
+    top: 0;
+    bottom: 0;
+    z-index: 200;
+    .mask {
+      position: fixed;
+      left: 0;
+      right: 0;
+      top: 0;
+      bottom: 0;
+      background: #000;
+      opacity: 0.5;
+    }
+    .intro-banner {
+      top: 50%;
+      left: 50%;
+      position: fixed;
+      transform: translate(-50%, -50%);
+      box-sizing: border-box;
+      text-align: center;
+      z-index: 100;
+      img {
+        width: 100%;
+      }
+      .intro-text {
+        position: absolute;
+        top: 50%;
+        left: 0;
+        right: 0;
+        p {
+          padding: 0;
+          margin: 0;
+          font-size: 48px;
+          font-weight: bold;
+          color: #fff;
+        }
+      }
+    }
+  }
+}
+</style>

+ 138 - 0
docs/.vitepress/vitepress/components/globals/resource.vue

@@ -0,0 +1,138 @@
+<script lang="ts" setup>
+import { computed } from 'vue'
+import { isClient } from '@vueuse/core'
+import { useLang } from '../../composables/lang'
+import resourceLocale from '../../../i18n/pages/resource.json'
+import { sendEvent } from '../../../config/analytics'
+const mirrorUrl = 'element-plus.gitee.io'
+const isMirrorUrl = () => {
+  if (!isClient) return
+  return window.location.hostname === mirrorUrl
+}
+const resourceUrl = {
+  github: {
+    sketch:
+      'https://github.com/ElementUI/Resources/raw/master/Element_Plus_Design_System_2022_1.0_Beta.zip',
+    axure:
+      'https://github.com/ElementUI/Resources/raw/master/Element_Components_v2.1.0.rplib',
+  },
+  gitee: {
+    sketch:
+      'https://gitee.com/element-plus/resources/raw/master/Element_Plus_Design_System_2022_1.0_Beta.zip',
+    axure:
+      'https://gitee.com/element-plus/resources/raw/master/Element_Components_v2.1.0.rplib',
+  },
+}[isMirrorUrl() ? 'gitee' : 'github']
+
+const lang = useLang()
+const resourceLang = computed(() => resourceLocale[lang.value])
+const onClick = (item: string) => {
+  sendEvent('resource_download', item)
+}
+</script>
+
+<template>
+  <div class="page-resource">
+    <h1>{{ resourceLang.title }}</h1>
+    <p>{{ resourceLang.lineOne }}</p>
+    <p v-html="resourceLang.lineTwo" />
+    <div class="flex flex-wrap justify-center mt-32px">
+      <div class="inline-flex w-full md:w-1/3" p="2" pl-0>
+        <el-card class="card" shadow="hover">
+          <axure-components-svg w="30" alt="axure" />
+          <h3>{{ resourceLang.axure }}</h3>
+          <p>
+            {{ resourceLang.axureIntro }}
+          </p>
+          <a
+            target="_blank"
+            :href="resourceUrl.axure"
+            @click="onClick('axure')"
+          >
+            <el-button type="primary">{{ resourceLang.download }}</el-button>
+          </a>
+        </el-card>
+      </div>
+      <div class="inline-flex w-full md:w-1/3" p="2">
+        <el-card class="card" shadow="hover">
+          <sketch-template-svg w="30" alt="Sketch" />
+          <h3>{{ resourceLang.sketch }}</h3>
+          <p>
+            {{ resourceLang.sketchIntro }}
+          </p>
+          <a
+            target="_blank"
+            :href="resourceUrl.sketch"
+            @click="onClick('sketch')"
+          >
+            <el-button type="primary">{{ resourceLang.download }}</el-button>
+          </a>
+        </el-card>
+      </div>
+      <div class="inline-flex w-full md:w-1/3" p="2">
+        <el-card class="card" shadow="hover">
+          <figma-template-svg w="30" alt="Figma" />
+          <h3>{{ resourceLang.figma }}</h3>
+          <p>
+            {{ resourceLang.figmaIntro }}
+          </p>
+          <a
+            href="https://www.figma.com/community/file/1021254029764378306"
+            target="_blank"
+            @click="onClick('figma')"
+          >
+            <el-button type="primary">{{ resourceLang.download }}</el-button>
+          </a>
+        </el-card>
+      </div>
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.page-resource {
+  box-sizing: border-box;
+  padding: 0 40px;
+
+  h1 {
+    color: var(--text-color);
+    margin-bottom: 24px;
+  }
+  p {
+    color: var(--text-color-light);
+    line-height: 24px;
+    margin: 0;
+    &:last-of-type {
+      margin-top: 8px;
+    }
+  }
+}
+
+.card {
+  text-align: center;
+  padding: 32px 0;
+
+  img {
+    margin: auto;
+    margin-bottom: 16px;
+    height: 87px;
+  }
+
+  h3 {
+    margin: 10px;
+    font-size: 18px;
+    font-weight: normal;
+  }
+
+  p {
+    font-size: 14px;
+    color: #99a9bf;
+    padding: 0 30px;
+    margin: 0;
+    word-break: break-word;
+    line-height: 1.8;
+    min-height: 75px;
+    margin-bottom: 16px;
+  }
+}
+</style>

+ 169 - 0
docs/.vitepress/vitepress/components/globals/resources/axure-components-svg.vue

@@ -0,0 +1,169 @@
+<template>
+  <svg
+    id="图层_1"
+    version="1.1"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    x="0px"
+    y="0px"
+    viewBox="0 0 120 120"
+    style="enable-background: new 0 0 120 120"
+    xml:space="preserve"
+  >
+    <path
+      class="st0"
+      d="M8,16h104c1.7,0,3,1.3,3,3v82c0,1.7-1.3,3-3,3H8c-1.7,0-3-1.3-3-3V19C5,17.3,6.3,16,8,16z"
+    />
+    <path
+      class="st1"
+      d="M13,22c0,1.1-0.9,2-2,2c-1.1,0-2-0.9-2-2s0.9-2,2-2C12.1,20,13,20.9,13,22z M19,22c0,1.1-0.9,2-2,2s-2-0.9-2-2
+    s0.9-2,2-2S19,20.9,19,22z M23,24c1.1,0,2-0.9,2-2s-0.9-2-2-2s-2,0.9-2,2S21.9,24,23,24z"
+    />
+    <path
+      class="st2"
+      d="M12,28h96c1.7,0,3,1.3,3,3v66c0,1.7-1.3,3-3,3H12c-1.7,0-3-1.3-3-3V31C9,29.3,10.3,28,12,28z"
+    />
+    <path
+      class="st3"
+      d="M39,34h42c1.1,0,2,0.9,2,2v22c0,1.1-0.9,2-2,2H39c-1.1,0-2-0.9-2-2V36C37,34.9,37.9,34,39,34z"
+    />
+    <path
+      class="st4"
+      d="M47,38h26c1.1,0,2,0.9,2,2l0,0c0,1.1-0.9,2-2,2H47c-1.1,0-2-0.9-2-2l0,0C45,38.9,45.9,38,47,38z"
+    />
+    <path
+      class="st4"
+      d="M54,52h12c1.1,0,2,0.9,2,2l0,0c0,1.1-0.9,2-2,2H54c-1.1,0-2-0.9-2-2l0,0C52,52.9,52.9,52,54,52z"
+    />
+    <path
+      class="st4"
+      d="M43,45h34c1.1,0,2,0.9,2,2l0,0c0,1.1-0.9,2-2,2H43c-1.1,0-2-0.9-2-2l0,0C41,45.9,41.9,45,43,45z"
+    />
+    <path
+      class="st5"
+      d="M17,75h22c1.1,0,2,0.9,2,2v15c0,1.1-0.9,2-2,2H17c-1.1,0-2-0.9-2-2V77C15,75.9,15.9,75,17,75z"
+    />
+    <path
+      class="st6"
+      d="M21,79h14c1.1,0,2,0.9,2,2l0,0c0,1.1-0.9,2-2,2H21c-1.1,0-2-0.9-2-2l0,0C19,79.9,19.9,79,21,79z"
+    />
+    <path
+      class="st6"
+      d="M24,86h8c1.1,0,2,0.9,2,2l0,0c0,1.1-0.9,2-2,2h-8c-1.1,0-2-0.9-2-2l0,0C22,86.9,22.9,86,24,86z"
+    />
+    <path
+      class="st7"
+      d="M49,75h22c1.1,0,2,0.9,2,2v15c0,1.1-0.9,2-2,2H49c-1.1,0-2-0.9-2-2V77C47,75.9,47.9,75,49,75z"
+    />
+    <path
+      class="st8"
+      d="M53,79h14c1.1,0,2,0.9,2,2l0,0c0,1.1-0.9,2-2,2H53c-1.1,0-2-0.9-2-2l0,0C51,79.9,51.9,79,53,79z"
+    />
+    <path
+      class="st8"
+      d="M56,86h8c1.1,0,2,0.9,2,2l0,0c0,1.1-0.9,2-2,2h-8c-1.1,0-2-0.9-2-2l0,0C54,86.9,54.9,86,56,86z"
+    />
+    <path
+      class="st9"
+      d="M81,75h22c1.1,0,2,0.9,2,2v15c0,1.1-0.9,2-2,2H81c-1.1,0-2-0.9-2-2V77C79,75.9,79.9,75,81,75z"
+    />
+    <path
+      class="st10"
+      d="M85,79h14c1.1,0,2,0.9,2,2l0,0c0,1.1-0.9,2-2,2H85c-1.1,0-2-0.9-2-2l0,0C83,79.9,83.9,79,85,79z"
+    />
+    <path
+      class="st10"
+      d="M88,86h8c1.1,0,2,0.9,2,2l0,0c0,1.1-0.9,2-2,2h-8c-1.1,0-2-0.9-2-2l0,0C86,86.9,86.9,86,88,86z"
+    />
+    <path
+      class="st11"
+      d="M58,60h4v5h30c1.1,0,2,0.9,2,2v2v6h-4v-6H62v6h-4v-6H30v6h-4v-6v-2c0-1.1,0.9-2,2-2h30V60z"
+    />
+  </svg>
+</template>
+
+<style scoped lang="scss">
+.st0 {
+  fill: #f2f8fe;
+}
+.st1 {
+  fill-rule: evenodd;
+  clip-rule: evenodd;
+  fill: #ddeafc;
+}
+.st2 {
+  fill: #ffffff;
+}
+.st3 {
+  fill: #20a0ff;
+}
+.st4 {
+  fill: #0d89e5;
+}
+.st5 {
+  fill: #80a8e1;
+}
+.st6 {
+  fill: #5289d6;
+}
+.st7 {
+  fill: #ffd6d2;
+}
+.st8 {
+  fill: #f8a3a4;
+}
+.st9 {
+  fill: #dbedff;
+}
+.st10 {
+  fill: #a2c6eb;
+}
+.st11 {
+  fill-rule: evenodd;
+  clip-rule: evenodd;
+  fill: #deebfd;
+}
+
+.dark {
+  .st0 {
+    fill: #272829;
+  }
+  .st1 {
+    fill-rule: evenodd;
+    clip-rule: evenodd;
+    fill: #494c52;
+  }
+  .st2 {
+    fill: #33383d;
+  }
+  .st3 {
+    fill: #20a0ff;
+  }
+  .st4 {
+    fill: #0d89e5;
+  }
+  .st5 {
+    fill: #80a8e1;
+  }
+  .st6 {
+    fill: #5289d6;
+  }
+  .st7 {
+    fill: #ffd6d2;
+  }
+  .st8 {
+    fill: #f8a3a4;
+  }
+  .st9 {
+    fill: #5d6874;
+  }
+  .st10 {
+    fill: #a2c6eb;
+  }
+  .st11 {
+    fill-rule: evenodd;
+    clip-rule: evenodd;
+    fill: #3e4652;
+  }
+}
+</style>

+ 86 - 0
docs/.vitepress/vitepress/components/globals/resources/figma-template-svg.vue

@@ -0,0 +1,86 @@
+<template>
+  <!-- Generator: Adobe Illustrator 26.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+  <svg
+    id="图层_1"
+    version="1.1"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    x="0px"
+    y="0px"
+    viewBox="0 0 120 120"
+    style="enable-background: new 0 0 120 120"
+    xml:space="preserve"
+  >
+    <path
+      class="st0"
+      d="M7,14h106c1.1,0,2,0.9,2,2v68c0,1.1-0.9,2-2,2H7c-1.1,0-2-0.9-2-2V16C5,14.9,5.9,14,7,14z"
+    />
+    <path
+      class="st1"
+      d="M60,46c0-3.3,2.7-6,6-6l0,0c3.3,0,6,2.7,6,6l0,0c0,3.3-2.7,6-6,6l0,0C62.7,52,60,49.3,60,46L60,46z"
+    />
+    <path
+      class="st2"
+      d="M48,58c0-3.3,2.7-6,6-6h6v6c0,3.3-2.7,6-6,6l0,0C50.7,64,48,61.3,48,58L48,58z"
+    />
+    <path class="st3" d="M60,28v12h6c3.3,0,6-2.7,6-6l0,0c0-3.3-2.7-6-6-6H60z" />
+    <path
+      class="st4"
+      d="M48,34c0,3.3,2.7,6,6,6h6V28h-6C50.7,28,48,30.7,48,34L48,34z"
+    />
+    <path
+      class="st5"
+      d="M48,46c0,3.3,2.7,6,6,6h6V40h-6C50.7,40,48,42.7,48,46L48,46z"
+    />
+    <path class="st6" d="M5,74h110v10c0,1.1-0.9,2-2,2H7c-1.1,0-2-0.9-2-2V74z" />
+    <circle class="st7" cx="60" cy="80" r="2" />
+    <path
+      class="st8"
+      d="M46.8,86L38,103.1c-0.7,1.3,0.3,2.9,1.8,2.9H60V86H46.8z M73.2,86l8.7,17.1c0.7,1.3-0.3,2.9-1.8,2.9H60V86H73.2
+	z"
+    />
+  </svg>
+</template>
+
+<style scoped lang="scss">
+.st0 {
+  fill: #f2f8fe;
+}
+.st1 {
+  fill: #1abcfe;
+}
+.st2 {
+  fill: #0acf83;
+}
+.st3 {
+  fill: #ff7262;
+}
+.st4 {
+  fill: #f24e1e;
+}
+.st5 {
+  fill: #a259ff;
+}
+.st6 {
+  fill: #20a0ff;
+}
+.st7 {
+  fill: #ffffff;
+}
+.st8 {
+  fill-rule: evenodd;
+  clip-rule: evenodd;
+  fill: #deecf9;
+}
+
+.dark {
+  .st0 {
+    fill: #272829;
+  }
+  .st8 {
+    fill-rule: evenodd;
+    clip-rule: evenodd;
+    fill: #373f48;
+  }
+}
+</style>

+ 82 - 0
docs/.vitepress/vitepress/components/globals/resources/sketch-template-svg.vue

@@ -0,0 +1,82 @@
+<template>
+  <svg
+    id="图层_1"
+    version="1.1"
+    xmlns="http://www.w3.org/2000/svg"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    x="0px"
+    y="0px"
+    viewBox="0 0 120 120"
+    style="enable-background: new 0 0 120 120"
+    xml:space="preserve"
+  >
+    <path
+      class="st0"
+      d="M13,15h48c3.3,0,6,2.7,6,6v33c0,3.3-2.7,6-6,6H13c-3.3,0-6-2.7-6-6V21C7,17.7,9.7,15,13,15z"
+    />
+    <path
+      class="st0"
+      d="M81,15h20c3.3,0,6,2.7,6,6v33c0,3.3-2.7,6-6,6H81c-3.3,0-6-2.7-6-6V21C75,17.7,77.7,15,81,15z"
+    />
+    <path
+      class="st1"
+      d="M12.5,15h49c3,0,5.5,2.5,5.5,5.5l0,0c0,3-2.5,5.5-5.5,5.5h-49c-3,0-5.5-2.5-5.5-5.5l0,0C7,17.5,9.5,15,12.5,15z
+      "
+    />
+    <path
+      class="st2"
+      d="M80.5,15h21c3,0,5.5,2.5,5.5,5.5l0,0c0,3-2.5,5.5-5.5,5.5h-21c-3,0-5.5-2.5-5.5-5.5l0,0
+      C75,17.5,77.5,15,80.5,15z"
+    />
+    <path
+      class="st0"
+      d="M90,68c3.3,0,6,2.7,6,6v28c0,2.2-1.8,4-4,4H13c-3.3,0-6-2.7-6-6V74c0-3.3,2.7-6,6-6H90z"
+    />
+    <path
+      class="st2"
+      d="M18,73.5v27c0,3-2.5,5.5-5.5,5.5l0,0c-3,0-5.5-2.5-5.5-5.5v-27c0-3,2.5-5.5,5.5-5.5l0,0
+      C15.5,68,18,70.5,18,73.5z"
+    />
+    <path class="st3" d="M84.3,78.7L79.5,68h13.1L84.3,78.7z" />
+    <path class="st4" d="M102.1,78.7H84.2L92.6,68L102.1,78.7z" />
+    <path class="st5" d="M70,78.7L79.5,68l4.8,10.7H70z" />
+    <path class="st6" d="M92.6,68l9.5,10.7l4.8-10.7H92.6z" />
+    <path class="st5" d="M106.8,68l8.3,10.7h-13.1L106.8,68z" />
+    <path class="st7" d="M93.8,106l21.4-27.3h-13.1L93.8,106z" />
+    <path class="st3" d="M70,78.7L93.8,106l-9.5-27.3H70z" />
+    <path class="st5" d="M93.8,106l8.3-27.3H84.2L93.8,106z" />
+  </svg>
+</template>
+
+<style scoped lang="scss">
+.st0 {
+  fill: #f2f8fe;
+}
+.st1 {
+  fill: #ffd6d2;
+}
+.st2 {
+  fill: #20a0ff;
+}
+.st3 {
+  fill: #6496dc;
+}
+.st4 {
+  fill: #afc8ea;
+}
+.st5 {
+  fill: #80a8e1;
+}
+.st6 {
+  fill: #93b8ee;
+}
+.st7 {
+  fill: #afc8eb;
+}
+
+.dark {
+  .st0 {
+    fill: #3e444a;
+  }
+}
+</style>

+ 40 - 0
docs/.vitepress/vitepress/components/globals/secondary-colors.vue

@@ -0,0 +1,40 @@
+<script lang="ts" setup>
+import { getColorValue, useCopyColor } from '../../utils'
+
+const colorsType = ['success', 'warning', 'danger', 'info']
+
+const colorLevel = [3, 5, 7, 8, 9].map((item) => `light-${item}`)
+colorLevel.unshift('dark-2')
+
+const { copyColor } = useCopyColor()
+</script>
+
+<template>
+  <el-row :gutter="12">
+    <el-col
+      v-for="(type, i) in colorsType"
+      :key="i"
+      :span="6"
+      :xs="{ span: 12 }"
+    >
+      <div class="demo-color-box" :style="{ background: getColorValue(type) }">
+        {{ type.charAt(0).toUpperCase() + type.slice(1) }}
+        <div class="value" text="xs">
+          {{ getColorValue(type).toUpperCase() }}
+        </div>
+        <div class="bg-color-sub">
+          <div
+            v-for="(level, key) in colorLevel"
+            :key="key"
+            class="bg-secondary-sub-item transition cursor-pointer hover:shadow"
+            :style="{
+              width: `${100 / 6}%`,
+              background: `var(--el-color-${type}-` + level + ')',
+            }"
+            @click="copyColor(type + '-' + level)"
+          />
+        </div>
+      </div>
+    </el-col>
+  </el-row>
+</template>

+ 7 - 0
docs/.vitepress/vitepress/components/globals/vp-api-bool.vue

@@ -0,0 +1,7 @@
+<script setup lang="ts">
+import Primitive from './vp-api-primitive.vue'
+</script>
+
+<template>
+  <Primitive type="boolean" />
+</template>

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio