├── .stylelintignore
├── docs
├── modal.md
├── notice.md
├── guide
│ ├── introduce.md
│ └── install.md
├── icon.md
├── layout.md
├── tag.md
├── message.md
├── checkbox.md
├── button.md
└── input.md
├── demo
├── styles
│ ├── demo
│ │ ├── button.scss
│ │ ├── icon.scss
│ │ └── layout.scss
│ ├── mixins.scss
│ ├── reset.scss
│ ├── index.scss
│ └── variables.scss
├── favicon.ico
├── images
│ ├── fast-ui.png
│ ├── github.png
│ └── fast-logo.png
├── app.tsx
├── pages
│ └── home
│ │ ├── index.scss
│ │ └── index.tsx
├── components
│ ├── footer
│ │ ├── index.scss
│ │ └── index.tsx
│ ├── all-icon
│ │ ├── index.scss
│ │ ├── index.tsx
│ │ └── icons.json
│ ├── sidebar
│ │ ├── index.scss
│ │ └── index.tsx
│ ├── doc-layout
│ │ ├── index.tsx
│ │ └── index.scss
│ ├── demo-container
│ │ ├── index.scss
│ │ ├── atom-one-light.css
│ │ └── index.tsx
│ └── header
│ │ ├── index.scss
│ │ └── index.tsx
├── utils
│ ├── compile.ts
│ ├── clipboard.ts
│ └── group.ts
├── constant
│ └── index.ts
├── shims.d.ts
├── index.ts
├── index.html
└── router
│ └── index.tsx
├── .commitlintrc.js
├── README.md
├── postcss.config.js
├── packages
├── checkbox-group
│ ├── index.scss
│ └── index.tsx
├── styles
│ ├── index.scss
│ ├── mixins.scss
│ ├── reset.scss
│ └── variables.scss
├── utils
│ ├── event-bus.ts
│ ├── emitter.ts
│ └── props-type.ts
├── row
│ ├── index.scss
│ └── index.tsx
├── icon
│ ├── index.scss
│ └── index.tsx
├── row-col
│ ├── index.scss
│ └── index.tsx
├── button-group
│ ├── index.scss
│ └── index.tsx
├── checkbox
│ ├── icons.tsx
│ ├── index.scss
│ └── index.tsx
├── message
│ ├── index.scss
│ └── index.tsx
├── tag
│ ├── index.tsx
│ └── index.scss
├── button
│ ├── index.tsx
│ └── index.scss
└── input
│ ├── index.scss
│ └── index.tsx
├── test
├── .eslintrc.js
└── utils
│ └── group.test.js
├── .eslintignore
├── .stylelintrc.js
├── .babelrc.js
├── .gitignore
├── bin
└── generate-icon.js
├── .github
├── workflows
│ └── deploy-gh-page.yml
└── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── jest.config.js
├── tsconfig.json
├── LICENSE
├── package.json
└── .eslintrc.js
/.stylelintignore:
--------------------------------------------------------------------------------
1 | demo/styles/reset.scss
--------------------------------------------------------------------------------
/docs/modal.md:
--------------------------------------------------------------------------------
1 | [toc]
2 |
3 | ## Modal 模态框
--------------------------------------------------------------------------------
/docs/notice.md:
--------------------------------------------------------------------------------
1 | [toc]
2 |
3 | ## Notice 通知
--------------------------------------------------------------------------------
/demo/styles/demo/button.scss:
--------------------------------------------------------------------------------
1 | .demo-button {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/docs/guide/introduce.md:
--------------------------------------------------------------------------------
1 | [toc]
2 | ## FAST UI
3 |
4 | 这是一个基于Vue3.x的ui组件
5 |
--------------------------------------------------------------------------------
/demo/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mario34/fast-ui/HEAD/demo/favicon.ico
--------------------------------------------------------------------------------
/docs/guide/install.md:
--------------------------------------------------------------------------------
1 | [toc]
2 | ## 安装
3 |
4 | The document is being written...
5 |
6 |
--------------------------------------------------------------------------------
/.commitlintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@commitlint/config-conventional']
3 | }
--------------------------------------------------------------------------------
/demo/images/fast-ui.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mario34/fast-ui/HEAD/demo/images/fast-ui.png
--------------------------------------------------------------------------------
/demo/images/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mario34/fast-ui/HEAD/demo/images/github.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FAST UI
2 |
3 | 🌈 一个基于Vue3.x的UI组件库
4 |
5 | [预览地址](https://mario34.github.io/fast-ui)
6 |
--------------------------------------------------------------------------------
/demo/images/fast-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Mario34/fast-ui/HEAD/demo/images/fast-logo.png
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('autoprefixer'),
4 | ],
5 | };
6 |
--------------------------------------------------------------------------------
/packages/checkbox-group/index.scss:
--------------------------------------------------------------------------------
1 | @import "../styles/variables.scss";
2 |
3 | .fa-check-box-group {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/test/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | 'eslint:recommended',
4 | 'plugin:jest/recommended',
5 | ]
6 | }
--------------------------------------------------------------------------------
/packages/styles/index.scss:
--------------------------------------------------------------------------------
1 | @import "../styles/mixins.scss";
2 | @import "../styles/reset.scss";
3 |
4 | body {
5 | color: #555;
6 | }
7 |
--------------------------------------------------------------------------------
/packages/utils/event-bus.ts:
--------------------------------------------------------------------------------
1 | import mitt from 'mitt';
2 |
3 | /**
4 | * EventBus替代方案
5 | * https://v3.vuejs.org/guide/migration/events-api.html#overview
6 | */
7 | export default mitt();
8 |
--------------------------------------------------------------------------------
/demo/app.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent } from 'vue';
2 |
3 | const App = defineComponent({
4 | render() {
5 | return ;
6 | },
7 | });
8 |
9 | export default App;
10 |
--------------------------------------------------------------------------------
/demo/styles/demo/icon.scss:
--------------------------------------------------------------------------------
1 | @import "@/demo/styles/variables.scss";
2 |
3 | .demo-icon {
4 | .fa-icon {
5 | padding: 12px;
6 | font-size: 26px;
7 | color: $__color_text4;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | src/utils/popper.js
2 | src/utils/date.js
3 | examples/play
4 | *.sh
5 | node_modules
6 | lib
7 | coverage
8 | *.md
9 | *.scss
10 | *.woff
11 | *.ttf
12 | .eslintrc*
13 | build/
14 | bin/
15 | ./dist
--------------------------------------------------------------------------------
/.stylelintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ["stylelint-config-recommended"],
3 | plugins: ["stylelint-scss"],
4 | rules: {
5 | "at-rule-no-unknown": null,
6 | "scss/at-rule-no-unknown": true
7 | }
8 | };
--------------------------------------------------------------------------------
/demo/pages/home/index.scss:
--------------------------------------------------------------------------------
1 | @import "@/demo/styles/variables.scss";
2 |
3 | .home__main {
4 | min-height: calc(100vh - 120px);
5 | color: $__color_text4;
6 | display: flex;
7 | align-items: center;
8 | justify-content: center;
9 | font-size: 14px;
10 | }
11 |
--------------------------------------------------------------------------------
/packages/row/index.scss:
--------------------------------------------------------------------------------
1 | .fa-row {
2 | position: relative;
3 | box-sizing: border-box;
4 |
5 | &::before {
6 | display: table;
7 | content: "";
8 | }
9 |
10 | &::after {
11 | display: table;
12 | content: "";
13 | clear: both;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.babelrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "presets": [
3 | "@babel/preset-typescript",
4 | [
5 | '@babel/preset-env',
6 | {
7 | targets: {
8 | node: 'current',
9 | },
10 | },
11 | ],
12 | ],
13 | "plugins": [
14 | "@ant-design-vue/babel-plugin-jsx"
15 | ]
16 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | pnpm-debug.log*
14 | .eslintcache
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
--------------------------------------------------------------------------------
/demo/components/footer/index.scss:
--------------------------------------------------------------------------------
1 | @import "@/demo/styles/variables.scss";
2 |
3 | .footer {
4 | border-top: 1px solid $__color_line3;
5 |
6 | &__container {
7 | height: 59px;
8 | display: flex;
9 | justify-content: center;
10 | align-items: center;
11 | }
12 |
13 | &__tips {
14 | font-weight: 500;
15 | color: #333;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/demo/styles/demo/layout.scss:
--------------------------------------------------------------------------------
1 | @import "@/demo/styles/variables.scss";
2 |
3 | .demo-layout {
4 | .fa-col {
5 | line-height: 40px;
6 | background-color: #f0f0f0;
7 | text-align: center;
8 | color: $__color_text4;
9 |
10 | &:nth-child(2n) {
11 | background-color: #efefef;
12 | }
13 |
14 | &:nth-child(2n + 1) {
15 | background-color: #eff6fc;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/icon/index.scss:
--------------------------------------------------------------------------------
1 | [class^="fa-icon"],
2 | [class*=" fa-icon"] {
3 | font-family: 'tabler-icons' !important;
4 | speak: none;
5 | font-style: normal;
6 | font-weight: normal;
7 | font-variant: normal;
8 | text-transform: none;
9 | line-height: 1em;
10 | vertical-align: -0.125em;
11 |
12 | /* Better Font Rendering */
13 | -webkit-font-smoothing: antialiased;
14 | -moz-osx-font-smoothing: grayscale;
15 | }
16 |
--------------------------------------------------------------------------------
/demo/styles/mixins.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * 单行文本省略
3 | */
4 | @mixin single-overflow($width) {
5 | overflow: hidden;
6 | white-space: nowrap;
7 | text-overflow: ellipsis;
8 | max-width: $width;
9 | }
10 |
11 | /**
12 | * 多行文本省略
13 | */
14 | @mixin multiline-overflow($line) {
15 | overflow: hidden;
16 | text-overflow: ellipsis;
17 | display: -webkit-box;
18 | -webkit-line-clamp: $line;
19 | -webkit-box-orient: vertical;
20 | }
21 |
--------------------------------------------------------------------------------
/demo/components/footer/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent } from 'vue';
2 | import './index.scss';
3 |
4 | const Footer = defineComponent({
5 | setup() {
6 | return () => (
7 |
14 | );
15 | },
16 | });
17 |
18 | export default Footer;
19 |
--------------------------------------------------------------------------------
/bin/generate-icon.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 读取feather-icons/dist/icons/下的文件,生成JSON数组
3 | * 用于Icon文档展示所有icon
4 | */
5 |
6 | const fs = require('fs');
7 | const path = require('path');
8 | const files = fs.readdirSync(path.join(__dirname, '../node_modules/feather-icons/dist/icons/'))
9 | .filter(i => (/\.svg$/).test(i))
10 |
11 | const iconsJson = files.map(item => item.slice(0, -4))
12 |
13 | fs.writeFileSync(path.join(__dirname, '../demo/components/all-icon/icons.json'), JSON.stringify(iconsJson))
--------------------------------------------------------------------------------
/demo/pages/home/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent } from 'vue';
2 | import Footer from '@/demo/components/footer';
3 | import Header from '@/demo/components/header';
4 | import './index.scss';
5 |
6 | export default defineComponent({
7 | setup() {
8 | return () => (
9 | <>
10 |
11 |
12 | 🛠 The home page is under construction.
13 |
14 |
15 | >
16 | );
17 | },
18 | });
19 |
--------------------------------------------------------------------------------
/packages/row-col/index.scss:
--------------------------------------------------------------------------------
1 | .fa-col {
2 | float: left;
3 | box-sizing: border-box;
4 | }
5 |
6 | @for $i from 0 through 24 {
7 | .fa-col-#{$i} {
8 | width: (1 / 24 * $i * 100) * 1%;
9 | }
10 |
11 | .fa-col-offset-#{$i} {
12 | margin-left: (1 / 24 * $i * 100) * 1%;
13 | }
14 |
15 | .fa-col-pull-#{$i} {
16 | position: relative;
17 | right: (1 / 24 * $i * 100) * 1%;
18 | }
19 |
20 | .fa-col-push-#{$i} {
21 | position: relative;
22 | left: (1 / 24 * $i * 100) * 1%;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/docs/icon.md:
--------------------------------------------------------------------------------
1 | [toc]
2 |
3 | ## Icon 图标
4 |
5 | 使用SVG Sprite实现的图标组件(基于开源图标 [feathericons](https://github.com/feathericons/feather#svg-sprite))
6 |
7 | ### 基本使用方法
8 |
9 | :::demo 通过`icon`属性指定图标
10 |
11 | ```html
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | ```
22 |
23 | :::
24 |
25 | ### 所有图标
26 |
27 |
--------------------------------------------------------------------------------
/packages/button-group/index.scss:
--------------------------------------------------------------------------------
1 | @import "../styles/variables.scss";
2 | @import "../styles/mixins.scss";
3 |
4 | .fa-button-group {
5 | position: relative;
6 | box-sizing: border-box;
7 |
8 | .fa-button {
9 | border-radius: 0;
10 | border-right: 1px solid red;
11 |
12 | &:first-child {
13 | border-radius: $__radius_2 0 0 $__radius_2;
14 | }
15 |
16 | &:last-child {
17 | border-radius: 0 $__radius_2 $__radius_2 0;
18 | }
19 |
20 | & + .fa-button {
21 | margin-left: 1px;
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.github/workflows/deploy-gh-page.yml:
--------------------------------------------------------------------------------
1 | name: Deploy Document
2 | on:
3 | push:
4 | branches:
5 | - master
6 | jobs:
7 | build-and-deploy:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout codes
11 | uses: actions/checkout@v2
12 |
13 | - name: Build and Deploy
14 | uses: JamesIves/github-pages-deploy-action@master
15 | env:
16 | ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
17 | BRANCH: gh-page
18 | FOLDER: dist
19 | BUILD_SCRIPT: npm install && npm run build
20 |
--------------------------------------------------------------------------------
/packages/button-group/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent } from 'vue';
2 | import './index.scss';
3 |
4 | const ButtonGroup = defineComponent({
5 | name: 'fa-button-group',
6 | props: {
7 | space: {
8 | type: Number,
9 | default: 0,
10 | },
11 | },
12 | setup(props, ctx) {
13 | const { default: default_ } = ctx.slots;
14 | return () => (
15 |
16 | {default_ && default_()}
17 |
18 | );
19 | },
20 | });
21 |
22 | export default {
23 | name: ButtonGroup.name,
24 | component: ButtonGroup,
25 | };
26 |
--------------------------------------------------------------------------------
/demo/utils/compile.ts:
--------------------------------------------------------------------------------
1 | export function stripScript(content: string) {
2 | const result = content.match(/<(script)>([\s\S]+)<\/\1>/);
3 | return result && result[2] ? result[2].trim() : '';
4 | }
5 |
6 | export function stripStyle(content: string) {
7 | const result = content.match(/<(style)\s*>([\s\S]+)<\/\1>/);
8 | return result && result[2] ? result[2].trim() : '';
9 | }
10 |
11 | export function stripTemplate(content: string) {
12 | content = content.trim();
13 | if (!content) {
14 | return content;
15 | }
16 | return content.replace(/<(script|style)[\s\S]+<\/\1>/g, '').trim();
17 | }
18 |
--------------------------------------------------------------------------------
/packages/checkbox/icons.tsx:
--------------------------------------------------------------------------------
1 | export default {
2 | checked: () => (
3 |
4 | ),
5 | indeterminate: () => (
6 |
7 | ),
8 | };
9 |
--------------------------------------------------------------------------------
/test/utils/group.test.js:
--------------------------------------------------------------------------------
1 | import { generateGuideGroups } from '../../demo/utils/group';
2 |
3 | const routes = [
4 | {
5 | name: 'introduce',
6 | path: '/introduce',
7 | },
8 | {
9 | name: 'install',
10 | path: '/install',
11 | },
12 | ];
13 |
14 | test('generateGuideGroups', () => {
15 | expect(generateGuideGroups(routes)).toEqual([
16 | {
17 | title: '',
18 | items: [
19 | {
20 | name: 'introduce',
21 | title: 'FAST UI',
22 | },
23 | {
24 | name: 'install',
25 | title: '快速上手',
26 | },
27 | ],
28 | },
29 | ]);
30 | });
31 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | verbose: true,
3 | preset: 'ts-jest',
4 | transform: {
5 | '^.+\\.(ts|tsx)?$': 'ts-jest',
6 | '^.+\\.(js|jsx)$': 'babel-jest',
7 | '.*\\.(vue)$': 'vue-jest',
8 | },
9 | testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$',
10 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
11 | moduleNameMapper: {
12 | '\\.(css|less)$': '/__mocks__/styleMock.js',
13 | '\\.(gif|ttf|eot|svg)$': '/__mocks__/fileMock.js',
14 | '@/(.*)$': '/$1',
15 | },
16 | collectCoverage: true,
17 | collectCoverageFrom: ['packages/*.{js,ts,tsx}', '!**/node_modules/**'],
18 | };
19 |
--------------------------------------------------------------------------------
/packages/styles/mixins.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * 单行文本省略
3 | */
4 | @mixin single-overflow($width) {
5 | overflow: hidden;
6 | white-space: nowrap;
7 | text-overflow: ellipsis;
8 | max-width: $width;
9 | }
10 |
11 | /**
12 | * 多行文本省略
13 | */
14 | @mixin multiline-overflow($line) {
15 | overflow: hidden;
16 | text-overflow: ellipsis;
17 | display: -webkit-box;
18 | -webkit-line-clamp: $line;
19 | -webkit-box-orient: vertical;
20 | }
21 |
22 | /**
23 | * 多行文本省略
24 | */
25 | @mixin multiline-overflow($line) {
26 | overflow: hidden;
27 | text-overflow: ellipsis;
28 | display: -webkit-box;
29 | -webkit-line-clamp: $line;
30 | -webkit-box-orient: vertical;
31 | }
32 |
--------------------------------------------------------------------------------
/demo/utils/clipboard.ts:
--------------------------------------------------------------------------------
1 | function copy(str: string) {
2 | return new Promise((resolve, reject) => {
3 | const input = document.createElement('input');
4 | input.setAttribute('readonly', 'readonly');
5 | input.setAttribute('value', str);
6 | input.setAttribute('style', 'dispaly: none; position: fixed');
7 | document.body.appendChild(input);
8 | input.setSelectionRange(0, 99999);
9 | input.select();
10 | if (document.execCommand('copy')) {
11 | resolve();
12 | } else {
13 | reject(new Error('Does not support "document.execCommand"'));
14 | }
15 | document.body.removeChild(input);
16 | });
17 | }
18 |
19 | export default copy;
20 |
--------------------------------------------------------------------------------
/packages/row/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent } from 'vue';
2 | import './index.scss';
3 |
4 | const Row = defineComponent({
5 | name: 'fa-row',
6 | props: {
7 | space: {
8 | type: Number,
9 | default: 0,
10 | },
11 | },
12 | setup(props, ctx) {
13 | const { default: default_ } = ctx.slots;
14 | const { space } = props;
15 | return () => (
16 |
22 | {default_ && default_()}
23 |
24 | );
25 | },
26 | });
27 |
28 | export default {
29 | name: Row.name,
30 | component: Row,
31 | };
32 |
--------------------------------------------------------------------------------
/packages/utils/emitter.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /**
3 | * 参考 https://github.com/ElemeFE/element/blob/dev/src/mixins/emitter.js
4 | * 处理组件组合使用时事件共享机制
5 | */
6 | export default {
7 | methods: {
8 | dispatch(componentName, eventName, params) {
9 | let parent = this.$parent || this.$root;
10 | let name = parent.$options.name;
11 |
12 | while (parent && (!name || name !== componentName)) {
13 | parent = parent.$parent;
14 |
15 | if (parent) {
16 | name = parent.$options.componentName;
17 | }
18 | }
19 | if (parent) {
20 | parent.$emit.apply(parent, [eventName].concat(params));
21 | }
22 | }
23 | },
24 | };
25 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2019",
4 | "module": "commonjs",
5 | "strict": true,
6 | "jsx": "preserve",
7 | "importHelpers": true,
8 | "moduleResolution": "node",
9 | "esModuleInterop": true,
10 | "resolveJsonModule": true,
11 | "allowSyntheticDefaultImports": true,
12 | "sourceMap": true,
13 | "baseUrl": "./",
14 | "types": [
15 | "webpack-env"
16 | ],
17 | "paths": {
18 | "@/*": [
19 | "./*"
20 | ]
21 | },
22 | "lib": [
23 | "ES2019",
24 | "dom",
25 | "dom.iterable",
26 | "scripthost"
27 | ]
28 | },
29 | "include": [
30 | "demo/**/*",
31 | "packages/**/*",
32 | ],
33 | "exclude": [
34 | "node_modules"
35 | ]
36 | }
--------------------------------------------------------------------------------
/packages/row-col/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent, toRefs } from 'vue';
2 | import './index.scss';
3 |
4 | const Col = defineComponent({
5 | name: 'fa-col',
6 | props: {
7 | col: {
8 | type: Number,
9 | default: 24,
10 | },
11 | offset: {
12 | type: Number,
13 | default: 0,
14 | },
15 | },
16 | setup(props, ctx) {
17 | const { default: default_ } = ctx.slots;
18 | const { col, offset } = toRefs(props);
19 | return () => (
20 |
29 | {default_ && default_()}
30 |
31 | );
32 | },
33 | });
34 |
35 | export default {
36 | name: Col.name,
37 | component: Col,
38 | };
39 |
--------------------------------------------------------------------------------
/packages/icon/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent, h } from 'vue';
2 | import sprite from 'feather-icons/dist/feather-sprite.svg';
3 | import './index.scss';
4 |
5 | const Icon = defineComponent({
6 | name: 'fa-icon',
7 | props: {
8 | icon: {
9 | type: String,
10 | required: true,
11 | },
12 | },
13 | render() {
14 | return h(
15 | 'svg',
16 | {
17 | width: '1em',
18 | height: '1em',
19 | fill: 'none',
20 | stroke: 'currentColor',
21 | 'stroke-width': '2',
22 | 'stroke-linecap': 'round',
23 | 'stroke-linejoin': 'round',
24 | class: 'fa-icon',
25 | },
26 | [
27 | h(
28 | 'use',
29 | {
30 | 'xlink:href': `${sprite}#${this.icon}`,
31 | },
32 | ),
33 | ],
34 | );
35 | },
36 | });
37 |
38 | export default {
39 | name: Icon.name,
40 | component: Icon,
41 | };
42 |
--------------------------------------------------------------------------------
/demo/constant/index.ts:
--------------------------------------------------------------------------------
1 | export interface GroupRule {
2 | groups: string;
3 | title: string;
4 | items: string[][];
5 | }
6 |
7 | export const COMPONENTS_GROUP: GroupRule[] = [
8 | {
9 | groups: 'basic',
10 | title: '基础组件',
11 | items: [
12 | ['layout', '布局'],
13 | ['icon', '图标'],
14 | ['button', '按钮'],
15 | ['tag', '标签'],
16 | ],
17 | },
18 | {
19 | groups: 'form',
20 | title: '表单数据',
21 | items: [
22 | ['input', '输入框'],
23 | ['checkbox', '多选框'],
24 | ],
25 | },
26 | {
27 | groups: '',
28 | title: '交互',
29 | items: [
30 | ['message', '消息'],
31 | ['notice', '通知*'],
32 | ['modal', '模态框*'],
33 | ],
34 | },
35 | ];
36 |
37 | export const GUIDE_GROUP: GroupRule[] = [
38 | {
39 | groups: 'guide',
40 | title: '',
41 | items: [
42 | ['introduce', 'FAST UI'],
43 | ['install', '快速上手'],
44 | ],
45 | },
46 | ];
47 |
--------------------------------------------------------------------------------
/demo/shims.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import { defineComponent } from 'vue';
3 | const component: ReturnType;
4 | export default component;
5 | }
6 |
7 | declare module 'slash2';
8 | declare module '*.css';
9 | declare module '*.less';
10 | declare module '*.scss';
11 | declare module '*.sass';
12 | declare module '*.svg';
13 | declare module '*.png';
14 | declare module '*.jpg';
15 | declare module '*.jpeg';
16 | declare module '*.gif';
17 | declare module '*.md';
18 | declare module '*.json';
19 | declare module '*.woff';
20 | declare module '*.ttf';
21 |
22 | declare interface CustomEleProps {
23 | style: {
24 | [key: string]: string;
25 | } | string;
26 | className: string;
27 | }
28 |
29 | declare interface W {
30 | style: {
31 | [key: string]: string;
32 | } | string;
33 | className: string;
34 | }
35 |
36 | interface Window {
37 | hljs: {
38 | highlightBlock: () => void;
39 | };
40 | }
41 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/demo/components/all-icon/index.scss:
--------------------------------------------------------------------------------
1 | @import "@/demo/styles/variables.scss";
2 |
3 | .all-icon {
4 | margin-top: 30px;
5 | display: flex;
6 | flex-wrap: wrap;
7 |
8 | &__item {
9 | display: flex;
10 | flex-direction: column;
11 | align-items: center;
12 | justify-content: center;
13 | width: 140px;
14 | height: 120px;
15 | border: 1px solid $__color_line3;
16 | border-radius: 4px;
17 | padding: 0 10px;
18 | box-sizing: border-box;
19 | margin: 5px;
20 | transition: box-shadow 0.2s;
21 |
22 | &:hover {
23 | box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1);
24 | cursor: pointer;
25 | }
26 |
27 | &:active {
28 | cursor: pointer;
29 | border: 1px solid $__color_second;
30 | }
31 |
32 | .fa-icon {
33 | font-size: 26px;
34 | color: $__color_text4;
35 | margin-bottom: 10px;
36 | }
37 |
38 | span {
39 | font-size: 12px;
40 | color: $__color_text4;
41 | text-align: center;
42 | line-height: 14px;
43 | height: 30px;
44 | display: flex;
45 | align-items: center;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Mario34
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/demo/components/all-icon/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent } from 'vue';
2 | import icons from './icons.json';
3 | import Icon from '@/packages/icon';
4 | import copy from '@/demo/utils/clipboard';
5 | import { message } from '@/packages/message';
6 | import './index.scss';
7 | const AllIcon = defineComponent({
8 | setup() {
9 | const onClick = (item: string) => {
10 | copy(``).then(() => {
11 | message({
12 | type: 'success',
13 | content: ` has been copied`,
14 | });
15 | });
16 | };
17 | return () => (
18 |
21 | {
22 | icons.map(item => (
23 |
onClick(item)}
28 | >
29 |
30 | {item}
31 |
32 | ))
33 | }
34 |
35 | );
36 | },
37 | });
38 |
39 | export default AllIcon;
40 |
--------------------------------------------------------------------------------
/demo/components/sidebar/index.scss:
--------------------------------------------------------------------------------
1 | @import "@/demo/styles/variables.scss";
2 |
3 | .sidebar {
4 | flex-shrink: 0;
5 | width: 220px;
6 | box-sizing: border-box;
7 | height: calc(100vh - 60px);
8 | overflow-y: auto;
9 | border-right: 1px solid $__color_line2;
10 | padding-bottom: 60px;
11 |
12 | &__group {
13 | padding: 10px 0;
14 |
15 | &-title {
16 | padding: 20px 20px;
17 | font-size: 16px;
18 | font-weight: 500;
19 | color: $__color_text2;
20 | border-bottom: 1px solid $__color_line4;
21 | margin-bottom: 10px;
22 | }
23 |
24 | &-item {
25 | padding: 10px 20px;
26 | font-size: 14px;
27 | color: $__color_text3;
28 | transition: all 0.2s;
29 | margin-bottom: 8px;
30 | line-height: 24px;
31 | text-transform: capitalize;
32 |
33 | &:hover {
34 | cursor: pointer;
35 | color: $__color_second;
36 | }
37 |
38 | &--active {
39 | color: $__color_second;
40 | background-color: #f9f9f9;
41 | }
42 | }
43 | }
44 |
45 | &__footer {
46 | position: absolute;
47 | bottom: 0;
48 | width: 219px;
49 | background-color: #fff;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/demo/index.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue';
2 | import AppContainer from './app';
3 | import router from './router';
4 | import DemoContainer from './components/demo-container';
5 | import AllIcon from './components/all-icon';
6 | import '@/demo/styles/index.scss';
7 | import '@/packages/styles/index.scss';
8 | import 'normalize.css';
9 |
10 | const app = createApp(AppContainer);
11 |
12 | const demoStyles = require.context('@/demo/styles/demo/', true, /\.scss$/);
13 | demoStyles.keys().forEach(key => {
14 | require(`@/demo/styles/demo/${key.slice(2)}`);
15 | });
16 |
17 | /**
18 | * 全局注册组件
19 | * 目录格式 packages/components/index.tsx
20 | */
21 | const components = require.context('@/packages/', true, /index\.tsx$/);
22 | components.keys().forEach(key => {
23 | // eslint-disable-next-line @typescript-eslint/no-var-requires
24 | const { name, component, plugin } = require(`@/packages/${key.slice(2)}`).default;
25 | if (component) {
26 | app.component(name, component);
27 | }
28 | if (plugin) {
29 | app.use(plugin);
30 | }
31 | });
32 | app.component('demo-container', DemoContainer);
33 | app.component('all-icon', AllIcon);
34 |
35 | app.use(router);
36 | router.isReady().then(() => app.mount('#app'));
37 |
--------------------------------------------------------------------------------
/demo/styles/reset.scss:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/ */
2 | /* v1.0 | 20080212 */
3 |
4 | html, body, div, span, applet, object, iframe, blockquote, pre,
5 | a, abbr, acronym, address, big, cite, code,
6 | del, dfn, em, font, img, ins, kbd, q, s, samp,
7 | small, strike, strong, sub, sup, tt, var,
8 | b, u, i, center,
9 | dl, dt, dd, ol, ul, li,
10 | fieldset, form, label, legend,
11 | table, caption, tbody, tfoot, thead, tr, th, td {
12 | margin: 0;
13 | padding: 0;
14 | border: 0;
15 | outline: 0;
16 | font-size: 100%;
17 | vertical-align: baseline;
18 | background: transparent;
19 | }
20 | body {
21 | line-height: 1;
22 | }
23 | ol, ul, li {
24 | list-style: none;
25 | }
26 | blockquote, q {
27 | quotes: none;
28 | }
29 | blockquote:before, blockquote:after,
30 | q:before, q:after {
31 | content: '';
32 | content: none;
33 | }
34 |
35 | /* remember to define focus styles! */
36 | :focus {
37 | outline: 0;
38 | }
39 |
40 | /* remember to highlight inserts somehow! */
41 | ins {
42 | text-decoration: none;
43 | }
44 | del {
45 | text-decoration: line-through;
46 | }
47 |
48 | /* tables still need 'cellspacing="0"' in the markup */
49 | table {
50 | border-collapse: collapse;
51 | border-spacing: 0;
52 | }
--------------------------------------------------------------------------------
/packages/styles/reset.scss:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/ */
2 | /* v1.0 | 20080212 */
3 |
4 | html, body, div, span, applet, object, iframe, blockquote, pre,
5 | a, abbr, acronym, address, big, cite, code,
6 | del, dfn, em, font, img, ins, kbd, q, s, samp,
7 | small, strike, strong, sub, sup, tt, var,
8 | b, u, i, center,
9 | dl, dt, dd, ol, ul, li,
10 | fieldset, form, label, legend,
11 | table, caption, tbody, tfoot, thead, tr, th, td, input {
12 | margin: 0;
13 | padding: 0;
14 | border: 0;
15 | outline: 0;
16 | font-size: 100%;
17 | vertical-align: baseline;
18 | background: transparent;
19 | }
20 | body {
21 | line-height: 1;
22 | }
23 | ol, ul, li {
24 | list-style: none;
25 | }
26 | blockquote, q {
27 | quotes: none;
28 | }
29 | blockquote:before, blockquote:after,
30 | q:before, q:after {
31 | content: '';
32 | content: none;
33 | }
34 |
35 | /* remember to define focus styles! */
36 | :focus {
37 | outline: 0;
38 | }
39 |
40 | /* remember to highlight inserts somehow! */
41 | ins {
42 | text-decoration: none;
43 | }
44 | del {
45 | text-decoration: line-through;
46 | }
47 |
48 | /* tables still need 'cellspacing="0"' in the markup */
49 | table {
50 | border-collapse: collapse;
51 | border-spacing: 0;
52 | }
--------------------------------------------------------------------------------
/demo/styles/index.scss:
--------------------------------------------------------------------------------
1 | @import "./reset.scss";
2 | @import "./variables.scss";
3 |
4 | body {
5 | color: $__color_text1;
6 | font-family:
7 | -apple-system,
8 | BlinkMacSystemFont,
9 | "Helvetica Neue",
10 | Helvetica,
11 | Segoe UI,
12 | Arial,
13 | Roboto,
14 | "PingFang SC",
15 | "Hiragino Sans GB",
16 | "Microsoft Yahei",
17 | sans-serif;
18 | }
19 |
20 | // 统一滚动条样式
21 | .__scroll-box {
22 |
23 | $--scrollbar-thumb-background: #ccc;
24 | $--scrollbar-track-background: #fff;
25 |
26 | &::-webkit-scrollbar {
27 | z-index: 11;
28 | width: 6px;
29 |
30 | &:horizontal {
31 | height: 6px;
32 | }
33 |
34 | &-thumb {
35 | border-radius: 5px;
36 | width: 6px;
37 | background: $--scrollbar-thumb-background;
38 | }
39 |
40 | &-corner {
41 | background: $--scrollbar-track-background;
42 | }
43 |
44 | &-track {
45 | background: $--scrollbar-track-background;
46 |
47 | &-piece {
48 | background: $--scrollbar-track-background;
49 | width: 6px;
50 | }
51 | }
52 | }
53 | }
54 |
55 | a {
56 | text-decoration: none;
57 | color: $__color_text3;
58 |
59 | &:hover {
60 | color: $__color_second;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/demo/components/doc-layout/index.tsx:
--------------------------------------------------------------------------------
1 | import { ref, toRefs, onUpdated } from 'vue';
2 | import { RouterView } from 'vue-router';
3 | import Header from '@/demo/components/header';
4 | import Sidebar, { SidebarConfigItem } from '@/demo/components/sidebar';
5 | import './index.scss';
6 |
7 | const DocLayout = ({
8 | props: {
9 | navConfig: {
10 | type: Array,
11 | },
12 | },
13 | setup(props: Readonly<{ navConfig: SidebarConfigItem[] }>) {
14 | const catalogue = ref();
15 | const scrollBox = ref();
16 | const { navConfig } = toRefs(props);
17 |
18 | onUpdated(() => {
19 | scrollBox.value.scrollTop = 0;
20 | });
21 |
22 | return () => {
23 | return (
24 |
39 | );
40 | };
41 | },
42 | });
43 |
44 | export default DocLayout;
45 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Mario34/fast
9 |
10 | <% if (process.env.NODE_ENV === 'production') { %>
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | <% } %>
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/packages/utils/props-type.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 快速配置propType
3 | */
4 |
5 | class PropsType {
6 | public validator?: (value: any) => boolean
7 | public type: any
8 | public default?: any
9 |
10 | pickType(types: any[]) {
11 | this.type = [...types];
12 | this.validator = function(value) {
13 | return types.some((i: any) => value instanceof i);
14 | };
15 | return this;
16 | }
17 |
18 | pick(values: string | any[]) {
19 | this.type = [this.boolean, this.string, this.number];
20 | this.validator = function(value) {
21 | return values.includes(value);
22 | };
23 | return this;
24 | }
25 |
26 | def(value: any) {
27 | this.default = value;
28 | return this;
29 | }
30 |
31 | val(validator: (value: any) => boolean) {
32 | this.validator = validator;
33 | }
34 |
35 | get any() {
36 | return null;
37 | }
38 |
39 | get string() {
40 | return String;
41 | }
42 |
43 | get number() {
44 | return Number;
45 | }
46 |
47 | get boolean() {
48 | return Boolean;
49 | }
50 |
51 | get array() {
52 | return Array;
53 | }
54 |
55 | get object() {
56 | return Object;
57 | }
58 |
59 | get date() {
60 | return Date;
61 | }
62 |
63 | get function() {
64 | return Function;
65 | }
66 | }
67 |
68 | export default PropsType;
69 |
--------------------------------------------------------------------------------
/demo/components/demo-container/index.scss:
--------------------------------------------------------------------------------
1 | @import "@/demo/styles/variables.scss";
2 |
3 | .demo-container {
4 | color: $__color_text1;
5 | border: 1px solid $__color_line2;
6 | overflow: hidden;
7 | max-width: 1000px;
8 |
9 | &__source {
10 | padding: 18px;
11 | border-bottom: 1px solid $__color_line2;
12 | }
13 |
14 | &__description {
15 | padding: 8px 18px;
16 | border-bottom: 1px solid $__color_line2;
17 | font-size: 14px;
18 | color: $__color_text2;
19 | }
20 |
21 | &__control {
22 | line-height: 30px;
23 | padding: 5px;
24 | font-size: 14px;
25 | text-align: center;
26 | color: $__color_second;
27 | user-select: none;
28 |
29 | &-button {
30 | cursor: pointer;
31 | }
32 | }
33 |
34 | &__highlight-box {
35 | overflow: hidden;
36 | transition: height 160ms ease;
37 | height: 0;
38 | }
39 |
40 | &__highlight {
41 | border-bottom: none;
42 |
43 | code.hljs {
44 | margin: 0;
45 | border: none;
46 | max-height: none;
47 | border-radius: 0;
48 | font-size: 12px;
49 | line-height: 1.8;
50 | font-family: Menlo, Monaco, Consolas, Courier, monospace;
51 | padding: 16px 20px;
52 | border-bottom: 1px solid $__color_line2;
53 |
54 | &::before {
55 | content: none;
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/demo/components/header/index.scss:
--------------------------------------------------------------------------------
1 | @import "@/demo/styles/variables.scss";
2 |
3 | .header {
4 | border-bottom: 1px solid $__color_line2;
5 |
6 | &__container {
7 | height: 59px;
8 | margin: 0 auto;
9 | padding: 0 30px;
10 | display: flex;
11 | align-items: center;
12 | }
13 |
14 | &__fast-logo {
15 | display: flex;
16 | align-items: center;
17 | cursor: pointer;
18 | width: 190px;
19 |
20 | img {
21 | height: 50px;
22 | }
23 |
24 | &-text {
25 | font-size: 26px;
26 | margin-left: 12px;
27 | font-weight: 500;
28 | color: $__color_second;
29 | }
30 | }
31 |
32 | &__nav {
33 | display: flex;
34 | align-items: center;
35 | flex: 1;
36 | padding: 0 20px;
37 | height: 100%;
38 |
39 | & > a {
40 | color: $__color_text3;
41 | margin-right: 20px;
42 | cursor: pointer;
43 | font-size: 14px;
44 | transition: all 0.2s;
45 | font-weight: bold;
46 |
47 | &:hover {
48 | color: $__color_second;
49 | }
50 |
51 | &.router-link-active {
52 | color: $__color_second;
53 | }
54 | }
55 | }
56 |
57 | &__memu {
58 | font-size: 14px;
59 | cursor: pointer;
60 |
61 | .github-logo {
62 | height: 20px;
63 | margin-right: 6px;
64 | }
65 |
66 | &-item {
67 | transition: all 0.2s;
68 | display: flex;
69 | align-items: center;
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/packages/message/index.scss:
--------------------------------------------------------------------------------
1 | @import "../styles/variables.scss";
2 | @import "../styles/mixins.scss";
3 | @import "../styles/reset.scss";
4 |
5 | #__fa-message-container__ {
6 | position: fixed;
7 | top: 20px;
8 | left: 50%;
9 | transform: translate(-50%, 0);
10 | }
11 |
12 | [data-prop='__fa-message-root__'] {
13 | text-align: center;
14 | }
15 |
16 | .fa-message {
17 | text-align: left;
18 | margin: 0 auto 10px;
19 | border: 1px solid #f0f0f0;
20 | border-radius: $__radius_2;
21 | padding: 12px 16px;
22 | background-color: #fff;
23 | box-shadow: 0 0 6px 0 rgba(0, 0, 0, 0.1);
24 | opacity: 1;
25 | transform: translate(0, 0);
26 | transition: opacity 0.3s ease, transform 0.2s ease;
27 | display: inline-block;
28 | line-height: 18px;
29 |
30 | &__icon {
31 | margin-right: 10px;
32 | vertical-align: text-bottom;
33 | }
34 |
35 | &__content {
36 | color: $__color_text2;
37 | font-size: 14px;
38 | }
39 |
40 | &.--before-active {
41 | opacity: 0;
42 | transform: translate(0, -50%);
43 | }
44 |
45 | @mixin type-color($iconColor, $type) {
46 | &.--#{$type} {
47 | & .fa-message__icon {
48 | color: $iconColor;
49 | }
50 | }
51 | }
52 |
53 | @include type-color($__color_text3, default);
54 | @include type-color($__color_primary, primary);
55 | @include type-color($__color_second, second);
56 | @include type-color($__color_success, success);
57 | @include type-color($__color_warning, warning);
58 | @include type-color($__color_danger, danger);
59 | }
60 |
--------------------------------------------------------------------------------
/demo/utils/group.ts:
--------------------------------------------------------------------------------
1 | import { SidebarConfigItem, SubItem } from '@/demo/components/sidebar';
2 | import { RouteRecordRaw } from 'vue-router';
3 | import { GUIDE_GROUP, COMPONENTS_GROUP } from '@/demo/constant';
4 |
5 | function generateComponentsGroups(routes:RouteRecordRaw[]):SidebarConfigItem[] {
6 | return COMPONENTS_GROUP.map(group => {
7 | const items:SubItem[] = [];
8 | group.items.forEach(keys => {
9 | const target = routes.find(route => route.name === keys[0]);
10 | if (target) {
11 | items.push({
12 | title: toHump(`${String(target.name)} ${keys[1] || ''}`),
13 | name: String(target.name),
14 | });
15 | }
16 | });
17 | return {
18 | title: group.title,
19 | items,
20 | };
21 | });
22 | }
23 |
24 | function generateGuideGroups(routes:RouteRecordRaw[]):SidebarConfigItem[] {
25 | return GUIDE_GROUP.map(group => {
26 | const items:SubItem[] = [];
27 | group.items.forEach(keys => {
28 | const target = routes.find(route => route.name === keys[0]);
29 | if (target) {
30 | items.push({
31 | title: toHump(`${keys[1]}`),
32 | name: String(target.name),
33 | });
34 | }
35 | });
36 | return {
37 | title: group.title,
38 | items,
39 | };
40 | });
41 | }
42 |
43 | export function toHump(name:string) {
44 | return name.replace(/-(\w)/g, function(all, letter) {
45 | return letter.toUpperCase();
46 | });
47 | }
48 |
49 | export {
50 | generateComponentsGroups,
51 | generateGuideGroups,
52 | };
53 |
54 | export default generateComponentsGroups;
55 |
--------------------------------------------------------------------------------
/demo/components/header/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent } from 'vue';
2 | import fastLogo from '@/demo/images/fast-logo.png';
3 | import githubLogo from '@/demo/images/github.png';
4 | import packageJson from '@/package.json';
5 | import { RouterLink } from 'vue-router';
6 | import './index.scss';
7 |
8 | const Header = defineComponent({
9 | setup() {
10 | return () => (
11 |
50 | );
51 | },
52 | });
53 |
54 | export default Header;
55 |
--------------------------------------------------------------------------------
/demo/components/demo-container/atom-one-light.css:
--------------------------------------------------------------------------------
1 | /*
2 | Atom One Light by Daniel Gamage
3 | Original One Light Syntax theme from https://github.com/atom/one-light-syntax
4 | base: #fafafa
5 | mono-1: #383a42
6 | mono-2: #686b77
7 | mono-3: #a0a1a7
8 | hue-1: #0184bb
9 | hue-2: #4078f2
10 | hue-3: #a626a4
11 | hue-4: #50a14f
12 | hue-5: #e45649
13 | hue-5-2: #c91243
14 | hue-6: #986801
15 | hue-6-2: #c18401
16 | */
17 |
18 | .hljs {
19 | display: block;
20 | overflow-x: auto;
21 | padding: 0.5em;
22 | color: #383a42;
23 | background: #fff;
24 | }
25 |
26 | .hljs-comment,
27 | .hljs-quote {
28 | color: #a0a1a7;
29 | font-style: italic;
30 | }
31 |
32 | .hljs-doctag,
33 | .hljs-keyword,
34 | .hljs-formula {
35 | color: #a626a4;
36 | }
37 |
38 | .hljs-section,
39 | .hljs-name,
40 | .hljs-selector-tag,
41 | .hljs-deletion,
42 | .hljs-subst {
43 | color: #e45649;
44 | }
45 |
46 | .hljs-literal {
47 | color: #0184bb;
48 | }
49 |
50 | .hljs-string,
51 | .hljs-regexp,
52 | .hljs-addition,
53 | .hljs-attribute,
54 | .hljs-meta-string {
55 | color: #50a14f;
56 | }
57 |
58 | .hljs-built_in,
59 | .hljs-class .hljs-title {
60 | color: #c18401;
61 | }
62 |
63 | .hljs-attr,
64 | .hljs-variable,
65 | .hljs-template-variable,
66 | .hljs-type,
67 | .hljs-selector-class,
68 | .hljs-selector-attr,
69 | .hljs-selector-pseudo,
70 | .hljs-number {
71 | color: #986801;
72 | }
73 |
74 | .hljs-symbol,
75 | .hljs-bullet,
76 | .hljs-link,
77 | .hljs-meta,
78 | .hljs-selector-id,
79 | .hljs-title {
80 | color: #4078f2;
81 | }
82 |
83 | .hljs-emphasis {
84 | font-style: italic;
85 | }
86 |
87 | .hljs-strong {
88 | font-weight: bold;
89 | }
90 |
91 | .hljs-link {
92 | text-decoration: underline;
93 | }
--------------------------------------------------------------------------------
/demo/styles/variables.scss:
--------------------------------------------------------------------------------
1 | /* Color */
2 | $__color_primary: #2f67ba !default;
3 | $__color_second: #74bbf1 !default;
4 | $__color_white: #fff !default;
5 | $__color_black: #000 !default;
6 | $__color_success: #67ac5b !default;
7 | $__color_warning: #ec9134 !default;
8 | $__color_danger: #ec5e58 !default;
9 | $__color_disable: #909399 !default;
10 |
11 | $__color_line1: #dcdfe6 !default;
12 | $__color_line2: #e4e7ed !default;
13 | $__color_line3: #ebeef5 !default;
14 | $__color_line4: #f2f6fc !default;
15 |
16 | $__color_text1: #212121 !default;
17 | $__color_text2: #424242 !default;
18 | $__color_text3: #616161 !default;
19 | $__color_text4: #757575 !default;
20 |
21 | $__color_text_light1: #f5f5f5 !default;
22 | $__color_text_light2: #eee !default;
23 | $__color_text_light3: #e0e0e0 !default;
24 | $__color_text_light4: #bdbdbd !default;
25 |
26 | $__color_primary_light2: mix($__color_white, $__color_primary, 20%) !default;
27 | $__color_primary_light4: mix($__color_white, $__color_primary, 40%) !default;
28 | $__color_primary_light6: mix($__color_white, $__color_primary, 60%) !default;
29 | $__color_primary_light8: mix($__color_white, $__color_primary, 80%) !default;
30 |
31 | $__color_second_light2: mix($__color_white, $__color_second, 20%) !default;
32 | $__color_second_light4: mix($__color_white, $__color_second, 40%) !default;
33 | $__color_second_light6: mix($__color_white, $__color_second, 60%) !default;
34 | $__color_second_light8: mix($__color_white, $__color_second, 80%) !default;
35 |
36 | // 导出样式变量到js环境
37 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
38 | /* stylelint-disable */
39 | :export {
40 | __color_primary: $__color_primary;
41 | __color_white: $__color_white;
42 | __color_black: $__color_black;
43 | __color_success: $__color_success;
44 | __color_warning: $__color_warning;
45 | __color_danger: $__color_danger;
46 | __color_disable: $__color_disable;
47 | }
48 |
--------------------------------------------------------------------------------
/demo/components/sidebar/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent, Ref } from 'vue';
2 | import Footer from '@/demo/components/footer';
3 | import { useRouter, RouterLink, useRoute } from 'vue-router';
4 | import './index.scss';
5 |
6 | export interface SubItem {
7 | title: string;
8 | name: string;
9 | }
10 |
11 | export interface SidebarConfigItem {
12 | title: string;
13 | items: SubItem[];
14 | }
15 |
16 | export interface SidebarProps {
17 | /**
18 | * Sidebar 配置
19 | */
20 | config: Ref;
21 | }
22 |
23 | const Sidebar = defineComponent({
24 | props: [
25 | 'config',
26 | ],
27 | setup(props) {
28 | const { config } = props as SidebarProps;
29 | const router = useRouter();
30 | const route = useRoute();
31 |
32 | return () => (
33 |
65 | );
66 | },
67 | });
68 |
69 | export default Sidebar;
70 |
--------------------------------------------------------------------------------
/packages/checkbox/index.scss:
--------------------------------------------------------------------------------
1 | @import "../styles/variables.scss";
2 |
3 | .fa-check-box {
4 | display: inline-block;
5 | position: relative;
6 | cursor: pointer;
7 |
8 | & + & {
9 | margin-left: 26px;
10 | }
11 |
12 | &__original-input {
13 | opacity: 0;
14 | position: absolute;
15 | }
16 |
17 | &__icon {
18 | display: inline-block;
19 | height: 16px;
20 | width: 16px;
21 | border: 1px solid $__color_line1;
22 | border-radius: $__radius_1;
23 | margin-right: 8px;
24 | vertical-align: middle;
25 | transition: background-color 0.3s, border-color 0.2s;
26 | box-sizing: border-box;
27 |
28 | &-checked {
29 | height: 12px;
30 | width: 12px;
31 | padding: 1px;
32 | color: $__color_white;
33 | }
34 |
35 | &-indeterminate {
36 | height: 12px;
37 | width: 12px;
38 | padding: 1px;
39 | color: $__color_white;
40 | }
41 | }
42 |
43 | &__label {
44 | font-size: 14px;
45 | color: $__color_text3;
46 | user-select: none;
47 | vertical-align: middle;
48 | transition: color 0.2s;
49 | }
50 |
51 | &:hover {
52 | .fa-check-box__icon {
53 | border-color: $__color_primary;
54 | }
55 |
56 | .fa-check-box__label {
57 | color: $__color_primary;
58 | }
59 | }
60 |
61 | &.--is-checked,
62 | &.--is-indeterminate {
63 | .fa-check-box__icon {
64 | background-color: $__color_primary;
65 | border-color: transparent;
66 | }
67 |
68 | .fa-check-box__label {
69 | color: $__color_primary;
70 | }
71 | }
72 |
73 | &.--is-disabled {
74 | .fa-check-box__icon {
75 | background-color: $__color_text_light1;
76 | border-color: $__color_line1;
77 |
78 | &-indeterminate {
79 | color: $__color_disable;
80 | }
81 |
82 | &-checked {
83 | color: $__color_disable;
84 | }
85 | }
86 |
87 | .fa-check-box__label {
88 | color: $__color_text_light4;
89 | }
90 |
91 | // &.--is-checked {
92 | // }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/packages/styles/variables.scss:
--------------------------------------------------------------------------------
1 | /* Color */
2 | $__color_primary: #2f67ba !default;
3 | $__color_second: #74bbf1 !default;
4 | $__color_white: #fff !default;
5 | $__color_black: #000 !default;
6 | $__color_success: #67ac5b !default;
7 | $__color_warning: #ec9134 !default;
8 | $__color_danger: #ec5e58 !default;
9 | $__color_disable: #909399 !default;
10 |
11 | $__color_line1: #dcdfe6 !default;
12 | $__color_line2: #e4e7ed !default;
13 | $__color_line3: #ebeef5 !default;
14 | $__color_line4: #f2f6fc !default;
15 |
16 | $__color_text1: #212121 !default;
17 | $__color_text2: #424242 !default;
18 | $__color_text3: #616161 !default;
19 | $__color_text4: #757575 !default;
20 |
21 | $__color_text_light1: #f5f5f5 !default;
22 | $__color_text_light2: #eee !default;
23 | $__color_text_light3: #e0e0e0 !default;
24 | $__color_text_light4: #bdbdbd !default;
25 |
26 | $__color_primary_light1: mix($__color_white, $__color_primary, 10%) !default;
27 | $__color_primary_light2: mix($__color_white, $__color_primary, 20%) !default;
28 | $__color_primary_light4: mix($__color_white, $__color_primary, 40%) !default;
29 | $__color_primary_light6: mix($__color_white, $__color_primary, 60%) !default;
30 | $__color_primary_light8: mix($__color_white, $__color_primary, 80%) !default;
31 |
32 | $__color_second_light2: mix($__color_white, $__color_second, 20%) !default;
33 | $__color_second_light4: mix($__color_white, $__color_second, 40%) !default;
34 | $__color_second_light6: mix($__color_white, $__color_second, 60%) !default;
35 | $__color_second_light8: mix($__color_white, $__color_second, 80%) !default;
36 |
37 | $__radius_1: 2px !default;
38 | $__radius_2: 4px !default;
39 | $__radius_3: 6px !default;
40 | $__radius_4: 8px !default;
41 | $__radius_5: 10px !default;
42 |
43 | // 导出样式变量到js环境
44 | // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
45 | /* stylelint-disable */
46 | :export {
47 | __color_primary: $__color_primary;
48 | __color_second: $__color_second;
49 | __color_white: $__color_white;
50 | __color_black: $__color_black;
51 | __color_success: $__color_success;
52 | __color_warning: $__color_warning;
53 | __color_danger: $__color_danger;
54 | __color_disable: $__color_disable;
55 | }
56 |
--------------------------------------------------------------------------------
/packages/checkbox-group/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent, ref, toRefs } from 'vue';
2 | import Emitter from '@/packages/utils/emitter';
3 | import EventBus from '@/packages/utils/event-bus';
4 | import { v4 as uuidv4 } from 'uuid';
5 | import './index.scss';
6 |
7 | export interface ButtonProps extends CustomEleProps {
8 | disabled?: boolean;
9 | inline?: boolean;
10 | value?: [];
11 | }
12 |
13 | const CheckBoxGroup = defineComponent({
14 | name: 'fa-checkbox-group',
15 | mixins: [Emitter],
16 | props: {
17 | modelValue: {
18 | type: Array,
19 | default: () => [],
20 | },
21 | disabled: {
22 | type: Boolean,
23 | default: false,
24 | },
25 | },
26 | emits: ['update:modelValue', 'change'],
27 | setup(props, ctx) {
28 | const id = uuidv4();
29 | const test = ref('');
30 | const { modelValue } = toRefs(props);
31 | const currentValue = ref(modelValue?.value || []);
32 | EventBus.on(id + 'checkbox_change', (e) => {
33 | const values = [...currentValue.value];
34 | const index = values.indexOf(e.label);
35 | if (index > -1) {
36 | if (!e.checked) {
37 | values.splice(index, 1);
38 | }
39 | } else {
40 | if (e.checked) {
41 | values.push(e.label);
42 | }
43 | }
44 | ctx.emit('update:modelValue', values);
45 | ctx.emit('change', e, values);
46 | });
47 | const notice = () => {
48 | EventBus.emit(id + 'checkbox-group_modelValue', modelValue.value);
49 | currentValue.value = modelValue.value;
50 | };
51 |
52 | return {
53 | id,
54 | test,
55 | currentValue,
56 | notice,
57 | };
58 | },
59 | watch: {
60 | modelValue(value) {
61 | EventBus.emit(this.id + 'checkbox-group_modelValue', this.modelValue);
62 | this.currentValue = value;
63 | },
64 | },
65 | mounted() {
66 | this.notice();
67 | },
68 | render() {
69 | const { default: default_ } = this.$slots;
70 | return (
71 |
74 | {this.test}{default_ && default_()}
75 |
76 | );
77 | },
78 | });
79 |
80 | export default {
81 | name: CheckBoxGroup.name,
82 | component: CheckBoxGroup,
83 | };
84 |
--------------------------------------------------------------------------------
/packages/tag/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent, toRefs } from 'vue';
2 | import Icon from '@/packages/icon';
3 | import './index.scss';
4 |
5 | export interface TagProps extends CustomEleProps {
6 | color?: 'default' | 'primary' | 'second' | 'success' | 'danger' | 'warning' | 'text';
7 | round?: boolean;
8 | closable?: boolean;
9 | size?: 'small' | 'medium' | 'large';
10 | icon?: string;
11 | }
12 |
13 | const Tag = defineComponent({
14 | name: 'fa-tag',
15 | inheritAttrs: false,
16 | props: {
17 | color: {
18 | type: String,
19 | default: 'default',
20 | },
21 | size: {
22 | type: String,
23 | default: 'medium',
24 | },
25 | icon: {
26 | type: String,
27 | default: '',
28 | },
29 | round: {
30 | type: Boolean,
31 | default: false,
32 | },
33 | closable: {
34 | type: Boolean,
35 | default: false,
36 | },
37 | },
38 | emits: ['close'],
39 | setup(props, ctx) {
40 | const {
41 | slots: {
42 | default: _default,
43 | },
44 | attrs,
45 | } = ctx;
46 | const {
47 | color,
48 | size,
49 | round,
50 | icon,
51 | closable,
52 | } = toRefs(props);
53 | const preset = ['default', 'primary', 'second', 'success', 'danger', 'warning', 'text'];
54 | const isPreset = preset.includes(color.value);
55 | const onClose = (e:Event) => {
56 | e.stopPropagation();
57 | ctx.emit('close');
58 | };
59 | return () => (
60 |
76 | {icon.value && }
77 | {_default && _default()}
78 | {closable.value && (
79 |
84 | )}
85 |
86 | );
87 | },
88 | });
89 |
90 | export default {
91 | name: Tag.name,
92 | component: Tag,
93 | };
94 |
--------------------------------------------------------------------------------
/demo/components/demo-container/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent, ref, onMounted, nextTick, computed } from 'vue';
2 | import hljs from 'highlight.js';
3 | import './atom-one-light.css';
4 | import './index.scss';
5 |
6 | const DemoContainer = defineComponent({
7 | props: [],
8 | setup(_props, ctx) {
9 | const {
10 | slots: {
11 | default: _default,
12 | source,
13 | highlight,
14 | },
15 | } = ctx;
16 | const descriptionRef = ref();
17 | const highlightRef = ref();
18 | const isExpanded = ref(false);
19 | const highlightHeight = computed(() => {
20 | if (isExpanded.value) {
21 | return highlightRef.value ? highlightRef.value.offsetHeight : 0;
22 | }
23 | return 0;
24 | });
25 | const controlText = computed(() => isExpanded.value ? '隐藏代码' : '显示代码');
26 |
27 | const switchExpand = () => {
28 | isExpanded.value = !isExpanded.value;
29 | };
30 |
31 | onMounted(() => {
32 | nextTick(() => {
33 | const blocks = document.querySelectorAll('pre code:not(.hljs)');
34 | Array.prototype.forEach.call(blocks, hljs.highlightBlock);
35 | });
36 | });
37 |
38 | return () => (
39 |
40 | {source && (
41 |
42 | {source()}
43 |
44 | )}
45 |
70 |
71 |
75 | {controlText.value}
76 |
77 |
78 |
79 | );
80 | },
81 | });
82 |
83 | export default DemoContainer;
84 |
--------------------------------------------------------------------------------
/packages/button/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent, toRefs } from 'vue';
2 | import Icon from '@/packages/icon';
3 | import './index.scss';
4 |
5 | export interface ButtonProps extends CustomEleProps {
6 | disabled?: boolean;
7 | loading?: boolean;
8 | type?: 'default' | 'primary' | 'second' | 'success' | 'danger' | 'warning' | 'text';
9 | plain?: boolean;
10 | round?: boolean;
11 | circle?: boolean;
12 | size?: 'small' | 'medium' | 'large';
13 | icon?: string;
14 | }
15 |
16 | const Button = defineComponent({
17 | name: 'fa-button',
18 | inheritAttrs: false,
19 | props: {
20 | disabled: {
21 | type: Boolean,
22 | default: false,
23 | },
24 | loading: {
25 | type: Boolean,
26 | default: false,
27 | },
28 | type: {
29 | type: String,
30 | default: 'default',
31 | },
32 | size: {
33 | type: String,
34 | default: 'medium',
35 | },
36 | icon: {
37 | type: String,
38 | default: '',
39 | },
40 | plain: {
41 | type: Boolean,
42 | default: false,
43 | },
44 | round: {
45 | type: Boolean,
46 | default: false,
47 | },
48 | circle: {
49 | type: Boolean,
50 | default: false,
51 | },
52 | },
53 | setup(props, ctx) {
54 | const {
55 | slots: {
56 | default: _default,
57 | },
58 | attrs,
59 | } = ctx;
60 | const {
61 | loading,
62 | disabled,
63 | type,
64 | size,
65 | plain,
66 | round,
67 | icon,
68 | circle,
69 | } = toRefs(props);
70 |
71 | return () => (
72 |
92 | );
93 | },
94 | });
95 |
96 | export default {
97 | name: Button.name,
98 | component: Button,
99 | };
100 |
--------------------------------------------------------------------------------
/demo/router/index.tsx:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
2 | import { generateComponentsGroups, generateGuideGroups } from '@/demo/utils/group';
3 |
4 | const devMode = process.env.NODE_ENV === 'development';
5 |
6 | /**
7 | * 路由加载规则
8 | * docs/*.md 作为组件的文档 /components
9 | * docs/guide/*.md 作为指南文档 /guide
10 | * demo/pages/**.tsx 作为普通页面
11 | */
12 |
13 | /**
14 | * 按文件夹引入*.md文件,并组装路由
15 | */
16 | function loadDocs() {
17 | const guideRoutes: RouteRecordRaw[] = [];
18 | const componentsRoutes: RouteRecordRaw[] = [];
19 | const docs = require.context('@/docs/', true, /\.md$/);
20 | docs.keys().forEach(key => {
21 | if (key.match(/guide\/.+\.md$/)) {
22 | const match = key.match(/^\.\/guide\/(\S*)\.md$/) as string[];
23 | guideRoutes.push({
24 | name: match[1],
25 | path: `/${match[1]}`,
26 | component: () => import(`@/docs/${key.slice(2)}`),
27 | });
28 | } else {
29 | const match = key.match(/^\.\/(\S*)\.md/) as string[];
30 | componentsRoutes.push({
31 | name: match[1],
32 | path: `/${match[1]}`,
33 | component: () => import(`@/docs/${key.slice(2)}`),
34 | });
35 | }
36 | });
37 | return {
38 | guideRoutes,
39 | componentsRoutes,
40 | };
41 | }
42 |
43 | const { guideRoutes, componentsRoutes } = loadDocs();
44 |
45 | const router = createRouter({
46 | history: createWebHashHistory(devMode ? '/' : '/fast-ui/'),
47 | routes: [
48 | {
49 | path: '/',
50 | redirect: '/home',
51 | },
52 | {
53 | path: '/home',
54 | component: () => import('@/demo/pages/home'),
55 | },
56 | {
57 | path: '/components',
58 | component: () => import('@/demo/components/doc-layout'),
59 | props: {
60 | navConfig: generateComponentsGroups(componentsRoutes),
61 | },
62 | children: [
63 | {
64 | path: '',
65 | redirect: '/layout',
66 | },
67 | ...componentsRoutes,
68 | ],
69 | },
70 | {
71 | path: '/guide',
72 | component: () => import('@/demo/components/doc-layout'),
73 | props: {
74 | navConfig: generateGuideGroups(guideRoutes),
75 | },
76 | children: [
77 | {
78 | path: '',
79 | redirect: '/introduce',
80 | },
81 | ...guideRoutes,
82 | ],
83 | },
84 | {
85 | path: '/:data(.*)',
86 | redirect: '/home',
87 | },
88 | ],
89 | });
90 |
91 | export {
92 | guideRoutes,
93 | componentsRoutes,
94 | };
95 |
96 | export default router;
97 |
98 |
--------------------------------------------------------------------------------
/packages/tag/index.scss:
--------------------------------------------------------------------------------
1 | @import "../styles/variables.scss";
2 | @import "../styles/mixins.scss";
3 |
4 | .fa-tag {
5 | border-radius: $__radius_1;
6 | transition: background-color 0.1s;
7 | box-sizing: border-box;
8 | display: inline-block;
9 | cursor: pointer;
10 | padding: 0 12px;
11 | font-size: 14px;
12 | color: $__color_text4;
13 | background-color: $__color_white;
14 | line-height: 30px;
15 |
16 | & + & {
17 | margin-left: 10px;
18 | }
19 |
20 | @mixin type-color($type, $color, $bg-color) {
21 | &.--#{$type} {
22 | color: $color;
23 | background-color: $bg-color;
24 | border: 1px solid transparent;
25 |
26 | &:hover {
27 | background-color: mix($__color_white, $bg-color, 20%);
28 | }
29 |
30 | .fa-tag__close {
31 | background-color: transparent;
32 |
33 | &:hover {
34 | color: #fff;
35 | background-color: mix($__color_white, $color, 20%);
36 | }
37 | }
38 | }
39 | }
40 |
41 | @mixin size($size, $font-size, $height, $padding) {
42 | &.--#{$size} {
43 | font-size: $font-size;
44 | line-height: $height;
45 | padding: $padding;
46 |
47 | &.--round {
48 | border-radius: #{$height/2};
49 | }
50 |
51 | .fa-tag__close {
52 | border-radius: 50%;
53 | background-color: transparent;
54 |
55 | &:hover {
56 | color: #fff;
57 | background-color: rgba(255, 255, 255, 0.2);
58 | }
59 | }
60 | }
61 | }
62 |
63 | @include size(small, 12px, 20px, 0 10px);
64 | @include size(medium, 14px, 28px, 0 12px);
65 | @include size(large, 16px, 30px, 0 16px);
66 |
67 | @include type-color(
68 | default,
69 | mix($__color_white, $__color_text4, 20%),
70 | mix($__color_white, $__color_text4, 92%)
71 | );
72 | @include type-color(
73 | primary,
74 | mix($__color_white, $__color_primary, 10%),
75 | mix($__color_white, $__color_primary, 90%)
76 | );
77 | @include type-color(
78 | second,
79 | mix($__color_white, $__color_second, 0%),
80 | mix($__color_white, $__color_second, 82%)
81 | );
82 | @include type-color(
83 | success,
84 | mix($__color_white, $__color_success, 30%),
85 | mix($__color_white, $__color_success, 90%)
86 | );
87 | @include type-color(
88 | warning,
89 | mix($__color_white, $__color_warning, 30%),
90 | mix($__color_white, $__color_warning, 90%)
91 | );
92 | @include type-color(
93 | danger,
94 | mix($__color_white, $__color_danger, 30%),
95 | mix($__color_white, $__color_danger, 90%)
96 | );
97 | }
98 |
--------------------------------------------------------------------------------
/docs/layout.md:
--------------------------------------------------------------------------------
1 | [toc]
2 |
3 | ## Layout 布局
4 |
5 | ### 栅格布局
6 |
7 | :::demo 将一行分为 24 份,根据`col`配置所占比率不同
8 |
9 | ```html
10 |
11 |
12 | 24
13 |
14 |
15 |
16 |
17 | 4
18 |
19 |
20 | 12
21 |
22 |
23 |
24 |
25 | 6
26 |
27 |
28 | 7
29 |
30 |
31 | 5
32 |
33 |
34 | 6
35 |
36 |
37 | ```
38 |
39 | :::
40 |
41 | ### 偏移
42 |
43 | :::demo 可以通过 col 的`offset`属性控制块元素的向右偏移量
44 |
45 | ```html
46 |
47 |
48 | 12
49 |
50 |
51 |
52 |
53 | 4
54 |
55 |
56 | 12
57 |
58 |
59 |
60 |
61 | 6
62 |
63 |
64 | 7
65 |
66 |
67 | 6
68 |
69 |
70 | ```
71 |
72 | :::
73 |
74 | ### 行间距
75 |
76 | :::demo 通过 row 的`space`属性控制行后间距
77 |
78 | ```html
79 |
80 |
81 | 12
82 |
83 |
84 |
85 |
86 | 4
87 |
88 |
89 | 12
90 |
91 |
92 |
93 |
94 | 6
95 |
96 |
97 | 7
98 |
99 |
100 | 6
101 |
102 |
103 | ```
104 |
105 | :::
106 |
107 | ### Row Props
108 |
109 | | 参数 | 说明 | required | 类型 | 默认值 |
110 | | ----- | ---------- | -------- | ------ | ------ |
111 | | space | 栅格行间隔 | 否 | number | 0 |
112 |
113 | ### Col Props
114 |
115 | | 参数 | 说明 | required | 类型 | 默认值 |
116 | | ------ | ------------------ | -------- | ------ | ------ |
117 | | col | 栅格占据的列数 | 否 | number | 24 |
118 | | offset | 栅格左侧的间隔格数 | 否 | number | 0 |
119 |
--------------------------------------------------------------------------------
/demo/components/all-icon/icons.json:
--------------------------------------------------------------------------------
1 | ["activity","airplay","alert-circle","alert-octagon","alert-triangle","align-center","align-justify","align-left","align-right","anchor","aperture","archive","arrow-down-circle","arrow-down-left","arrow-down-right","arrow-down","arrow-left-circle","arrow-left","arrow-right-circle","arrow-right","arrow-up-circle","arrow-up-left","arrow-up-right","arrow-up","at-sign","award","bar-chart-2","bar-chart","battery-charging","battery","bell-off","bell","bluetooth","bold","book-open","book","bookmark","box","briefcase","calendar","camera-off","camera","cast","check-circle","check-square","check","chevron-down","chevron-left","chevron-right","chevron-up","chevrons-down","chevrons-left","chevrons-right","chevrons-up","chrome","circle","clipboard","clock","cloud-drizzle","cloud-lightning","cloud-off","cloud-rain","cloud-snow","cloud","code","codepen","codesandbox","coffee","columns","command","compass","copy","corner-down-left","corner-down-right","corner-left-down","corner-left-up","corner-right-down","corner-right-up","corner-up-left","corner-up-right","cpu","credit-card","crop","crosshair","database","delete","disc","divide-circle","divide-square","divide","dollar-sign","download-cloud","download","dribbble","droplet","edit-2","edit-3","edit","external-link","eye-off","eye","facebook","fast-forward","feather","figma","file-minus","file-plus","file-text","file","film","filter","flag","folder-minus","folder-plus","folder","framer","frown","gift","git-branch","git-commit","git-merge","git-pull-request","github","gitlab","globe","grid","hard-drive","hash","headphones","heart","help-circle","hexagon","home","image","inbox","info","instagram","italic","key","layers","layout","life-buoy","link-2","link","linkedin","list","loader","lock","log-in","log-out","mail","map-pin","map","maximize-2","maximize","meh","menu","message-circle","message-square","mic-off","mic","minimize-2","minimize","minus-circle","minus-square","minus","monitor","moon","more-horizontal","more-vertical","mouse-pointer","move","music","navigation-2","navigation","octagon","package","paperclip","pause-circle","pause","pen-tool","percent","phone-call","phone-forwarded","phone-incoming","phone-missed","phone-off","phone-outgoing","phone","pie-chart","play-circle","play","plus-circle","plus-square","plus","pocket","power","printer","radio","refresh-ccw","refresh-cw","repeat","rewind","rotate-ccw","rotate-cw","rss","save","scissors","search","send","server","settings","share-2","share","shield-off","shield","shopping-bag","shopping-cart","shuffle","sidebar","skip-back","skip-forward","slack","slash","sliders","smartphone","smile","speaker","square","star","stop-circle","sun","sunrise","sunset","tablet","tag","target","terminal","thermometer","thumbs-down","thumbs-up","toggle-left","toggle-right","tool","trash-2","trash","trello","trending-down","trending-up","triangle","truck","tv","twitch","twitter","type","umbrella","underline","unlock","upload-cloud","upload","user-check","user-minus","user-plus","user-x","user","users","video-off","video","voicemail","volume-1","volume-2","volume-x","volume","watch","wifi-off","wifi","wind","x-circle","x-octagon","x-square","x","youtube","zap-off","zap","zoom-in","zoom-out"]
--------------------------------------------------------------------------------
/packages/checkbox/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent } from 'vue';
2 | import Emitter from '@/packages/utils/emitter';
3 | import EventBus from '@/packages/utils/event-bus';
4 | import Icons from './icons';
5 | import './index.scss';
6 |
7 | export interface ButtonProps extends CustomEleProps {
8 | modelValue?: boolean;
9 | label?: string | number | boolean;
10 | disabled?: boolean;
11 | indeterminate?: boolean;
12 | }
13 |
14 | const CheckBox = defineComponent({
15 | name: 'fa-checkbox',
16 | mixins: [Emitter],
17 | props: {
18 | modelValue: {
19 | type: Boolean,
20 | default: false,
21 | },
22 | label: {
23 | type: [Boolean, String, Number],
24 | default: false,
25 | },
26 | disabled: {
27 | type: Boolean,
28 | default: false,
29 | },
30 | indeterminate: {
31 | type: Boolean,
32 | default: false,
33 | },
34 | },
35 | emits: ['update:modelValue', 'change'],
36 | data() {
37 | return {
38 | checkboxGroup: null,
39 | currentValue: this.modelValue,
40 | };
41 | },
42 | computed: {
43 | group() {
44 | let parent = this.$parent;
45 | while (parent) {
46 | if (parent.$options.name !== 'fa-checkbox-group') {
47 | parent = parent.$parent;
48 | } else {
49 | return parent;
50 | }
51 | }
52 | return null;
53 | },
54 | groupId() {
55 | if (this.group) {
56 | return this.group.id;
57 | }
58 | return null;
59 | },
60 | isDisabled() {
61 | return this.disabled || this.group?.disabled;
62 | },
63 | },
64 | watch: {
65 | modelValue(value) {
66 | this.currentValue = value;
67 | },
68 | },
69 | mounted() {
70 | EventBus.on(this.groupId + 'checkbox-group_modelValue', value => {
71 | this.currentValue = value.includes(this.label);
72 | });
73 | },
74 | methods: {
75 | onInput(e: Event) {
76 | const { checked } = e.target as { checked: boolean } & EventTarget;
77 | this.currentValue = checked;
78 | this.$emit('change', checked);
79 | this.$emit('update:modelValue', checked);
80 | this.groupId && EventBus.emit(this.groupId + 'checkbox_change', {
81 | checked,
82 | label: this.label,
83 | });
84 | },
85 | },
86 | render() {
87 | const { default: default_ } = this.$slots;
88 | const { label, indeterminate } = this.$props;
89 | const showIcon = () => {
90 | if (indeterminate) {
91 | return ;
92 | }
93 | if (this.currentValue) {
94 | return ;
95 | }
96 | };
97 | return (
98 |
120 | );
121 | },
122 | });
123 |
124 | export default {
125 | name: CheckBox.name,
126 | component: CheckBox,
127 | };
128 |
--------------------------------------------------------------------------------
/docs/tag.md:
--------------------------------------------------------------------------------
1 | [toc]
2 |
3 | ## Tag 标签
4 |
5 | ### 基础用法
6 |
7 | :::demo 使用`icon`来控制按钮的颜色,使用`round`来控制是否为圆角按钮,使用`icon`给按钮添加图标
8 |
9 | ```html
10 |
11 | 标签一
12 | 标签二
13 | 标签三
14 | 标签四
15 | 标签五
16 | 标签六
17 |
18 |
19 | 标签一
20 | 标签二
21 | 标签三
22 | 标签四
23 | 标签五
24 | 标签六
25 |
26 |
27 | 标签一
28 | 标签二
29 | 标签三
30 | 标签四
31 | 标签五
32 | 标签六
33 |
34 | ```
35 |
36 | :::
37 |
38 | ### 自定义颜色
39 |
40 | :::demo `icon`可以为预设值,也可以自定义
41 |
42 | ```html
43 |
44 | 标签一
45 | 标签二
46 | 标签三
47 | 标签四
48 | 标签五
49 |
50 |
51 | 标签一
52 | 标签二
53 | 标签三
54 | 标签四
55 | 标签五
56 |
57 | ```
58 |
59 | :::
60 |
61 | ### 不同尺寸
62 |
63 | :::demo `size`指定标签尺寸
64 |
65 | ```html
66 |
67 | 标签一
68 | 标签一
69 | 标签一
70 |
71 |
72 | 标签二
73 | 标签二
74 | 标签二
75 |
76 | ```
77 |
78 | :::
79 |
80 | ### 显示关闭按钮
81 |
82 | :::demo `closable`控制是否显示关闭按钮
83 |
84 | ```html
85 |
86 | 标签一
87 | 标签二
88 | 标签三
89 | 标签四
90 | 标签五
91 | 标签六
92 |
93 |
94 |
106 | ```
107 |
108 | :::
109 |
110 | ### Props
111 |
112 | | 参数 | 说明 | required | 类型 | 默认值 |
113 | | -------- | ------------ | -------- | --------------------------------------------------------- | ---------- |
114 | | color | 颜色类型 | 否 | `default` `primary` `second` `success` `danger` `warning` | `default` |
115 | | closable | 显示关闭按钮 | 否 | boolean | `false` |
116 | | size | 尺寸 | 否 | `small` `medium` `large` | `medium`| |
117 |
118 | ### Events
119 |
120 | | 参数 | 说明 | 参数 |
121 | | ----- | ------------ | ------- |
122 | | close | 点击关闭按钮 | `Event` |
123 | | ... | 其他原生事件 | `Event` |
124 |
--------------------------------------------------------------------------------
/packages/input/index.scss:
--------------------------------------------------------------------------------
1 | @import "../styles/variables.scss";
2 | @import "../styles/mixins.scss";
3 |
4 | .fa-input {
5 | color: $__color_text3;
6 | background-color: $__color_white;
7 | display: inline-block;
8 | position: relative;
9 | width: 200px;
10 |
11 | &__input {
12 | display: inline-block;
13 | width: 100%;
14 | outline: none;
15 | box-shadow: none;
16 | color: $__color_text3;
17 | box-sizing: border-box;
18 | background-color: transparent;
19 | border: 1px solid $__color_line2;
20 | border-radius: $__radius_1;
21 | transition: box-shadow 0.3s, border-color 0.2s;
22 | position: relative;
23 |
24 | &::placeholder {
25 | color: mix($__color_text3, $__color_white, 70%);
26 | }
27 |
28 | &:hover {
29 | border-color: mix($__color_primary, $__color_white, 40%);
30 | }
31 |
32 | &:focus {
33 | border-color: mix($__color_primary, $__color_white, 80%);
34 | box-shadow: 0 0 6px 0 mix($__color_second, rgba(0, 0, 0, 0), 40%);
35 | }
36 | }
37 |
38 | @mixin size($size, $font-size, $height, $padding1, $padding2) {
39 | &.--#{$size} {
40 | font-size: $font-size;
41 | height: $height;
42 | line-height: $height;
43 |
44 | .fa-input__input {
45 | box-sizing: border-box;
46 | line-height: $height;
47 | padding: $padding1 $padding2;
48 | }
49 |
50 | .fa-input__prefix-icon,
51 | .fa-input__suffix-icon,
52 | .fa-input__clearable-icon {
53 | position: absolute;
54 | top: #{($height - $font-size) / 2 + 1px};
55 | color: $__color_text_light4;
56 | }
57 |
58 | &.--prefix-icon {
59 | .fa-input__prefix-icon {
60 | left: #{($padding2 + $font-size) / 2};
61 | }
62 |
63 | .fa-input__input {
64 | padding-left: #{$padding2 + $font-size + 4px};
65 | }
66 | }
67 |
68 | &.--password,
69 | &.--suffix-icon {
70 | .fa-input__suffix-icon {
71 | right: #{($padding2 + $font-size) / 2};
72 | }
73 |
74 | .fa-input__input {
75 | padding-right: #{$padding2 + $font-size + 4px};
76 | }
77 | }
78 |
79 | &.--clearable {
80 | .fa-input__clearable-icon {
81 | transition: opacity 0.2s;
82 | right: #{($padding2 + $font-size) / 2};
83 | cursor: pointer;
84 |
85 | &:hover {
86 | color: $__color_text4;
87 | }
88 | }
89 |
90 | .fa-input__input {
91 | padding-right: #{$padding2 + $font-size + 4px};
92 | }
93 | }
94 |
95 | &.--clearable.--password,
96 | &.--suffix-icon.--password,
97 | &.--suffix-icon.--clearable {
98 | color: red;
99 | .fa-input__clearable-icon {
100 | right: #{($padding2 + $font-size) / 2 + $font-size + 4px};
101 | }
102 |
103 | .fa-input__input {
104 | padding-right: #{$padding2 + ($font-size + 4px) * 2};
105 | }
106 | }
107 | }
108 | }
109 |
110 | @include size(small, 12px, 32px, 0, 10px);
111 | @include size(medium, 14px, 40px, 0, 14px);
112 | @include size(large, 16px, 46px, 0, 18px);
113 |
114 | &.--plain {
115 | .fa-input__input {
116 | border: none;
117 | box-shadow: none;
118 | }
119 | }
120 |
121 | &.--is-error {
122 | .fa-input__input {
123 | border-color: mix($__color_danger, $__color_white, 100%);
124 | }
125 | }
126 |
127 | &.--disabled {
128 | .fa-input__input,
129 | .fa-input__input:hover {
130 | background-color: $__color_text_light1;
131 | border-color: $__color_line2;
132 | cursor: not-allowed;
133 | &::placeholder {
134 | color: $__color_text_light4;
135 | }
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/docs/message.md:
--------------------------------------------------------------------------------
1 | [toc]
2 |
3 | ## Message 消息
4 |
5 | ### 基础用法
6 |
7 | :::demo 通过调用方法唤起消息,通过`type`属性指定消息的类型,提供以下预设
8 |
9 | ```html
10 |
11 | {{item.text}}
17 |
18 |
19 |
61 | ```
62 |
63 | :::
64 |
65 | ### 自定义持续时间
66 |
67 | :::demo 通过`duration`属性控制消息提示的延迟关闭时间
68 |
69 | ```html
70 |
71 | {{item.text}}
77 |
78 |
79 |
110 | ```
111 |
112 | :::
113 |
114 | ### 消息关闭时的回调
115 |
116 | :::demo `onClose`属性方法将会在消息关闭时调用
117 |
118 | ```html
119 |
120 | 关闭后的回调
121 | Promise化调用
122 |
123 |
124 |
153 | ```
154 |
155 | :::
156 |
157 | ### message 方法参数
158 |
159 | | 参数 | 说明 | required | 类型 | 默认值 |
160 | | -------- | ------------ | -------- | --------------------------------------------------------- | --------- |
161 | | content | 消息内容 | 是 | string | - |
162 | | type | 类型 | 否 | `default` `primary` `second` `success` `danger` `warning` | `default` |
163 | | duration | 持续时间/ms | 否 | boolean | `3000` |
164 | | onClose | 关闭时的回调 | 否 | function | - |
165 |
--------------------------------------------------------------------------------
/packages/message/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent, createApp, App, PropType, onMounted, onBeforeMount, ref } from 'vue';
2 | import Icon from '@/packages/icon';
3 | import './index.scss';
4 |
5 | const Message = defineComponent({
6 | name: 'fa-message',
7 | props: {
8 | rootEl: {
9 | type: Element as PropType,
10 | required: true,
11 | },
12 | containerEl: {
13 | type: Element as PropType,
14 | required: true,
15 | },
16 | type: {
17 | type: String,
18 | default: 'default',
19 | },
20 | content: {
21 | type: String as PropType,
22 | required: true,
23 | },
24 | duration: {
25 | type: Number,
26 | default: 3000,
27 | },
28 | resolve: {
29 | type: Function,
30 | required: false,
31 | },
32 | onClose: {
33 | type: Function,
34 | required: false,
35 | },
36 | },
37 | setup(props) {
38 | const { rootEl, containerEl, content, type, duration, resolve, onClose } = props;
39 | const iconMap: { [key: string]: string } = {
40 | default: 'info',
41 | success: 'check',
42 | warning: 'alert-circle',
43 | danger: 'x-circle',
44 | primary: 'info',
45 | second: 'info',
46 | };
47 | const mounted = ref(false);
48 | const timeout = ref();
49 | const delayClose = () => {
50 | timeout.value = setTimeout(() => {
51 | mounted.value = false;
52 | resolve && resolve();
53 | onClose && onClose();
54 | setTimeout(() => {
55 | containerEl.removeChild(rootEl);
56 | }, 300);
57 | }, duration);
58 | };
59 | const onMouseenter = () => {
60 | timeout.value && clearTimeout(timeout.value);
61 | };
62 | const onMouseleave = () => {
63 | delayClose();
64 | };
65 | onBeforeMount(() => {
66 | containerEl.appendChild(rootEl);
67 | });
68 | onMounted(() => {
69 | setTimeout(() => {
70 | mounted.value = true;
71 | delayClose();
72 | }, 0);
73 | });
74 |
75 | return () => {
76 | return (
77 |
88 |
92 |
93 | {content}
94 |
95 |
96 | );
97 | };
98 | },
99 | });
100 |
101 | /**
102 | * 获取挂载节点
103 | */
104 | function getMountElm(): HTMLDivElement {
105 | const tarElm = document.createElement('div');
106 | tarElm.setAttribute('data-prop', '__fa-message-root__');
107 | return tarElm;
108 | }
109 |
110 | export interface MessageConfig {
111 | type?: 'primary' | 'second' | 'success' | 'warning' | 'danger';
112 | content: string;
113 | /**
114 | * 持续时间
115 | */
116 | duration?: number;
117 | onClose?: () => void;
118 | }
119 |
120 | /**
121 | * 用于唤起消息的方法
122 | */
123 | function messageMethod(config: MessageConfig) {
124 | return new Promise((resolve) => {
125 | const rootEl = getMountElm();
126 | createApp(Message, {
127 | ...config,
128 | resolve,
129 | rootEl,
130 | containerEl: getContainerElm(),
131 | }).mount(rootEl);
132 | });
133 | }
134 |
135 | /**
136 | * 以插件方式注入全局方法
137 | */
138 | function install(app: App) {
139 | getContainerElm();
140 | app.config.globalProperties.$message = messageMethod;
141 | }
142 |
143 | function getContainerElm(): Element {
144 | let container = document.getElementById('__fa-message-container__');
145 | if (!container) {
146 | const elm = document.createElement('div');
147 | elm.setAttribute('id', '__fa-message-container__');
148 | container = elm;
149 | document.querySelector('body')?.appendChild(elm);
150 | }
151 | return container;
152 | }
153 |
154 | export const message = messageMethod;
155 |
156 | export default {
157 | name: Message.name,
158 | plugin: { install },
159 | };
160 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "fast-ui",
3 | "version": "1.0.0",
4 | "description": "🌈 一个基于Vue3.x的UI组件库",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "cross-env NODE_ENV=production node ./build/build-demo.js",
8 | "build:analyzer": "cross-env NODE_ENV=production ANALYZER=true node ./build/build-demo.js",
9 | "dev": "cross-env NODE_ENV=development webpack-dev-server --inline --progress --config ./build/webpack.dev.js",
10 | "test": "jest",
11 | "lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx",
12 | "commit": "git-cz --disable-emoji",
13 | "demo:icon": "node ./bin/generate-icon.js"
14 | },
15 | "husky": {
16 | "hooks": {
17 | "pre-commit": "lint-staged",
18 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
19 | }
20 | },
21 | "lint-staged": {
22 | "*.{ts,tsx,js}": "eslint --fix"
23 | },
24 | "config": {
25 | "commitizen": {
26 | "path": "git-cz"
27 | }
28 | },
29 | "repository": {
30 | "type": "git",
31 | "url": "git+https://github.com/Mario34/fast.git"
32 | },
33 | "keywords": [
34 | "vue3.x"
35 | ],
36 | "author": "Mario34",
37 | "license": "MIT",
38 | "bugs": {
39 | "url": "https://github.com/Mario34/fast/issues"
40 | },
41 | "homepage": "https://github.com/Mario34/fast-ui#readme",
42 | "devDependencies": {
43 | "@ant-design-vue/babel-plugin-jsx": "^1.0.0-beta.4",
44 | "@babel/core": "^7.10.5",
45 | "@babel/preset-env": "^7.10.4",
46 | "@babel/preset-typescript": "^7.10.4",
47 | "@commitlint/cli": "^9.1.1",
48 | "@commitlint/config-conventional": "^9.1.1",
49 | "@types/jest": "^26.0.9",
50 | "@types/uuid": "^8.3.0",
51 | "@types/webpack-env": "^1.15.2",
52 | "@typescript-eslint/eslint-plugin": "^3.7.1",
53 | "@typescript-eslint/parser": "^3.7.1",
54 | "@vue/compiler-sfc": "^3.0.0",
55 | "@vue/eslint-config-standard": "^5.1.2",
56 | "@vue/eslint-config-typescript": "^5.0.2",
57 | "@vue/test-utils": "^2.0.0-beta.5",
58 | "add": "^2.0.6",
59 | "autoprefixer": "^9.8.6",
60 | "babel-loader": "^8.1.0",
61 | "chalk": "^4.1.0",
62 | "clean-webpack-plugin": "^3.0.0",
63 | "cross-env": "^7.0.2",
64 | "css-loader": "^4.0.0",
65 | "cssnano": "^4.1.10",
66 | "eslint": "^7.5.0",
67 | "eslint-config-standard": "^14.1.1",
68 | "eslint-plugin-import": "^2.22.0",
69 | "eslint-plugin-jest": "^23.20.0",
70 | "eslint-plugin-node": "^11.1.0",
71 | "eslint-plugin-promise": "^4.2.1",
72 | "eslint-plugin-react": "^7.20.5",
73 | "eslint-plugin-standard": "^4.0.1",
74 | "eslint-plugin-vue": "^6.2.2",
75 | "file-loader": "^6.0.0",
76 | "fork-ts-checker-webpack-plugin": "^5.0.11",
77 | "friendly-errors-webpack-plugin": "^1.7.0",
78 | "git-cz": "^4.7.0",
79 | "html-webpack-plugin": "^4.3.0",
80 | "husky": "^4.2.5",
81 | "jest": "^26.3.0",
82 | "lint-staged": "^10.2.11",
83 | "markdown-it": "^11.0.0",
84 | "markdown-it-anchor": "^5.3.0",
85 | "markdown-it-chain": "^1.3.0",
86 | "markdown-it-container": "^3.0.0",
87 | "markdown-it-toc-done-right": "^4.1.0",
88 | "mini-css-extract-plugin": "^0.9.0",
89 | "optimize-css-assets-webpack-plugin": "^5.0.3",
90 | "ora": "^4.0.5",
91 | "postcss-loader": "^3.0.0",
92 | "sass": "^1.26.10",
93 | "sass-loader": "^9.0.2",
94 | "style-loader": "^1.2.1",
95 | "stylelint": "^13.6.1",
96 | "stylelint-config-standard": "^20.0.0",
97 | "stylelint-scss": "^3.18.0",
98 | "transliteration": "^2.1.11",
99 | "ts-jest": "^26.2.0",
100 | "typescript": "^3.9.7",
101 | "uglifyjs-webpack-plugin": "^2.2.0",
102 | "url-loader": "^4.1.0",
103 | "vue-jest": "^5.0.0-alpha.4",
104 | "vue-loader": "^16.0.0-beta.7",
105 | "vue-template-compiler": "^2.6.11",
106 | "webpack": "^4.44.0",
107 | "webpack-bundle-analyzer": "^3.8.0",
108 | "webpack-cli": "^3.3.12",
109 | "webpack-dev-server": "^3.11.0",
110 | "webpack-merge": "^5.0.9"
111 | },
112 | "dependencies": {
113 | "feather-icons": "^4.28.0",
114 | "highlight.js": "^10.1.2",
115 | "mitt": "^2.1.0",
116 | "normalize.css": "^8.0.1",
117 | "uuid": "^8.3.0",
118 | "vue": "^3.0.0",
119 | "vue-router": "^4.0.0-beta.10"
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/demo/components/doc-layout/index.scss:
--------------------------------------------------------------------------------
1 | @import "@/demo/styles/variables.scss";
2 |
3 | @mixin code-style {
4 | background-color: rgba(255, 255, 255, 0.7);
5 | color: #445368;
6 | font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier,
7 | monospace;
8 | }
9 |
10 | .doc-page {
11 | &__container {
12 | display: flex;
13 | height: calc(100vh - 60px);
14 | overflow: auto;
15 | }
16 |
17 | code:not(.hljs) {
18 | color: $__color_text4;
19 | background-color: mix($__color_white, $__color_primary, 93%);
20 | font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier,
21 | monospace;
22 | margin: 0 4px;
23 | display: inline-block;
24 | padding: 1px 5px;
25 | font-size: 12px;
26 | border-radius: 3px;
27 | height: 18px;
28 | line-height: 18px;
29 | }
30 |
31 | &__router-view {
32 | flex: 1;
33 | overflow: auto;
34 | height: 100%;
35 | display: flex;
36 | position: relative;
37 |
38 | &-doc {
39 | flex: 1;
40 | position: relative;
41 | }
42 | }
43 |
44 | &__catalogue {
45 | width: 220px;
46 | height: 100%;
47 | }
48 | }
49 |
50 | .doc-content {
51 | padding: 50px 20px 40px;
52 | min-height: calc(100vh - 60px);
53 | box-sizing: border-box;
54 | width: 1000px;
55 | margin: 0 auto;
56 |
57 | .doc-toc {
58 | position: fixed;
59 | right: 0;
60 | top: 80px;
61 | width: 210px;
62 | border: 1px solid $__color_line2;
63 | max-height: calc(100vh - 140px);
64 | overflow: auto;
65 | margin-right: 20px;
66 |
67 | &__list {
68 | padding: 12px 10px;
69 | }
70 |
71 | &__item {
72 | padding: 12px 0;
73 | }
74 |
75 | &__link {
76 | color: $__color_text3;
77 | text-decoration: none;
78 | line-height: 20px;
79 | font-size: 12px;
80 |
81 | &:hover {
82 | color: $__color_second;
83 | cursor: pointer;
84 | }
85 | }
86 | }
87 |
88 | h2 {
89 | font-size: 28px;
90 | color: $__color_text2;
91 | margin: 0;
92 | }
93 |
94 | h3 {
95 | font-size: 22px;
96 | }
97 |
98 | p,
99 | h2,
100 | h3,
101 | h4,
102 | h5 {
103 | font-weight: normal;
104 | color: $__color_text1;
105 |
106 | .header-anchor {
107 | opacity: 0;
108 | }
109 |
110 | &:hover .header-anchor {
111 | opacity: 0.5;
112 | }
113 |
114 | a {
115 | color: $__color_second;
116 | opacity: 1;
117 | cursor: pointer;
118 |
119 | &:hover {
120 | opacity: 0.8;
121 | }
122 | }
123 | }
124 |
125 | p {
126 | font-size: 14px;
127 | color: $__color_text3;
128 | line-height: 1.5em;
129 | }
130 |
131 | .tip {
132 | padding: 8px 16px;
133 | background-color: #ecf8ff;
134 | border-radius: 4px;
135 | border-left: #50bfff 5px solid;
136 | margin: 20px 0;
137 |
138 | code {
139 | @include code-style();
140 | }
141 | }
142 |
143 | .warning {
144 | padding: 8px 16px;
145 | background-color: #fff6f7;
146 | border-radius: 4px;
147 | border-left: #fe6c6f 5px solid;
148 | margin: 20px 0;
149 |
150 | code {
151 | @include code-style();
152 | }
153 | }
154 |
155 | > {
156 | h3 {
157 | margin: 55px 0 20px;
158 | }
159 |
160 | table {
161 | border-collapse: collapse;
162 | width: 100%;
163 | background-color: #fff;
164 | font-size: 14px;
165 | margin-bottom: 45px;
166 | line-height: 1.5em;
167 | border: 1px solid $__color_line3;
168 |
169 | strong {
170 | font-weight: normal;
171 | }
172 |
173 | thead {
174 | background-color: #f9f9f9;
175 | border-top: #f0f0f0;
176 | }
177 |
178 | td,
179 | th {
180 | border-bottom: 1px solid #dcdfe6;
181 | padding: 15px;
182 | max-width: 250px;
183 | font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier,
184 | monospace;
185 | }
186 |
187 | th {
188 | text-align: left;
189 | white-space: nowrap;
190 | color: #909399;
191 | font-weight: normal;
192 | }
193 |
194 | td {
195 | color: #606266;
196 | }
197 |
198 | th:first-child,
199 | td:first-child {
200 | padding-left: 10px;
201 | font-weight: bold;
202 | }
203 | }
204 |
205 | ul:not(.timeline) {
206 | margin: 10px 0;
207 | padding: 0 0 0 20px;
208 | font-size: 14px;
209 | color: #5e6d82;
210 | line-height: 2em;
211 | }
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/packages/button/index.scss:
--------------------------------------------------------------------------------
1 | @import "../styles/variables.scss";
2 | @import "../styles/mixins.scss";
3 |
4 | .fa-button {
5 | outline: none;
6 | box-shadow: none;
7 | height: 40px;
8 | padding: 0 12px;
9 | border-radius: $__radius_2;
10 | font-weight: bold;
11 | font-size: 14px;
12 | color: $__color_text4;
13 | background-color: $__color_white;
14 | cursor: pointer;
15 | transition: background-color 0.1s;
16 | box-sizing: border-box;
17 | border: 1px solid mix($__color_text4, #f5f5f5, 10%);
18 | display: inline-block;
19 | line-height: 1;
20 |
21 | &.--disabled {
22 | cursor: not-allowed;
23 | }
24 |
25 | & + & {
26 | margin-left: 10px;
27 | }
28 |
29 | &:hover {
30 | background-color: mix($__color_white, $__color_primary, 96%);
31 | }
32 |
33 | &:active {
34 | background-color: mix($__color_black, $__color_white, 4%);
35 | }
36 |
37 | .fa-button__loading {
38 | vertical-align: -0.125em;
39 | height: 1em;
40 | width: 1em;
41 | display: inline-block;
42 | box-sizing: border-box;
43 | border-radius: 0.5em;
44 | border-right: 2px solid $__color_text_light1;
45 | border-bottom: 2px solid $__color_text_light1;
46 | border-top: 2px solid $__color_text_light1;
47 | border-left: 2px solid transparent;
48 | animation: rotate 0.6s infinite linear;
49 | }
50 |
51 | &.--default {
52 | &.--loading,
53 | &.--disabled {
54 | color: $__color_text_light4;
55 | background-color: $__color_text_light2;
56 |
57 | .fa-button__loading {
58 | border-color: #ccc #ccc #ccc transparent;
59 | }
60 | }
61 | }
62 |
63 | @mixin type-color($color, $type) {
64 | &.--#{$type} {
65 | color: $__color_white;
66 | background-color: $color;
67 | border: 1px solid transparent;
68 |
69 | &:hover {
70 | background-color: mix($__color_white, $color, 10%);
71 | }
72 |
73 | &:active {
74 | background-color: mix($__color_black, $color, 4%);
75 | }
76 |
77 | &.--disabled,
78 | &.--loading {
79 | background-color: mix($__color_white, $color, 50%);
80 | color: $__color_text_light1;
81 |
82 | .fa-button__loading {
83 | border-color: $__color_text_light1 $__color_text_light1
84 | $__color_text_light1 transparent;
85 | }
86 | }
87 |
88 | &.--plain {
89 | background-color: #fff;
90 | border: 1px solid mix($__color_white, $color, 40%);
91 | color: mix($__color_white, $color, 10%);
92 |
93 | &:hover {
94 | background-color: mix($__color_white, $color, 90%);
95 | }
96 |
97 | &:active {
98 | background-color: mix($__color_white, $color, 86%);
99 | }
100 |
101 | &.--disabled,
102 | &.--loading {
103 | background-color: #fff;
104 | border: 1px solid mix($__color_white, $color, 80%);
105 | color: mix($__color_white, $color, 40%);
106 |
107 | .fa-button__loading {
108 | border-color: mix($__color_white, $color, 40%)
109 | mix($__color_white, $color, 40%) mix($__color_white, $color, 40%)
110 | transparent;
111 | }
112 | }
113 | }
114 | }
115 | }
116 |
117 | @mixin size($size, $font-size, $height, $padding) {
118 | &.--#{$size} {
119 | font-size: $font-size;
120 | height: $height;
121 | padding: $padding;
122 |
123 | &.--circle {
124 | width: $height;
125 | border-radius: 50%;
126 | padding: 0;
127 |
128 | @include single-overflow(auto);
129 | }
130 |
131 | &.--round {
132 | border-radius: #{$height/2};
133 | }
134 | }
135 | }
136 |
137 | &.--text {
138 | color: $__color_primary;
139 | background-color: transparent;
140 | border: 1px solid transparent;
141 |
142 | &:hover {
143 | background-color: mix($__color_black, transparent, 2%);
144 | }
145 |
146 | &:active {
147 | background-color: mix($__color_black, transparent, 4%);
148 | }
149 | }
150 |
151 | @include type-color($__color_primary, primary);
152 | @include type-color($__color_second, second);
153 | @include type-color($__color_success, success);
154 | @include type-color($__color_warning, warning);
155 | @include type-color($__color_danger, danger);
156 |
157 | @include size(small, 12px, 32px, 0 14px);
158 | @include size(medium, 14px, 40px, 0 18px);
159 | @include size(large, 16px, 46px, 0 22px);
160 |
161 | @keyframes rotate {
162 | 0% {
163 | transform: rotate(0);
164 | }
165 |
166 | 100% {
167 | transform: rotate(360deg);
168 | }
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/docs/checkbox.md:
--------------------------------------------------------------------------------
1 | [toc]
2 |
3 | ## CheckBox 多选框
4 |
5 | ### 基础用法
6 |
7 | :::demo 单个多选框绑定值为`Boolean`,一般用来控制开关
8 |
9 | ```html
10 |
11 | 选项A
12 |
13 |
14 |
28 | ```
29 |
30 | :::
31 |
32 | ### 多选框组
33 |
34 | :::demo 多选框组绑定值为一个数组,由选中状态 checkbox 的`label`组成
35 |
36 | ```html
37 |
38 |
39 | {{'选项' + item}}
42 |
43 |
44 |
45 |
60 | ```
61 |
62 | :::
63 |
64 | ### 禁用状态
65 |
66 | :::demo `disabled`控制是否禁用,`checkbox-group`禁用选项开启时,子组件中的`checkbox`也会被禁用
67 |
68 | ```html
69 |
70 | 是否禁用
71 |
72 |
73 | 选项1
74 | 选项2
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
100 | ```
101 |
102 | :::
103 |
104 | ### indeterminate 状态
105 |
106 | 参考 MDM 文档 [https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/Input/checkbox](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/Input/checkbox)
107 |
108 | :::demo `indeterminate`指定 checkbox 处于不定状态
109 |
110 | ```html
111 |
112 | 全选
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
150 | ```
151 |
152 | :::
153 |
154 | ### Checkbox Props
155 |
156 | | 参数 | 说明 | required | 类型 | 默认值 |
157 | | ------------------ | ---------------- | -------- | --------------------- | ------- |
158 | | modelValue/v-model | 绑定值 | 否 | boolean | `false` |
159 | | label | 配合选项组时使用 | 时 | boolean/number/string | - |
160 | | disabled | 禁用状态 | 否 | boolean | `false` |
161 | | indeterminate | 不定状态 | 否 | boolean | `false` |
162 |
163 | ### Checkbox Event
164 |
165 | | 参数 | 说明 | 参数 |
166 | | ----------------- | --------------------------- | ------- |
167 | | update:modelValue | 触发默认 v-model 绑定值更新 | boolean |
168 | | change | 选项发生改变 | boolean |
169 | | ... | 其他原生事件 | `Event` |
170 |
171 | ### CheckboxGroup Props
172 |
173 | | 参数 | 说明 | required | 类型 | 默认值 |
174 | | ------------------ | -------- | -------- | ------- | ------- |
175 | | modelValue/v-model | 绑定值 | 否 | array | [] |
176 | | disabled | 禁用状态 | 否 | boolean | `false` |
177 |
178 | ### CheckboxGroup Event
179 |
180 | | 参数 | 说明 | 参数 |
181 | | ----------------- | --------------------------- | ------------------------- |
182 | | update:modelValue | 触发默认 v-model 绑定值更新 | label |
183 | | change | 选项发生改变 | [{label,checked},[...labels]] |
184 | | ... | 其他原生事件 | `Event` |
185 |
--------------------------------------------------------------------------------
/packages/input/index.tsx:
--------------------------------------------------------------------------------
1 | import { defineComponent, toRefs, ref, watch } from 'vue';
2 | import Icon from '@/packages/icon';
3 | import './index.scss';
4 |
5 | export interface InputProps extends CustomEleProps {
6 | value?: string | number;
7 | placeholder: string | number;
8 | type?: 'text' | 'password' | 'textarea';
9 | maxlength?: number;
10 | clearable?: boolean;
11 | passwordSwitch?: boolean;
12 | disabled?: boolean;
13 | size?: 'large' | 'middle' | 'small';
14 | /**
15 | * 头部 icon
16 | */
17 | prefixIcon?: string;
18 | /**
19 | * 尾部 icon
20 | */
21 | suffixIcon?: string;
22 | isError?: boolean;
23 | plain?: boolean;
24 | }
25 |
26 | const Input = defineComponent({
27 | name: 'fa-input',
28 | inheritAttrs: false,
29 | props: {
30 | value: {
31 | type: [String, Number],
32 | default: '',
33 | },
34 | placeholder: {
35 | type: [String, Number],
36 | default: '',
37 | },
38 | type: {
39 | type: String,
40 | default: 'text',
41 | },
42 | maxlength: {
43 | type: Number,
44 | },
45 | clearable: {
46 | type: Boolean,
47 | default: false,
48 | },
49 | passwordSwitch: {
50 | type: Boolean,
51 | default: false,
52 | },
53 | disabled: {
54 | type: Boolean,
55 | default: false,
56 | },
57 | size: {
58 | type: String,
59 | default: 'medium',
60 | },
61 | prefixIcon: {
62 | type: String,
63 | },
64 | suffixIcon: {
65 | type: String,
66 | },
67 | isError: {
68 | type: Boolean,
69 | default: false,
70 | },
71 | plain: {
72 | type: Boolean,
73 | default: false,
74 | },
75 | },
76 | emits: ['input', 'update:value', 'change', 'clear'],
77 | setup(props, ctx) {
78 | const {
79 | value,
80 | placeholder,
81 | type,
82 | maxlength,
83 | clearable,
84 | disabled,
85 | passwordSwitch,
86 | size,
87 | prefixIcon,
88 | suffixIcon,
89 | isError,
90 | plain,
91 | } = toRefs(props);
92 | const currentValue = ref('');
93 | const inputRef = ref();
94 | const currentType = ref(type.value);
95 | const onInput = (e: Event) => {
96 | const target = e.target as HTMLInputElement;
97 | currentValue.value = target.value;
98 | ctx.emit('input', target.value);
99 | ctx.emit('update:value', target.value);
100 | };
101 | const onChange = (e: Event) => {
102 | const target = e.target as HTMLInputElement;
103 | ctx.emit('change', target.value);
104 | };
105 | const onClear = () => {
106 | inputRef.value.focus();
107 | currentValue.value = '';
108 | ctx.emit('clear');
109 | ctx.emit('update:value', '');
110 | };
111 | const isEmpty = (val: string | null | undefined | number) => val === '' || val === undefined || val === null;
112 |
113 | watch(type, (curr) => {
114 | currentType.value = curr;
115 | });
116 |
117 | return () => (
118 |
134 | {
135 | prefixIcon?.value && (
136 |
140 | )
141 | }
142 | inputRef.value = n}
148 | type={currentType.value}
149 | value={value?.value}
150 | onChange={onChange}
151 | onInput={onInput}
152 | {...ctx.attrs}
153 | />
154 | {
155 | clearable.value && !isEmpty(currentValue.value) && (
156 |
161 | )
162 | }
163 | {
164 | suffixIcon?.value && !passwordSwitch.value && (
165 |
169 | )
170 | }
171 | {
172 | passwordSwitch.value && (
173 | {
177 | currentType.value = currentType.value === 'password' ? 'text' : 'password';
178 | }}
179 | />
180 | )
181 | }
182 |
183 | );
184 | },
185 | });
186 |
187 | export default {
188 | name: Input.name,
189 | component: Input,
190 | };
191 |
--------------------------------------------------------------------------------
/docs/button.md:
--------------------------------------------------------------------------------
1 | [toc]
2 |
3 | ## Button 按钮
4 |
5 | ### 基础用法
6 |
7 | :::demo 使用`type`来控制按钮的类型,使用`plain`来控制是否为朴素按钮,使用`round`来控制是否为圆角按钮,使用`circle`来控制是否为圆形按钮
8 |
9 | ```html
10 |
11 | 主要按钮
12 | 次要按钮
13 | 安全按钮
14 | 危险按钮
15 | 警告按钮
16 | 默认按钮
17 |
18 |
19 | 主要按钮
20 | 次要按钮
21 | 安全按钮
22 | 危险按钮
23 | 警告按钮
24 | 默认按钮
25 |
26 |
27 | 默认按钮
28 | 默认按钮
29 |
30 |
31 |
32 |
33 | ```
34 |
35 | :::
36 |
37 | ### 禁用状态
38 |
39 | :::demo 使用`disabled`来控制按钮的禁用状态
40 |
41 | ```html
42 |
43 | 主要按钮
44 | 次要按钮
45 | 安全按钮
46 | 危险按钮
47 | 警告按钮
48 | 默认按钮
49 |
50 |
51 | 主要按钮
52 | 次要按钮
53 | 安全按钮
54 | 危险按钮
55 | 警告按钮
56 | 默认按钮
57 |
58 | ```
59 |
60 | :::
61 |
62 | ### 加载状态
63 |
64 | :::demo 使用`loading`来控制按钮的禁用状态
65 |
66 | ```html
67 |
68 | 主要按钮
69 | 次要按钮
70 | 安全按钮
71 | 危险按钮
72 | 警告按钮
73 | 默认按钮
74 |
75 |
76 | 主要按钮
77 | 次要按钮
78 | 安全按钮
79 | 危险按钮
80 | 警告按钮
81 | 默认按钮
82 |
83 | ```
84 |
85 | :::
86 |
87 | ### 按钮尺寸
88 |
89 | :::demo 使用`size`来控制按钮的类型 `'small'|'medium'|'large'`,默认值为`medium`
90 |
91 | ```html
92 | 小按钮
93 | 默认按钮
94 | 大按钮
95 | ```
96 |
97 | :::
98 |
99 | ### 带图标按钮
100 |
101 | :::demo 使用`icon`给按钮添加图标,同时也可以直接在 button 内部添加``实现
102 |
103 | ```html
104 | 赞
105 |
106 | 23
107 | 24
108 | 收藏
109 | ```
110 |
111 | :::
112 |
113 | ### 文字按钮
114 |
115 | :::demo 文字按钮一般适用于非重要性操作
116 |
117 | ```html
118 | 取消
119 | 下一页
120 | 查看详情
121 | ```
122 |
123 | :::
124 |
125 | ### 按钮组
126 |
127 | :::demo 使用`button-group`包裹多个`button`实现按钮组
128 |
129 | ```html
130 |
131 |
132 | 按钮1
133 | 按钮2
134 | 按钮3
135 | 按钮4
136 | 按钮5
137 |
138 |
139 |
140 |
141 | 按钮1
142 | 按钮2
143 | 按钮3
144 | 按钮4
145 | 按钮5
146 |
147 |
148 | ```
149 |
150 | :::
151 |
152 | ### Props
153 |
154 | | 参数 | 说明 | required | 类型 | 默认值 |
155 | | -------- | -------- | -------- | ---------------------------------------------------------------- | --------- |
156 | | type | 颜色类型 | 否 | `default` `primary` `second` `success` `danger` `warning` `text` | `default` |
157 | | plain | 朴素风格 | 否 | boolean | `false` |
158 | | round | 圆角按钮 | 否 | boolean | `false` |
159 | | circle | 圆形按钮 | 否 | boolean | `false` |
160 | | icon | 按钮图标 | 否 | string | - |
161 | | disabled | 禁用状态 | 否 | boolean | `false` |
162 | | loading | 加载状态 | 否 | boolean | `false` |
163 | | size | 尺寸 | 否 | `large` `middle` `small` | `middle` |
164 |
165 | ### Event
166 |
167 | | 参数 | 说明 | 参数 |
168 | | ----- | ------------ | ------- |
169 | | click | 点击事件 | `Event` |
170 | | ... | 其他原生事件 | `Event` |
171 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | extends: [
7 | 'eslint:recommended',
8 | '@vue/standard',
9 | '@vue/typescript/recommended',
10 | 'plugin:react/recommended',
11 | 'plugin:@typescript-eslint/recommended'
12 | ],
13 | parserOptions: {
14 | ecmaVersion: 2020
15 | },
16 | rules: {
17 | "@typescript-eslint/member-delimiter-style": [
18 | "error",
19 | {
20 | "multiline": {
21 | "delimiter": "semi",
22 | "requireLast": true
23 | },
24 | "singleline": {
25 | "delimiter": "semi",
26 | "requireLast": false
27 | }
28 | }
29 | ],
30 | '@typescript-eslint/no-unused-vars': [
31 | 1,
32 | {
33 | "vars": "all",
34 | "args": "after-used",
35 | "ignoreRestSiblings": false
36 | }
37 | ],
38 |
39 | // 试验性 🛠
40 | 'react/react-in-jsx-scope': 0,
41 | 'react/no-unknown-property': 0,
42 | 'react/display-name': 0,
43 | 'react/jsx-filename-extension': [
44 | 1,
45 | { 'extensions': ['.js', '.jsx', '.tsx'] },
46 | ],
47 | 'react/jsx-filename-extension': [
48 | 1,
49 | { 'extensions': ['.js', '.jsx', '.tsx'] },
50 | ],
51 | 'react/jsx-indent-props': [1, 2],
52 | 'react/jsx-indent': [1, 2, {
53 | 'checkAttributes': true,
54 | 'indentLogicalExpressions': true,
55 | }],
56 | 'react/jsx-wrap-multilines': [1, {
57 | 'declaration': 'parens-new-line',
58 | 'assignment': 'parens-new-line',
59 | 'return': 'parens-new-line',
60 | 'arrow': 'parens-new-line',
61 | 'condition': 'ignore',
62 | 'logical': 'ignore',
63 | 'prop': 'ignore',
64 | }],
65 | 'react/jsx-closing-bracket-location': [1, {
66 | 'nonEmpty': 'tag-aligned',
67 | 'selfClosing': 'tag-aligned',
68 | }],
69 | 'jsx-quotes': [2, 'prefer-single'],
70 | 'react/jsx-closing-tag-location': [1],
71 | 'react/sort-comp': [1, {
72 | 'order': [
73 | 'weapp',
74 | 'static-variables',
75 | 'static-methods',
76 | 'lifecycle',
77 | 'everything-else',
78 | 'render',
79 | ],
80 | 'groups': {
81 | 'weapp': ['config'],
82 | },
83 | }],
84 | 'react/jsx-tag-spacing': [1, {
85 | 'closingSlash': 'never',
86 | 'beforeSelfClosing': 'always',
87 | 'afterOpening': 'never',
88 | 'beforeClosing': 'allow',
89 | }],
90 | 'react/jsx-max-props-per-line': [1, {
91 | 'maximum': 1,
92 | 'when': 'multiline',
93 | }],
94 | 'react/jsx-first-prop-new-line': [1, 'multiline'],
95 | 'react/jsx-sort-props': [
96 | 1,
97 | {
98 | 'callbacksLast': true,
99 | 'shorthandFirst': true,
100 | 'ignoreCase': false,
101 | 'noSortAlphabetically': false,
102 | },
103 | ],
104 | 'react/jsx-no-duplicate-props': [2, { 'ignoreCase': false }],
105 |
106 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
107 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
108 | 'no-tabs': 2,
109 | 'no-mixed-spaces-and-tabs': 2,
110 | 'indent': [1, 2],
111 | 'comma-dangle': [
112 | 'error',
113 | {
114 | 'arrays': 'always-multiline',
115 | 'objects': 'always-multiline',
116 | 'imports': 'always-multiline',
117 | 'exports': 'always-multiline',
118 | 'functions': 'always-multiline',
119 | },
120 | ],
121 | 'comma-spacing': [2, { 'before': false, 'after': true }],
122 | 'comma-style': [2, 'last'],
123 | 'semi': [2, 'always'],
124 | 'semi-spacing': [2, { 'before': false, 'after': true }],
125 | '@typescript-eslint/explicit-module-boundary-types': [0],
126 | 'quotes': [2, 'single', { 'avoidEscape': true, 'allowTemplateLiterals': true }],
127 | // 代码风格
128 | 'accessor-pairs': 2,
129 | 'arrow-spacing': [2, { 'before': true, 'after': true }],
130 | 'block-spacing': [2, 'always'],
131 | 'camelcase': [0, { 'properties': 'always' }],
132 | 'dot-location': [2, 'property'],
133 | 'eqeqeq': ['error', 'always', { 'null': 'ignore' }],
134 | 'key-spacing': [2, { 'beforeColon': false, 'afterColon': true }],
135 | 'keyword-spacing': [2, { 'before': true, 'after': true }],
136 | 'new-cap': [2, { 'newIsCap': true, 'capIsNew': false }],
137 | 'no-console': [2, { 'allow': ['warn', 'error'] }],
138 | 'no-eval': 1,
139 | 'no-extend-native': 2, // 禁止扩展原生对象
140 | 'no-floating-decimal': 2,
141 | 'no-implied-eval': 2,
142 | 'no-multi-spaces': 2,
143 | 'no-multiple-empty-lines': [2, { 'max': 1 }],
144 | 'no-return-assign': [0, 'except-parens'],
145 | 'func-call-spacing': 2,
146 | 'no-trailing-spaces': 1,
147 | 'no-unmodified-loop-condition': 2,
148 | 'no-unneeded-ternary': [2, { 'defaultAssignment': false }], // 三元运算符号优化(实验)
149 | 'no-whitespace-before-property': 2,
150 | 'operator-linebreak': [
151 | 2,
152 | 'after',
153 | { 'overrides': { '?': 'before', ':': 'before' } },
154 | ],
155 | 'space-before-blocks': [2, 'always'],
156 | 'space-before-function-paren': [2, 'never'],
157 | 'space-infix-ops': 1,
158 | 'space-unary-ops': [2, { 'words': true, 'nonwords': false }],
159 | 'spaced-comment': [
160 | 2,
161 | 'always',
162 | {
163 | 'markers': [
164 | 'global',
165 | 'globals',
166 | 'eslint',
167 | 'eslint-disable',
168 | '*package',
169 | '!',
170 | ',',
171 | ],
172 | },
173 | ],
174 | 'wrap-iife': [2, 'any'],
175 | 'prefer-const': 2,
176 | 'no-debugger': 0,
177 | 'object-curly-spacing': [2, 'always', { 'objectsInObjects': false }],
178 | 'array-bracket-spacing': [2, 'never'],
179 | },
180 | overrides: [
181 | {
182 | files: [
183 | '**/__tests__/*.{j,t}s?(x)',
184 | '**/tests/unit/**/*.spec.{j,t}s?(x)'
185 | ],
186 | env: {
187 | mocha: true
188 | }
189 | }
190 | ]
191 | }
192 |
--------------------------------------------------------------------------------
/docs/input.md:
--------------------------------------------------------------------------------
1 | [toc]
2 |
3 | ## Input 输入框
4 |
5 | ### 基础用法
6 |
7 | :::demo
8 |
9 | ```html
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
28 | ```
29 |
30 | :::
31 |
32 | ### 限制长度
33 |
34 | :::demo 使用`maxlength`属性控制输入框内容的最大长度
35 |
36 | ```html
37 |
38 |
39 |
45 |
46 |
47 |
53 |
54 |
55 |
56 |
66 | ```
67 |
68 | :::
69 |
70 | ### 带图标
71 |
72 | :::demo 使用`prefixIcon`属性配置输入框头部的 icon,使用`suffixIcon`属性配置输入框尾部的 icon
73 |
74 | ```html
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | ```
84 |
85 | :::
86 |
87 | ### 可清除内容
88 |
89 | :::demo 使用`clearable`属性控制输入框显示清除按钮
90 |
91 | ```html
92 |
93 |
94 |
95 |
96 |
97 |
103 |
104 |
105 |
106 |
116 | ```
117 |
118 | :::
119 |
120 | ### 密码输入框
121 |
122 | :::demo 使用`type`属性置为`password`,开启密码框功能;使用`passwordSwitch`属性开启密码隐藏/显示开关
123 |
124 | ```html
125 |
126 |
127 |
133 |
134 |
135 |
142 |
143 |
144 |
145 |
155 | ```
156 |
157 | :::
158 |
159 | ### 错误状态
160 |
161 | :::demo 使用`isError`属性控制输入框是否为错误状态
162 |
163 | ```html
164 |
165 |
166 |
173 |
174 |
175 |
182 |
183 |
184 |
185 |
195 | ```
196 |
197 | :::
198 |
199 | ### 不同尺寸
200 |
201 | :::demo 使用`size`属性配置输入框的尺寸,默认为`meduim`
202 |
203 | ```html
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 | ```
216 |
217 | :::
218 |
219 | ### 无边框输入框
220 |
221 | :::demo 使用`plain`属性控制输入框是否无标框
222 |
223 | ```html
224 |
225 |
226 |
233 |
234 |
235 |
242 |
243 |
244 |
245 |
255 | ```
256 |
257 | :::
258 |
259 | ### Props
260 |
261 | | 参数 | 说明 | required | 类型 | 默认值 |
262 | | -------------- | ------------------------ | -------- | ---------------------------- | ------- |
263 | | value | 绑定值 | 是 | string/number | - |
264 | | placeholder | 占位字符 | 否 | string | - |
265 | | type | 输入框类型 | 否 | `text` `textarea` `password` | `text` |
266 | | maxlength | 原生属性 | 否 | number | - |
267 | | clearable | 可清除的(显示清除按钮) | 否 | boolean | `false` |
268 | | size | 尺寸 | 否 | `large` `middle` `small` | - |
269 | | passwordSwitch | 是否显示切换密码显示 | 否 | boolean | `false` |
270 | | disabled | 禁用 | 否 | boolean | `false` |
271 | | prefixIcon | 头部 icon | 否 | string | - |
272 | | prefixIcon | 尾部 icon | 否 | string | - |
273 | | isError | 错误状态 | 否 | boolean | `false` |
274 | | border | 待边框 | 否 | boolean | `true` |
275 |
--------------------------------------------------------------------------------