├── .env.docs
├── .browserslistrc
├── babel.config.js
├── tests
└── unit
│ ├── .eslintrc.js
│ ├── __mocks__
│ └── popper.js.js
│ ├── Button.spec.js
│ ├── FormBuilder.spec.js
│ ├── Radio.spec.js
│ ├── data
│ └── formSchema.js
│ ├── Checkbox.spec.js
│ ├── Input.spec.js
│ └── Select.spec.js
├── postcss.config.js
├── src
├── assets
│ └── fonts
│ │ ├── icomoon.ttf
│ │ └── icomoon.woff
├── utils
│ ├── index.js
│ └── directives.js
├── scss
│ ├── _fonts.scss
│ ├── _variables.scss
│ └── _mixins.scss
└── components
│ ├── checkbox
│ ├── CheckboxGroup.vue
│ └── Checkbox.vue
│ ├── index.js
│ ├── form
│ ├── FormItem.vue
│ └── Form.vue
│ ├── select
│ ├── Option.vue
│ └── Select.vue
│ ├── button
│ └── Button.vue
│ ├── radio
│ └── Radio.vue
│ ├── from-builder
│ └── FormBuilder.vue
│ ├── input
│ └── Input.vue
│ └── popper
│ └── Popper.vue
├── dist
├── demo.html
└── vfc.css
├── example
├── main.js
├── App.vue
├── assets
│ ├── variables.scss
│ ├── logo.svg
│ ├── main.scss
│ └── normalize.scss
├── router.js
├── components
│ ├── Tabs
│ │ ├── TabsItem.vue
│ │ └── Tabs.vue
│ └── CarbonAd.vue
├── navigation.js
├── views
│ ├── Home.vue
│ └── Page.vue
├── index.html
└── util.js
├── dev
├── main.js
├── App.vue
└── index.html
├── .travis.yml
├── .gitignore
├── public
└── docs
│ ├── CHANGELOG.md
│ ├── install.md
│ ├── button.md
│ ├── radio.md
│ ├── checkbox.md
│ ├── input.md
│ ├── select.md
│ ├── form.md
│ └── form-builder.md
├── .eslintrc.js
├── jest.config.js
├── vue.config.js
├── LICENSE
├── README.md
└── package.json
/.env.docs:
--------------------------------------------------------------------------------
1 | NODE_ENV=docs
2 |
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not ie <= 8
4 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/app'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/tests/unit/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | jest: true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | autoprefixer: {}
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/assets/fonts/icomoon.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonreshetov/vue-form-components/HEAD/src/assets/fonts/icomoon.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/icomoon.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/antonreshetov/vue-form-components/HEAD/src/assets/fonts/icomoon.woff
--------------------------------------------------------------------------------
/dist/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
vfc demo
3 |
4 |
5 |
6 |
7 |
8 |
11 |
--------------------------------------------------------------------------------
/example/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import router from './router'
3 | import App from './App.vue'
4 |
5 | Vue.config.productionTip = false
6 |
7 | new Vue({
8 | render: h => h(App),
9 | router
10 | }).$mount('#app')
11 |
--------------------------------------------------------------------------------
/dev/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import VFC from '../src/components'
4 |
5 | Vue.config.productionTip = false
6 | Vue.use(VFC)
7 |
8 | new Vue({
9 | render: h => h(App)
10 | }).$mount('#app')
11 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | export function cloneShallow (obj) {
2 | return Object.keys(obj).reduce((acc, prop) => {
3 | if (Array.isArray(obj[prop])) {
4 | acc[prop] = obj[prop].slice(0)
5 | } else {
6 | acc[prop] = obj[prop]
7 | }
8 | return acc
9 | }, {})
10 | }
11 |
--------------------------------------------------------------------------------
/tests/unit/__mocks__/popper.js.js:
--------------------------------------------------------------------------------
1 | import PopperJs from 'popper.js'
2 |
3 | export default class Popper {
4 | static placements = PopperJs.placements
5 |
6 | constructor () {
7 | return {
8 | destroy: () => {},
9 | scheduleUpdate: () => {}
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | languange: node_js
2 | node_js:
3 | - stable
4 | script:
5 | - npm install
6 | - npm run build:docs
7 | deploy:
8 | provider: pages
9 | skip-cleanup: true
10 | github-token: $GITHUB_TOKEN
11 | keep-history: true
12 | local_dir: docs
13 | on:
14 | branch: dev
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /docs
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 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw*
22 |
--------------------------------------------------------------------------------
/public/docs/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## `2.0.0`
4 |
5 | ### BREAKING
6 | - Drop async validation
7 | - Add VeeValidate for form validation
8 |
9 | ### ADD
10 | - Form Builder
11 |
12 | ## `1.1.0`
13 |
14 | ### ADD
15 | - Collapse tags props in Select
16 |
17 | ## `1.0.0`
18 |
19 | First release
20 |
--------------------------------------------------------------------------------
/example/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
20 |
21 |
24 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | 'extends': [
7 | 'plugin:vue/recommended',
8 | '@vue/standard'
9 | ],
10 | rules: {
11 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
12 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
13 | },
14 | parserOptions: {
15 | parser: 'babel-eslint'
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/dev/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Workbench
4 |
5 |
6 |
7 |
18 |
19 |
26 |
--------------------------------------------------------------------------------
/example/assets/variables.scss:
--------------------------------------------------------------------------------
1 | /*------------------------------------*/
2 | /* COLORS */
3 | /*------------------------------------*/
4 | $color-primary: #498aed;
5 | $color-success: #68cc68;
6 | $color-warning: #efc669;
7 | $color-danger: #e4474e;
8 | $color-text-regular: #303133;
9 | $color-text-faded: lighten($color-text-regular, 45%);
10 | $color-text-faded-low: lighten($color-text-regular, 30%);
11 | $color-grey: #dddddd;
12 | $color-grey-light: #fafafa;
--------------------------------------------------------------------------------
/example/router.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 | import Home from './views/Home.vue'
4 | import Page from './views/Page.vue'
5 |
6 | Vue.use(Router)
7 |
8 | export default new Router({
9 | linkActiveClass: 'active',
10 | routes: [
11 | {
12 | path: '/',
13 | name: 'home',
14 | component: Home
15 | },
16 | {
17 | path: '/components/:component',
18 | component: Page,
19 | meta: 'docs'
20 | },
21 | {
22 | path: '/changelog',
23 | component: Page
24 | }
25 | ]
26 | })
27 |
--------------------------------------------------------------------------------
/example/components/Tabs/TabsItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
27 |
28 |
31 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | moduleFileExtensions: [
3 | 'js',
4 | 'jsx',
5 | 'json',
6 | 'vue'
7 | ],
8 | transform: {
9 | '^.+\\.vue$': 'vue-jest',
10 | '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
11 | '^.+\\.jsx?$': 'babel-jest'
12 | },
13 | transformIgnorePatterns: [
14 | '/node_modules/'
15 | ],
16 | moduleNameMapper: {
17 | '^@/(.*)$': '/src/$1'
18 | },
19 | snapshotSerializers: [
20 | 'jest-serializer-vue'
21 | ],
22 | testMatch: [
23 | '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
24 | ],
25 | testURL: 'http://localhost/'
26 | }
27 |
--------------------------------------------------------------------------------
/example/navigation.js:
--------------------------------------------------------------------------------
1 | export default {
2 | development: [
3 | {
4 | title: 'Install',
5 | path: '/components/install'
6 | }
7 | ],
8 | components: [
9 | {
10 | title: 'Input',
11 | path: '/components/input'
12 | },
13 | {
14 | title: 'Select',
15 | path: '/components/select'
16 | },
17 | {
18 | title: 'Checkbox',
19 | path: '/components/checkbox'
20 | },
21 | {
22 | title: 'Radio',
23 | path: '/components/radio'
24 | },
25 | {
26 | title: 'Button',
27 | path: '/components/button'
28 | },
29 | {
30 | title: 'Form',
31 | path: '/components/form'
32 | },
33 | {
34 | title: 'Form Builder',
35 | path: '/components/form-builder'
36 | }
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | const isDev = process.env.NODE_ENV === 'development'
2 | const isDocs = process.env.APP_TARGET === 'docs'
3 |
4 | module.exports = {
5 | publicPath: isDocs ? '/vue-form-components/' : '/',
6 |
7 | chainWebpack: config => {
8 | config.entryPoints.delete('app')
9 |
10 | if (isDocs) {
11 | config
12 | .entry('docs')
13 | .add('./example/main.js')
14 | .end()
15 | .plugin('html')
16 | .tap(args => {
17 | args[0].template = './example/index.html'
18 | return args
19 | })
20 | }
21 | if (isDev) {
22 | config
23 | .entry('dev')
24 | .add('./dev/main.js')
25 | .end()
26 | .plugin('html')
27 | .tap(args => {
28 | args[0].template = './dev/index.html'
29 | return args
30 | })
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/dev/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
11 | Vue Form Component - Development
12 |
13 |
14 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/scss/_fonts.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'icomoon';
3 | src: url('~@/assets/fonts/icomoon.ttf') format('truetype'),
4 | url('~@/assets/fonts/icomoon.woff') format('woff');
5 | font-weight: normal;
6 | font-style: normal;
7 | }
8 |
9 | [class^='icon-'],
10 | [class*=' icon-'] {
11 | /* use !important to prevent issues with browser extensions that change fonts */
12 | font-family: 'icomoon' !important;
13 | font-style: normal;
14 | font-weight: normal;
15 | font-variant: normal;
16 | text-transform: none;
17 | line-height: 1;
18 |
19 | /* Better Font Rendering =========== */
20 | -webkit-font-smoothing: antialiased;
21 | -moz-osx-font-smoothing: grayscale;
22 | }
23 |
24 | .icon-check:before {
25 | content: '\e900';
26 | }
27 | .icon-chevron-down:before {
28 | content: '\e901';
29 | }
30 | .icon-close:before {
31 | content: '\e902';
32 | }
33 |
--------------------------------------------------------------------------------
/src/utils/directives.js:
--------------------------------------------------------------------------------
1 | export const clickOutside = {
2 | bind (el, binding, vNode) {
3 | if (typeof binding.value !== 'function') {
4 | const compName = vNode.context.name
5 |
6 | let warn = `provided expression '${
7 | binding.expression
8 | }' is not a function, but has to be`
9 |
10 | if (compName) warn += `Found in component '${compName}'`
11 |
12 | console.warn('[v-click-outside]', warn)
13 | }
14 |
15 | const bubble = binding.modifiers.bubble
16 | const handler = e => {
17 | if (bubble || (!el.contains(e.target) && el !== e.target)) {
18 | binding.value(e)
19 | }
20 | }
21 | el.$_vfcClickOutside_ = handler
22 |
23 | document.addEventListener('click', handler)
24 | },
25 |
26 | unbind (el, binding) {
27 | document.removeEventListener('click', el.$_vfcClickOutside_)
28 | el.$_vfcClickOutside_ = null
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/scss/_variables.scss:
--------------------------------------------------------------------------------
1 | /*------------------------------------*/
2 | /* COLORS */
3 | /*------------------------------------*/
4 | $color-primary: #498aed;
5 | $color-success: #68cc68;
6 | $color-warning: #efc669;
7 | $color-danger: #e4474e;
8 | $color-text-regular: #303133;
9 | $color-text-faded: lighten($color-text-regular, 45%);
10 | $color-text-faded-low: lighten($color-text-regular, 30%);
11 | $color-grey: #dddddd;
12 | $color-grey-dark: #aaa;
13 | $color-grey-light: #f6f6f6;
14 | $color-border: $color-grey;
15 | /*------------------------------------*/
16 | /* FONTS */
17 | /*------------------------------------*/
18 | $font: Helvetica, Arial, sans-serif;
19 | /*------------------------------------*/
20 | /* FROM */
21 | /*------------------------------------*/
22 | $input-font-size: 14px;
23 | $input-height: 40px;
24 | $input-inner-padding: 0 15px;
25 | $input-border-radius: 4px;
26 | $input-border: 1px solid $color-border;
--------------------------------------------------------------------------------
/src/components/checkbox/CheckboxGroup.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
59 |
--------------------------------------------------------------------------------
/public/docs/install.md:
--------------------------------------------------------------------------------
1 | # Install
2 |
3 | ## NPM
4 |
5 | Installing with npm is recommended and it works seamlessly with webpack.
6 |
7 | ```js
8 | npm i vfc
9 | ```
10 |
11 | ## Quick start
12 |
13 | ### Global
14 |
15 | To use in your project, just import vfc and install into Vue.
16 |
17 | ```js
18 | import Vue from 'vue'
19 | import App from './App.vue'
20 | import VFC from 'vfc'
21 | import 'vfc/dist/vfc.css'
22 |
23 | Vue.use(VFC)
24 |
25 | new Vue({
26 | render: h => h(App)
27 | }).$mount('#app')
28 | ```
29 |
30 | ### On demand
31 |
32 | ```html
33 |
34 |
35 |
36 |
37 |
47 | ```
48 |
49 | ```js
50 | import {
51 | Input,
52 | Button,
53 | Checkbox,
54 | CheckboxGroup,
55 | Radio,
56 | Select,
57 | Option,
58 | Form,
59 | FormItem,
60 | FormBuilder
61 | } from 'vfc'
62 | ```
63 |
64 | ## License
65 |
66 | MIT © 2018-present [Anton Reshetov](http://antonreshetov.com)
67 |
--------------------------------------------------------------------------------
/example/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |

6 |
7 |
Vue Form Components
8 |
Clean & minimal vue form elements with validation
9 |
Get started
10 |
11 |
12 |
13 |
14 |
25 |
26 |
49 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018-present Anton Reshetov
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.
--------------------------------------------------------------------------------
/example/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | import Input from './input/Input'
2 | import Button from './button/Button'
3 | import Checkbox from './checkbox/Checkbox.vue'
4 | import CheckboxGroup from './checkbox/CheckboxGroup.vue'
5 | import Radio from './radio/Radio.vue'
6 | import Select from './select/Select.vue'
7 | import Option from './select/Option.vue'
8 | import Form from './form/Form.vue'
9 | import FormItem from './form/FormItem.vue'
10 | import VeeValidate from 'vee-validate'
11 | import FormBuilder from './from-builder/FormBuilder.vue'
12 |
13 | const components = [
14 | Input,
15 | Button,
16 | Checkbox,
17 | CheckboxGroup,
18 | Radio,
19 | Select,
20 | Option,
21 | Form,
22 | FormItem,
23 | FormBuilder
24 | ]
25 |
26 | export default {
27 | install (Vue, options = {}) {
28 | let veeValidateOptions = {
29 | events: 'change|input|blur'
30 | }
31 |
32 | if (options.veeValidate) {
33 | veeValidateOptions = Object.assign(veeValidateOptions, options.veeValidate)
34 | }
35 |
36 | Vue.use(VeeValidate, veeValidateOptions)
37 |
38 | components.forEach(component => {
39 | Vue.component(component.name, component)
40 | })
41 | }
42 | }
43 |
44 | export {
45 | Input,
46 | Button,
47 | Checkbox,
48 | CheckboxGroup,
49 | Radio,
50 | Select,
51 | Option,
52 | Form,
53 | FormItem,
54 | FormBuilder
55 | }
56 |
--------------------------------------------------------------------------------
/src/scss/_mixins.scss:
--------------------------------------------------------------------------------
1 | /*------------------------------------*/
2 | /* FORM */
3 | /*------------------------------------*/
4 | @mixin form-input-default() {
5 | box-sizing: border-box;
6 | -webkit-appearance: none;
7 | border-radius: $input-border-radius;
8 | outline: none;
9 | border: $input-border;
10 | line-height: $input-height;
11 | padding: $input-inner-padding;
12 | height: $input-height;
13 | background-color: transparent;
14 | font-size: 14px;
15 | color: $color-text-regular;
16 | transition: border-color 0.3s;
17 | &:focus {
18 | border-color: $color-primary;
19 | transition: border-color 0.3s;
20 | }
21 | &::-webkit-input-placeholder {
22 | color: $color-text-faded;
23 | }
24 | }
25 | /*------------------------------------*/
26 | /* BUTTON */
27 | /*------------------------------------*/
28 | @mixin button-hover-active($bg-color, $text-color, $lighten-amount, $darken-amount) {
29 | background-color: $bg-color;
30 | border-color: $bg-color;
31 | color: $text-color;
32 | &:hover {
33 | background-color: lighten($bg-color, $lighten-amount);
34 | }
35 | &:active {
36 | background-color: darken($bg-color, $darken-amount);
37 | }
38 | }
39 | @mixin button-disabled($main-color, $lighten-amount) {
40 | background-color: lighten($main-color, $lighten-amount);
41 | border-color: lighten($main-color, $lighten-amount);
42 | cursor: no-drop;
43 | }
44 |
--------------------------------------------------------------------------------
/tests/unit/Button.spec.js:
--------------------------------------------------------------------------------
1 | import { shallowMount } from '@vue/test-utils'
2 | import Button from '../../src/components/button/Button.vue'
3 |
4 | describe('Button.vue', () => {
5 | it('is rendered', () => {
6 | const wrapper = shallowMount(Button, {
7 | propsData: {
8 | type: 'primary',
9 | disabled: false
10 | }
11 | })
12 | expect(wrapper.exists()).toBe(true)
13 | expect(wrapper.props().type).toBe('primary')
14 | expect(wrapper.props().disabled).toBe(false)
15 | })
16 | it('all types is rendered', () => {
17 | const wrapper = shallowMount(Button)
18 | const types = ['primary', 'success', 'warning', 'danger']
19 | types.forEach(type => {
20 | wrapper.setProps({ type })
21 | expect(wrapper.find(`.vue-button--${type}`))
22 | })
23 | })
24 | it('is disabled', () => {
25 | const wrapper = shallowMount(Button, {
26 | propsData: {
27 | disabled: true
28 | }
29 | })
30 | expect(wrapper.attributes().disabled).toBe('disabled')
31 | })
32 | it('default slot is rendered', () => {
33 | const wrapper = shallowMount(Button, {
34 | slots: {
35 | default: 'text'
36 | }
37 | })
38 | expect(wrapper.text()).toBe('text')
39 | })
40 | it('emitted "click"', () => {
41 | const wrapper = shallowMount(Button)
42 | wrapper.trigger('click')
43 | expect(wrapper.emitted().click).toBeTruthy()
44 | })
45 | })
46 |
--------------------------------------------------------------------------------
/example/assets/main.scss:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Roboto:100,300,400,500');
2 | @import url('https://fonts.googleapis.com/css?family=Fira+Mono');
3 | @import './normalize';
4 | @import './variables';
5 |
6 | html,
7 | body {
8 | font-family: 'Helvetica Neue', 'Roboto';
9 | font-weight: 400;
10 | font-size: 14px;
11 | color: $color-text-regular;
12 | }
13 |
14 | pre {
15 | padding: 0;
16 | border: none;
17 | font-family: 'Fira Mono', monospace;
18 | }
19 |
20 | code {
21 | border-radius: 3px;
22 | font-family: 'Fira Mono', monospace;
23 | background-color: #eee;
24 | padding: 0 5px;
25 | }
26 |
27 | h1 {
28 | font-size: 36px;
29 | }
30 |
31 | h2 {
32 | font-size: 26px;
33 | margin-top: 40px;
34 | }
35 |
36 | h1,
37 | h2,
38 | h3 {
39 | font-weight: 400 !important;
40 | > a {
41 | color: inherit;
42 | text-decoration: none;
43 | &:hover {
44 | text-decoration: none;
45 | color: inherit;
46 | }
47 | }
48 | }
49 |
50 | h2 {
51 | .anchor {
52 | position: relative;
53 | cursor: pointer;
54 | &::before {
55 | content: '#';
56 | position: absolute;
57 | left: -25px;
58 | color: #ccc;
59 | transition: all 0.3s;
60 | }
61 | &:hover {
62 | &::before {
63 | color: $color-primary;
64 | }
65 | }
66 | }
67 | }
68 |
69 | p {
70 | -webkit-font-smoothing: antialiased;
71 | }
72 |
73 | .app {
74 | max-width: 1140px;
75 | margin: 0 auto;
76 | }
77 |
--------------------------------------------------------------------------------
/example/components/CarbonAd.vue:
--------------------------------------------------------------------------------
1 |
30 |
31 |
67 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
11 | Vue Form Component
12 |
13 |
14 |
15 |
16 |
19 |
20 |
21 | <% if (process.env.NODE_ENV === 'production') { %>
22 |
33 | <% } %>
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/components/form/FormItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
24 |
25 |
26 |
56 |
57 |
70 |
--------------------------------------------------------------------------------
/tests/unit/FormBuilder.spec.js:
--------------------------------------------------------------------------------
1 | import { mount, createLocalVue } from '@vue/test-utils'
2 | import flushPromises from 'flush-promises'
3 | import FormBuilder from '../../src/components/from-builder/FormBuilder.vue'
4 | import schema from './data/formSchema'
5 | import VeeValidate from 'vee-validate'
6 |
7 | const localVue = createLocalVue()
8 | localVue.use(VeeValidate)
9 |
10 | describe('FormBuilder', () => {
11 | const wrapper = mount(FormBuilder, {
12 | localVue,
13 | propsData: {
14 | model: {
15 | id: 1,
16 | name: 'John Doe',
17 | password: '123',
18 | passwordConfirm: '123',
19 | skills: [1],
20 | email: 'john.do@gmail.com',
21 | status: true,
22 | addons: [1, 3],
23 | delivery: 1,
24 | comment: 'some text'
25 | },
26 | schema
27 | }
28 | })
29 | it('rendered all fields from schema', () => {
30 | expect(wrapper.exists()).toBe(true)
31 | schema.fields.map(f => {
32 | if (f.type !== 'actions') {
33 | expect(wrapper.find(`[name="${f.name}"]`).exists()).toBe(true)
34 | } else {
35 | f.buttons.map((b, i) => {
36 | expect(wrapper.findAll('.vue-button').at(i).exists()).toBe(true)
37 | })
38 | }
39 | })
40 | })
41 | it('check validation', async () => {
42 | const input = wrapper.find('[name="delivery"]')
43 | expect(wrapper.vm.errors.count()).toBe(0)
44 | wrapper.setData({ clonedModel: { ...wrapper.vm.clonedModel, delivery: null } })
45 | input.trigger('input')
46 | await flushPromises()
47 | expect(wrapper.vm.errors.count()).toBe(1)
48 | })
49 | })
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Vue Form Components
5 |
6 |
7 |
8 |
9 |
10 |
11 | ## Documentation
12 |
13 | [https://antonreshetov.github.io/vue-form-components](https://antonreshetov.github.io/vue-form-components/)
14 |
15 | ## Install
16 |
17 | ### NPM
18 |
19 | Installing with npm is recommended and it works seamlessly with webpack.
20 |
21 | ```js
22 | npm i vfc
23 | ```
24 |
25 | ### Download
26 |
27 | You can download latest version from the Github: Download
28 |
29 | ## Quick start
30 |
31 | ### Global
32 |
33 | To use in your project, just import vfc and install into Vue.
34 |
35 | ```js
36 | import Vue from 'vue'
37 | import App from './App.vue'
38 | import VFC from 'vfc'
39 | import 'vfc/dist/vfc.css'
40 |
41 | Vue.use(VFC)
42 |
43 | new Vue({
44 | render: h => h(App)
45 | }).$mount('#app')
46 | ```
47 |
48 | ### On demand
49 |
50 | ```html
51 |
52 |
53 |
54 |
55 |
65 | ```
66 |
67 | Full component list:
68 |
69 | ```js
70 | import {
71 | Input,
72 | Button,
73 | Checkbox,
74 | CheckboxGroup,
75 | Radio,
76 | Select,
77 | Option,
78 | Form,
79 | FormItem,
80 | FormBuilder
81 | } from 'vfc'
82 | ```
83 |
84 | ## License
85 |
86 | MIT © 2018-present [Anton Reshetov](http://antonreshetov.com)
87 |
--------------------------------------------------------------------------------
/src/components/form/Form.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
41 |
42 |
91 |
--------------------------------------------------------------------------------
/src/components/select/Option.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 | {{ label }}
13 |
14 |
15 |
16 |
65 |
66 |
92 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vfc",
3 | "description": "Vue form components with validation",
4 | "version": "1.1.2",
5 | "private": false,
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/antonreshetov/vue-form-components.git"
9 | },
10 | "files": [
11 | "dist",
12 | "src"
13 | ],
14 | "keywords": [
15 | "vue",
16 | "vuejs",
17 | "vue-component",
18 | "vue-form",
19 | "components",
20 | "form",
21 | "form-validation"
22 | ],
23 | "main": "dist/vfc.common.js",
24 | "style": "dist/vfc.css",
25 | "license": "MIT",
26 | "scripts": {
27 | "serve": "vue-cli-service serve",
28 | "serve:docs": "APP_TARGET=docs vue-cli-service serve --mode docs",
29 | "build:docs": "APP_TARGET=docs vue-cli-service build --dest docs",
30 | "build": "vue-cli-service build --target lib --name vfc ./src/components/index.js",
31 | "lint": "vue-cli-service lint",
32 | "test:unit": "vue-cli-service test:unit"
33 | },
34 | "dependencies": {
35 | "popper.js": "^1.14.4",
36 | "vee-validate": "^2.2.0",
37 | "vue": "^2.6.0",
38 | "vue-router": "^3.0.1"
39 | },
40 | "devDependencies": {
41 | "@vue/cli-plugin-babel": "^3.5.0",
42 | "@vue/cli-plugin-eslint": "^3.5.0",
43 | "@vue/cli-plugin-unit-jest": "^3.5.0",
44 | "@vue/cli-service": "^3.5.0",
45 | "@vue/eslint-config-standard": "^4.0.0",
46 | "@vue/test-utils": "^1.0.0-beta.20",
47 | "axios": "^0.18.0",
48 | "babel-core": "^7.0.0-bridge.0",
49 | "babel-eslint": "^10.0.1",
50 | "babel-jest": "^23.0.1",
51 | "eslint": "^5.16.0",
52 | "eslint-plugin-vue": "^5.2.2",
53 | "flush-promises": "^1.0.2",
54 | "highlight.js": "^9.12.0",
55 | "lint-staged": "^8.1.5",
56 | "marked": "^0.5.1",
57 | "node-sass": "^4.13.1",
58 | "sass-loader": "^7.1.0",
59 | "vue-svg-loader": "^0.10.0",
60 | "vue-template-compiler": "^2.5.17"
61 | },
62 | "gitHooks": {
63 | "pre-commit": "lint-staged"
64 | },
65 | "lint-staged": {
66 | "*.js": [
67 | "vue-cli-service lint",
68 | "git add"
69 | ],
70 | "*.vue": [
71 | "vue-cli-service lint",
72 | "git add"
73 | ]
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/example/components/Tabs/Tabs.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 | {{ label }}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
69 |
70 |
105 |
--------------------------------------------------------------------------------
/public/docs/button.md:
--------------------------------------------------------------------------------
1 | # Button
2 |
3 | Simple button component
4 |
5 | ## Basic usage
6 |
7 | ```example
8 |
9 | Default
10 | Primary
11 | Success
12 | Warning
13 | Danger
14 |
15 |
22 | ```
23 |
24 | ## Disabled
25 |
26 | ```example
27 |
28 |
30 | Default
31 |
32 |
35 | Primary
36 |
37 |
40 | Success
41 |
42 |
45 | Warning
46 |
47 |
50 | Danger
51 |
52 |
53 |
60 | ```
61 |
62 | ## With icon
63 |
64 | ```example
65 |
66 |
67 | Search
68 |
69 |
70 | Edit
71 |
72 |
73 | Delete
74 |
75 |
76 |
83 | ```
84 |
85 | ## Attributes
86 |
87 | | Attributes | Description | Type | Accepted values | Default |
88 | | ---------- | ------------------ | --------- | ----------------------------------------- | ------- |
89 | | `type` | Type of button | `String` | `primary`, `success`, `warning`, `danger` | - |
90 | | `disabled` | Disable the button | `Boolean` | - | `false` |
91 |
92 | ## Events
93 |
94 | | Name | Description | Payload |
95 | | ------- | ---------------------------- | ------- |
96 | | `click` | Triggers when button clicked | - |
97 |
--------------------------------------------------------------------------------
/src/components/button/Button.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
33 |
34 |
97 |
--------------------------------------------------------------------------------
/public/docs/radio.md:
--------------------------------------------------------------------------------
1 | # Radio
2 |
3 | ## Basic usage
4 |
5 | ```example
6 |
7 |
10 | Radio 1
11 |
12 |
15 | Radio 2
16 |
17 |
18 |
27 | ```
28 |
29 | ## Disabled
30 |
31 | ```example
32 |
33 |
37 | Radio 1
38 |
39 |
43 | Radio 2
44 |
45 |
46 |
55 | ```
56 |
57 | ## Bordered
58 |
59 | ```example
60 |
61 |
65 | Radio 1
66 |
67 |
71 | Radio 2
72 |
73 |
78 | Radio 3
79 |
80 |
81 |
90 | ```
91 |
92 | ## Attributes
93 |
94 | | Attributes | Description | Type | Accepted values | Default |
95 | | ---------- | ------------------------------ | ------------------ | --------------- | ------- |
96 | | `value` | Value of radio | `String`, `Number` | - | - |
97 | | `type` | Type of radio | `String` | `border` | - |
98 | | `disabled` | Disable the radio | `Boolean` | - | `false` |
99 | | `label` | Label of the radio | `String` | - | - |
100 | | `name` | Same as `name` in native radio | `String` | - | - |
101 |
102 | ## Events
103 |
104 | | Name | Description | Payload |
105 | | -------- | -------------------------------- | ------- |
106 | | `change` | Triggers when radio change value | `value` |
107 |
--------------------------------------------------------------------------------
/tests/unit/Radio.spec.js:
--------------------------------------------------------------------------------
1 | import { shallowMount, mount } from '@vue/test-utils'
2 | import Radio from '../../src/components/radio/Radio.vue'
3 |
4 | describe('Radio.vue', () => {
5 | it('is rendered', () => {
6 | const wrapper = shallowMount(Radio, {
7 | propsData: {
8 | value: 1,
9 | label: 'label',
10 | type: 'border',
11 | name: 'name',
12 | disabled: false
13 | }
14 | })
15 | expect(wrapper.exists()).toBe(true)
16 | expect(wrapper.props().value).toBe(1)
17 | expect(wrapper.props().label).toBe('label')
18 | expect(wrapper.props().type).toBe('border')
19 | expect(wrapper.props().name).toBe('name')
20 | expect(wrapper.props().disabled).toBe(false)
21 | })
22 | it('label is rendered (label & slot)', () => {
23 | const wrapper = shallowMount(Radio, {
24 | slots: {
25 | default: ''
26 | }
27 | })
28 | expect(wrapper.find('.vue-radio__label').contains('div')).toBe(true)
29 | wrapper.setProps({ label: 'label' })
30 | expect(wrapper.find('.vue-radio__label span').text()).toBe('label')
31 | })
32 | it('is disabled', () => {
33 | const wrapper = shallowMount(Radio, {
34 | propsData: {
35 | disabled: true
36 | }
37 | })
38 | expect(wrapper.find('.vue-radio--disabled').exists()).toBe(true)
39 | })
40 | it('attributes is rendered', () => {
41 | const wrapper = shallowMount(Radio)
42 | wrapper.setProps({ name: 'text', disabled: true })
43 | const attrs = wrapper.find('input').attributes()
44 | expect(attrs.name).toContain('text')
45 | expect(attrs.disabled).toContain('disabled')
46 | })
47 | it('is checked', () => {
48 | const wrapper = mount({
49 | template: '',
50 | components: {
51 | [Radio.name]: Radio
52 | },
53 | data () {
54 | return {
55 | radio: '1'
56 | }
57 | }
58 | })
59 | expect(wrapper.find('.vue-radio--checked').exists()).toBe(true)
60 | })
61 | it('type is border', () => {
62 | const wrapper = shallowMount(Radio, {
63 | propsData: {
64 | type: 'border'
65 | }
66 | })
67 | expect(wrapper.find('.vue-radio--bordered').exists()).toBe(true)
68 | })
69 | it('change event (v-model)', () => {
70 | const wrapper = mount({
71 | template: `
72 |
73 |
74 |
75 |
76 | `,
77 | components: {
78 | [Radio.name]: Radio
79 | },
80 | data () {
81 | return {
82 | radio: '2'
83 | }
84 | }
85 | })
86 | const radioInput = wrapper.find('.vue-radio input')
87 | radioInput.trigger('change')
88 | expect(wrapper.vm.radio).toBe('1')
89 | })
90 | })
91 |
--------------------------------------------------------------------------------
/example/util.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue/dist/vue'
2 | import VFC from '../src/components'
3 | import Tabs from './components/Tabs/Tabs.vue'
4 | import TabsItem from './components/Tabs/TabsItem.vue'
5 | import { escape } from 'he'
6 | import marked from 'marked'
7 |
8 | Vue.use(VFC)
9 | Vue.component('app-tabs', Tabs)
10 | Vue.component('app-tabs-item', TabsItem)
11 |
12 | function sluggify (text) {
13 | return text
14 | .toLowerCase()
15 | .trim()
16 | .replace(/:.*:/g, '')
17 | .replace(/ +$/g, '')
18 | .replace(/(&| & )/g, '-and-')
19 | .replace(/&(.+?);/g, '')
20 | .replace(/[\s\W-]+/g, '-')
21 | }
22 |
23 | export function parse (markdown, cb) {
24 | const renderer = new marked.Renderer({ langPrefix: 'lang-' })
25 | const base = new marked.Renderer({ langPrefix: 'lang-' })
26 | const vms = []
27 | let vm
28 |
29 | const example = code => {
30 | let template = code.match(/(.|\n)*<\/template>/g)
31 | let params = code.match(/export default\s+((.|\s)+(?=<\/script>))/)
32 |
33 | /* eslint-disable no-eval */
34 | params = eval(`params = ${params[1]}`)
35 |
36 | const exampleOptions = Object.assign({}, params, {
37 | template: `${template}
`
38 | })
39 |
40 | const exampleVm = new Vue(exampleOptions).$mount()
41 |
42 | vm = new Vue({
43 | template: `
44 |
45 |
46 |
47 |
48 | ${escape(code)}
49 |
50 |
51 |
52 | `,
53 | data () {
54 | return {
55 | activeTab: 'preview'
56 | }
57 | }
58 | }).$mount()
59 |
60 | vm.$el.querySelector('.app-tabs__item').appendChild(exampleVm.$el)
61 | vms.push(vm)
62 |
63 | return ``
64 | }
65 |
66 | renderer.strong = text => {
67 | return text === 'Note'
68 | ? `${text}`
69 | : `${text}`
70 | }
71 | renderer.list = text => ``
72 | renderer.link = (href, title, text) => {
73 | return href.match(/\.md/)
74 | ? base.link(href.replace(/.md(.*)/, '$1'), title, text)
75 | : base.link(href, title, text)
76 | }
77 | renderer.code = (code, lang, escaped) => {
78 | return lang === 'example'
79 | ? example(code)
80 | : `${base.code(code, lang, escaped)}
`
81 | }
82 | renderer.hr = () => `
`
83 | renderer.table = (header, body) => {
84 | return `
85 | ${header}
86 | ${body}
87 |
`
88 | }
89 | renderer.heading = (text, level) => {
90 | return `${text}`
93 | }
94 |
95 | return [marked(markdown, { renderer }), vms]
96 | }
97 |
--------------------------------------------------------------------------------
/public/docs/checkbox.md:
--------------------------------------------------------------------------------
1 | # Checkbox
2 |
3 | ## Basic usage
4 |
5 | Simple two state `true` and `false` checkbox
6 |
7 | ```example
8 |
9 | Option
10 |
11 |
20 | ```
21 |
22 | ## Disabled
23 |
24 | ```example
25 |
26 | Option
29 |
30 |
31 |
40 | ```
41 |
42 | ## Checkbox group
43 |
44 | Combine multiple checkboxes in group
45 |
46 | ```example
47 |
48 |
49 | Option 1
50 | Option 2
51 |
52 |
53 |
62 | ```
63 |
64 | ## Bordered
65 |
66 | ```example
67 |
68 |
71 | Option
72 |
73 |
77 | Option
78 |
79 |
80 |
90 | ```
91 |
92 | ## Checkbox attributes
93 |
94 | | Attributes | Description | Type | Accepted values | Default |
95 | | ---------- | ----------------------------------------- | ------------------ | --------------- | ------- |
96 | | `value` | Value of checkbox when used in group mode | `String`, `Number` | - | - |
97 | | `type` | Type of checkbox | `String` | `border` | - |
98 | | `disabled` | Disable the checkbox | `Boolean` | - | `false` |
99 | | `label` | Label of the checkbox | `String` | - | - |
100 | | `name` | Same as `name` in native checkbox | `String` | - | - |
101 |
102 | ## Checkbox events
103 |
104 | | Name | Description | Payload |
105 | | -------- | ----------------------------------- | ------- |
106 | | `change` | Triggers when checkbox change value | `value` |
107 |
108 | ## Checkbox group events
109 |
110 | | Name | Description | Payload |
111 | | -------- | ----------------------------------------- | ------- |
112 | | `change` | Triggers when checkbox group change value | `value` |
113 |
--------------------------------------------------------------------------------
/tests/unit/data/formSchema.js:
--------------------------------------------------------------------------------
1 | export default {
2 | fields: [
3 | {
4 | type: 'input',
5 | inputType: 'input',
6 | label: 'ID',
7 | name: 'input',
8 | model: 'id',
9 | readonly: true,
10 | disabled: true,
11 | validate: {
12 | required: true
13 | }
14 | },
15 | {
16 | type: 'input',
17 | inputType: 'password',
18 | label: 'Password',
19 | name: 'password',
20 | placeholder: 'Type password',
21 | model: 'password',
22 | validate: {
23 | required: true
24 | }
25 | },
26 | {
27 | type: 'input',
28 | inputType: 'password',
29 | label: 'Password confirm',
30 | name: 'passwordConfirm',
31 | placeholder: 'Type password',
32 | model: 'passwordConfirm',
33 | validate: {
34 | required: true,
35 | confirmed: 'password'
36 | }
37 | },
38 | {
39 | type: 'select',
40 | label: 'Skills',
41 | model: 'skills',
42 | name: 'skills',
43 | placeholder: 'Select',
44 | options: [
45 | { label: 'label 1', value: 1 },
46 | { label: 'label 2', value: 2 },
47 | { label: 'label 3', value: 3 }
48 | ],
49 | validate: {
50 | required: true,
51 | included: [1, 2]
52 | }
53 | },
54 | {
55 | type: 'input',
56 | inputType: 'input',
57 | label: 'Email',
58 | name: 'email',
59 | placeholder: 'Type email',
60 | model: 'email',
61 | validate: {
62 | required: true,
63 | email: true
64 | }
65 | },
66 | {
67 | type: 'checkbox',
68 | label: 'Status',
69 | name: 'status',
70 | checkboxLabel: 'Some text',
71 | model: 'status',
72 | validate: {
73 | required: [true]
74 | }
75 | },
76 | {
77 | type: 'checkbox',
78 | label: 'Addons',
79 | name: 'addons',
80 | model: 'addons',
81 | options: [
82 | { label: 'label 1', value: 1 },
83 | { label: 'label 2', value: 2 },
84 | { label: 'label 3', value: 3 }
85 | ],
86 | validate: {
87 | required: true
88 | }
89 | },
90 | {
91 | type: 'radio',
92 | label: 'Delivery',
93 | name: 'delivery',
94 | model: 'delivery',
95 | options: [
96 | { label: 'label 1', value: 1 },
97 | { label: 'label 2', value: 2 },
98 | { label: 'label 3', value: 3 }
99 | ],
100 | validate: {
101 | required: true
102 | }
103 | },
104 | {
105 | type: 'input',
106 | inputType: 'textarea',
107 | name: 'comment',
108 | label: 'Comment',
109 | model: 'comment',
110 | validate: {
111 | required: true,
112 | min: 10
113 | }
114 | },
115 | {
116 | type: 'actions',
117 | buttons: [
118 | {
119 | type: 'cancel',
120 | buttonType: 'default',
121 | buttonLabel: 'Cancel'
122 | },
123 | {
124 | type: 'submit',
125 | buttonType: 'success',
126 | buttonLabel: 'Submit'
127 | }
128 | ]
129 | }
130 | ],
131 | formOptions: {
132 | labelPosition: 'right',
133 | labelWidth: '120px'
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/components/radio/Radio.vue:
--------------------------------------------------------------------------------
1 |
2 |
27 |
28 |
29 |
87 |
88 |
159 |
--------------------------------------------------------------------------------
/tests/unit/Checkbox.spec.js:
--------------------------------------------------------------------------------
1 | import { shallowMount, mount } from '@vue/test-utils'
2 | import Checkbox from '../../src/components/checkbox/Checkbox.vue'
3 | import CheckboxGroup from '../../src/components/checkbox/CheckboxGroup.vue'
4 |
5 | describe('Checkbox.vue', () => {
6 | it('is rendered', () => {
7 | const wrapper = shallowMount(Checkbox, {
8 | propsData: {
9 | name: 'name',
10 | label: 'label',
11 | value: true,
12 | type: 'border',
13 | disabled: false
14 | }
15 | })
16 | expect(wrapper.exists()).toBe(true)
17 | expect(wrapper.props().name).toBe('name')
18 | expect(wrapper.props().label).toBe('label')
19 | expect(wrapper.props().value).toBe(true)
20 | expect(wrapper.props().type).toBe('border')
21 | expect(wrapper.props().disabled).toBe(false)
22 | })
23 | it('label is rendered', () => {
24 | const wrapper = shallowMount(Checkbox, {
25 | propsData: {
26 | label: 'label'
27 | }
28 | })
29 | expect(wrapper.find('.vue-checkbox__label span').text()).toBe('label')
30 | })
31 | it('attributes is rendered', () => {
32 | const wrapper = shallowMount(Checkbox)
33 | wrapper.setProps({ name: 'text', disabled: true })
34 | const attrs = wrapper.find('input').attributes()
35 | expect(attrs.name).toContain('text')
36 | expect(attrs.disabled).toContain('disabled')
37 | })
38 | it('is checked', () => {
39 | const wrapper = mount({
40 | template: ``,
41 | components: {
42 | [Checkbox.name]: Checkbox
43 | },
44 | data () {
45 | return {
46 | check: true
47 | }
48 | }
49 | })
50 | expect(wrapper.find('.vue-checkbox--checked').exists()).toBe(true)
51 | expect(wrapper.find('.icon-check').exists()).toBe(true)
52 | })
53 | it('is disabled', () => {
54 | const wrapper = shallowMount(Checkbox, {
55 | propsData: {
56 | disabled: true
57 | }
58 | })
59 | expect(wrapper.find('.vue-checkbox--disabled').exists()).toBe(true)
60 | })
61 | it('type is border', () => {
62 | const wrapper = shallowMount(Checkbox, {
63 | propsData: {
64 | type: 'border'
65 | }
66 | })
67 | expect(wrapper.find('.vue-checkbox--bordered').exists()).toBe(true)
68 | })
69 | })
70 |
71 | describe('CheckboxGroup.vue', () => {
72 | it('is rendered with checkbox and checked initially', () => {
73 | const wrapper = mount({
74 | template: `
75 |
76 | Option
77 |
78 | `,
79 | components: {
80 | [Checkbox.name]: Checkbox,
81 | [CheckboxGroup.name]: CheckboxGroup
82 | },
83 | data () {
84 | return {
85 | group: ['1']
86 | }
87 | }
88 | })
89 | expect(wrapper.find('.vue-checkbox-group').exists()).toBe(true)
90 | expect(wrapper.find('.vue-checkbox').exists()).toBe(true)
91 | expect(wrapper.find('.vue-checkbox__label').text()).toBe('Option')
92 | expect(wrapper.find('.vue-checkbox--checked').exists()).toBe(true)
93 | expect(wrapper.find('.icon-check').exists()).toBe(true)
94 | })
95 | it('change event (v-model)', () => {
96 | const wrapper = mount({
97 | template: `
98 |
99 | Option
100 |
101 | `,
102 | components: {
103 | [Checkbox.name]: Checkbox,
104 | [CheckboxGroup.name]: CheckboxGroup
105 | },
106 | data () {
107 | return {
108 | group: []
109 | }
110 | }
111 | })
112 | const checkboxInput = wrapper.find('.vue-checkbox input')
113 | checkboxInput.trigger('change')
114 | expect(wrapper.vm.group[0]).toBe('1')
115 | expect(wrapper.find('.vue-checkbox--checked').exists()).toBe(true)
116 | expect(wrapper.find('.icon-check').exists()).toBe(true)
117 | })
118 | })
119 |
--------------------------------------------------------------------------------
/public/docs/input.md:
--------------------------------------------------------------------------------
1 | # Input
2 |
3 | Input data component
4 |
5 | ## Basic usage
6 |
7 | ```example
8 |
9 |
10 |
11 |
18 | ```
19 |
20 | ## Disabled
21 |
22 | ```example
23 |
24 |
27 |
28 |
29 |
36 | ```
37 |
38 | ## Prefix or suffix
39 |
40 | You can add for example icons
41 |
42 | ```example
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
62 | ```
63 |
64 | ## Prepend or append
65 |
66 | ```example
67 |
68 |
69 |
70 | http://
71 |
72 |
73 |
74 |
75 | .com
76 |
77 |
78 |
79 |
86 | ```
87 |
88 | ## As textarea
89 |
90 | ```example
91 |
92 |
95 |
96 |
97 |
104 | ```
105 |
106 | ## Attributes
107 |
108 | | Attributes | Description | Type | Accepted values | Default |
109 | | -------------- | ---------------------------------------------------------------------- | ------------------ | ---------------------- | ------- |
110 | | `type` | Type of input | `String` | text, number, textarea | text |
111 | | `value` | Binding value | `String`, `Number` | - | - |
112 | | `disabled` | Disable the input | `Boolean` | - | `false` |
113 | | `placeholder` | Placeholder of value | `String` | - | - |
114 | | `autocomplete` | Same as `autocomplete` in native input | `Boolean` | - | `false` |
115 | | `name` | Same as `name` in native input | `String` | - | - |
116 | | `readonly` | Same as `readonly` in native input | `Boolean` | - | `false` |
117 | | `min` | Same as `min` in native input | `Number` | - | - |
118 | | `max` | Same as `max` in native input | `Number` | - | - |
119 | | `rows` | Same as `rows` in native textarea, only work when `type` is `textarea` | `Number` | - | - |
120 |
121 | ## Input slots
122 |
123 | Only works when type is `text`
124 |
125 | | Name | Description |
126 | | --------- | ------------------------------- |
127 | | `prefix` | Content as Input prefix |
128 | | `suffix` | Content as Input sufix |
129 | | `prepend` | Content to prepend before Input |
130 | | `append` | Content to append before Input |
131 |
--------------------------------------------------------------------------------
/tests/unit/Input.spec.js:
--------------------------------------------------------------------------------
1 | import { shallowMount, mount } from '@vue/test-utils'
2 | import Input from '../../src/components/input/Input.vue'
3 |
4 | const defaultProps = {
5 | placeholder: 'text',
6 | autocomplete: true,
7 | name: 'text',
8 | disabled: true,
9 | readonly: true,
10 | min: 1,
11 | max: 1,
12 | rows: 1
13 | }
14 |
15 | describe('Input.vue', () => {
16 | it('is rendered as input', () => {
17 | const wrapper = shallowMount(Input, {
18 | propsData: {
19 | name: 'name',
20 | type: 'text',
21 | value: 1,
22 | min: 1,
23 | max: 2,
24 | rows: 4,
25 | autocomplete: true
26 | }
27 | })
28 | expect(wrapper.exists()).toBe(true)
29 | expect(wrapper.props().name).toBe('name')
30 | expect(wrapper.props().type).toBe('text')
31 | expect(wrapper.props().value).toBe(1)
32 | expect(wrapper.props().min).toBe(1)
33 | expect(wrapper.props().max).toBe(2)
34 | expect(wrapper.props().rows).toBe(4)
35 | expect(wrapper.props().autocomplete).toBe(true)
36 | })
37 | it('is rendered as textarea ', () => {
38 | const wrapper = shallowMount(Input)
39 | wrapper.setProps({ type: 'textarea' })
40 | expect(wrapper.find('textarea').exists()).toBe(true)
41 | })
42 | it('input attributes is rendered', () => {
43 | const wrapper = shallowMount(Input)
44 | wrapper.setProps(defaultProps)
45 | const attrs = wrapper.find('input').attributes()
46 | expect(attrs.type).toContain('text')
47 | expect(attrs.autocomplete).toContain('off')
48 | expect(attrs.placeholder).toContain('text')
49 | expect(attrs.name).toContain('text')
50 | expect(attrs.disabled).toContain('disabled')
51 | expect(attrs.readonly).toContain('readonly')
52 | expect(attrs.min).toContain(1)
53 | expect(attrs.max).toContain(1)
54 | })
55 | it('textarea attributes is rendered', () => {
56 | const wrapper = shallowMount(Input)
57 | wrapper.setProps({ ...defaultProps, type: 'textarea' })
58 | const attrs = wrapper.find('textarea').attributes()
59 | expect(attrs.type).toContain('textarea')
60 | expect(attrs.placeholder).toContain('text')
61 | expect(attrs.name).toContain('text')
62 | expect(attrs.disabled).toContain('disabled')
63 | expect(attrs.readonly).toContain('readonly')
64 | expect(attrs.rows).toContain(1)
65 | })
66 | it('is disabled ', () => {
67 | const wrapper = shallowMount(Input)
68 | wrapper.setProps({ disabled: true })
69 | const disabledAttr = wrapper.find('input').attributes().disabled
70 | expect(disabledAttr).toContain('disabled')
71 | })
72 | it('input event (v-model)', () => {
73 | const wrapper = mount({
74 | template: ``,
75 | components: {
76 | [Input.name]: Input
77 | },
78 | data () {
79 | return {
80 | inputVal: null
81 | }
82 | }
83 | })
84 | const inputEl = wrapper.find('input')
85 | inputEl.element.value = 'text'
86 | inputEl.trigger('input')
87 | expect(wrapper.vm.inputVal).toBe('text')
88 | })
89 | describe('slots', () => {
90 | it('prefix is rendered', () => {
91 | const wrapper = shallowMount(Input, {
92 | slots: {
93 | prefix: ''
94 | }
95 | })
96 | expect(wrapper.find('.vue-input__prefix').exists()).toBe(true)
97 | })
98 | it('suffix is rendered', () => {
99 | const wrapper = shallowMount(Input, {
100 | slots: {
101 | suffix: ''
102 | }
103 | })
104 | expect(wrapper.find('.vue-input__suffix').exists()).toBe(true)
105 | })
106 | it('prepend is rendered', () => {
107 | const wrapper = shallowMount(Input, {
108 | slots: {
109 | prepend: ''
110 | }
111 | })
112 | expect(wrapper.find('.vue-input__prepend').exists()).toBe(true)
113 | })
114 | it('append is rendered', () => {
115 | const wrapper = shallowMount(Input, {
116 | slots: {
117 | append: ''
118 | }
119 | })
120 | expect(wrapper.find('.vue-input__append').exists()).toBe(true)
121 | })
122 | })
123 | })
124 |
--------------------------------------------------------------------------------
/src/components/checkbox/Checkbox.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
31 |
32 |
33 |
34 |
119 |
120 |
200 |
--------------------------------------------------------------------------------
/public/docs/select.md:
--------------------------------------------------------------------------------
1 | # Select
2 |
3 | Single and multiple select component with dropdown.
4 |
5 | ## Basic usage
6 |
7 | ```example
8 |
9 |
13 |
18 |
19 |
20 |
21 |
35 | ```
36 |
37 | ## Disabled
38 |
39 | ```example
40 |
41 |
46 |
51 |
52 |
53 |
54 |
68 | ```
69 |
70 | ## Disabled option
71 |
72 | ```example
73 |
74 |
78 |
84 |
85 |
86 |
87 |
101 | ```
102 |
103 | ## Multiple
104 |
105 | Multiple select uses tags to display selected options.
106 |
107 | ```example
108 |
109 |
114 |
119 |
120 |
121 |
127 |
132 |
133 |
134 |
135 |
150 | ```
151 |
152 | ## Select attributes
153 |
154 | | Attributes | Description | Type | Accepted values | Default |
155 | | --------------- | ----------------------------------------------- | --------- | --------------- | ------- |
156 | | `data` | List of select options | `Array` | - | - |
157 | | `multiple` | Multiple select options | `Boolean` | | `false` |
158 | | `collapse-tags` | Collapse tags to a text when multiple selecting | `Boolean` | | `false` |
159 | | `disabled` | Disable the select | `Boolean` | - | `false` |
160 | | `placeholder` | Placeholder of select | `String` | - | - |
161 | | `name` | Same as `name` in native select | `String` | - | - |
162 | | `empty-text` | Displayed text when there is no options | `String` | - | - |
163 |
164 | ## Option attributes
165 |
166 | | Attributes | Description | Type | Accepted values | Default |
167 | | ---------- | ------------------ | ------------------ | --------------- | ------- |
168 | | `value` | Value of option | `String`, `Number` | - | - |
169 | | `label` | Label of option | `String` | - | - |
170 | | `disabled` | Disable the option | `Boolean` | - | `false` |
171 |
172 | ## Select events
173 |
174 | | Name | Description | Payload |
175 | | ----------------- | ----------------------------------------------- | ------------------------------------------------- |
176 | | `change` | Triggers when selected value changed | In single mode `value`, in multiple mode `object` |
177 | | `option:selected` | Triggers when select the option | Selected `object` |
178 | | `remove-tag` | Triggers when a tag is removed in multiple mode | Removed tag `object` |
179 |
--------------------------------------------------------------------------------
/src/components/from-builder/FormBuilder.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
16 |
17 |
20 |
32 |
33 |
34 |
35 |
44 |
50 |
51 |
52 |
53 |
54 |
61 |
68 |
69 |
78 |
79 |
80 |
81 |
92 |
93 |
94 |
95 |
101 | {{ i.buttonLabel }}
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
189 |
190 |
193 |
--------------------------------------------------------------------------------
/tests/unit/Select.spec.js:
--------------------------------------------------------------------------------
1 | import { shallowMount, mount } from '@vue/test-utils'
2 | import Select from '../../src/components/select/Select.vue'
3 | import Option from '../../src/components/select/option.vue'
4 |
5 | const options = [
6 | { label: 'label 1', value: 1, disabled: false },
7 | { label: 'label 2', value: 2, disabled: false },
8 | { label: 'label 3', value: 3, disabled: true }
9 | ]
10 |
11 | const singleTemplate = `
12 |
15 |
21 |
22 |
23 | `
24 | const multipleTemplate = `
25 |
29 |
35 |
36 |
37 | `
38 |
39 | const selectBase = {
40 | template: singleTemplate,
41 | components: {
42 | [Select.name]: Select,
43 | [Option.name]: Option
44 | },
45 | data () {
46 | return {
47 | options,
48 | selectedOption: 1
49 | }
50 | }
51 | }
52 |
53 | const selectMultipleBase = Object.assign({}, selectBase, {
54 | template: multipleTemplate,
55 | data () {
56 | return {
57 | options,
58 | selectedOption: []
59 | }
60 | }
61 | })
62 |
63 | describe('Select.vue', () => {
64 | it('is rendered', () => {
65 | const wrapper = shallowMount(Select, {
66 | propsData: {
67 | data: options,
68 | value: 1,
69 | placeholder: 'placeholder',
70 | multiple: false,
71 | disabled: false,
72 | emptyText: 'text'
73 | }
74 | })
75 | expect(wrapper.exists()).toBe(true)
76 | expect(wrapper.props().data).toEqual(options)
77 | expect(wrapper.props().value).toBe(1)
78 | expect(wrapper.props().placeholder).toBe('placeholder')
79 | expect(wrapper.props().multiple).toBe(false)
80 | expect(wrapper.props().disabled).toBe(false)
81 | expect(wrapper.props().emptyText).toBe('text')
82 | })
83 | it('placeholder is rendered', () => {
84 | const wrapper = mount(Select, {
85 | propsData: {
86 | placeholder: 'placeholder'
87 | }
88 | })
89 | const placeholderAttr = wrapper.find('input').attributes().placeholder
90 | expect(placeholderAttr).toBe('placeholder')
91 | })
92 | it('is disabled', () => {
93 | const wrapper = mount(Select, {
94 | propsData: {
95 | disabled: true
96 | }
97 | })
98 | const disabledAttr = wrapper.find('input').attributes().disabled
99 | expect(disabledAttr).toBe('disabled')
100 | expect(wrapper.find('.vue-select--disabled').exists()).toBe(true)
101 | })
102 | it('option is disabled', () => {
103 | const wrapper = mount(selectBase)
104 | wrapper.trigger('click')
105 | expect(
106 | wrapper
107 | .findAll('.vue-select__option')
108 | .at(2)
109 | .attributes().disabled
110 | ).toBe('disabled')
111 | })
112 | it('is render with empty list', () => {
113 | const selectBaseExtended = Object.assign({}, selectBase, {
114 | data () {
115 | return {
116 | options: [],
117 | selectedOption: ''
118 | }
119 | }
120 | })
121 | const wrapper = mount(selectBaseExtended)
122 | wrapper.trigger('click')
123 | const emptyEl = wrapper.find('.vue-select__option-list-empty')
124 | expect(emptyEl.exists()).toBe(true)
125 | expect(emptyEl.text()).toBe('Empty list')
126 | })
127 | it('is rendered with options with init value', () => {
128 | const wrapper = mount(selectBase)
129 | wrapper.trigger('click')
130 | const selectedEl = wrapper.find('.vue-select__option--selected')
131 | expect(wrapper.find('.vue-select__option').exists()).toBe(true)
132 | expect(selectedEl.exists()).toBe(true)
133 | expect(selectedEl.text()).toBe('label 1')
134 | })
135 | it('change event (v-model)', done => {
136 | const wrapper = mount(selectBase)
137 | wrapper.trigger('click')
138 | wrapper.vm.$nextTick(() => {
139 | const option = wrapper.findAll('.vue-select__option').at(1)
140 | option.trigger('click')
141 | expect(wrapper.vm.selectedOption).toBe(2)
142 | done()
143 | })
144 | })
145 | it('change event (v-model) multiple', done => {
146 | const wrapper = mount(selectMultipleBase)
147 | wrapper.trigger('click')
148 | wrapper.vm.$nextTick(() => {
149 | const option = wrapper.findAll('.vue-select__option').at(0)
150 | option.trigger('click')
151 | const tagEl = wrapper.find('.vue-select__tag-item')
152 | expect(tagEl.exists()).toBe(true)
153 | expect(tagEl.text()).toBe('label 1')
154 | expect(wrapper.vm.selectedOption).toEqual([
155 | { label: 'label 1', value: 1, disabled: false }
156 | ])
157 | tagEl.find('i').trigger('click')
158 | expect(wrapper.vm.selectedOption).toEqual([])
159 | expect(wrapper.find('.vue-select__tag-item').exists()).toBe(false)
160 | done()
161 | })
162 | })
163 | it('is rendered as multiple with init value', () => {
164 | const selectMultipleBaseExtended = Object.assign({}, selectMultipleBase, {
165 | data () {
166 | return {
167 | options,
168 | selectedOption: [{ label: 'label 1', value: 1, disabled: false }]
169 | }
170 | }
171 | })
172 | const wrapper = mount(selectMultipleBaseExtended)
173 | expect(wrapper.find('.vue-select__tag-item').text()).toBe('label 1')
174 | })
175 | })
176 |
--------------------------------------------------------------------------------
/src/components/input/Input.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
16 |
17 |
18 |
22 |
23 |
24 |
28 |
29 |
30 |
44 |
56 |
60 |
61 |
62 |
63 |
64 |
65 |
133 |
134 |
248 |
--------------------------------------------------------------------------------
/src/components/popper/Popper.vue:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
94 |
95 |
254 |
--------------------------------------------------------------------------------
/public/docs/form.md:
--------------------------------------------------------------------------------
1 | # Form
2 |
3 | Form may consist of components: `input`, `select`, `checkbox`, `radio`, `button`
4 |
5 | ## Basic usage
6 |
7 | Each of the `form` components must include form item, which is a container for input field
8 |
9 | ```example
10 |
11 |
14 |
15 |
18 |
19 |
20 |
21 |
25 |
30 |
31 |
32 |
33 |
34 |
37 | Company 1
38 |
39 |
42 | Company 2
43 |
44 |
45 |
46 | I'am agree
47 |
48 |
49 | Cancel
50 | Submit
51 |
52 |
53 |
54 |
70 | ```
71 |
72 | ## Alignment
73 |
74 | Change the alignment of labels
75 |
76 | ```example
77 |
78 |
81 | Left
82 |
83 |
86 | Right
87 |
88 |
91 | Top
92 |
93 |
94 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
122 | ```
123 |
124 | ## Validation
125 |
126 | Form component allows you to verify your input data.
127 |
128 | To validate form item just add the `v-validate` attribute for Input, Select, Radio or Checkbox components to pass validation rules.
129 |
130 | See more information at [VeeValidate](https://baianat.github.io/vee-validate/guide/rules.html?ref=vfc).
131 |
132 | ```example
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 | Radio 1
143 | Radio 2
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 | I'm agree
152 |
153 |
154 | Reset
155 | Submit
156 |
157 |
158 |
159 |
191 | ```
192 |
193 | ## Form attributes
194 |
195 | | Attributes | Description | Type | Accepted values | Default |
196 | | ---------------- | --------------------------- | -------- | ---------------- | ------- |
197 | | `model` | Data of form components | `Object` | - | - |
198 | | `label-position` | Position of label form item | `String` | left, right, top | right |
199 | | `label-width` | Width of label form item | `String` | | 100px |
200 |
201 | ## Form item attributes
202 |
203 | | Attributes | Description | Type | Accepted values | Default |
204 | | ---------- | ----------------------------- | -------- | --------------- | ------- |
205 | | `field` | A key for `model` to validate | `String` | - | - |
206 | | `label` | Label of the form item | `String` | - | - |
207 |
--------------------------------------------------------------------------------
/example/views/Page.vue:
--------------------------------------------------------------------------------
1 |
2 |
34 |
35 |
36 |
132 |
133 |
262 |
--------------------------------------------------------------------------------
/example/assets/normalize.scss:
--------------------------------------------------------------------------------
1 | /*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /* Document
4 | ========================================================================== */
5 |
6 | /**
7 | * 1. Correct the line height in all browsers.
8 | * 2. Prevent adjustments of font size after orientation changes in iOS.
9 | */
10 |
11 | html {
12 | line-height: 1.15; /* 1 */
13 | -webkit-text-size-adjust: 100%; /* 2 */
14 | }
15 |
16 | /* Sections
17 | ========================================================================== */
18 |
19 | /**
20 | * Remove the margin in all browsers.
21 | */
22 |
23 | body {
24 | margin: 0;
25 | }
26 |
27 | /**
28 | * Correct the font size and margin on `h1` elements within `section` and
29 | * `article` contexts in Chrome, Firefox, and Safari.
30 | */
31 |
32 | h1 {
33 | font-size: 2em;
34 | margin: 0.67em 0;
35 | }
36 |
37 | /* Grouping content
38 | ========================================================================== */
39 |
40 | /**
41 | * 1. Add the correct box sizing in Firefox.
42 | * 2. Show the overflow in Edge and IE.
43 | */
44 |
45 | hr {
46 | box-sizing: content-box; /* 1 */
47 | height: 0; /* 1 */
48 | overflow: visible; /* 2 */
49 | }
50 |
51 | /**
52 | * 1. Correct the inheritance and scaling of font size in all browsers.
53 | * 2. Correct the odd `em` font sizing in all browsers.
54 | */
55 |
56 | pre {
57 | font-family: monospace, monospace; /* 1 */
58 | font-size: 1em; /* 2 */
59 | }
60 |
61 | /* Text-level semantics
62 | ========================================================================== */
63 |
64 | /**
65 | * Remove the gray background on active links in IE 10.
66 | */
67 |
68 | a {
69 | background-color: transparent;
70 | }
71 |
72 | /**
73 | * 1. Remove the bottom border in Chrome 57-
74 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
75 | */
76 |
77 | abbr[title] {
78 | border-bottom: none; /* 1 */
79 | text-decoration: underline; /* 2 */
80 | text-decoration: underline dotted; /* 2 */
81 | }
82 |
83 | /**
84 | * Add the correct font weight in Chrome, Edge, and Safari.
85 | */
86 |
87 | b,
88 | strong {
89 | font-weight: bolder;
90 | }
91 |
92 | /**
93 | * 1. Correct the inheritance and scaling of font size in all browsers.
94 | * 2. Correct the odd `em` font sizing in all browsers.
95 | */
96 |
97 | code,
98 | kbd,
99 | samp {
100 | font-family: monospace, monospace; /* 1 */
101 | font-size: 1em; /* 2 */
102 | }
103 |
104 | /**
105 | * Add the correct font size in all browsers.
106 | */
107 |
108 | small {
109 | font-size: 80%;
110 | }
111 |
112 | /**
113 | * Prevent `sub` and `sup` elements from affecting the line height in
114 | * all browsers.
115 | */
116 |
117 | sub,
118 | sup {
119 | font-size: 75%;
120 | line-height: 0;
121 | position: relative;
122 | vertical-align: baseline;
123 | }
124 |
125 | sub {
126 | bottom: -0.25em;
127 | }
128 |
129 | sup {
130 | top: -0.5em;
131 | }
132 |
133 | /* Embedded content
134 | ========================================================================== */
135 |
136 | /**
137 | * Remove the border on images inside links in IE 10.
138 | */
139 |
140 | img {
141 | border-style: none;
142 | }
143 |
144 | /* Forms
145 | ========================================================================== */
146 |
147 | /**
148 | * 1. Change the font styles in all browsers.
149 | * 2. Remove the margin in Firefox and Safari.
150 | */
151 |
152 | button,
153 | input,
154 | optgroup,
155 | select,
156 | textarea {
157 | font-family: inherit; /* 1 */
158 | font-size: 100%; /* 1 */
159 | line-height: 1.15; /* 1 */
160 | margin: 0; /* 2 */
161 | }
162 |
163 | /**
164 | * Show the overflow in IE.
165 | * 1. Show the overflow in Edge.
166 | */
167 |
168 | button,
169 | input { /* 1 */
170 | overflow: visible;
171 | }
172 |
173 | /**
174 | * Remove the inheritance of text transform in Edge, Firefox, and IE.
175 | * 1. Remove the inheritance of text transform in Firefox.
176 | */
177 |
178 | button,
179 | select { /* 1 */
180 | text-transform: none;
181 | }
182 |
183 | /**
184 | * Correct the inability to style clickable types in iOS and Safari.
185 | */
186 |
187 | button,
188 | [type="button"],
189 | [type="reset"],
190 | [type="submit"] {
191 | -webkit-appearance: button;
192 | }
193 |
194 | /**
195 | * Remove the inner border and padding in Firefox.
196 | */
197 |
198 | button::-moz-focus-inner,
199 | [type="button"]::-moz-focus-inner,
200 | [type="reset"]::-moz-focus-inner,
201 | [type="submit"]::-moz-focus-inner {
202 | border-style: none;
203 | padding: 0;
204 | }
205 |
206 | /**
207 | * Restore the focus styles unset by the previous rule.
208 | */
209 |
210 | button:-moz-focusring,
211 | [type="button"]:-moz-focusring,
212 | [type="reset"]:-moz-focusring,
213 | [type="submit"]:-moz-focusring {
214 | outline: 1px dotted ButtonText;
215 | }
216 |
217 | /**
218 | * Correct the padding in Firefox.
219 | */
220 |
221 | fieldset {
222 | padding: 0.35em 0.75em 0.625em;
223 | }
224 |
225 | /**
226 | * 1. Correct the text wrapping in Edge and IE.
227 | * 2. Correct the color inheritance from `fieldset` elements in IE.
228 | * 3. Remove the padding so developers are not caught out when they zero out
229 | * `fieldset` elements in all browsers.
230 | */
231 |
232 | legend {
233 | box-sizing: border-box; /* 1 */
234 | color: inherit; /* 2 */
235 | display: table; /* 1 */
236 | max-width: 100%; /* 1 */
237 | padding: 0; /* 3 */
238 | white-space: normal; /* 1 */
239 | }
240 |
241 | /**
242 | * Add the correct vertical alignment in Chrome, Firefox, and Opera.
243 | */
244 |
245 | progress {
246 | vertical-align: baseline;
247 | }
248 |
249 | /**
250 | * Remove the default vertical scrollbar in IE 10+.
251 | */
252 |
253 | textarea {
254 | overflow: auto;
255 | }
256 |
257 | /**
258 | * 1. Add the correct box sizing in IE 10.
259 | * 2. Remove the padding in IE 10.
260 | */
261 |
262 | [type="checkbox"],
263 | [type="radio"] {
264 | box-sizing: border-box; /* 1 */
265 | padding: 0; /* 2 */
266 | }
267 |
268 | /**
269 | * Correct the cursor style of increment and decrement buttons in Chrome.
270 | */
271 |
272 | [type="number"]::-webkit-inner-spin-button,
273 | [type="number"]::-webkit-outer-spin-button {
274 | height: auto;
275 | }
276 |
277 | /**
278 | * 1. Correct the odd appearance in Chrome and Safari.
279 | * 2. Correct the outline style in Safari.
280 | */
281 |
282 | [type="search"] {
283 | -webkit-appearance: textfield; /* 1 */
284 | outline-offset: -2px; /* 2 */
285 | }
286 |
287 | /**
288 | * Remove the inner padding in Chrome and Safari on macOS.
289 | */
290 |
291 | [type="search"]::-webkit-search-decoration {
292 | -webkit-appearance: none;
293 | }
294 |
295 | /**
296 | * 1. Correct the inability to style clickable types in iOS and Safari.
297 | * 2. Change font properties to `inherit` in Safari.
298 | */
299 |
300 | ::-webkit-file-upload-button {
301 | -webkit-appearance: button; /* 1 */
302 | font: inherit; /* 2 */
303 | }
304 |
305 | /* Interactive
306 | ========================================================================== */
307 |
308 | /*
309 | * Add the correct display in Edge, IE 10+, and Firefox.
310 | */
311 |
312 | details {
313 | display: block;
314 | }
315 |
316 | /*
317 | * Add the correct display in all browsers.
318 | */
319 |
320 | summary {
321 | display: list-item;
322 | }
323 |
324 | /* Misc
325 | ========================================================================== */
326 |
327 | /**
328 | * Add the correct display in IE 10+.
329 | */
330 |
331 | template {
332 | display: none;
333 | }
334 |
335 | /**
336 | * Add the correct display in IE 10.
337 | */
338 |
339 | [hidden] {
340 | display: none;
341 | }
--------------------------------------------------------------------------------
/public/docs/form-builder.md:
--------------------------------------------------------------------------------
1 | # Form builder
2 |
3 | Is a schema-based builder to generate form with components and validation
4 |
5 | ## Basic usage
6 |
7 | ```example
8 |
9 |
10 |
17 |
18 |
19 |
20 |
21 |
185 | ```
186 |
187 | ## Attributes
188 |
189 | | Attributes | Description | Type | Accepted values | Default |
190 | | ---------- | -------------------------------------------- | -------- | --------------- | ------- |
191 | | `model` | Model for form fields (components `v-model`) | `Object` | - | - |
192 | | `schema` | Schema to generate form | `Object` | - | - |
193 | | `options` | Options for Form component: label position & label width | `Object` | - | - |
194 |
195 | ## Schema
196 |
197 | | Property | Description | Type | Accepted values |
198 | | --------------------- | ----------------------------------------------------------- | ------------------ | ------------------------------------------------------- |
199 | | `type` | Type of field | `String` | input, select, checkbox, radio, actions |
200 | | `inputType` | Type of input | `String` | text, number, textarea, password and border1 |
201 | | `buttons` | Form action buttons. Available if type is `actions` | `Array` | Array of object |
202 | | `buttons[index].type` | Type of button. Also event emitter type for `@action` | `String` | submit, cancel |
203 | | `buttons[index].buttonType` | Type of Button component | `String` | primary, success, warning, danger |
204 | | `buttons[index].buttonLabel` | Label for button | `String` | |
205 | | `name` | Field detection to start validation and error messages | `Object` | |
206 | | `label` | Label of the form item | `String` | |
207 | | `model` | Name of property in the model | `String` | |
208 | | `disabled` | Disable the field | `Boolean` | |
209 | | `readonly` | Same as `readonly` in native input | `Boolean` | |
210 | | `placeholder` | Placeholder of value | `Boolean` | |
211 | | `options` | Options for list components, like Select or Checkbox | `Array` | Array of object |
212 | | `options[index].label` | Label of option | `String` | |
213 | | `options[index].value` | Value of option | `String`, `Number` | |
214 | | `validate` | VeeValidate rules | `Object` | |
215 |
216 | - 1 `border` is available only if `type` is checkbox or radio.
217 |
218 | ## Form events
219 |
220 | | Name | Description | Payload |
221 | | -------- | --------------------------------- | --------------------- |
222 | | `action` | Triggers when clicked action button | Type of action button |
223 |
--------------------------------------------------------------------------------
/src/components/select/Select.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
18 |
19 |
20 |
25 | {{ item.label }}
26 |
30 |
31 |
36 | +{{ selected.length - 1 }}
37 |
38 |
43 | {{ item.label }}
44 |
48 |
49 |
50 |
51 |
52 |
61 |
62 |
63 |
64 |
65 |
71 |
75 |
79 | {{ emptyText }}
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
330 |
331 |
407 |
--------------------------------------------------------------------------------
/dist/vfc.css:
--------------------------------------------------------------------------------
1 | .vue-input{display:inline-block;font-family:Helvetica,Arial,sans-serif}.vue-input,.vue-input__inner{position:relative;font-size:14px;width:100%}.vue-input__inner{-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-appearance:none;border-radius:4px;outline:none;border:1px solid #ddd;line-height:40px;padding:0 15px;height:40px;background-color:transparent;color:#303133}.vue-input__inner,.vue-input__inner:focus{-webkit-transition:border-color .3s;transition:border-color .3s}.vue-input__inner:focus{border-color:#498aed}.vue-input__inner::-webkit-input-placeholder{color:#a2a3a7}.vue-input__inner[disabled]{cursor:no-drop;background-color:#f6f6f6}.vue-input__inner[type=number]::-webkit-inner-spin-button,.vue-input__inner[type=number]::-webkit-outer-spin-button{-webkit-appearance:none}.vue-input__prefix,.vue-input__suffix{color:#303133;position:absolute;top:0;bottom:0;width:40px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;z-index:10}.vue-input__prefix{left:0}.vue-input__suffix{right:0}.vue-input__prepend{border-left:1px solid #ddd;border-top:1px solid #ddd;border-bottom:1px solid #ddd;border-top-left-radius:4px;border-bottom-left-radius:4px}.vue-input__append,.vue-input__prepend{display:table-cell;color:#7a7d82;padding:0 15px;background-color:#f6f6f6;position:relative;width:1px;white-space:nowrap}.vue-input__append{border-top:1px solid #ddd;border-right:1px solid #ddd;border-bottom:1px solid #ddd;border-top-right-radius:4px;border-bottom-right-radius:4px}.vue-input--prefix .vue-input__inner{padding-left:40px}.vue-input--suffix .vue-input__inner{padding-right:40px}.vue-input--prepend{display:inline-table;border-collapse:separate}.vue-input--prepend .vue-input__inner{border-top-left-radius:0;border-bottom-left-radius:0}.vue-input--append{display:inline-table;border-collapse:separate}.vue-input--append .vue-input__inner{border-top-right-radius:0;border-bottom-right-radius:0}.vue-textarea__inner{-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-appearance:none;border-radius:4px;outline:none;border:1px solid #ddd;line-height:40px;padding:0 15px;height:40px;background-color:transparent;font-size:14px;color:#303133;padding:5px 15px;height:auto;line-height:1.5;resize:vertical}.vue-textarea__inner,.vue-textarea__inner:focus{-webkit-transition:border-color .3s;transition:border-color .3s}.vue-textarea__inner:focus{border-color:#498aed}.vue-textarea__inner::-webkit-input-placeholder{color:#a2a3a7}.vue-textarea__inner[disabled]{cursor:no-drop;background-color:#f6f6f6}.vue-button{position:relative;display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#fff;text-align:center;-webkit-box-sizing:border-box;box-sizing:border-box;outline:0;margin:0;-webkit-transition:.1s;transition:.1s;font-weight:500;-webkit-appearance:none;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;padding:12px 20px;font-size:14px;border-radius:4px;-webkit-transition:all .3s;transition:all .3s;background-color:#fff;border-color:#fff;color:#303133;border:1px solid #ddd}.vue-button:hover{background-color:#fff}.vue-button:active{background-color:#f2f2f2}.vue-button[disabled]{background-color:#f6f6f6;border-color:#f6f6f6;cursor:no-drop}.vue-button+.vue-button{margin-left:10px}.vue-button--primary{background-color:#498aed;border-color:#498aed;color:#fff}.vue-button--primary:hover{background-color:#6099ef}.vue-button--primary:active{background-color:#327beb}.vue-button--primary[disabled]{background-color:#8fb7f4;border-color:#8fb7f4;cursor:no-drop}.vue-button--success{background-color:#68cc68;border-color:#68cc68;color:#fff}.vue-button--success:hover{background-color:#7bd27b}.vue-button--success:active{background-color:#55c655}.vue-button--success[disabled]{background-color:#a1dfa1;border-color:#a1dfa1;cursor:no-drop}.vue-button--warning{background-color:#efc669;border-color:#efc669;color:#fff}.vue-button--warning:hover{background-color:#f1cf80}.vue-button--warning:active{background-color:#edbd52}.vue-button--warning[disabled]{background-color:#f4d897;border-color:#f4d897;cursor:no-drop}.vue-button--danger{background-color:#e4474e;border-color:#e4474e;color:#fff}.vue-button--danger:hover{background-color:#e75d63}.vue-button--danger:active{background-color:#e13139}.vue-button--danger[disabled]{background-color:#f4b6b9;border-color:#f4b6b9;cursor:no-drop}.vue-checkbox{font-size:14px;cursor:pointer;display:inline-table;color:#303133}.vue-checkbox+.vue-checkbox{margin-left:10px}.vue-checkbox--checked{color:#498aed}.vue-checkbox--checked .vue-checkbox__inner{background-color:#498aed;border-color:#498aed}.vue-checkbox--checked.vue-checkbox--bordered{border-color:#498aed}.vue-checkbox--checked.vue-checkbox--disabled .vue-checkbox__inner{border-color:#aaa}.vue-checkbox--checked.vue-checkbox--disabled i{color:#aaa}.vue-checkbox--disabled{cursor:no-drop}.vue-checkbox--disabled .vue-checkbox__inner{background-color:#f6f6f6;cursor:no-drop}.vue-checkbox--disabled .vue-checkbox__label{color:#aaa}.vue-checkbox--bordered{border:1px solid #ddd;border-radius:4px;padding:0 15px;line-height:38px;-webkit-box-sizing:content-box;box-sizing:content-box;-webkit-transition:all .2s;transition:all .2s}.vue-checkbox:last-of-type{margin-right:0}.vue-checkbox__label{display:table-cell;width:100%;line-height:1.3em}.vue-checkbox__inner{top:3px;width:14px;height:14px;margin-right:5px;border:1px solid #ddd;border-radius:3px;position:relative;cursor:pointer;display:inline-block;margin-right:10px}.vue-checkbox__inner i{position:absolute;color:#fff;left:0}.vue-checkbox input{display:none}.vue-radio{font-size:14px;cursor:pointer;color:#303133}.vue-radio+.vue-radio{margin-left:10px}.vue-radio__inner{width:16px;height:16px;border-radius:100%;cursor:pointer;display:inline-block;border:1px solid #ddd;margin-right:10px;position:relative;top:2px;-webkit-box-sizing:border-box;box-sizing:border-box}.vue-radio--checked .vue-radio__inner{background-color:#498aed;border-color:#498aed}.vue-radio--checked .vue-radio__inner:after{position:absolute;content:"";width:6px;height:6px;background-color:#fff;border-radius:100%;left:50%;top:50%;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.vue-radio--checked.vue-radio--bordered{border-color:#498aed}.vue-radio--disabled{color:#aaa;cursor:no-drop}.vue-radio--disabled .vue-radio__inner{background-color:#f6f6f6;border-color:#aaa}.vue-radio--disabled .vue-radio__inner:after{background-color:#ddd}.vue-radio--bordered{border-color:#498aed;border:1px solid #ddd;border-radius:4px;padding:0 15px;line-height:38px;height:38px;-webkit-transition:all .2s;transition:all .2s;display:inline-block}.vue-radio input{display:none}.vue-popper{background-color:#fff;border-radius:4px;border:1px solid #fff;font-family:Arial,Helvetica,sans-serif;-webkit-box-shadow:0 0 10px rgba(0,0,0,.1);box-shadow:0 0 10px rgba(0,0,0,.1);z-index:1010}.vue-popper,.vue-popper *{-webkit-box-sizing:border-box;box-sizing:border-box}.vue-popper__inner{padding:10px;overflow-y:auto}.vue-popper__arrow{border-width:6px;-webkit-filter:drop-shadow(0 2px 12px rgba(0,0,0,.03));filter:drop-shadow(0 2px 12px rgba(0,0,0,.03))}.vue-popper__arrow,.vue-popper__arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.vue-popper__arrow:after{content:" "}.vue-popper[x-placement^=bottom]{margin-top:5px}.vue-popper[x-placement^=bottom] .vue-popper__arrow{top:-6px;left:50%!important;-webkit-transform:translateX(-50%);transform:translateX(-50%);margin-right:3px;border-top-width:0;border-bottom-color:#fff}.vue-popper[x-placement^=bottom] .vue-popper__arrow:after{top:1px;border-top-width:1px;margin-top:0;margin-left:-3px;border-color:transparent;border-style:solid;-webkit-transform:scale(1.5);transform:scale(1.5);border-bottom-color:#fff}.vue-popper[x-placement^=bottom-start]{margin-top:5px}.vue-popper[x-placement^=bottom-start] .vue-popper__arrow{top:-6px;left:10px!important;margin-right:3px;border-top-width:0;border-bottom-color:#fff}.vue-popper[x-placement^=bottom-start] .vue-popper__arrow:after{top:1px;border-top-width:1px;margin-top:0;margin-left:-3px;border-color:transparent;border-style:solid;-webkit-transform:scale(1.5);transform:scale(1.5);border-bottom-color:#fff}.vue-popper[x-placement^=bottom-end]{margin-top:5px}.vue-popper[x-placement^=bottom-end] .vue-popper__arrow{top:-6px;left:calc(100% - 10px)!important;margin-right:3px;border-top-width:0;border-bottom-color:#fff}.vue-popper[x-placement^=bottom-end] .vue-popper__arrow:after{top:1px;border-top-width:1px;margin-top:0;margin-left:-3px;border-color:transparent;border-style:solid;-webkit-transform:scale(1.5);transform:scale(1.5);border-bottom-color:#fff}.vue-popper[x-placement^=top]{margin:0;margin-bottom:6px}.vue-popper[x-placement^=top] .vue-popper__arrow{bottom:-6px;left:50%;margin-right:3px;border-bottom-width:0;border-top-color:#fff}.vue-popper[x-placement^=top] .vue-popper__arrow:after{bottom:1px;margin-right:3px;border-bottom-width:1px;margin-top:-6px;margin-left:-3px;-webkit-transform:scale(1.5);transform:scale(1.5);border-top-color:#fff}.vue-popper[x-placement^=top-start]{margin:0;margin-bottom:6px}.vue-popper[x-placement^=top-start] .vue-popper__arrow{bottom:-6px;left:10px!important;margin-right:3px;border-bottom-width:0;border-top-color:#fff}.vue-popper[x-placement^=top-start] .vue-popper__arrow:after{bottom:1px;margin-right:3px;border-bottom-width:1px;margin-top:-6px;margin-left:-3px;-webkit-transform:scale(1.5);transform:scale(1.5);border-top-color:#fff}.vue-popper[x-placement^=top-end]{margin:0;margin-bottom:6px}.vue-popper[x-placement^=top-end] .vue-popper__arrow{bottom:-6px;left:calc(100% - 15px)!important;margin-right:3px;border-bottom-width:0;border-top-color:#fff}.vue-popper[x-placement^=top-end] .vue-popper__arrow:after{bottom:1px;margin-right:3px;border-bottom-width:1px;margin-top:-6px;margin-left:-3px;-webkit-transform:scale(1.5);transform:scale(1.5);border-top-color:#fff}@font-face{font-family:icomoon;src:url(data:font/ttf;base64,AAEAAAALAIAAAwAwT1MvMg8SBawAAAC8AAAAYGNtYXAXVtKJAAABHAAAAFRnYXNwAAAAEAAAAXAAAAAIZ2x5ZtfZZSAAAAF4AAABYGhlYWQScJwaAAAC2AAAADZoaGVhBzUDyAAAAxAAAAAkaG10eBIAAlEAAAM0AAAAHGxvY2EAmgEMAAADUAAAABBtYXhwAAkAKQAAA2AAAAAgbmFtZZlKCfsAAAOAAAABhnBvc3QAAwAAAAAFCAAAACAAAwOAAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADpAgPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAAAAAADAAAAAwAAABwAAQADAAAAHAADAAEAAAAcAAQAOAAAAAoACAACAAIAAQAg6QL//f//AAAAAAAg6QD//f//AAH/4xcEAAMAAQAAAAAAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAABAI0AqwNzAskAFgAAASYiBwEnJiIHBhQfAR4BMzI2NwE2NCcDcw0iDf5Jtw0iDQ0N1QcNCgoNBwHVDQ0CyQwM/ki4DAwNIg3WBgYGBgHWDSINAAABAOIBAAMeAkkAFgAAASYiDwEnJiIHBhQXAR4BMzI2NwE2NCcDHg0iDeLiDSINDQ0BAAcRBgYRBwEADQ0CSQwM4+MMDA0iDf8ABwYGBwEADSINAAABAOIAgAMeAskAJgAAATc2NCcmIg8BJyYiBwYUHwEHBhQXHgEzMjY/ARceATMyNjc2NC8BAjziDQ0NIg3i4g0iDQ0N4uINDQcNCgoNB+LiBxEGBhEHDQ3iAaviDSINDAzj4wwMDSIN4uINIg0HBgYH4uIHBgYHDSIN4gAAAQAAAAEAALaGQp9fDzz1AAsEAAAAAADX76wqAAAAANfvrCoAAAAAA3MCyQAAAAgAAgAAAAAAAAABAAADwP/AAAAEAAAAAAADcwABAAAAAAAAAAAAAAAAAAAABwQAAAAAAAAAAAAAAAIAAAAEAACNBAAA4gQAAOIAAAAAAAoAFAAeAEgAcgCwAAEAAAAHACcAAQAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAOAK4AAQAAAAAAAQAHAAAAAQAAAAAAAgAHAGAAAQAAAAAAAwAHADYAAQAAAAAABAAHAHUAAQAAAAAABQALABUAAQAAAAAABgAHAEsAAQAAAAAACgAaAIoAAwABBAkAAQAOAAcAAwABBAkAAgAOAGcAAwABBAkAAwAOAD0AAwABBAkABAAOAHwAAwABBAkABQAWACAAAwABBAkABgAOAFIAAwABBAkACgA0AKRpY29tb29uAGkAYwBvAG0AbwBvAG5WZXJzaW9uIDEuMABWAGUAcgBzAGkAbwBuACAAMQAuADBpY29tb29uAGkAYwBvAG0AbwBvAG5pY29tb29uAGkAYwBvAG0AbwBvAG5SZWd1bGFyAFIAZQBnAHUAbABhAHJpY29tb29uAGkAYwBvAG0AbwBvAG5Gb250IGdlbmVyYXRlZCBieSBJY29Nb29uLgBGAG8AbgB0ACAAZwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABJAGMAbwBNAG8AbwBuAC4AAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA) format("truetype"),url(data:font/woff;base64,d09GRgABAAAAAAV0AAsAAAAABSgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABCAAAAGAAAABgDxIFrGNtYXAAAAFoAAAAVAAAAFQXVtKJZ2FzcAAAAbwAAAAIAAAACAAAABBnbHlmAAABxAAAAWAAAAFg19llIGhlYWQAAAMkAAAANgAAADYScJwaaGhlYQAAA1wAAAAkAAAAJAc1A8hobXR4AAADgAAAABwAAAAcEgACUWxvY2EAAAOcAAAAEAAAABAAmgEMbWF4cAAAA6wAAAAgAAAAIAAJACluYW1lAAADzAAAAYYAAAGGmUoJ+3Bvc3QAAAVUAAAAIAAAACAAAwAAAAMDgAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAA6QIDwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADgAAAAKAAgAAgACAAEAIOkC//3//wAAAAAAIOkA//3//wAB/+MXBAADAAEAAAAAAAAAAAAAAAEAAf//AA8AAQAAAAAAAAAAAAIAADc5AQAAAAABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQCNAKsDcwLJABYAAAEmIgcBJyYiBwYUHwEeATMyNjcBNjQnA3MNIg3+SbcNIg0NDdUHDQoKDQcB1Q0NAskMDP5IuAwMDSIN1gYGBgYB1g0iDQAAAQDiAQADHgJJABYAAAEmIg8BJyYiBwYUFwEeATMyNjcBNjQnAx4NIg3i4g0iDQ0NAQAHEQYGEQcBAA0NAkkMDOPjDAwNIg3/AAcGBgcBAA0iDQAAAQDiAIADHgLJACYAAAE3NjQnJiIPAScmIgcGFB8BBwYUFx4BMzI2PwEXHgEzMjY3NjQvAQI84g0NDSIN4uINIg0NDeLiDQ0HDQoKDQfi4gcRBgYRBw0N4gGr4g0iDQwM4+MMDA0iDeLiDSINBwYGB+LiBwYGBw0iDeIAAAEAAAABAAC2hkKfXw889QALBAAAAAAA1++sKgAAAADX76wqAAAAAANzAskAAAAIAAIAAAAAAAAAAQAAA8D/wAAABAAAAAAAA3MAAQAAAAAAAAAAAAAAAAAAAAcEAAAAAAAAAAAAAAACAAAABAAAjQQAAOIEAADiAAAAAAAKABQAHgBIAHIAsAABAAAABwAnAAEAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAADgCuAAEAAAAAAAEABwAAAAEAAAAAAAIABwBgAAEAAAAAAAMABwA2AAEAAAAAAAQABwB1AAEAAAAAAAUACwAVAAEAAAAAAAYABwBLAAEAAAAAAAoAGgCKAAMAAQQJAAEADgAHAAMAAQQJAAIADgBnAAMAAQQJAAMADgA9AAMAAQQJAAQADgB8AAMAAQQJAAUAFgAgAAMAAQQJAAYADgBSAAMAAQQJAAoANACkaWNvbW9vbgBpAGMAbwBtAG8AbwBuVmVyc2lvbiAxLjAAVgBlAHIAcwBpAG8AbgAgADEALgAwaWNvbW9vbgBpAGMAbwBtAG8AbwBuaWNvbW9vbgBpAGMAbwBtAG8AbwBuUmVndWxhcgBSAGUAZwB1AGwAYQByaWNvbW9vbgBpAGMAbwBtAG8AbwBuRm9udCBnZW5lcmF0ZWQgYnkgSWNvTW9vbi4ARgBvAG4AdAAgAGcAZQBuAGUAcgBhAHQAZQBkACAAYgB5ACAASQBjAG8ATQBvAG8AbgAuAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==) format("woff");font-weight:400;font-style:normal}[class*=" icon-"],[class^=icon-]{font-family:icomoon!important;font-style:normal;font-weight:400;font-variant:normal;text-transform:none;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.icon-check:before{content:"\E900"}.icon-chevron-down:before{content:"\E901"}.icon-close:before{content:"\E902"}.vue-select{display:inline-block;cursor:pointer;position:relative;width:100%}.vue-select__tag{position:absolute;top:0;left:0;right:0;bottom:0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:2px 40px 2px 5px;z-index:10}.vue-select__tag-item{-webkit-box-sizing:border-box;box-sizing:border-box;font-size:14px;background-color:#f6f6f6;border:1px solid #ddd;border-radius:4px;color:#303133;padding:2px 5px;-ms-flex-negative:0;flex-shrink:0;margin:2px;display:inline-block}.vue-select__tag-item>i{position:relative;top:1px;color:#aaa}.vue-select__tag-item>i:hover{color:#303133}.vue-select__option-list-empty{font-size:14px}.vue-select .vue-input__inner{cursor:pointer}.vue-select .vue-input i{-webkit-transition:all .2s;transition:all .2s}.vue-select--opened .vue-input i{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.vue-select--disabled .vue-input__suffix i{color:#aaa}.vue-select--disabled .vue-input__inner{color:#aaa;cursor:no-drop!important}.vue-select__option{cursor:pointer;margin:0 -10px;padding:10px 15px;font-size:14px}.vue-select__option[disabled]{cursor:no-drop!important;color:#ddd}.vue-select__option--hovered{background-color:#f6f6f6}.vue-select__option--hovered[disabled]{background-color:inherit}.vue-select__option--selected{color:#498aed}.vue-select__option--selected:hover{background-color:#f6f6f6}.vue-form{width:400px}.vue-form--label-right .vue-form__item-label{text-align:right}.vue-form--label-top .vue-form__item{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-flow:column;flex-flow:column}.vue-form--label-top .vue-form__item-label{-ms-flex-preferred-size:auto!important;flex-basis:auto!important;-ms-flex-item-align:start;align-self:flex-start;margin-bottom:15px}.vue-form__item{margin-bottom:22px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;position:relative}.vue-form__item-label{-webkit-box-sizing:border-box;box-sizing:border-box;font-size:14px;color:#303133;padding-right:20px;-ms-flex-item-align:center;align-self:center}.vue-form__item-content{-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.vue-form__item-error{position:absolute;font-size:12px;color:#e4474e;line-height:20px}.vue-form__item-validate{overflow:hidden}.form-slide-fade-enter-active,.form-slide-fade-leave-active{-webkit-transition:all .2s;transition:all .2s}.form-slide-fade-enter,.form-slide-fade-leave-to{-webkit-transform:translateY(-10px);transform:translateY(-10px);opacity:0}
--------------------------------------------------------------------------------