├── .commitlintrc.js ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── codecov.yml │ └── docs.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .npmrc ├── .prettierrc.js ├── .release-it.json ├── .stylelintignore ├── .stylelintrc.js ├── LICENSE.txt ├── README.md ├── README_ZH.md ├── package.json ├── packages ├── cli │ ├── cli.js │ ├── gitClone.js │ ├── index.js │ └── package.json ├── components │ ├── index.ts │ ├── package.json │ ├── scripts │ │ ├── build │ │ │ └── index.ts │ │ ├── publish │ │ │ └── index.ts │ │ └── utils │ │ │ ├── delpath.ts │ │ │ ├── paths.ts │ │ │ ├── run.ts │ │ │ └── tools.ts │ ├── src │ │ ├── actionsheet │ │ │ ├── __test__ │ │ │ │ ├── actionSheetFn.test.ts │ │ │ │ └── actionsheet.test.tsx │ │ │ ├── actionSheet.ts │ │ │ ├── actionSheet.vue │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── alert │ │ │ ├── __test__ │ │ │ │ ├── alert.test.ts │ │ │ │ └── alertFn.test.ts │ │ │ ├── alert.ts │ │ │ ├── alert.vue │ │ │ └── index.ts │ │ ├── article │ │ │ ├── __test__ │ │ │ │ └── article.test.ts │ │ │ ├── article.vue │ │ │ └── index.ts │ │ ├── badge │ │ │ ├── __test__ │ │ │ │ └── badge.test.ts │ │ │ ├── badge.less │ │ │ ├── badge.vue │ │ │ └── index.ts │ │ ├── button │ │ │ ├── __test__ │ │ │ │ └── button.test.ts │ │ │ ├── button.vue │ │ │ └── index.ts │ │ ├── cells │ │ │ ├── __test__ │ │ │ │ ├── cell.test.ts │ │ │ │ └── cells.test.ts │ │ │ ├── cell.vue │ │ │ ├── cells.vue │ │ │ └── index.ts │ │ ├── common │ │ │ └── styles.less │ │ ├── components.d.ts │ │ ├── datePicker │ │ │ ├── __test__ │ │ │ │ └── datePicker.test.ts │ │ │ ├── datePicker.less │ │ │ ├── datePicker.vue │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── dialog │ │ │ ├── __test__ │ │ │ │ ├── dialog.test.tsx │ │ │ │ └── dialogFn.test.ts │ │ │ ├── dialog.ts │ │ │ ├── dialog.vue │ │ │ └── index.ts │ │ ├── flex │ │ │ ├── __test__ │ │ │ │ └── flex.test.ts │ │ │ ├── flex.vue │ │ │ ├── flexItem.vue │ │ │ └── index.ts │ │ ├── footer │ │ │ ├── __test__ │ │ │ │ └── footer.test.ts │ │ │ ├── footer.vue │ │ │ ├── footerLink.vue │ │ │ └── index.ts │ │ ├── form │ │ │ ├── __test__ │ │ │ │ └── form.test.tsx │ │ │ ├── constants.ts │ │ │ ├── form.less │ │ │ ├── form.vue │ │ │ ├── formGroup.vue │ │ │ ├── formItem.vue │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── gallery │ │ │ ├── __test__ │ │ │ │ ├── gallery.test.ts │ │ │ │ └── galleryFn.test.ts │ │ │ ├── gallery.less │ │ │ ├── gallery.ts │ │ │ ├── gallery.vue │ │ │ └── index.ts │ │ ├── grid │ │ │ ├── __test__ │ │ │ │ ├── grid.test.ts │ │ │ │ └── grids.test.ts │ │ │ ├── grid.vue │ │ │ ├── grids.vue │ │ │ └── index.ts │ │ ├── halfScreenDialog │ │ │ ├── __test__ │ │ │ │ └── halfscreenDialog.test.ts │ │ │ ├── halfScreenDialog.vue │ │ │ └── index.ts │ │ ├── hooks │ │ │ └── useSlideDialog.ts │ │ ├── icon │ │ │ ├── __test__ │ │ │ │ └── icon.test.ts │ │ │ ├── icon.vue │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── input │ │ │ ├── __test__ │ │ │ │ └── input.test.ts │ │ │ ├── index.ts │ │ │ ├── input.less │ │ │ └── input.vue │ │ ├── loading │ │ │ ├── __test__ │ │ │ │ └── loading.test.ts │ │ │ ├── index.ts │ │ │ ├── loading.less │ │ │ └── loading.vue │ │ ├── loadmore │ │ │ ├── __test__ │ │ │ │ └── loadmore.test.ts │ │ │ ├── index.ts │ │ │ └── loadmore.vue │ │ ├── mask │ │ │ ├── __test__ │ │ │ │ └── mask.test.ts │ │ │ ├── index.ts │ │ │ └── mask.vue │ │ ├── mediabox │ │ │ ├── __test__ │ │ │ │ └── mediabox.test.ts │ │ │ ├── index.ts │ │ │ └── mediaBox.vue │ │ ├── msg │ │ │ ├── __test__ │ │ │ │ └── msg.test.ts │ │ │ ├── index.ts │ │ │ └── msg.vue │ │ ├── navbar │ │ │ ├── __test__ │ │ │ │ └── navbar.test.tsx │ │ │ ├── constant.ts │ │ │ ├── index.ts │ │ │ ├── navbar.vue │ │ │ ├── navbarItem.vue │ │ │ └── types.ts │ │ ├── panel │ │ │ ├── __test__ │ │ │ │ └── panel.test.ts │ │ │ ├── index.ts │ │ │ └── panel.vue │ │ ├── picker │ │ │ ├── __test__ │ │ │ │ └── picker.test.ts │ │ │ ├── index.ts │ │ │ ├── picker.less │ │ │ ├── picker.vue │ │ │ └── types.ts │ │ ├── preview │ │ │ ├── __test__ │ │ │ │ ├── preview.test.ts │ │ │ │ ├── previewBtn.test.ts │ │ │ │ └── previewItem.test.ts │ │ │ ├── index.ts │ │ │ ├── preview.vue │ │ │ ├── previewBtn.vue │ │ │ └── previewItem.vue │ │ ├── progress │ │ │ ├── __test__ │ │ │ │ └── progress.test.ts │ │ │ ├── index.ts │ │ │ ├── progress.less │ │ │ └── progress.vue │ │ ├── searchbar │ │ │ ├── __test__ │ │ │ │ └── searchbar.test.ts │ │ │ ├── index.ts │ │ │ └── searchbar.vue │ │ ├── slider │ │ │ ├── __test__ │ │ │ │ └── slider.test.ts │ │ │ ├── index.ts │ │ │ └── slider.vue │ │ ├── steps │ │ │ ├── __test__ │ │ │ │ ├── stepItem.test.ts │ │ │ │ └── steps.test.tsx │ │ │ ├── constant.ts │ │ │ ├── index.ts │ │ │ ├── stepItem.vue │ │ │ └── steps.vue │ │ ├── switch │ │ │ ├── index.ts │ │ │ ├── switch.vue │ │ │ └── test │ │ │ │ └── switch.test.ts │ │ ├── tabbar │ │ │ ├── __test__ │ │ │ │ └── tabbar.test.tsx │ │ │ ├── constant.ts │ │ │ ├── index.ts │ │ │ ├── tabbar.vue │ │ │ ├── tabbarItem.vue │ │ │ └── types.ts │ │ ├── textarea │ │ │ ├── __test__ │ │ │ │ └── textarea.test.ts │ │ │ ├── index.ts │ │ │ ├── textarea.less │ │ │ └── textarea.vue │ │ ├── timePicker │ │ │ ├── __test__ │ │ │ │ └── timePicker.test.ts │ │ │ ├── cron.ts │ │ │ ├── index.ts │ │ │ ├── timePicker.less │ │ │ ├── timePicker.vue │ │ │ └── types.ts │ │ ├── toast │ │ │ ├── __test__ │ │ │ │ ├── toast.test.ts │ │ │ │ └── toastFn.test.ts │ │ │ ├── index.ts │ │ │ ├── toast.ts │ │ │ └── toast.vue │ │ ├── types │ │ │ └── index.ts │ │ ├── uploader │ │ │ ├── __test__ │ │ │ │ └── uploader.test.ts │ │ │ ├── compress.ts │ │ │ ├── filehash.ts │ │ │ ├── hooks │ │ │ │ ├── index.ts │ │ │ │ └── useAccept.ts │ │ │ ├── index.ts │ │ │ ├── tools.ts │ │ │ ├── types.ts │ │ │ ├── upload.ts │ │ │ ├── uploader.less │ │ │ └── uploader.vue │ │ ├── utils │ │ │ ├── exports.ts │ │ │ ├── index.ts │ │ │ ├── pickerManager.ts │ │ │ ├── shortid.ts │ │ │ ├── theme.ts │ │ │ └── withInstall.ts │ │ └── vite-env.d.ts │ ├── tsconfig.json │ └── vite.config.ts └── vue-weui │ ├── README.md │ ├── README_ZH.md │ └── package.json ├── play ├── index.html ├── package.json ├── src │ ├── App.vue │ └── main.ts ├── vite.config.ts └── vue-shim.d.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── site ├── docs │ ├── .vitepress │ │ ├── components │ │ │ ├── ActionSheet.vue │ │ │ ├── Alert.vue │ │ │ ├── DatePicker.vue │ │ │ ├── Dialog.vue │ │ │ ├── DynamicLayout.vue │ │ │ ├── FrameDefaultLayout.vue │ │ │ ├── Gallery.vue │ │ │ ├── HalfScreenDialog.vue │ │ │ ├── NavBar.vue │ │ │ ├── Picker.vue │ │ │ ├── SlideHalfScreen.vue │ │ │ ├── TabBar.vue │ │ │ ├── TimePicker.vue │ │ │ ├── Toast.vue │ │ │ ├── Uploader.vue │ │ │ └── ValidateForm.vue │ │ ├── config.js │ │ └── theme │ │ │ └── index.js │ ├── components │ │ ├── actionsheet │ │ │ └── index.md │ │ ├── alert │ │ │ └── index.md │ │ ├── article │ │ │ └── index.md │ │ ├── badge │ │ │ └── index.md │ │ ├── button │ │ │ └── index.md │ │ ├── cells │ │ │ └── index.md │ │ ├── datePicker │ │ │ └── index.md │ │ ├── dialog │ │ │ └── index.md │ │ ├── flex │ │ │ └── index.md │ │ ├── footer │ │ │ └── index.md │ │ ├── form │ │ │ └── index.md │ │ ├── gallery │ │ │ └── index.md │ │ ├── grid │ │ │ └── index.md │ │ ├── halfscreen-dialog │ │ │ └── index.md │ │ ├── icon │ │ │ └── index.md │ │ ├── input │ │ │ └── index.md │ │ ├── loading │ │ │ └── index.md │ │ ├── loadmore │ │ │ └── index.md │ │ ├── mediabox │ │ │ └── index.md │ │ ├── msg │ │ │ └── index.md │ │ ├── navbar │ │ │ └── index.md │ │ ├── panel │ │ │ └── index.md │ │ ├── picker │ │ │ └── index.md │ │ ├── preview │ │ │ └── index.md │ │ ├── progress │ │ │ └── index.md │ │ ├── searchbar │ │ │ └── index.md │ │ ├── slider │ │ │ └── index.md │ │ ├── steps │ │ │ └── index.md │ │ ├── switch │ │ │ └── index.md │ │ ├── tabbar │ │ │ └── index.md │ │ ├── textarea │ │ │ └── index.md │ │ ├── timePicker │ │ │ └── index.md │ │ ├── toast │ │ │ └── index.md │ │ └── uploader │ │ │ └── index.md │ ├── guide │ │ └── index.md │ └── index.md └── package.json └── tsconfig.json /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | rules: { 4 | 'type-enum': [ 5 | 2, 6 | 'always', 7 | [ 8 | 'feat', 9 | 'fix', 10 | 'docs', 11 | 'style', 12 | 'refactor', 13 | 'test', 14 | 'build', 15 | 'revert', 16 | 'merge', 17 | ], 18 | ], 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **.d.ts 2 | /packages/vue-weui 3 | dist 4 | node_modules -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | node: true 6 | }, 7 | extends: [ 8 | 'eslint:recommended', 9 | 'plugin:vue/vue3-essential', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier', 12 | 'plugin:prettier/recommended' 13 | ], 14 | globals: { 15 | defineOptions: true 16 | }, 17 | parser: 'vue-eslint-parser', 18 | parserOptions: { 19 | ecmaVersion: 'latest', 20 | sourceType: 'module', 21 | parser: '@typescript-eslint/parser' 22 | }, 23 | plugins: ['vue', '@typescript-eslint'], 24 | rules: { 25 | 'prettier/prettier': 'error', 26 | '@typescript-eslint/ban-ts-comment': 'off', 27 | 'vue/multi-word-component-names': 'off', 28 | '@typescript-eslint/no-explicit-any': 'off' 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | # .github/workflows/main.yml 2 | # This is a basic workflow to help you get started with Actions 3 | 4 | name: Coverage 5 | 6 | # Controls when the action will run. Triggers the workflow on push or pull request 7 | # events but only for the master branch 8 | on: 9 | push: 10 | branches: [master] 11 | pull_request: 12 | branches: [master] 13 | 14 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 15 | jobs: 16 | CodeCov: 17 | # The type of runner that the job will run on 18 | runs-on: ubuntu-latest 19 | # Steps represent a sequence of tasks that will be executed as part of the job 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: pnpm/action-setup@v2.1.0 23 | with: 24 | version: 7.2.1 25 | - name: Install modules 26 | run: pnpm install 27 | - name: Run Test Coverage 28 | run: pnpm run coverage:weui 29 | - name: Upload coverage to Codecov 30 | uses: codecov/codecov-action@v4.0.1 31 | with: 32 | directory: packages/components/coverage 33 | token: ${{ secrets.CODECOV_TOKEN }} 34 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | # 将静态内容部署到 GitHub Pages 的简易工作流程 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | push: 6 | branches: ['master'] 7 | workflow_dispatch: 8 | 9 | # 设置 GITHUB_TOKEN 的权限,以允许部署到 GitHub Pages。 10 | permissions: 11 | contents: read 12 | pages: write 13 | id-token: write 14 | 15 | # 允许一个并发的部署 16 | concurrency: 17 | group: 'pages' 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | deploy: 22 | environment: 23 | name: github-pages 24 | url: ${{ steps.deployment.outputs.page_url }} 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v4 29 | - uses: pnpm/action-setup@v2.1.0 30 | with: 31 | version: 7.2.1 32 | - name: Install dependencies 33 | run: pnpm install 34 | - name: Build weui 35 | run: pnpm run build:weui 36 | - name: Build 37 | run: pnpm run docs:build 38 | - name: Setup Pages 39 | uses: actions/configure-pages@v3 40 | - name: Upload artifact 41 | uses: actions/upload-pages-artifact@v2 42 | with: 43 | # Upload dist repository 44 | path: './site/docs/.vitepress/dist' 45 | - name: Deploy to GitHub Pages 46 | id: deployment 47 | uses: actions/deploy-pages@v2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # User-specific stuff: 2 | .idea/ 3 | # CMake 4 | cmake-build-debug/ 5 | 6 | # IntelliJ 7 | out/ 8 | 9 | # mpeltonen/sbt-idea plugin 10 | .idea_modules/ 11 | 12 | 13 | node_modules/ 14 | 15 | # Typescript v1 declaration files 16 | typings/ 17 | 18 | # JetBrains Rider 19 | .idea/ 20 | *.sln.iml 21 | 22 | # CodeRush 23 | .cr/ 24 | 25 | ### macOS template 26 | # General 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | ======= 35 | # Local 36 | .env 37 | dist 38 | logs 39 | package-lock.json 40 | yarn-error.log 41 | .vscode/ 42 | .cache/ 43 | cache 44 | es 45 | lib 46 | coverage -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm run lint:script && pnpm run lint:style -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | strict-peer-dependencies=false -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 80, 3 | tabWidth: 2, 4 | useTabs: false, 5 | singleQuote: true, 6 | semi: true, 7 | trailingComma: 'none', 8 | bracketSpacing: true 9 | }; 10 | -------------------------------------------------------------------------------- /.release-it.json: -------------------------------------------------------------------------------- 1 | { 2 | "git": { 3 | "commitMessage": "chore(release): v${version}" 4 | } 5 | } -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | **.d.ts 2 | /packages/vue-weui 3 | dist 4 | node_modules -------------------------------------------------------------------------------- /.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['stylelint-prettier'], 3 | extends: [ 4 | 'stylelint-config-standard', 5 | 'stylelint-config-recommended-less', 6 | 'stylelint-config-recess-order', 7 | 'stylelint-config-prettier', 8 | 'stylelint-prettier/recommended' 9 | ], 10 | rules: { 11 | 'prettier/prettier': true, 12 | "custom-property-pattern": null, 13 | "selector-class-pattern": "^[a-z][a-z0-9_-]+$", 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [中文版](./README_ZH.md) | English 2 | 3 |
4 | 5 |
6 | 7 | # vue-weui 8 | 9 | [![](https://shields.io/github/package-json/v/bangtz/vue-weui/master/packages/vue-weui)](https://github.com/bangtz/vue-weui/tree/master/packages/vue-weui) 10 | [![](https://pkg-size.dev/badge/bundle/105782)](https://pkg-size.dev/vue-weui-next) 11 | [![codecov](https://codecov.io/gh/bangtz/vue-weui/graph/badge.svg?token=6TNVSF7OYT)](https://codecov.io/gh/bangtz/vue-weui) 12 | [![](https://img.shields.io/badge/language-vue-orange.svg)](https://vuejs.org/) 13 | ![](https://img.shields.io/npm/l/vue-weui-next.svg) 14 | 15 | Weui style UI component library developed and implemented based on Vue3. 16 | 17 | ## Quick start 18 | 19 | Before starting to use it, you need to introduce the weui style file into the html file. ([WeUI](https://github.com/Tencent/weui)) 20 | 21 | ```html 22 | 23 | ``` 24 | 25 | Install component package. 26 | 27 | ```bash 28 | npm install vue-weui-next 29 | 30 | # or 31 | 32 | yarn install vue-weui-next 33 | ``` 34 | 35 | Complete introduction. 36 | 37 | ```ts 38 | import { createApp } from 'vue'; 39 | import App from './App.vue'; 40 | import Weui from 'vue-weui-next'; 41 | 42 | const app = createApp(App); 43 | app.use(Weui); 44 | app.mount('#app'); 45 | ``` 46 | 47 | Manual import. 48 | 49 | ```html 50 | 53 | 59 | ``` 60 | 61 | ## Contact 62 | 63 | Currently, the content of the basic components of weui has been improved. We will continue to update and improve the components related to the weui form form in the future. If you are interested in this project, you are welcome to contribute. 64 | 65 | If you find any code problems, or there are new components or capabilities that you hope to add, please feel free to submit an issue. The author will update and fix them in time. Thank you. 66 | 67 | > ![](https://open.weixin.qq.com/zh_CN/htmledition/res/assets/res-design-download/icon16_wx_logo.png) *bangtz* (Add please note vue-weui) 68 | 69 | # License 70 | 71 | [Apache License 2.0](LICENSE) 72 | -------------------------------------------------------------------------------- /README_ZH.md: -------------------------------------------------------------------------------- 1 | 中文版 | [English](./README.md) 2 | 3 |
4 | 5 |
6 | 7 | # vue-weui 8 | 9 | [![](https://shields.io/github/package-json/v/bangtz/vue-weui/master/packages/vue-weui)](https://github.com/bangtz/vue-weui/tree/master/packages/vue-weui) 10 | [![](https://pkg-size.dev/badge/bundle/68018)](https://pkg-size.dev/vue-weui-next) 11 | [![codecov](https://codecov.io/gh/bangtz/vue-weui/graph/badge.svg?token=6TNVSF7OYT)](https://codecov.io/gh/bangtz/vue-weui) 12 | [![](https://img.shields.io/badge/language-vue-orange.svg)](https://vuejs.org/) 13 | ![](https://img.shields.io/npm/l/vue-weui-next.svg) 14 | 15 | 基于 Vue3 开发实现的 WeUI 风格UI组件库。 16 | 17 | ## 快速开始 18 | 19 | 在开始使用之前,需要在html文件中引入weui样式文件。([WeUI](https://github.com/Tencent/weui)) 20 | 21 | ```html 22 | 23 | ``` 24 | 25 | 安装组件包 26 | 27 | ```bash 28 | npm install vue-weui-next 29 | 30 | # or 31 | 32 | yarn install vue-weui-next 33 | ``` 34 | 35 | 完整引入 36 | 37 | ```ts 38 | import { createApp } from 'vue'; 39 | import App from './App.vue'; 40 | import Weui from 'vue-weui-next'; 41 | 42 | const app = createApp(App); 43 | app.use(Weui); 44 | app.mount('#app'); 45 | ``` 46 | 47 | 手动引入 48 | 49 | ```html 50 | 53 | 59 | ``` 60 | 61 | ## 联系 62 | 63 | 当前完善了weui基础组件内容,后续将持续更新完善weui form表单相关的组件,如果对此项目感兴趣,欢迎一起贡献。 64 | 65 | 如果您发现任何代码问题,或者有希望新增的组件或能力,请随时提交问题。作者会及时更新和修复。谢谢 66 | 67 | > ![](https://open.weixin.qq.com/zh_CN/htmledition/res/assets/res-design-download/icon16_wx_logo.png) *bangtz* (Add please note vue-weui) 68 | 69 | # License 70 | 71 | [Apache License 2.0](LICENSE) 72 | -------------------------------------------------------------------------------- /packages/cli/cli.js: -------------------------------------------------------------------------------- 1 | import commandLineArgs from 'command-line-args'; 2 | import commandLineUsage from 'command-line-usage'; 3 | import { readFile } from 'fs/promises'; 4 | import prompts from 'prompts'; 5 | import gitClone from './gitClone.js'; 6 | 7 | const pkg = JSON.parse( 8 | await readFile(new URL('./package.json', import.meta.url)) 9 | ); 10 | 11 | // 配置命令参数 12 | const optionDefinitions = [ 13 | { name: 'version', alias: 'v', type: Boolean }, 14 | { name: 'help', alias: 'h', type: Boolean } 15 | ]; 16 | const options = commandLineArgs(optionDefinitions); 17 | const helpSections = [ 18 | { 19 | header: 'create-vue-weui', 20 | content: '一个快速生成vue-weui组件库开发环境的脚手架' 21 | }, 22 | { 23 | header: 'Options', 24 | optionList: [ 25 | { 26 | name: 'version', 27 | alias: 'v', 28 | typeLabel: '{underline boolean}', 29 | description: '版本号' 30 | }, 31 | { 32 | name: 'help', 33 | alias: 'h', 34 | typeLabel: '{underline boolean}', 35 | description: '帮助' 36 | } 37 | ] 38 | } 39 | ]; 40 | 41 | if (options.version) { 42 | console.log(`v${pkg.version}`); 43 | } 44 | if (options.help) { 45 | console.log(commandLineUsage(helpSections)); 46 | } 47 | 48 | const promptsOptions = [ 49 | { 50 | type: 'text', 51 | name: 'name', 52 | message: '请输入项目名称' 53 | }, 54 | { 55 | type: 'select', 56 | name: 'template', 57 | message: '请选择一个模版', 58 | choices: [{ title: 'vue-weui', value: 1 }] 59 | } 60 | ]; 61 | 62 | const remoteList = { 63 | 1: 'https://github.com/webChen725/test-vercel.git' 64 | }; 65 | const getUserInfo = async () => { 66 | const res = await prompts(promptsOptions); 67 | if (!res.name || !res.template) { 68 | return; 69 | } 70 | gitClone(`direct:${remoteList[res.template]}`, res.name, { 71 | clone: true 72 | }); 73 | }; 74 | 75 | getUserInfo(); 76 | -------------------------------------------------------------------------------- /packages/cli/gitClone.js: -------------------------------------------------------------------------------- 1 | import download from 'download-git-repo'; 2 | import chalk from 'chalk'; 3 | import ora from 'ora'; 4 | 5 | export default (remote, name, options) => { 6 | const downSpinner = ora('开始下载模版...').start(); 7 | return new Promise((resolve, reject) => { 8 | download(remote, name, options, (err) => { 9 | if (err) { 10 | downSpinner.fail(); 11 | console.log('err', chalk.red(err)); 12 | reject(err); 13 | return; 14 | } 15 | downSpinner.succeed(chalk.green('模版下载成功!')); 16 | resolve(); 17 | }); 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /packages/cli/index.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | import './cli.js'; 4 | -------------------------------------------------------------------------------- /packages/cli/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-vue-weui", 3 | "version": "1.0.0", 4 | "description": "", 5 | "type": "module", 6 | "bin": "index.js", 7 | "keywords": [], 8 | "author": "", 9 | "license": "MIT", 10 | "dependencies": { 11 | "chalk": "^5.3.0", 12 | "command-line-args": "^5.2.1", 13 | "command-line-usage": "^7.0.1", 14 | "download-git-repo": "^3.0.2", 15 | "ora": "^7.0.1", 16 | "prompts": "^2.4.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/components/index.ts: -------------------------------------------------------------------------------- 1 | import * as components from './src'; 2 | export * from './src'; 3 | import { App } from 'vue'; 4 | 5 | export default { 6 | install: (app: App) => { 7 | for (const c in components) { 8 | app.use((components as any)[c]); 9 | } 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /packages/components/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-weui-component", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.ts", 6 | "scripts": { 7 | "build": "vite build", 8 | "test": "vitest", 9 | "coverage": "vitest run --coverage" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "@types/lodash": "^4.17.0", 16 | "@vitejs/plugin-vue-jsx": "^3.1.0", 17 | "@vitest/coverage-v8": "^1.1.0" 18 | }, 19 | "dependencies": { 20 | "async-validator": "^4.2.5", 21 | "dayjs": "^1.11.10", 22 | "lodash": "^4.17.21" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/components/scripts/publish/index.ts: -------------------------------------------------------------------------------- 1 | import { series } from 'gulp'; 2 | import run from '../utils/run'; 3 | import { pkgPath } from '../utils/paths'; 4 | 5 | export const publishComponent = async () => { 6 | run('release-it', `${pkgPath}/vue-weui`); 7 | }; 8 | 9 | export default series(async () => publishComponent()); 10 | -------------------------------------------------------------------------------- /packages/components/scripts/utils/delpath.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { resolve } from 'path'; 3 | import { pkgPath } from './paths'; 4 | 5 | // 需要保留的文件 6 | const stayFile = ['package.json', 'README.md']; 7 | 8 | const delPath = async (path: string) => { 9 | let files: string[] = []; 10 | 11 | if (fs.existsSync(path)) { 12 | files = fs.readdirSync(path); 13 | 14 | files.forEach(async (file) => { 15 | const curPath = resolve(path, file); 16 | 17 | if (fs.statSync(curPath).isDirectory()) { 18 | if (file !== 'node_modules') { 19 | await delPath(curPath); 20 | } 21 | } else { 22 | if (!stayFile.includes(file)) { 23 | fs.unlinkSync(curPath); 24 | } 25 | } 26 | }); 27 | 28 | if (path !== `${pkgPath}/vue-weui`) { 29 | fs.rmdirSync(path); 30 | } 31 | } 32 | }; 33 | 34 | export default delPath; 35 | -------------------------------------------------------------------------------- /packages/components/scripts/utils/paths.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | 3 | // 组件库根目录 4 | export const componentPath = resolve(__dirname, '../../'); 5 | // pkg 根目录 6 | export const pkgPath = resolve(__dirname, '../../../'); 7 | export const rootPath = resolve(__dirname, '../../../../'); 8 | -------------------------------------------------------------------------------- /packages/components/scripts/utils/run.ts: -------------------------------------------------------------------------------- 1 | import { spawn } from 'child_process'; 2 | 3 | export default async (command: string, path: string) => { 4 | const [cmd, ...args] = command.split(' '); 5 | return new Promise((resolve) => { 6 | const app = spawn(cmd, args, { 7 | cwd: path, 8 | stdio: 'inherit', 9 | shell: true 10 | }); 11 | app.on('close', resolve); 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /packages/components/scripts/utils/tools.ts: -------------------------------------------------------------------------------- 1 | export function toPascalCase(str: string) { 2 | return str.replace(/(^\w|_+\w)/g, function (match) { 3 | return match.toUpperCase().replace(/_+/g, ''); 4 | }); 5 | } 6 | -------------------------------------------------------------------------------- /packages/components/src/actionsheet/__test__/actionSheetFn.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest'; 2 | import { actionSheet } from '../actionSheet'; 3 | 4 | describe('weui-action-sheet-fn', () => { 5 | it('actionSheet api mount', () => { 6 | const menus = [{ label: '菜单1' }, { label: '菜单1' }]; 7 | const actions = [{ label: '取消' }]; 8 | const fn = vi.fn(); 9 | const result = actionSheet({ 10 | title: '标题', 11 | actions, 12 | menus, 13 | onClose: fn 14 | }); 15 | expect(typeof result).toBe('function'); 16 | expect(result()).toBe(undefined); 17 | expect(fn).toHaveBeenCalled(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/components/src/actionsheet/actionSheet.ts: -------------------------------------------------------------------------------- 1 | import { h, render } from 'vue'; 2 | import ActionSheet from './actionSheet.vue'; 3 | import { shortid } from '../utils'; 4 | import type { ActionSheetMenuOrActions } from './types'; 5 | 6 | interface ActionSheetOptions { 7 | title?: string; 8 | menus?: ActionSheetMenuOrActions[]; 9 | actions?: ActionSheetMenuOrActions[]; 10 | onClick?: (item: ActionSheetMenuOrActions) => void; 11 | onClose?: () => void; 12 | } 13 | 14 | export function actionSheet(options: ActionSheetOptions) { 15 | const { title, menus, actions, onClick, onClose } = options; 16 | 17 | const actionSheetFragment = document.createElement('div'); 18 | actionSheetFragment.classList.add(`weui-actionsheet_${shortid(16)}`); 19 | document.body.appendChild(actionSheetFragment); 20 | const closeSheet = () => { 21 | render(null, actionSheetFragment); 22 | document.body.removeChild(actionSheetFragment); 23 | onClose?.(); 24 | }; 25 | 26 | const vnode = h(ActionSheet, { 27 | title, 28 | menus, 29 | actions, 30 | onClick, 31 | onClose: closeSheet, 32 | modelValue: true 33 | }); 34 | 35 | render(vnode, actionSheetFragment); 36 | 37 | return closeSheet; 38 | } 39 | -------------------------------------------------------------------------------- /packages/components/src/actionsheet/index.ts: -------------------------------------------------------------------------------- 1 | import _ActionSheet from './actionSheet.vue'; 2 | import { actionSheet } from './actionSheet'; 3 | import { withInstall } from '../utils'; 4 | 5 | type ActionSheetType = typeof _ActionSheet & { 6 | actionSheet: typeof actionSheet; 7 | }; 8 | export const ActionSheet = withInstall( 9 | _ActionSheet as ActionSheetType 10 | ); 11 | ActionSheet.actionSheet = actionSheet; 12 | export default ActionSheet; 13 | -------------------------------------------------------------------------------- /packages/components/src/actionsheet/types.ts: -------------------------------------------------------------------------------- 1 | export type ActionSheetMenuOrActions = T & { 2 | label: string; 3 | classNames?: string; 4 | [key: string]: any; 5 | }; 6 | -------------------------------------------------------------------------------- /packages/components/src/alert/__test__/alertFn.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest'; 2 | import { alert } from '../alert'; 3 | 4 | const sleep = (duration: number) => 5 | new Promise((resolve) => setTimeout(resolve, duration)); 6 | describe('weui-alert-fn', () => { 7 | it('alert api mount', () => { 8 | const text = 'alert text'; 9 | const fn = vi.fn(); 10 | const result = alert({ 11 | text, 12 | showClose: true, 13 | showIcon: true, 14 | type: 'warn-primary', 15 | onClose: fn, 16 | duration: 0 17 | }); 18 | expect(typeof result).toBe('function'); 19 | expect(result()).toBe(undefined); 20 | expect(fn).toHaveBeenCalled(); 21 | }); 22 | 23 | it('alert timeout unmount', async () => { 24 | const text = 'alert text'; 25 | const fn = vi.fn(); 26 | alert({ 27 | text, 28 | showClose: true, 29 | showIcon: true, 30 | type: 'warn-primary', 31 | onClose: fn, 32 | duration: 200 33 | }); 34 | await sleep(200); 35 | expect(fn).toHaveBeenCalled(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/components/src/alert/alert.ts: -------------------------------------------------------------------------------- 1 | import { h, render } from 'vue'; 2 | import Alert from './alert.vue'; 3 | import { shortid } from '../utils'; 4 | 5 | interface AlertOptions { 6 | text: string; 7 | type?: 'warn-primary' | 'warn' | 'default' | 'tips-primary' | 'tips'; 8 | showIcon?: boolean; 9 | showClose?: boolean; 10 | duration?: number; 11 | onClose?: () => void; 12 | } 13 | export function alert(options: AlertOptions) { 14 | const { text, showIcon, showClose, type, duration = 3000, onClose } = options; 15 | 16 | let timer: NodeJS.Timeout; 17 | const alertFragment = document.createElement('div'); 18 | alertFragment.classList.add(`weui-alert_${shortid(16)}`); 19 | document.body.appendChild(alertFragment); 20 | 21 | const closeAlert = () => { 22 | render(null, alertFragment); 23 | document.body.removeChild(alertFragment); 24 | timer && clearTimeout(timer); 25 | onClose?.(); 26 | }; 27 | 28 | const vnode = h(Alert, { 29 | text, 30 | type, 31 | showClose, 32 | showIcon, 33 | modelValue: true, 34 | onClose: closeAlert 35 | }); 36 | 37 | render(vnode, alertFragment); 38 | 39 | if (duration) { 40 | timer = setTimeout(closeAlert, duration); 41 | } 42 | 43 | return closeAlert; 44 | } 45 | -------------------------------------------------------------------------------- /packages/components/src/alert/alert.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 71 | -------------------------------------------------------------------------------- /packages/components/src/alert/index.ts: -------------------------------------------------------------------------------- 1 | import _Alert from './alert.vue'; 2 | import { withInstall } from '../utils'; 3 | import { alert } from './alert'; 4 | 5 | type AlertType = typeof _Alert & { 6 | alert: typeof alert; 7 | }; 8 | export const Alert = withInstall(_Alert as AlertType); 9 | Alert.alert = alert; 10 | export default Alert; 11 | -------------------------------------------------------------------------------- /packages/components/src/article/__test__/article.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Article from '../article.vue'; 4 | 5 | describe('weui-article', () => { 6 | it('render default slot', () => { 7 | const articleText = '这是文章内容'; 8 | const wrapper = mount(Article, { 9 | slots: { 10 | default: articleText 11 | } 12 | }); 13 | expect(wrapper.text()).toBe(articleText); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/components/src/article/article.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /packages/components/src/article/index.ts: -------------------------------------------------------------------------------- 1 | import _Article from './article.vue'; 2 | import { withInstall } from '../utils'; 3 | 4 | export const Article = withInstall(_Article); 5 | export default Article; 6 | -------------------------------------------------------------------------------- /packages/components/src/badge/__test__/badge.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Badge from '../badge.vue'; 4 | 5 | describe('weui-badge', () => { 6 | it('render value content', () => { 7 | const wrapper = mount(Badge, { 8 | props: { 9 | value: '50' 10 | } 11 | }); 12 | expect(wrapper.find('#b1_n1').text()).toBe('50'); 13 | }); 14 | 15 | it('render value max handle', async () => { 16 | const wrapperMin = mount(Badge, { 17 | props: { 18 | value: 49, 19 | max: 49 20 | } 21 | }); 22 | expect(wrapperMin.find('#b1_n1').text()).toBe('49'); 23 | const wrapperMax = mount(Badge, { 24 | props: { 25 | value: 50, 26 | max: 49 27 | } 28 | }); 29 | expect(wrapperMax.find('#b1_n1').text()).toBe('49+'); 30 | }); 31 | 32 | it('render string value max handle', () => { 33 | const wrapper = mount(Badge, { 34 | props: { 35 | value: '50', 36 | max: 49 37 | } 38 | }); 39 | expect(wrapper.find('#b1_n1').text()).toBe('50'); 40 | }); 41 | 42 | it('render value dot', () => { 43 | const wrapper = mount(Badge, { 44 | props: { 45 | dot: true, 46 | value: 50 47 | } 48 | }); 49 | expect(wrapper.find('#b1_n1').classes()).toContain('weui-badge_dot'); 50 | }); 51 | 52 | it('custom render value', () => { 53 | const wrapper = mount(Badge, { 54 | slots: { 55 | default: 'custom' 56 | } 57 | }); 58 | expect(wrapper.find('#b1_n1').text()).toBe('custom'); 59 | }); 60 | 61 | it('custom render content', () => { 62 | const wrapper = mount(Badge, { 63 | slots: { 64 | content: 'content' 65 | } 66 | }); 67 | expect(wrapper.classes()).toContain('weui-badge-content'); 68 | expect(wrapper.text()).toBe('content'); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /packages/components/src/badge/badge.less: -------------------------------------------------------------------------------- 1 | .weui-badge-content { 2 | position: relative; 3 | display: inline-block; 4 | 5 | .weui-badge { 6 | position: absolute; 7 | top: -0.4em; 8 | right: -0.4em; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/components/src/badge/badge.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 51 | -------------------------------------------------------------------------------- /packages/components/src/badge/index.ts: -------------------------------------------------------------------------------- 1 | import _Badge from './badge.vue'; 2 | import { withInstall } from '../utils'; 3 | 4 | export const Badge = withInstall(_Badge); 5 | export default Badge; 6 | -------------------------------------------------------------------------------- /packages/components/src/button/__test__/button.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Button from '../button.vue'; 4 | 5 | describe('weui-button', () => { 6 | it('should render solt', () => { 7 | const wrapper = mount(Button, { 8 | slots: { 9 | default: 'vue-weui' 10 | } 11 | }); 12 | expect(wrapper.text()).toContain('vue-weui'); 13 | }); 14 | it('should type class', () => { 15 | const types: any[] = ['default', 'primary', 'warn']; 16 | for (const type of types) { 17 | const wrapper = mount(Button, { 18 | props: { type } 19 | }); 20 | expect(wrapper.classes()).toContain(`weui-btn_${type}`); 21 | } 22 | }); 23 | it('should size class', () => { 24 | const sizes: any[] = ['medium', 'mini']; 25 | for (const size of sizes) { 26 | const wrapper = mount(Button, { 27 | props: { size } 28 | }); 29 | expect(wrapper.classes()).toContain(`weui-btn_${size}`); 30 | } 31 | }); 32 | it('should disabled', async () => { 33 | const wrapper = mount(Button, { 34 | props: { 35 | disabled: true 36 | } 37 | }); 38 | expect(wrapper.classes()).toContain(`weui-btn_disabled`); 39 | // 点击事件不可触发 40 | await wrapper.trigger('click'); 41 | expect(wrapper.emitted()).toEqual({}); 42 | expect(wrapper.props().disabled).toBe(true); 43 | }); 44 | it('should render loading', () => { 45 | const wrapper = mount(Button, { 46 | props: { 47 | loading: true 48 | } 49 | }); 50 | expect(wrapper.classes()).toContain('weui-btn_loading'); 51 | expect(wrapper.find('i.weui-mask-loading').exists()).toBe(true); 52 | }); 53 | 54 | it('click event emit', async () => { 55 | const wrapper = mount(Button, { 56 | slots: { 57 | default: 'button' 58 | } 59 | }); 60 | // 点击事件不可触发 61 | await wrapper.trigger('click'); 62 | expect(wrapper.emitted()).toHaveProperty('click'); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /packages/components/src/button/button.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 49 | -------------------------------------------------------------------------------- /packages/components/src/button/index.ts: -------------------------------------------------------------------------------- 1 | import _Button from './button.vue'; 2 | import { withInstall } from '../utils'; 3 | 4 | export const Button = withInstall(_Button); 5 | export default Button; 6 | -------------------------------------------------------------------------------- /packages/components/src/cells/__test__/cell.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Cell from '../cell.vue'; 4 | 5 | describe('weui-cell', () => { 6 | it('render cell props', () => { 7 | const wrapper = mount(Cell, { 8 | props: { 9 | arrow: true, 10 | link: true 11 | } 12 | }); 13 | expect(wrapper.classes()).toContain('weui-cell_access'); 14 | expect(wrapper.classes()).toContain('weui-cell_link'); 15 | }); 16 | 17 | it('render slots', () => { 18 | const defaultSlotText = 'default slot contet'; 19 | const hdSlotText = 'hd slot contet'; 20 | const ftSlotText = 'ft slot contet'; 21 | const wrapper = mount(Cell, { 22 | slots: { 23 | default: defaultSlotText, 24 | hd: hdSlotText, 25 | ft: ftSlotText 26 | } 27 | }); 28 | expect(wrapper.find('.weui-cell__bd').text()).toBe(defaultSlotText); 29 | expect(wrapper.find('.weui-cell__hd').text()).toBe(hdSlotText); 30 | expect(wrapper.find('.weui-cell__ft').text()).toBe(ftSlotText); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/components/src/cells/__test__/cells.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Cells from '../cells.vue'; 4 | 5 | describe('weui-cells', () => { 6 | it('render cells title', () => { 7 | const titleText = 'cells标题'; 8 | const wrapper = mount(Cells, { 9 | props: { 10 | title: titleText 11 | } 12 | }); 13 | expect(wrapper.find('.weui-cells__title').text()).toBe(titleText); 14 | }); 15 | 16 | it('render default slot', () => { 17 | const slotText = 'custom slot contet'; 18 | const wrapper = mount(Cells, { 19 | slots: { 20 | default: slotText 21 | } 22 | }); 23 | expect(wrapper.find('.weui-cells').text()).toBe(slotText); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/components/src/cells/cell.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 36 | -------------------------------------------------------------------------------- /packages/components/src/cells/cells.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 23 | -------------------------------------------------------------------------------- /packages/components/src/cells/index.ts: -------------------------------------------------------------------------------- 1 | import _Cells from './cells.vue'; 2 | import _Cell from './cell.vue'; 3 | import { withInstall } from '../utils'; 4 | 5 | type CellsType = typeof _Cells & { 6 | Cell: typeof _Cell; 7 | }; 8 | export const Cells = withInstall(_Cells as CellsType); 9 | export const Cell = withInstall(_Cell); 10 | Cells.Cell = Cell; 11 | export default Cells; 12 | -------------------------------------------------------------------------------- /packages/components/src/common/styles.less: -------------------------------------------------------------------------------- 1 | /* region fade */ 2 | .weui-fade-enter-active, 3 | .weui-fade-leave-active { 4 | transition: opacity 0.3s; 5 | } 6 | 7 | /* vue3 */ 8 | .weui-fade-enter-from, 9 | .weui-fade-leave-to { 10 | opacity: 0 !important; 11 | } 12 | 13 | .weui-fade-enter-to, 14 | .weui-fade-leave-from { 15 | opacity: 1 !important; 16 | } 17 | 18 | /* endregion fade */ 19 | 20 | /* region slide-up */ 21 | .weui-slide-up-enter-active, 22 | .weui-slide-up-leave-active { 23 | transition: transform 0.3s; 24 | } 25 | 26 | /* vue3 */ 27 | .weui-slide-up-enter-from, 28 | .weui-slide-up-leave-to { 29 | transform: translateY(100%) !important; 30 | } 31 | 32 | .weui-slide-up-enter-to, 33 | .weui-slide-up-leave-from { 34 | transform: translateY(0) !important; 35 | } 36 | 37 | /* endregion slide-up */ 38 | 39 | /* region slide-down */ 40 | .weui-slide-down-enter-active, 41 | .weui-slide-down-leave-active { 42 | transition: transform 0.3s; 43 | } 44 | 45 | /* vue3 */ 46 | .weui-slide-down-enter-from, 47 | .weui-slide-down-leave-to { 48 | transform: translateY(-100%) !important; 49 | } 50 | 51 | .weui-slide-down-enter-to, 52 | .weui-slide-down-leave-from { 53 | transform: translateY(0) !important; 54 | } 55 | 56 | /* endregion slide-down */ 57 | -------------------------------------------------------------------------------- /packages/components/src/components.d.ts: -------------------------------------------------------------------------------- 1 | import * as components from '.'; 2 | 3 | declare module '@vue/runtime-core' { 4 | export interface GlobalComponents { 5 | weuiIcon: typeof components.Icon; 6 | weuiButton: typeof components.Button; 7 | weuiSlider: typeof components.Slider; 8 | weuiLoading: typeof components.Loading; 9 | weuiProgress: typeof components.Progress; 10 | weuiBadge: typeof components.Badge; 11 | weuiArticle: typeof components.Article; 12 | weuiFlex: typeof components.Flex; 13 | weuiFlexItem: typeof components.FlexItem; 14 | weuiFooter: typeof components.Footer; 15 | weuiFooterLink: typeof components.FooterLink; 16 | weuiGrids: typeof components.Grids; 17 | weuiGrid: typeof components.Grid; 18 | weuiLoadmore: typeof components.Loadmore; 19 | weuiCells: typeof components.Cells; 20 | weuiCell: typeof components.Cell; 21 | weuiToast: typeof components.Toast; 22 | weuiPanel: typeof components.Panel; 23 | weuiMediaBox: typeof components.MediaBox; 24 | weuiPreview: typeof components.Preview; 25 | weuiPreviewItem: typeof components.PreviewItem; 26 | weuiPreviewBtn: typeof components.PreviewBtn; 27 | weuiSteps: typeof components.Steps; 28 | weuiStepItem: typeof components.StepItem; 29 | weuiMask: typeof components.Mask; 30 | weuiActionSheet: typeof components.ActionSheet; 31 | weuiDialog: typeof components.Dialog; 32 | weuiHalfScreenDialog: typeof components.HalfScreenDialog; 33 | weuiMsg: typeof components.Msg; 34 | weuiAlert: typeof components.Alert; 35 | weuiNavbar: typeof components.NavBar; 36 | weuiNavbarItem: typeof components.NavBarItem; 37 | weuiTabbar: typeof components.TabBar; 38 | weuiTabbarItem: typeof components.TabBarItem; 39 | weuiSearchbar: typeof components.SearchaBar; 40 | weuiForm: typeof components.Form; 41 | weuiFormGroup: typeof components.FormGroup; 42 | weuiFormItem: typeof components.FormItem; 43 | weuiInput: typeof components.Input; 44 | weuiPicker: typeof components.Picker; 45 | weuiDatePicker: typeof components.DatePicker; 46 | weuiTimePicker: typeof components.TimePicker; 47 | weuiTextarea: typeof components.Textarea; 48 | weuiSwitch: typeof components.Switch; 49 | weuiUploader: typeof components.Uploader; 50 | weuiGallery: typeof components.Gallery; 51 | } 52 | } 53 | export {}; 54 | -------------------------------------------------------------------------------- /packages/components/src/datePicker/__test__/datePicker.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import DatePicker from '../datePicker.vue'; 4 | import dayjs from 'dayjs'; 5 | 6 | describe('weui-date-picker', () => { 7 | it('render datePicker', async () => { 8 | const wrapper = mount(DatePicker, { 9 | props: { 10 | modelValue: undefined, 11 | placeholder: 'placeholder', 12 | start: new Date('2021-1-1'), 13 | end: new Date('2025-1-1') 14 | } 15 | }); 16 | expect(wrapper.find('.weui-date-value').text()).toBe('placeholder'); 17 | 18 | // 触发打开选择器 19 | wrapper.trigger('click'); 20 | await wrapper.vm.$nextTick(); 21 | expect(document.querySelector('.weui-date-picker-selector')).not.toBeNull(); 22 | 23 | // 触发picker事件 24 | const pickerGroup = document.querySelector( 25 | '.weui-date-picker-selector .weui-picker__group' 26 | ); 27 | if (pickerGroup) { 28 | pickerGroup.scrollTop = 56; 29 | pickerGroup.dispatchEvent(new Event('scroll')); 30 | await wrapper.vm.$nextTick(); 31 | expect(wrapper.emitted()).toHaveProperty('selectChange'); 32 | // 触发confirm事件 33 | const confirmBtn = document.querySelector( 34 | '.weui-date-picker-selector .weui-picker__btn' 35 | ); 36 | confirmBtn?.dispatchEvent(new Event('click')); 37 | expect(wrapper.emitted()).toHaveProperty('change'); 38 | expect(wrapper.emitted()).toHaveProperty('update:modelValue'); 39 | } 40 | }); 41 | 42 | it('render datePicker disabled', async () => { 43 | const wrapper = mount(DatePicker, { 44 | props: { 45 | modelValue: new Date(), 46 | placeholder: 'placeholder', 47 | disabled: true 48 | } 49 | }); 50 | expect(wrapper.find('.weui-date-value').text()).toBe( 51 | dayjs().format('YYYY-MM-DD') 52 | ); 53 | expect(wrapper.classes()).toContain('weui-picker_diabled'); 54 | 55 | wrapper.trigger('click'); 56 | }); 57 | 58 | it('render datePicker formatter', async () => { 59 | const wrapper = mount(DatePicker, { 60 | props: { 61 | modelValue: new Date(), 62 | placeholder: 'placeholder', 63 | formatter: 'YYYY年MM月DD日' 64 | } 65 | }); 66 | expect(wrapper.find('.weui-date-value').text()).toBe( 67 | dayjs().format('YYYY年MM月DD日') 68 | ); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /packages/components/src/datePicker/datePicker.less: -------------------------------------------------------------------------------- 1 | .weui-date-picker-wrapper { 2 | align-items: center; 3 | width: 100%; 4 | 5 | .weui-date-value { 6 | flex: 1; 7 | padding-left: 0; 8 | } 9 | 10 | &.weui-picker_diabled .weui-date-value { 11 | color: var(--weui-FG-1); 12 | } 13 | 14 | &.weui-picker_placeholder .weui-date-value { 15 | color: var(--weui-FG-2); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/components/src/datePicker/index.ts: -------------------------------------------------------------------------------- 1 | import _DatePicker from './datePicker.vue'; 2 | import { withInstall } from '../utils'; 3 | 4 | export const DatePicker = withInstall(_DatePicker); 5 | export default DatePicker; 6 | -------------------------------------------------------------------------------- /packages/components/src/datePicker/types.ts: -------------------------------------------------------------------------------- 1 | export interface DateItem { 2 | label: string; 3 | value: number; 4 | children?: DateItem[]; 5 | } 6 | -------------------------------------------------------------------------------- /packages/components/src/dialog/__test__/dialogFn.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest'; 2 | import { dialog } from '../dialog'; 3 | 4 | describe('weui-dialog-fn', () => { 5 | it('actionSheet api mount', () => { 6 | const cancelFn = vi.fn(); 7 | const okFn = vi.fn(); 8 | const result = dialog({ 9 | title: '标题', 10 | desc: '描述', 11 | onCancel: cancelFn, 12 | onOk: okFn 13 | }); 14 | expect(typeof result).toBe('function'); 15 | expect(result()).toBe(undefined); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/components/src/dialog/dialog.ts: -------------------------------------------------------------------------------- 1 | import { h, render } from 'vue'; 2 | import Dialog from './dialog.vue'; 3 | import { shortid } from '../utils'; 4 | 5 | interface DialogOptions { 6 | title?: string; 7 | desc?: string; 8 | cancelText?: string; 9 | okText?: string; 10 | onCancel?: () => void; 11 | onOk?: () => void; 12 | } 13 | export function dialog(options: DialogOptions) { 14 | const { title, desc, cancelText, okText, onCancel, onOk } = options; 15 | 16 | const dialogFragment = document.createElement('div'); 17 | dialogFragment.classList.add(`weui-dialog_${shortid(16)}`); 18 | document.body.appendChild(dialogFragment); 19 | const closeDialog = () => { 20 | render(null, dialogFragment); 21 | document.body.removeChild(dialogFragment); 22 | }; 23 | const eventHandlerFactory = (handler?: () => void) => { 24 | return () => { 25 | closeDialog(); 26 | handler?.(); 27 | }; 28 | }; 29 | 30 | const vnode = h(Dialog, { 31 | title, 32 | desc, 33 | cancelText, 34 | okText, 35 | modelValue: true, 36 | onCancel: eventHandlerFactory(onCancel), 37 | onOk: eventHandlerFactory(onOk) 38 | }); 39 | 40 | render(vnode, dialogFragment); 41 | 42 | return closeDialog; 43 | } 44 | -------------------------------------------------------------------------------- /packages/components/src/dialog/index.ts: -------------------------------------------------------------------------------- 1 | import _Dialog from './dialog.vue'; 2 | import { dialog } from './dialog'; 3 | import { withInstall } from '../utils'; 4 | 5 | type DialogType = typeof _Dialog & { 6 | dialog: typeof dialog; 7 | }; 8 | export const Dialog = withInstall(_Dialog as DialogType); 9 | Dialog.dialog = dialog; 10 | export default Dialog; 11 | -------------------------------------------------------------------------------- /packages/components/src/flex/__test__/flex.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Flex from '../flex.vue'; 4 | import FlexItem from '../flexItem.vue'; 5 | 6 | describe('weui-flex', () => { 7 | it('render flex slot', () => { 8 | const flexText = 'Flex'; 9 | const wrapper = mount(Flex, { 10 | slots: { 11 | default: flexText 12 | } 13 | }); 14 | expect(wrapper.text()).toBe(flexText); 15 | }); 16 | it('render flex gap', () => { 17 | const wrapperNumber = mount(Flex, { 18 | props: { 19 | gap: 20 20 | } 21 | }); 22 | expect((wrapperNumber.element as HTMLElement).style.gap).toBe('20px'); 23 | const wrapperTuple = mount(Flex, { 24 | props: { 25 | gap: [10, 20] 26 | } 27 | }); 28 | expect((wrapperTuple.element as HTMLElement).style.gap).toBe('10px 20px'); 29 | }); 30 | }); 31 | 32 | describe('weui-flex-item', () => { 33 | it('render flex-item slot', () => { 34 | const flexText = 'Flex-item'; 35 | const wrapper = mount(FlexItem, { 36 | slots: { 37 | default: flexText 38 | } 39 | }); 40 | expect(wrapper.text()).toBe(flexText); 41 | }); 42 | it('render flex-item flex', () => { 43 | const wrapper = mount(FlexItem, { 44 | props: { 45 | flex: 2 46 | } 47 | }); 48 | expect((wrapper.element as HTMLElement).style.flex).toBe('2 1 0%'); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /packages/components/src/flex/flex.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 28 | -------------------------------------------------------------------------------- /packages/components/src/flex/flexItem.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | -------------------------------------------------------------------------------- /packages/components/src/flex/index.ts: -------------------------------------------------------------------------------- 1 | import _Flex from './flex.vue'; 2 | import _FlexItem from './flexItem.vue'; 3 | import { withInstall } from '../utils'; 4 | 5 | export const Flex = withInstall(_Flex); 6 | export const FlexItem = withInstall(_FlexItem); 7 | Flex.FlexItem = FlexItem; 8 | export default Flex; 9 | -------------------------------------------------------------------------------- /packages/components/src/footer/__test__/footer.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Footer from '../footer.vue'; 4 | import FooterLink from '../footerLink.vue'; 5 | 6 | describe('weui-footer', () => { 7 | it('render footer text', () => { 8 | const footerText = 'Flex'; 9 | const wrapper = mount(Footer, { 10 | props: { 11 | text: footerText 12 | } 13 | }); 14 | expect(wrapper.find('.weui-footer__text').text()).toBe(footerText); 15 | }); 16 | it('render footer default slot', () => { 17 | const footerSlot = 'custom text'; 18 | const wrapper = mount(Footer, { 19 | slots: { 20 | default: footerSlot 21 | } 22 | }); 23 | expect(wrapper.find('.weui-footer__text').text()).toBe(footerSlot); 24 | }); 25 | it('render footer links slot', () => { 26 | const footerSlot = 'custom links'; 27 | const wrapper = mount(Footer, { 28 | slots: { 29 | links: footerSlot 30 | } 31 | }); 32 | expect(wrapper.find('.weui-footer__links').exists()).toBe(true); 33 | expect(wrapper.find('.weui-footer__links').text()).toBe(footerSlot); 34 | }); 35 | }); 36 | 37 | describe('weui-footer-link', () => { 38 | it('render footer link props', () => { 39 | const link = 'test link'; 40 | const target = '_blank'; 41 | const wrapper = mount(FooterLink, { 42 | props: { 43 | link, 44 | target 45 | } 46 | }); 47 | expect(wrapper.attributes().href).toBe(link); 48 | expect(wrapper.attributes().target).toBe(target); 49 | }); 50 | it('render footer slot', () => { 51 | const linkSlot = 'link slot'; 52 | const wrapper = mount(FooterLink, { 53 | slots: { 54 | default: linkSlot 55 | } 56 | }); 57 | expect(wrapper.text()).toBe(linkSlot); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /packages/components/src/footer/footer.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 26 | -------------------------------------------------------------------------------- /packages/components/src/footer/footerLink.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 21 | -------------------------------------------------------------------------------- /packages/components/src/footer/index.ts: -------------------------------------------------------------------------------- 1 | import _Footer from './footer.vue'; 2 | import _FooterLink from './footerLink.vue'; 3 | import { withInstall } from '../utils'; 4 | 5 | export const Footer = withInstall(_Footer); 6 | export const FooterLink = withInstall(_FooterLink); 7 | Footer.FooterLink = FooterLink; 8 | export default Footer; 9 | -------------------------------------------------------------------------------- /packages/components/src/form/constants.ts: -------------------------------------------------------------------------------- 1 | export const REGISTER_NAME_LIST = Symbol('REGISTER_NAME_LIST'); 2 | export const REGISTER_RULE = Symbol('REGISTER_RULE'); 3 | export const VALIDATE_ERRORS = Symbol('VALIDATE_ERRORS'); 4 | export const VALIDATE_SHOW_WARN = Symbol('VALIDATE_SHOW_WARN'); 5 | export const REMOVE_VALIDATE_ERROR = Symbol('REMOVE_VALIDATE_ERROR'); 6 | export const FORM_MODEL_DATA = Symbol('FORM_MODEL_DATA'); 7 | -------------------------------------------------------------------------------- /packages/components/src/form/form.less: -------------------------------------------------------------------------------- 1 | .weui-form.weui-form-noheader { 2 | padding-top: 0; 3 | } 4 | -------------------------------------------------------------------------------- /packages/components/src/form/formGroup.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 24 | -------------------------------------------------------------------------------- /packages/components/src/form/formItem.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 74 | -------------------------------------------------------------------------------- /packages/components/src/form/index.ts: -------------------------------------------------------------------------------- 1 | import _Form from './form.vue'; 2 | import _FormGroup from './formGroup.vue'; 3 | import _FormItem from './formItem.vue'; 4 | import { withInstall } from '../utils'; 5 | export * from './types'; 6 | 7 | type FormType = typeof _Form & { 8 | FormGroup: typeof _FormGroup; 9 | FormItem: typeof _FormItem; 10 | }; 11 | export const Form = withInstall(_Form as FormType); 12 | export const FormGroup = withInstall(_FormGroup); 13 | export const FormItem = withInstall(_FormItem); 14 | Form.FormGroup = FormGroup; 15 | Form.FormItem = FormItem; 16 | export default Form; 17 | -------------------------------------------------------------------------------- /packages/components/src/form/types.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Values, 3 | RuleItem as _RuleItem, 4 | Rule as _Rule, 5 | Rules as _Rules 6 | } from 'async-validator'; 7 | 8 | export type Rules = _Rules; 9 | export type Rule = _Rule; 10 | export type RuleItem = _RuleItem; 11 | export interface FormInstance { 12 | validate: (nameList?: string[]) => Promise; 13 | resetFields: (nameList?: string[]) => void; 14 | clearValidate: (nameList?: string[]) => void; 15 | } 16 | -------------------------------------------------------------------------------- /packages/components/src/gallery/__test__/galleryFn.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest'; 2 | import { gallery } from '../gallery'; 3 | import userEvent from '@testing-library/user-event'; 4 | 5 | describe('weui-gallery function', () => { 6 | it('gallery api mount', async () => { 7 | const deleteFn = vi.fn(); 8 | const closeFn = vi.fn(); 9 | const changeFn = vi.fn(); 10 | const close = gallery({ 11 | current: 0, 12 | urls: ['test1.jpg', 'test2.jpg', 'test3.jpg'], 13 | onDelete: deleteFn, 14 | onClose: closeFn, 15 | onChange: changeFn 16 | }); 17 | 18 | const wrapper = document.body.querySelector( 19 | '.weui-gallery__teleport-container' 20 | ); 21 | const deleteBtn = wrapper?.querySelector('.weui-gallery__del'); 22 | deleteBtn && (await userEvent.click(deleteBtn)); 23 | expect(deleteFn).toBeCalled(); 24 | 25 | const closeBtn = wrapper?.querySelector('.gallery-close__btn'); 26 | closeBtn && (await userEvent.click(closeBtn)); 27 | expect(closeFn).toBeCalled(); 28 | 29 | const threshold = window.innerWidth / 3; 30 | const imageWrap = wrapper?.querySelector('.weui-gallery__img-wrap'); 31 | imageWrap?.dispatchEvent( 32 | new TouchEvent('touchstart', { 33 | changedTouches: [{ clientX: 50 + threshold + 20, clientY: 100 } as any] 34 | }) 35 | ); 36 | imageWrap?.dispatchEvent( 37 | new TouchEvent('touchmove', { 38 | changedTouches: [{ clientX: 50, clientY: 100 } as any] 39 | }) 40 | ); 41 | imageWrap?.dispatchEvent( 42 | new TouchEvent('touchend', { 43 | changedTouches: [{ clientX: 50, clientY: 100 } as any] 44 | }) 45 | ); 46 | expect(changeFn).toBeCalled(); 47 | expect(close()).toBe(undefined); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /packages/components/src/gallery/gallery.less: -------------------------------------------------------------------------------- 1 | .weui-gallery__container { 2 | z-index: 100000; 3 | display: flex; 4 | flex-direction: column; 5 | 6 | .weui-gallery__header { 7 | position: relative; 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | width: 100%; 12 | height: 60px; 13 | font-size: 15px; 14 | color: var(--weui-WHITE); 15 | 16 | .gallery-close__btn { 17 | position: absolute; 18 | right: 16px; 19 | display: flex; 20 | align-items: center; 21 | justify-content: center; 22 | width: 24px; 23 | height: 24px; 24 | 25 | .weui-icon-close { 26 | width: 16px; 27 | height: 16px; 28 | } 29 | } 30 | } 31 | 32 | .weui-gallery__img-wrap { 33 | display: flex; 34 | flex: 1; 35 | touch-action: none; 36 | transform: translateX(0); 37 | 38 | .weui-gallery__img { 39 | position: relative; 40 | display: flex; 41 | flex-shrink: 0; 42 | align-items: center; 43 | justify-content: center; 44 | width: 100%; 45 | height: 100%; 46 | padding: 44px 0; 47 | 48 | > img { 49 | max-width: 100%; 50 | max-height: 100%; 51 | object-fit: contain; 52 | } 53 | } 54 | } 55 | 56 | .weui-gallery__opr { 57 | position: relative; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/components/src/gallery/gallery.ts: -------------------------------------------------------------------------------- 1 | import { h, render } from 'vue'; 2 | import Gallery from './gallery.vue'; 3 | import { shortid } from '../utils'; 4 | 5 | interface GalleryOptions { 6 | urls: string[]; 7 | current?: number; 8 | onChange?: (current: number) => void; 9 | onDelete?: (index: number, url: string) => void; 10 | onClose?: () => void; 11 | } 12 | export function gallery(options: GalleryOptions) { 13 | const { urls, current, onChange, onDelete, onClose } = options; 14 | 15 | const vnode = h(Gallery, { 16 | urls, 17 | initialIndex: current, 18 | visible: true, 19 | onChange, 20 | onDelete, 21 | onClose 22 | }); 23 | const galleryFragment = document.createElement('div'); 24 | galleryFragment.classList.add(`weui-gallery__teleport-container`); 25 | galleryFragment.classList.add(`weui-gallery_${shortid(16)}`); 26 | document.body.appendChild(galleryFragment); 27 | render(vnode, galleryFragment); 28 | 29 | const closeGallery = () => { 30 | render(null, galleryFragment); 31 | document.body.removeChild(galleryFragment); 32 | }; 33 | 34 | return closeGallery; 35 | } 36 | -------------------------------------------------------------------------------- /packages/components/src/gallery/index.ts: -------------------------------------------------------------------------------- 1 | import _Gallery from './gallery.vue'; 2 | import { gallery } from './gallery'; 3 | import { withInstall } from '../utils'; 4 | 5 | type GalleryType = typeof _Gallery & { 6 | gallery: typeof gallery; 7 | }; 8 | export const Gallery = withInstall(_Gallery as GalleryType); 9 | Gallery.gallery = gallery; 10 | export default Gallery; 11 | -------------------------------------------------------------------------------- /packages/components/src/grid/__test__/grid.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Grid from '../grid.vue'; 4 | 5 | describe('weui-grid', () => { 6 | it('render grid props', () => { 7 | const gridIcon = 'https://weui.io//images/icon_tabbar.png'; 8 | const gridLabel = 'Grid'; 9 | const wrapper = mount(Grid, { 10 | props: { 11 | icon: gridIcon, 12 | label: gridLabel 13 | } 14 | }); 15 | expect(wrapper.find('.weui-grid__icon img').attributes().src).toBe( 16 | gridIcon 17 | ); 18 | expect(wrapper.find('.weui-grid__label').text()).toBe(gridLabel); 19 | }); 20 | 21 | it('render grid slots', () => { 22 | const iconSlot = 'custom Icon'; 23 | const labelSlot = 'custom lable'; 24 | const wrapper = mount(Grid, { 25 | slots: { 26 | icon: iconSlot, 27 | label: labelSlot 28 | } 29 | }); 30 | expect(wrapper.find('.weui-grid__icon').text()).toBe(iconSlot); 31 | expect(wrapper.find('.weui-grid__label').text()).toBe(labelSlot); 32 | }); 33 | 34 | it('render grid click event', async () => { 35 | const wrapper = mount(Grid, { 36 | props: { 37 | label: 'Grid' 38 | } 39 | }); 40 | await wrapper.trigger('click'); 41 | expect(wrapper.emitted()).haveOwnProperty('click'); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/components/src/grid/__test__/grids.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Grids from '../grids.vue'; 4 | 5 | describe('weui-grids', () => { 6 | it('render grids slot', () => { 7 | const slotText = 'grids-slot'; 8 | const wrapper = mount(Grids, { 9 | slots: { 10 | default: slotText 11 | } 12 | }); 13 | expect(wrapper.text()).toBe(slotText); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /packages/components/src/grid/grid.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 37 | -------------------------------------------------------------------------------- /packages/components/src/grid/grids.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /packages/components/src/grid/index.ts: -------------------------------------------------------------------------------- 1 | import _Grids from './grids.vue'; 2 | import _Grid from './grid.vue'; 3 | import { withInstall } from '../utils'; 4 | 5 | export const Grids = withInstall(_Grids); 6 | export const Grid = withInstall(_Grid); 7 | Grids.Grid = Grid; 8 | export default Grids; 9 | -------------------------------------------------------------------------------- /packages/components/src/halfScreenDialog/index.ts: -------------------------------------------------------------------------------- 1 | import _HalfScreenDialoga from './halfScreenDialog.vue'; 2 | import { withInstall } from '../utils'; 3 | 4 | export const HalfScreenDialog = withInstall(_HalfScreenDialoga); 5 | export default HalfScreenDialog; 6 | -------------------------------------------------------------------------------- /packages/components/src/icon/__test__/icon.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Icon from '../icon.vue'; 4 | 5 | describe('weui-loading', () => { 6 | it('render icon type', () => { 7 | const icons = [ 8 | 'success', 9 | 'info', 10 | 'waiting', 11 | 'warn', 12 | 'success-no-circle', 13 | 'close', 14 | 'close-thin', 15 | 'slide-down', 16 | 'arrow', 17 | 'outlined-warn' 18 | ]; 19 | for (const icon in icons) { 20 | const wrapper = mount(Icon, { 21 | props: { 22 | type: icon as any 23 | } 24 | }); 25 | expect(wrapper.classes()).toContain(`weui-icon-${icon}`); 26 | } 27 | }); 28 | 29 | it('render icon size', () => { 30 | const wrapperNum = mount(Icon, { 31 | props: { 32 | type: 'success', 33 | size: 20 34 | } 35 | }); 36 | expect(wrapperNum.attributes().style).toBe('font-size: 20px;'); 37 | const wrapperStr = mount(Icon, { 38 | props: { 39 | type: 'success', 40 | size: '2em' 41 | } 42 | }); 43 | expect(wrapperStr.attributes().style).toBe('font-size: 2em;'); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /packages/components/src/icon/icon.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 37 | -------------------------------------------------------------------------------- /packages/components/src/icon/index.ts: -------------------------------------------------------------------------------- 1 | import _Icon from './icon.vue'; 2 | import { withInstall } from '../utils'; 3 | 4 | export const Icon = withInstall(_Icon); 5 | export default Icon; 6 | -------------------------------------------------------------------------------- /packages/components/src/index.ts: -------------------------------------------------------------------------------- 1 | import './common/styles.less'; 2 | export * from './icon'; 3 | export * from './button'; 4 | export * from './slider'; 5 | export * from './loading'; 6 | export * from './progress'; 7 | export * from './badge'; 8 | export * from './article'; 9 | export * from './flex'; 10 | export * from './footer'; 11 | export * from './grid'; 12 | export * from './loadmore'; 13 | export * from './cells'; 14 | export * from './toast'; 15 | export * from './panel'; 16 | export * from './mediabox'; 17 | export * from './preview'; 18 | export * from './steps'; 19 | export * from './mask'; 20 | export * from './actionsheet'; 21 | export * from './dialog'; 22 | export * from './halfScreenDialog'; 23 | export * from './msg'; 24 | export * from './alert'; 25 | export * from './navbar'; 26 | export * from './tabbar'; 27 | export * from './searchbar'; 28 | export * from './form'; 29 | export * from './input'; 30 | export * from './picker'; 31 | export * from './datePicker'; 32 | export * from './timePicker'; 33 | export * from './textarea'; 34 | export * from './switch'; 35 | export * from './uploader'; 36 | export * from './gallery'; 37 | export { default as utils } from './utils/exports'; 38 | -------------------------------------------------------------------------------- /packages/components/src/input/__test__/input.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Input from '../input.vue'; 4 | 5 | describe('weui-input', () => { 6 | it('render input', () => { 7 | const wrapper = mount(Input, { 8 | props: { 9 | modelValue: 'input', 10 | placeholder: 'placeholder', 11 | type: 'text', 12 | allowClear: true, 13 | disabled: true 14 | } 15 | }); 16 | expect(wrapper.find('.weui-input').attributes().type).toBe('text'); 17 | expect(wrapper.find('.weui-input').attributes().placeholder).toBe( 18 | 'placeholder' 19 | ); 20 | expect(wrapper.find('.weui-input').attributes()).toHaveProperty('disabled'); 21 | expect(wrapper.find('.weui-btn_reset').exists()).toBe(true); 22 | expect(wrapper.classes()).toContain('weui-cell_disabled'); 23 | }); 24 | 25 | it('change input value', () => { 26 | const wrapper = mount(Input, { 27 | props: { 28 | modelValue: 'input', 29 | placeholder: 'placeholder', 30 | type: 'text' 31 | } 32 | }); 33 | wrapper.find('.weui-input').setValue('new input'); 34 | expect(wrapper.emitted()).toHaveProperty('update:modelValue'); 35 | expect(wrapper.emitted()).toHaveProperty('change'); 36 | }); 37 | 38 | it('clear input value', async () => { 39 | const wrapper = mount(Input, { 40 | props: { 41 | modelValue: 'input', 42 | placeholder: 'placeholder', 43 | type: 'text', 44 | allowClear: true 45 | } 46 | }); 47 | await wrapper.find('.weui-btn_reset').trigger('click'); 48 | expect((wrapper.emitted().change[0] as any)?.[0]).toBe(''); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /packages/components/src/input/index.ts: -------------------------------------------------------------------------------- 1 | import _Input from './input.vue'; 2 | import { withInstall } from '../utils'; 3 | 4 | export const Input = withInstall(_Input); 5 | export default Input; 6 | -------------------------------------------------------------------------------- /packages/components/src/input/input.less: -------------------------------------------------------------------------------- 1 | .weui-input-wrapper { 2 | width: 100%; 3 | 4 | .weui-btn_reset:active { 5 | display: block; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/components/src/input/input.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 64 | -------------------------------------------------------------------------------- /packages/components/src/loading/__test__/loading.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Loading from '../loading.vue'; 4 | 5 | describe('weui-loading', () => { 6 | it('render loading type', () => { 7 | const wrapper = mount(Loading, { 8 | props: { 9 | type: 'primary' 10 | } 11 | }); 12 | expect(wrapper.classes()).toContain('weui-loading-primary'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /packages/components/src/loading/index.ts: -------------------------------------------------------------------------------- 1 | import _Loading from './loading.vue'; 2 | import { withInstall } from '../utils'; 3 | 4 | export const Loading = withInstall(_Loading); 5 | export default Loading; 6 | -------------------------------------------------------------------------------- /packages/components/src/loading/loading.less: -------------------------------------------------------------------------------- 1 | .weui-mask-loading.weui-loading-primary { 2 | color: var(--weui-BRAND); 3 | } 4 | -------------------------------------------------------------------------------- /packages/components/src/loading/loading.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 29 | -------------------------------------------------------------------------------- /packages/components/src/loadmore/__test__/loadmore.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Loadmore from '../loadmore.vue'; 4 | 5 | describe('weui-loadmore', () => { 6 | it('render loadmore type', () => { 7 | const types = ['default', 'line', 'dot']; 8 | for (const type in types) { 9 | const wrapper = mount(Loadmore, { 10 | props: { 11 | type: type as any 12 | } 13 | }); 14 | expect(wrapper.classes()).toContain('weui-loadmore'); 15 | if (type === 'default') { 16 | expect(wrapper.find('.weui-primary-loading').exists()).toBe(true); 17 | } 18 | if (type === 'line') { 19 | expect(wrapper.classes()).toContain('weui-loadmore_line'); 20 | } 21 | if (type === 'dot') { 22 | expect(wrapper.classes()).toContain('weui-loadmore_line'); 23 | expect(wrapper.classes()).toContain('weui-loadmore_line'); 24 | expect(wrapper.find('.weui-loadmore__tips').text()).toBe(''); 25 | } 26 | } 27 | }); 28 | 29 | it('render loadmore default slot', () => { 30 | const slotText = 'custom text'; 31 | const wrapper = mount(Loadmore, { 32 | slots: { 33 | default: slotText 34 | } 35 | }); 36 | expect(wrapper.find('.weui-loadmore__tips').text()).toBe(slotText); 37 | }); 38 | 39 | it('render loadmore props text', () => { 40 | const propsText = 'custom text'; 41 | const wrapper = mount(Loadmore, { 42 | props: { 43 | text: propsText 44 | } 45 | }); 46 | expect(wrapper.find('.weui-loadmore__tips').text()).toBe(propsText); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /packages/components/src/loadmore/index.ts: -------------------------------------------------------------------------------- 1 | import _Loadmore from './loadmore.vue'; 2 | import { withInstall } from '../utils'; 3 | 4 | export const Loadmore = withInstall(_Loadmore); 5 | export default Loadmore; 6 | -------------------------------------------------------------------------------- /packages/components/src/loadmore/loadmore.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 42 | -------------------------------------------------------------------------------- /packages/components/src/mask/__test__/mask.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Mask from '../mask.vue'; 4 | 5 | describe('weui-mask', () => { 6 | it('render mask type', () => { 7 | const types = ['transparent', 'default']; 8 | for (const type of types) { 9 | const wrapper = mount(Mask, { 10 | props: { 11 | type: type as any 12 | } 13 | }); 14 | if (type === 'transparant') { 15 | expect(wrapper.classes()).toContain('weui-mask_transparent'); 16 | } 17 | if (type === 'default') { 18 | expect(wrapper.classes()).toContain('weui-mask'); 19 | } 20 | } 21 | }); 22 | 23 | it('render default slot', () => { 24 | const defaultSlot = 'defaultSlot'; 25 | const wrapper = mount(Mask, { 26 | slots: { 27 | default: defaultSlot 28 | } 29 | }); 30 | expect(wrapper.text()).toBe(defaultSlot); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/components/src/mask/index.ts: -------------------------------------------------------------------------------- 1 | import _Mask from './mask.vue'; 2 | import { withInstall } from '../utils'; 3 | 4 | export const Mask = withInstall(_Mask); 5 | export default Mask; 6 | -------------------------------------------------------------------------------- /packages/components/src/mask/mask.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 27 | -------------------------------------------------------------------------------- /packages/components/src/mediabox/__test__/mediabox.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import MediaBox from '../mediaBox.vue'; 4 | 5 | describe('weui-media-box', () => { 6 | it('render props type', () => { 7 | const title = '标题'; 8 | const desc = '描述'; 9 | const wrapper = mount(MediaBox, { 10 | props: { 11 | title, 12 | desc 13 | } 14 | }); 15 | expect(wrapper.find('.weui-media-box__title').text()).toBe(title); 16 | expect(wrapper.find('.weui-media-box__desc').text()).toBe(desc); 17 | }); 18 | 19 | it('render default slot', () => { 20 | const defaultSlot = '自定义内容'; 21 | const wrapper = mount(MediaBox, { 22 | slots: { 23 | default: defaultSlot 24 | } 25 | }); 26 | expect(wrapper.find('.weui-media-box__bd').text()).toBe(defaultSlot); 27 | }); 28 | 29 | it('render hd slot', () => { 30 | const hdSlot = '自定义头部内容'; 31 | const wrapper = mount(MediaBox, { 32 | slots: { 33 | hd: hdSlot 34 | } 35 | }); 36 | expect(wrapper.find('.weui-media-box__hd').exists()).toBe(true); 37 | expect(wrapper.find('.weui-media-box__hd').text()).toBe(hdSlot); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /packages/components/src/mediabox/index.ts: -------------------------------------------------------------------------------- 1 | import _MediaBox from './mediaBox.vue'; 2 | import { withInstall } from '../utils'; 3 | 4 | export const MediaBox = withInstall(_MediaBox); 5 | export default MediaBox; 6 | -------------------------------------------------------------------------------- /packages/components/src/mediabox/mediaBox.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 36 | -------------------------------------------------------------------------------- /packages/components/src/msg/__test__/msg.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Msg from '../msg.vue'; 4 | 5 | describe('weui-msg', () => { 6 | it('render msg type', () => { 7 | const types = ['success', 'info', 'waiting', 'warn']; 8 | for (const type of types) { 9 | const wrapper = mount(Msg, { 10 | props: { 11 | type: type as any 12 | } 13 | }); 14 | expect(wrapper.find('.weui-icon_msg').classes()).toContain( 15 | `weui-icon-${type}` 16 | ); 17 | } 18 | }); 19 | 20 | it('render other prrops', () => { 21 | const title = 'title'; 22 | const desc = 'desc'; 23 | const wrapper = mount(Msg, { 24 | props: { 25 | title: title, 26 | desc: desc 27 | } 28 | }); 29 | expect(wrapper.find('.weui-msg__title').text()).toBe(title); 30 | expect(wrapper.find('.weui-msg__desc').text()).toBe(desc); 31 | }); 32 | 33 | it('render msg slots', () => { 34 | const iconSlot = 'icon slot'; 35 | const titleSlot = 'title slot'; 36 | const descSlot = 'title slot'; 37 | const customSlot = 'custom slot'; 38 | const oprSlot = 'opr slot'; 39 | const tipsSlot = 'tips slot'; 40 | const extraSlot = 'extra slot'; 41 | const wrapper = mount(Msg, { 42 | slots: { 43 | 'icon-area': iconSlot, 44 | title: titleSlot, 45 | desc: descSlot, 46 | 'custom-area': customSlot, 47 | 'opr-area': oprSlot, 48 | 'tips-area': tipsSlot, 49 | 'extra-area': extraSlot 50 | } 51 | }); 52 | expect(wrapper.find('.weui-msg__icon-area').text()).toBe(iconSlot); 53 | expect(wrapper.find('.weui-msg__title').text()).toBe(titleSlot); 54 | expect(wrapper.find('.weui-msg__desc').text()).toBe(descSlot); 55 | expect(wrapper.find('.weui-msg__custom-area').text()).toBe(customSlot); 56 | expect(wrapper.find('.weui-msg__opr-area').text()).toBe(oprSlot); 57 | expect(wrapper.find('.weui-msg__tips-area').text()).toBe(tipsSlot); 58 | expect(wrapper.find('.weui-msg__extra-area').text()).toBe(extraSlot); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /packages/components/src/msg/index.ts: -------------------------------------------------------------------------------- 1 | import _Msg from './msg.vue'; 2 | import { withInstall } from '../utils'; 3 | 4 | export const Msg = withInstall(_Msg); 5 | export default Msg; 6 | -------------------------------------------------------------------------------- /packages/components/src/msg/msg.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 50 | -------------------------------------------------------------------------------- /packages/components/src/navbar/__test__/navbar.test.tsx: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import { nextTick } from 'vue'; 4 | import NavBar from '../navbar.vue'; 5 | import NavBarItem from '../navbarItem.vue'; 6 | 7 | describe('weui-navbar', () => { 8 | it('render navbar', () => { 9 | const modelTab = 'tab1'; 10 | const tabs = [ 11 | { label: 'tab1', value: 'tab1' }, 12 | { label: 'tab2', value: 'tab2' } 13 | ]; 14 | const changeHandler = vi.fn(); 15 | const wrapper = mount( 16 | 17 | {tabs.map((item) => ( 18 | 19 | ))} 20 | 21 | ); 22 | nextTick(async () => { 23 | await wrapper.findAll('.weui-navbar__item')[0].trigger('click'); 24 | expect(changeHandler).toBeCalled(); 25 | }); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /packages/components/src/navbar/constant.ts: -------------------------------------------------------------------------------- 1 | export const ACTIVE_TAB = Symbol('AVTIVE_TAB'); 2 | 3 | export const NAVBAR_NAME = 'weui-navbar'; 4 | export const NAVBAR_ITEM_NAME = 'weui-navbar-item'; 5 | -------------------------------------------------------------------------------- /packages/components/src/navbar/index.ts: -------------------------------------------------------------------------------- 1 | import _Navbar from './navbar.vue'; 2 | import _NavbarItem from './navbarItem.vue'; 3 | import { withInstall } from '../utils'; 4 | 5 | type NavBarType = typeof _Navbar & { 6 | NavbarItem: typeof _NavbarItem; 7 | }; 8 | export const Navbar = withInstall(_Navbar as NavBarType); 9 | export const NavbarItem = withInstall(_NavbarItem); 10 | Navbar.NavbarItem = NavbarItem; 11 | export default Navbar; 12 | -------------------------------------------------------------------------------- /packages/components/src/navbar/navbar.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 70 | -------------------------------------------------------------------------------- /packages/components/src/navbar/navbarItem.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 32 | -------------------------------------------------------------------------------- /packages/components/src/navbar/types.ts: -------------------------------------------------------------------------------- 1 | export interface NavBarItem { 2 | label: string; 3 | value: T; 4 | } 5 | -------------------------------------------------------------------------------- /packages/components/src/panel/__test__/panel.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Panel from '../panel.vue'; 4 | 5 | describe('weui-panel', () => { 6 | it('render default slot', () => { 7 | const defaultSlot = 'default slot'; 8 | const wrapper = mount(Panel, { 9 | slots: { 10 | default: defaultSlot 11 | } 12 | }); 13 | expect(wrapper.find('.weui-panel__bd').text()).toBe(defaultSlot); 14 | }); 15 | 16 | it('render hd slot', () => { 17 | const hdSlot = 'hd slot'; 18 | const wrapper = mount(Panel, { 19 | slots: { 20 | hd: hdSlot 21 | } 22 | }); 23 | expect(wrapper.find('.weui-panel__hd').exists()).toBe(true); 24 | expect(wrapper.find('.weui-panel__hd').text()).toBe(hdSlot); 25 | }); 26 | 27 | it('render ft slot', () => { 28 | const ftSlot = 'ft slot'; 29 | const wrapper = mount(Panel, { 30 | slots: { 31 | ft: ftSlot 32 | } 33 | }); 34 | expect(wrapper.find('.weui-panel__ft').exists()).toBe(true); 35 | expect(wrapper.find('.weui-panel__ft').text()).toBe(ftSlot); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/components/src/panel/index.ts: -------------------------------------------------------------------------------- 1 | import _Panel from './panel.vue'; 2 | import { withInstall } from '../utils'; 3 | 4 | export const Panel = withInstall(_Panel); 5 | export default Panel; 6 | -------------------------------------------------------------------------------- /packages/components/src/panel/panel.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 20 | -------------------------------------------------------------------------------- /packages/components/src/picker/index.ts: -------------------------------------------------------------------------------- 1 | import _Picker from './picker.vue'; 2 | import { withInstall } from '../utils'; 3 | 4 | export const Picker = withInstall(_Picker); 5 | export default Picker; 6 | -------------------------------------------------------------------------------- /packages/components/src/picker/picker.less: -------------------------------------------------------------------------------- 1 | .weui-picker-wrapper { 2 | align-items: center; 3 | width: 100%; 4 | 5 | .weui-picker-value { 6 | flex: 1; 7 | padding-left: 0; 8 | } 9 | 10 | &.weui-picker_diabled .weui-picker-value { 11 | color: var(--weui-FG-1); 12 | } 13 | 14 | &.weui-picker_placeholder .weui-picker-value { 15 | color: var(--weui-FG-2); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/components/src/picker/types.ts: -------------------------------------------------------------------------------- 1 | export interface PickerItem { 2 | label: string; 3 | value: T; 4 | disabled?: boolean; 5 | children?: PickerItem[]; 6 | } 7 | -------------------------------------------------------------------------------- /packages/components/src/preview/__test__/preview.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Preview from '../preview.vue'; 4 | 5 | describe('weui-preview', () => { 6 | it('render default slot', () => { 7 | const defaultSlot = 'default slot'; 8 | const wrapper = mount(Preview, { 9 | slots: { 10 | default: defaultSlot 11 | } 12 | }); 13 | expect(wrapper.find('.weui-form-preview__bd').text()).toBe(defaultSlot); 14 | }); 15 | 16 | it('render hd slot', () => { 17 | const hdSlot = 'hd slot'; 18 | const wrapper = mount(Preview, { 19 | slots: { 20 | hd: hdSlot 21 | } 22 | }); 23 | expect(wrapper.find('.weui-form-preview__hd').exists()).toBe(true); 24 | expect(wrapper.find('.weui-form-preview__hd').text()).toBe(hdSlot); 25 | }); 26 | 27 | it('render ft slot', () => { 28 | const ftSlot = 'ft slot'; 29 | const wrapper = mount(Preview, { 30 | slots: { 31 | ft: ftSlot 32 | } 33 | }); 34 | expect(wrapper.find('.weui-form-preview__ft').exists()).toBe(true); 35 | expect(wrapper.find('.weui-form-preview__ft').text()).toBe(ftSlot); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/components/src/preview/__test__/previewBtn.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import PreviewBtn from '../previewBtn.vue'; 4 | 5 | describe('weui-preview-btn', () => { 6 | it('render preview btn type', () => { 7 | const types = ['default', 'primary']; 8 | for (const type of types) { 9 | const wrapper = mount(PreviewBtn, { 10 | props: { 11 | type: type as any 12 | } 13 | }); 14 | expect(wrapper.classes()).toContain(`weui-form-preview__btn_${type}`); 15 | } 16 | }); 17 | 18 | it('render default slot', () => { 19 | const defaultSlot = 'default slot'; 20 | const wrapper = mount(PreviewBtn, { 21 | slots: { 22 | default: defaultSlot 23 | } 24 | }); 25 | expect(wrapper.text()).toBe(defaultSlot); 26 | }); 27 | 28 | it('click event emit', async () => { 29 | const wrapper = mount(PreviewBtn, { 30 | slots: { 31 | default: 'button' 32 | } 33 | }); 34 | await wrapper.trigger('click'); 35 | expect(wrapper.emitted()).toHaveProperty('click'); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/components/src/preview/__test__/previewItem.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import PreviewItem from '../previewItem.vue'; 4 | 5 | describe('weui-preview-item', () => { 6 | it('render preview item props', () => { 7 | const label = 'label'; 8 | const value = 'value'; 9 | const wrapper = mount(PreviewItem, { 10 | props: { 11 | label, 12 | value 13 | } 14 | }); 15 | expect(wrapper.find('.weui-form-preview__label').text()).toBe(label); 16 | expect(wrapper.find('.weui-form-preview__value').text()).toBe(value); 17 | }); 18 | 19 | it('render label slot', () => { 20 | const labelSlot = 'label slot'; 21 | const wrapper = mount(PreviewItem, { 22 | slots: { 23 | label: labelSlot 24 | } 25 | }); 26 | expect(wrapper.find('.weui-form-preview__label').text()).toBe(labelSlot); 27 | }); 28 | 29 | it('render value slot', () => { 30 | const valueSlot = 'value slot'; 31 | const wrapper = mount(PreviewItem, { 32 | slots: { 33 | value: valueSlot 34 | } 35 | }); 36 | expect(wrapper.find('.weui-form-preview__value').text()).toBe(valueSlot); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/components/src/preview/index.ts: -------------------------------------------------------------------------------- 1 | import _Preview from './preview.vue'; 2 | import _PreviewItem from './previewItem.vue'; 3 | import _PreviewBtn from './previewBtn.vue'; 4 | import { withInstall } from '../utils'; 5 | 6 | type PreviewType = typeof _Preview & { 7 | PreviewItem: typeof _PreviewItem; 8 | PreviewBtn: typeof _PreviewBtn; 9 | }; 10 | 11 | export const Preview = withInstall(_Preview as PreviewType); 12 | export const PreviewItem = withInstall(_PreviewItem); 13 | export const PreviewBtn = withInstall(_PreviewBtn); 14 | Preview.PreviewItem = PreviewItem; 15 | Preview.PreviewBtn = PreviewBtn; 16 | export default Preview; 17 | -------------------------------------------------------------------------------- /packages/components/src/preview/preview.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 24 | -------------------------------------------------------------------------------- /packages/components/src/preview/previewBtn.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 40 | -------------------------------------------------------------------------------- /packages/components/src/preview/previewItem.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 30 | -------------------------------------------------------------------------------- /packages/components/src/progress/__test__/progress.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Progress from '../progress.vue'; 4 | 5 | describe('weui-progress', () => { 6 | it('render progress count', () => { 7 | const wrapper = mount(Progress, { 8 | props: { 9 | count: 50 10 | } 11 | }); 12 | expect( 13 | (wrapper.find('.weui-progress__inner-bar').element as HTMLElement).style 14 | .width 15 | ).toBe('50%'); 16 | }); 17 | 18 | it('render progress close btn', async () => { 19 | const wrapper = mount(Progress, { 20 | props: { 21 | count: 50, 22 | showClose: true 23 | } 24 | }); 25 | expect(wrapper.find('.weui-progress__opr').exists()).toBe(true); 26 | await wrapper.find('.weui-progress__opr').trigger('click'); 27 | expect(wrapper.emitted()).toHaveProperty('click'); 28 | }); 29 | 30 | it('should render solt', () => { 31 | const wrapper = mount(Progress, { 32 | slots: { 33 | extra: '50%' 34 | } 35 | }); 36 | expect(wrapper.find('.weui-progress-extra-wrapper').text()).toContain( 37 | '50%' 38 | ); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/components/src/progress/index.ts: -------------------------------------------------------------------------------- 1 | import _Progress from './progress.vue'; 2 | import { withInstall } from '../utils'; 3 | 4 | export const Progress = withInstall(_Progress); 5 | export default Progress; 6 | -------------------------------------------------------------------------------- /packages/components/src/progress/progress.less: -------------------------------------------------------------------------------- 1 | .weui-progress-extra-wrapper { 2 | margin-left: 15px; 3 | } 4 | -------------------------------------------------------------------------------- /packages/components/src/progress/progress.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 45 | -------------------------------------------------------------------------------- /packages/components/src/searchbar/__test__/searchbar.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import SearchBar from '../searchbar.vue'; 4 | 5 | describe('weui-searchbar', () => { 6 | it('render searchbar count', () => { 7 | const wrapper = mount(SearchBar, { 8 | props: { 9 | modelValue: 'search' 10 | } 11 | }); 12 | expect( 13 | (wrapper.find('.weui-search-bar__input').element as HTMLInputElement) 14 | .value 15 | ).toBe('search'); 16 | }); 17 | 18 | it('input searchbar', () => { 19 | const wrapper = mount(SearchBar, { 20 | props: { 21 | modelValue: 'search' 22 | } 23 | }); 24 | wrapper.find('.weui-search-bar__input').setValue('new search'); 25 | expect( 26 | (wrapper.find('.weui-search-bar__input').element as HTMLInputElement) 27 | .value 28 | ).toBe('new search'); 29 | }); 30 | 31 | it('render searchbar clear', async () => { 32 | const wrapper = mount(SearchBar, { 33 | props: { 34 | modelValue: 'search' 35 | } 36 | }); 37 | await wrapper.find('.weui-icon-clear').trigger('click'); 38 | expect(wrapper.emitted()).toHaveProperty('update:modelValue'); 39 | expect((wrapper.emitted()['update:modelValue'][0] as string[])[0]).toBe(''); 40 | }); 41 | 42 | it('render searchbar search event', async () => { 43 | const wrapper = mount(SearchBar, { 44 | props: { 45 | modelValue: 'search' 46 | } 47 | }); 48 | await wrapper.find('.weui-search-bar__label span').trigger('click'); 49 | expect(wrapper.emitted()).toHaveProperty('search'); 50 | expect((wrapper.emitted()['search'][0] as string[])[0]).toBe('search'); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /packages/components/src/searchbar/index.ts: -------------------------------------------------------------------------------- 1 | import _Searchbar from './searchbar.vue'; 2 | import { withInstall } from '../utils'; 3 | 4 | export const Searchbar = withInstall(_Searchbar); 5 | export default Searchbar; 6 | -------------------------------------------------------------------------------- /packages/components/src/slider/__test__/slider.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Slider from '../slider.vue'; 4 | 5 | describe('weui-slider', () => { 6 | it('render slider percent', () => { 7 | const wrapper = mount(Slider, { 8 | props: { 9 | modelValue: 50 10 | } 11 | }); 12 | expect( 13 | (wrapper.find('#sliderTrack').element as HTMLElement).style.width 14 | ).toBe('50%'); 15 | expect( 16 | (wrapper.find('#sliderHandler').element as HTMLElement).style.left 17 | ).toBe('50%'); 18 | }); 19 | 20 | it('render slider number', () => { 21 | const wrapper = mount(Slider, { 22 | props: { 23 | modelValue: 50, 24 | showNum: true 25 | } 26 | }); 27 | expect(wrapper.find('#sliderValue').text()).toBe('50'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/components/src/slider/index.ts: -------------------------------------------------------------------------------- 1 | import _Slider from './slider.vue'; 2 | import { withInstall } from '../utils'; 3 | 4 | export const Slider = withInstall(_Slider); 5 | export default Slider; 6 | -------------------------------------------------------------------------------- /packages/components/src/slider/slider.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 69 | -------------------------------------------------------------------------------- /packages/components/src/steps/__test__/stepItem.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import StepItem from '../stepItem.vue'; 4 | 5 | describe('weui-steps-item', () => { 6 | it('render props', () => { 7 | const title = 'title'; 8 | const desc = 'desc'; 9 | const wrapper = mount(StepItem, { 10 | props: { 11 | title, 12 | desc 13 | } 14 | }); 15 | expect(wrapper.find('.weui-steps__item__title').text()).toBe(title); 16 | expect(wrapper.find('.weui-steps__item__desc').text()).toBe(desc); 17 | }); 18 | 19 | it('render props icon', () => { 20 | const wrapper = mount(StepItem, { 21 | props: { 22 | icon: 'waiting' 23 | } 24 | }); 25 | expect(wrapper.classes()).toContain('weui-steps__item_icon'); 26 | expect(wrapper.find('.weui-steps__icon').exists()).toBe(true); 27 | 28 | const icons = ['waiting', 'success']; 29 | for (const icon of icons) { 30 | const wrapper = mount(StepItem, { 31 | props: { 32 | icon: icon as any 33 | } 34 | }); 35 | expect(wrapper.find('.weui-steps__icon').classes()).toContain( 36 | `weui-icon-${icon}` 37 | ); 38 | } 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /packages/components/src/steps/constant.ts: -------------------------------------------------------------------------------- 1 | export const STEP_DIRECTION = Symbol('STEP_DIRECTION'); 2 | export const ACTIVE_STEP = Symbol('AVTIVE_STEP'); 3 | export const SET_ACTIVE_STEP = Symbol('SET_ACTIVE_STEP'); 4 | -------------------------------------------------------------------------------- /packages/components/src/steps/index.ts: -------------------------------------------------------------------------------- 1 | import _Steps from './steps.vue'; 2 | import _StepItem from './stepItem.vue'; 3 | import { withInstall } from '../utils'; 4 | 5 | type StepsType = typeof _Steps & { 6 | StepItem: typeof _StepItem; 7 | }; 8 | export const Steps = withInstall(_Steps as StepsType); 9 | export const StepItem = withInstall(_StepItem); 10 | Steps.StepItem = StepItem; 11 | export default Steps; 12 | -------------------------------------------------------------------------------- /packages/components/src/steps/stepItem.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 60 | -------------------------------------------------------------------------------- /packages/components/src/steps/steps.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 43 | -------------------------------------------------------------------------------- /packages/components/src/switch/index.ts: -------------------------------------------------------------------------------- 1 | import _Switch from './switch.vue'; 2 | import { withInstall } from '../utils'; 3 | 4 | export const Switch = withInstall(_Switch); 5 | export default Switch; 6 | -------------------------------------------------------------------------------- /packages/components/src/switch/switch.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 45 | -------------------------------------------------------------------------------- /packages/components/src/switch/test/switch.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Switch from '../switch.vue'; 4 | 5 | describe('weui-switch', () => { 6 | it('render switch', async () => { 7 | const wrapper = mount(Switch, { 8 | props: { 9 | modelValue: true 10 | } 11 | }); 12 | expect( 13 | (wrapper.find('.weui-switch-cp__input').element as HTMLInputElement) 14 | .checked 15 | ).toBe(true); 16 | await wrapper.trigger('click'); 17 | expect(wrapper.emitted()).toHaveProperty('update:modelValue'); 18 | expect(wrapper.emitted()).toHaveProperty('change'); 19 | expect((wrapper.emitted().change[0] as any)[0]).toBe(false); 20 | }); 21 | 22 | it('render switch disabled', async () => { 23 | const wrapper = mount(Switch, { 24 | props: { 25 | modelValue: false, 26 | disabled: true 27 | } 28 | }); 29 | expect(wrapper.classes()).toContain('weui-cell_disabled'); 30 | await wrapper.trigger('click'); 31 | expect(wrapper.emitted().change).toBeUndefined(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/components/src/tabbar/__test__/tabbar.test.tsx: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import { nextTick } from 'vue'; 4 | import TabBar from '../tabbar.vue'; 5 | import TabBarItem from '../tabbarItem.vue'; 6 | 7 | describe('weui-tabbar', () => { 8 | it('render tabbar', () => { 9 | const modelTab = 'tab1'; 10 | const tabs = [ 11 | { label: 'tab1', value: 'tab1' }, 12 | { label: 'tab2', value: 'tab2' } 13 | ]; 14 | const changeHandler = vi.fn(); 15 | const wrapper = mount( 16 | 17 | {tabs.map((item) => ( 18 | 19 | ))} 20 | 21 | ); 22 | nextTick(async () => { 23 | await wrapper.findAll('.weui-tabbar__item')[0].trigger('click'); 24 | expect(changeHandler).toBeCalled(); 25 | }); 26 | }); 27 | 28 | it('render tabbar tab slot', () => { 29 | const modelTab = 'tab1'; 30 | const tabs = [ 31 | { label: 'tab1', value: 'tab1' }, 32 | { label: 'tab2', value: 'tab2' } 33 | ]; 34 | const changeHandler = vi.fn(); 35 | mount( 36 | 37 | {{ 38 | tab: () =>
tab
39 | }} 40 | {tabs.map((item) => ( 41 | 42 | ))} 43 |
44 | ); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /packages/components/src/tabbar/constant.ts: -------------------------------------------------------------------------------- 1 | export const ACTIVE_TAB = Symbol('AVTIVE_TAB'); 2 | export const SELECT_TAB = Symbol('SELECT_TAB'); 3 | 4 | export const TABBAR_ITEM_NAME = 'weui-tabbar-item'; 5 | export const TABBAR_NAME = 'weui-tabbar'; 6 | -------------------------------------------------------------------------------- /packages/components/src/tabbar/index.ts: -------------------------------------------------------------------------------- 1 | import _Tabbar from './tabbar.vue'; 2 | import _TabbarItem from './tabbarItem.vue'; 3 | import { withInstall } from '../utils'; 4 | 5 | type TabBarType = typeof _Tabbar & { 6 | TabbarItem: typeof _TabbarItem; 7 | }; 8 | export const Tabbar = withInstall(_Tabbar as TabBarType); 9 | export const TabbarItem = withInstall(_TabbarItem); 10 | Tabbar.TabbarItem = TabbarItem; 11 | export default Tabbar; 12 | -------------------------------------------------------------------------------- /packages/components/src/tabbar/tabbar.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 78 | -------------------------------------------------------------------------------- /packages/components/src/tabbar/tabbarItem.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 31 | -------------------------------------------------------------------------------- /packages/components/src/tabbar/types.ts: -------------------------------------------------------------------------------- 1 | export interface TabBarItem { 2 | label: string; 3 | value: T; 4 | icon: string; 5 | [key: string]: any; 6 | } 7 | -------------------------------------------------------------------------------- /packages/components/src/textarea/__test__/textarea.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { mount } from '@vue/test-utils'; 3 | import Textarea from '../textarea.vue'; 4 | 5 | describe('weui-textarea', () => { 6 | it('render textarea', () => { 7 | const wrapper = mount(Textarea, { 8 | props: { 9 | modelValue: 'input', 10 | placeholder: 'placeholder', 11 | disabled: true 12 | } 13 | }); 14 | expect(wrapper.find('.weui-textarea').attributes().placeholder).toBe( 15 | 'placeholder' 16 | ); 17 | expect(wrapper.find('.weui-textarea').attributes()).toHaveProperty( 18 | 'disabled' 19 | ); 20 | expect(wrapper.classes()).toContain('weui-cell_disabled'); 21 | }); 22 | 23 | it('change textarea value', () => { 24 | const wrapper = mount(Textarea, { 25 | props: { 26 | modelValue: 'input', 27 | placeholder: 'placeholder', 28 | type: 'text' 29 | } 30 | }); 31 | wrapper.find('.weui-textarea').setValue('new input'); 32 | expect(wrapper.emitted()).toHaveProperty('update:modelValue'); 33 | expect(wrapper.emitted()).toHaveProperty('change'); 34 | }); 35 | 36 | it('show textarea number', async () => { 37 | const wrapper = mount(Textarea, { 38 | props: { 39 | modelValue: 'input', 40 | placeholder: 'placeholder', 41 | maxlength: 10, 42 | showNum: true 43 | } 44 | }); 45 | wrapper.find('weui-textarea-counter').exists(); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /packages/components/src/textarea/index.ts: -------------------------------------------------------------------------------- 1 | import _Textarea from './textarea.vue'; 2 | import { withInstall } from '../utils'; 3 | 4 | export const Textarea = withInstall(_Textarea); 5 | export default Textarea; 6 | -------------------------------------------------------------------------------- /packages/components/src/textarea/textarea.less: -------------------------------------------------------------------------------- 1 | .weui-textarea-wrapper { 2 | width: 100%; 3 | } 4 | -------------------------------------------------------------------------------- /packages/components/src/textarea/textarea.vue: -------------------------------------------------------------------------------- 1 |