├── .eslintrc.js ├── .github └── ISSUE_TEMPLATE │ ├── ---bug-report.md │ └── ---feature-request.md ├── .gitignore ├── .vscode └── settings.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── build ├── babel.js ├── css.js └── resolve.js ├── index.html ├── package-lock.json ├── package.json ├── prettier.config.js ├── rollup.analyze.js ├── rollup.config.js ├── src ├── components │ ├── Accordion │ │ ├── Accordion.tsx │ │ ├── Event.ts │ │ └── index.ts │ ├── Avatar │ │ ├── Avatar.tsx │ │ ├── Presence.ts │ │ ├── Size.ts │ │ ├── index.ts │ │ └── style.scss │ ├── Bar │ │ ├── Bar.tsx │ │ └── index.ts │ ├── Breadcrumb │ │ ├── Breadcrumb.tsx │ │ ├── Crumb.ts │ │ └── index.ts │ ├── Btn │ │ ├── Btn.tsx │ │ ├── BtnGroup.tsx │ │ ├── Events.tsx │ │ ├── Size.ts │ │ ├── State.ts │ │ ├── Type.ts │ │ ├── index.ts │ │ └── styles.scss │ ├── Card │ │ ├── Card.tsx │ │ ├── CardBody.tsx │ │ ├── CardFooter.tsx │ │ ├── CardHeader.tsx │ │ ├── CardImage.tsx │ │ ├── Image.ts │ │ └── index.ts │ ├── Chip │ │ ├── Chip.tsx │ │ ├── Event.ts │ │ └── index.ts │ ├── Column │ │ ├── Column.tsx │ │ └── index.ts │ ├── Columns │ │ ├── Columns.tsx │ │ └── index.ts │ ├── Container │ │ ├── Container.tsx │ │ ├── Grid.ts │ │ └── index.ts │ ├── Divider │ │ ├── Divider.tsx │ │ └── index.ts │ ├── DropdownMenu │ │ ├── DropdownMenu.tsx │ │ └── index.ts │ ├── Empty │ │ ├── Empty.tsx │ │ ├── EmptyAction.tsx │ │ ├── EmptyContent.tsx │ │ ├── EmptyIcon.tsx │ │ ├── EmptySubtitle.tsx │ │ ├── EmptyTitle.tsx │ │ └── index.ts │ ├── FormCheckbox │ │ ├── Checkbox.tsx │ │ ├── Event.ts │ │ ├── Group.tsx │ │ ├── Size.ts │ │ ├── Type.ts │ │ └── index.ts │ ├── FormGroup │ │ ├── Group.tsx │ │ ├── Options.ts │ │ └── index.ts │ ├── FormHint │ │ ├── Hint.tsx │ │ ├── index.ts │ │ └── styles.scss │ ├── FormHorizontal │ │ ├── Horizontal.tsx │ │ └── index.ts │ ├── FormInput │ │ ├── Component.tsx │ │ ├── Icon.tsx │ │ ├── IconContainer.tsx │ │ ├── Input.tsx │ │ ├── Loading.tsx │ │ ├── Size.ts │ │ └── index.ts │ ├── FormLabel │ │ ├── Label.tsx │ │ ├── LabelSize.ts │ │ └── index.ts │ ├── FormRadio │ │ ├── Group.tsx │ │ ├── Radio.tsx │ │ ├── Size.ts │ │ └── index.ts │ ├── FormSelect │ │ ├── Option.tsx │ │ ├── Select.tsx │ │ ├── Size.ts │ │ └── index.ts │ ├── FormSlider │ │ ├── Events.ts │ │ ├── Slider.tsx │ │ └── index.ts │ ├── FormSwitch │ │ ├── Group.tsx │ │ ├── Switch.tsx │ │ └── index.tsx │ ├── FormTextarea │ │ ├── Textarea.tsx │ │ └── index.ts │ ├── Icon │ │ ├── Icon.tsx │ │ ├── Size.ts │ │ ├── Type.ts │ │ └── index.ts │ ├── Modal │ │ ├── Events.ts │ │ ├── Modal.tsx │ │ ├── ModalBody.tsx │ │ ├── ModalFooter.tsx │ │ ├── ModalHeader.tsx │ │ ├── Size.ts │ │ ├── index.ts │ │ └── styles.scss │ ├── Navigation │ │ ├── Navigation.tsx │ │ ├── NavigationItem.tsx │ │ └── index.ts │ ├── OffCanvas │ │ ├── Events.ts │ │ ├── OffCanvas.tsx │ │ ├── OffCanvasContent.tsx │ │ ├── OffCanvasOverlay.tsx │ │ ├── OffCanvasSidebar.tsx │ │ ├── OffCanvasToggle.tsx │ │ └── index.ts │ ├── Overlay │ │ ├── Events.ts │ │ ├── Overlay.tsx │ │ ├── index.ts │ │ └── styles.scss │ ├── Pagination │ │ ├── Events.ts │ │ ├── Pager.tsx │ │ ├── Pagination.tsx │ │ ├── SimplePager.tsx │ │ ├── index.ts │ │ └── styles.scss │ ├── Panel │ │ ├── Panel.tsx │ │ ├── PanelBody.tsx │ │ ├── PanelFooter.tsx │ │ ├── PanelHeader.tsx │ │ ├── PanelNav.tsx │ │ └── index.ts │ ├── Popover │ │ ├── Popover.tsx │ │ ├── Side.ts │ │ └── index.ts │ ├── Steps │ │ ├── Step.tsx │ │ ├── Steps.tsx │ │ └── index.ts │ ├── Tab │ │ ├── Events.ts │ │ ├── Tab.tsx │ │ ├── TabAction.tsx │ │ ├── Tabs.tsx │ │ ├── index.ts │ │ └── tab.scss │ ├── Tag │ │ ├── Tag.tsx │ │ ├── Type.ts │ │ ├── index.ts │ │ └── styles.scss │ ├── Tile │ │ ├── Tile.tsx │ │ ├── TileAction.tsx │ │ ├── TileContent.tsx │ │ ├── TileIcon.tsx │ │ ├── TileSubtitle.tsx │ │ ├── TileTitle.tsx │ │ ├── icon-styles.scss │ │ └── index.ts │ ├── Toast │ │ ├── Toast.tsx │ │ ├── ToastAction.tsx │ │ ├── ToastBody.tsx │ │ ├── ToastContent.tsx │ │ ├── ToastIcon.tsx │ │ ├── ToastTitle.tsx │ │ ├── Type.ts │ │ ├── index.ts │ │ └── styles.scss │ ├── VerticalMenu │ │ ├── VerticalMenu.tsx │ │ ├── VerticalMenuDivider.tsx │ │ ├── VerticalMenuItem.tsx │ │ ├── VerticalMenuItemBadge.tsx │ │ └── index.ts │ └── index.ts ├── directives │ ├── Badge │ │ ├── Badge.ts │ │ └── index.ts │ ├── ClickOutside │ │ ├── ClickOutside.ts │ │ └── index.ts │ ├── Loading │ │ ├── Loading.ts │ │ └── index.ts │ ├── Overlay │ │ ├── Overlay.ts │ │ ├── index.ts │ │ ├── styles.scss │ │ └── type.ts │ ├── Tooltip │ │ ├── Side.ts │ │ ├── Tooltip.ts │ │ └── index.ts │ └── index.ts ├── main.ts ├── mixins │ └── cache.ts ├── plugin.ts ├── shims-vue.d.ts └── utils │ ├── css.ts │ ├── listener.ts │ ├── plugin.ts │ ├── prefix.ts │ ├── string.ts │ └── uid.ts └── tsconfig.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | browser: true, 6 | }, 7 | parser: 'vue-eslint-parser', 8 | parserOptions: { 9 | parser: '@typescript-eslint/parser', 10 | ecmaVersion: 2018, 11 | sourceType: 'module', 12 | }, 13 | extends: [ 14 | 'plugin:vue/recommended', 15 | 'eslint:recommended', 16 | 'plugin:@typescript-eslint/recommended', 17 | 'prettier/@typescript-eslint', 18 | 'prettier/vue', 19 | 'plugin:prettier/recommended', 20 | ], 21 | rules: { 22 | '@typescript-eslint/no-explicit-any': 'off', 23 | '@typescript-eslint/explicit-function-return-type': 'off', // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/explicit-function-return-type.md#configuring-in-a-mixed-jsts-codebase 24 | 'getter-return': ['error', { allowImplicit: true }], 25 | 'vue/html-indent': [ 26 | 'error', 27 | 2, 28 | { 29 | attribute: 1, 30 | baseIndent: 1, 31 | closeBracket: 0, 32 | alignAttributesVertically: true, 33 | ignores: [], 34 | }, 35 | ], 36 | 'vue/component-tags-order': [ 37 | 'error', 38 | { 39 | order: ['template', 'script', 'style'], 40 | }, 41 | ], 42 | '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: 'h' }], 43 | }, 44 | overrides: [ 45 | { 46 | files: ['*.ts', '*.tsx'], 47 | rules: { 48 | '@typescript-eslint/explicit-function-return-type': ['error'], 49 | }, 50 | }, 51 | ], 52 | ignorePatterns: ['dist/*.*', 'node_modules/**/*.*', 'types/**/*.*'], 53 | }; 54 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: vatson 7 | 8 | --- 9 | 10 | 11 | 12 | 19 | 20 | 21 | **Issue Overview** 22 | 23 | **vectre** version: [X.X.X] 24 | **vuejs** version: [X.X.X] 25 | **OS/Browser**: any 26 | 27 | 28 | **Description** 29 | 30 | 31 | 32 | 33 | **Steps to reproduce** 34 | 35 | 41 | 42 | 43 | **Expected behavior** 44 | 45 | 46 | 47 | **Actual behavior** 48 | 49 | 50 | 51 | **Additional context** 52 | Add any other context about the problem here. 53 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/---feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F195 Feature request" 3 | about: Suggest an idea for this project 4 | title: "[FEAT]" 5 | labels: enhancement 6 | assignees: vatson 7 | 8 | --- 9 | 10 | **Description** 11 | 12 | 13 | **Why vectre need this feature** 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .size-snapshot.json 2 | /node_modules/ 3 | /dist/ 4 | /types/ -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "eslint.options": { 4 | "ext": [ 5 | ".ts", 6 | ".tsx", 7 | ".vue", 8 | ".js" 9 | ] 10 | }, 11 | "eslint.validate": [ 12 | "javascript", 13 | "javascriptreact", 14 | "typescript", 15 | "typescriptreact", 16 | "html" 17 | ] 18 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Vectre Changelog 2 | 3 | ## 1.1.0 4 | 5 | ### Breaking changes 6 | 7 | * Rename "size" → "rows" and "scale" → "size" properties of `FormSelect` to keep API uniformity [[commit](https://github.com/vectrejs/vectre/commit/43218e6a9e7c9fbe3fb7f32349274625c5ad0bbb)] 8 | 9 | ### Features 10 | 11 | * Add `Overlay` component [[docs](https://vectrejs.github.io/docs/#/pages/components/overlay)] 12 | * Add `FormSlider` component [[docs](https://vectrejs.github.io/docs/#/pages/form/slider)] 13 | * Add `FormSwitch` and `FormSwitchGroup` as separate components. No more need to use `FormCheckbox` with "switch" type [[updated docs](http://localhost:8080/docs/#/pages/form/switch)] 14 | * Add `ClickOutside` directive [[docs](https://vectrejs.github.io/docs/#/pages/utils/click-outside)] 15 | * Add `Overlay` directive [[docs](https://vectrejs.github.io/docs/#/pages/utils/overlay)] 16 | * Add `noScroll` prop to `Modal` to disable background scrolling 17 | * `overlay` prop of `Modal` can take from 0 to 99 as the opacity level 18 | * Add `htmlTag` prop to `Btn` component to render button as an ordinary link 19 | 20 | ### Fixes 21 | 22 | * Fix dropdown menu opening in Safari [[commit](https://github.com/vectrejs/vectre/commit/4ef59efc61724cbf66cc9170831eebe7bd5e906c)] 23 | * Make Card components susceptible to external attributes (e.g. style/class)[[commit](https://github.com/vectrejs/vectre/commit/4621a4ff8f7dee9cc53e5e9eef0f1830479c28c8)] 24 | * Now `Tooltip` is shown for null values except for undefined [[commit](https://github.com/vectrejs/vectre/commit/e268e89ede5b71f9063e9a7b43a8edb04d257008)] 25 | 26 | ## 1.0.2 27 | 28 | ### Fixes 29 | 30 | * Fix tree shaking -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at dev.vatson@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Vadim Tiukov 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 |

7 | A Lightweight, Simple and Responsive Component Framework 8 |

9 | 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 | Vectre is a set of lightweight, simple and responsive [Vue](https://vuejs.org/) components based on [Spectre CSS](https://picturepan2.github.io/spectre/index.html) 38 | 39 | 40 | 41 | ## Features 42 | 43 | * Clean, responsive and consistent design system 44 | * Only about 14kb min+gzip (plus ~9kb Spectre CSS) 45 | * Supports Typescript and TSX 46 | * Improved performance of most components thanks to [functional components]("https://vuejs.org/v2/guide/render-function.html#Functional-Components") 47 | * Optimized for legacy browsers 48 | * Focus on usability and rapid development 49 | * Tree shaking 50 | 51 | ## Documentation 52 | 53 | The documentation is in a separate [repository](https://github.com/vectrejs/docs) and the source code is an excellent example of using the framework. 54 | 55 | Browse [online documentation here](https://vectrejs.github.io/docs/#/pages/getting-started) 56 | 57 | 58 | 59 | 60 | ## Quick Start 61 | 62 | You need [Vue](https://vuejs.org/) version 2.5+. 63 | 64 | 65 | ### Install via npm or yarn 66 | ```bash 67 | npm install --save spectre.css @vectrejs/vectre 68 | #OR 69 | yarn add spectre.css @vectrejs/vectre 70 | ``` 71 | 72 | 73 | ### Import and use Vectre 74 | 75 | All components 76 | 77 | ```javascript 78 | import Vue from 'vue'; 79 | import 'spectre.css/dist/spectre-exp.css'; 80 | import 'spectre.css/dist/spectre-icons.css'; 81 | import 'spectre.css/dist/spectre.css'; 82 | import { VectrePlugin } from '@vectrejs/vectre'; 83 | 84 | Vue.use(VectrePlugin); 85 | ``` 86 | 87 | or specific components 88 | ```javascript 89 | import Vue from 'vue'; 90 | import 'spectre.css/dist/spectre.css'; 91 | import { Tag, Modal } from '@vectrejs/vectre'; 92 | 93 | Vue.component('Tag', Tag); 94 | Vue.component('Modal', Modal); 95 | ``` 96 | 97 | 98 | ### CDN 99 | 100 | Alternatively, you can download or reference the script and the stylesheet in your HTML: 101 | 102 | ```html 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | ``` 115 | 116 | 117 | 118 | ## Browser support 119 | 120 | At present, Vectre supports all modern browsers and additionally has a build to support older browsers like IE 10 or Safari 9 121 | 122 | ```html 123 | 124 | 125 | 126 | 127 | 128 | ``` 129 | 130 | ## Playgrounds 131 | 132 | * All in One [https://codesandbox.io/s/vectre-all-in-one-yff7f](https://codesandbox.io/s/vectre-all-in-one-yff7f) 133 | * Individual Components [https://codesandbox.io/s/vectre-components-4pd1n](https://codesandbox.io/s/vectre-components-4pd1n) 134 | * CDN [https://codepen.io/vatson_ua/pen/RwavXQY](https://codepen.io/vatson_ua/pen/RwavXQY) 135 | 136 | ## Links 137 | 138 | * [Spectre CSS](https://picturepan2.github.io/spectre/index.html) 139 | * [Typescript](https://www.typescriptlang.org/) 140 | * [TSX](https://github.com/wonderful-panda/vue-tsx-support/blob/v2/README.md) 141 | * Tree Shaking [Webpack](https://webpack.js.org/guides/tree-shaking/) and [Rollup](https://rollupjs.org/guide/en/#tree-shaking) 142 | 143 | 144 | 145 | ## Social 146 | 147 | * [Discord Chat](https://discord.gg/5a6Y8X2) 148 | * [Twitter](https://twitter.com/vectrejs) 149 | * [Issues](https://github.com/vectrejs/vectre/issues) 150 | 151 | 152 | 153 | ## License 154 | 155 | Code released under [MIT](https://github.com/vectrejs/vectre/blob/master/LICENSE) license. -------------------------------------------------------------------------------- /build/babel.js: -------------------------------------------------------------------------------- 1 | import { babel } from '@rollup/plugin-babel'; 2 | 3 | export default (treeshake = true, polyfills = false, legacy = false) => { 4 | const plugins = treeshake 5 | ? [ 6 | 'babel-plugin-typescript-iife-enum', 7 | ['@babel/plugin-transform-typescript', { isTSX: 'preserve' }], 8 | 'babel-plugin-pure-calls-annotation', 9 | [ 10 | '@babel/plugin-transform-runtime', 11 | { 12 | corejs: polyfills && '3', 13 | helpers: polyfills, 14 | regenerator: false, 15 | useESModules: true, 16 | }, 17 | ], 18 | ] 19 | : [['@babel/plugin-transform-typescript', { isTSX: 'preserve' }]]; 20 | 21 | const presets = legacy 22 | ? [ 23 | [ 24 | '@babel/preset-env', 25 | { 26 | modules: false, 27 | useBuiltIns: 'usage', 28 | corejs: { version: '3' }, 29 | targets: { 30 | ie: '10', 31 | }, 32 | }, 33 | ], 34 | '@vue/babel-preset-jsx', 35 | ] 36 | : ['@vue/babel-preset-jsx']; 37 | 38 | return babel({ 39 | plugins, 40 | presets, 41 | babelrc: false, 42 | babelHelpers: 'bundled', 43 | exclude: 'node_modules/**', 44 | extensions: ['.js', '.jsx', '.tsx', '.ts', '.vue'], 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /build/css.js: -------------------------------------------------------------------------------- 1 | import postcss from 'rollup-plugin-postcss'; 2 | 3 | export default () => postcss({ extensions: ['css', 'scss'] }); 4 | -------------------------------------------------------------------------------- /build/resolve.js: -------------------------------------------------------------------------------- 1 | import nodeResolve from '@rollup/plugin-node-resolve'; 2 | 3 | export default () => nodeResolve({ extensions: ['.js', '.jsx', '.tsx', '.ts', '.vue'] }); 4 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | VectreJS - Static 5 | 6 | 10 | 11 | 12 | 20 | 21 | 22 |
23 | 24 | 25 | 26 |

Welcome!

27 |
28 | 29 | 30 | 31 | 32 | 33 | Email 34 | 35 | 36 | 37 | Password 38 | 42 | 43 | 44 | Remember me 45 | 46 | 2 days 47 | 48 | 49 | Sign in 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | Email 61 | 62 | 63 | Sign up 64 | 65 | 66 | 67 |
68 |
69 |
70 | 71 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vectrejs/vectre", 3 | "version": "1.1.0", 4 | "description": "Complete implementation of Spectre.css on Vue 2.x", 5 | "main": "dist/vectre.js", 6 | "module": "dist/vectre.esm.js", 7 | "types": "types/main.d.ts", 8 | "keywords": [ 9 | "vue", 10 | "spectre", 11 | "ui", 12 | "css", 13 | "framework" 14 | ], 15 | "author": { 16 | "name": "Vadim Tiukov", 17 | "email": "dev.vatson@gmail.com" 18 | }, 19 | "license": "MIT", 20 | "files": [ 21 | "CHANGELOG", 22 | "dist/*.js", 23 | "types/**/*.d.ts" 24 | ], 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/vectrejs/vectre.git" 28 | }, 29 | "bugs": { 30 | "url": "https://github.com/vectrejs/vectre/issues" 31 | }, 32 | "homepage": "https://github.com/vectrejs/vectre#readme", 33 | "scripts": { 34 | "prebuild": "rm -rf types/* && tsc --emitDeclarationOnly && npm run lint && rm -rf dist/*", 35 | "build": "rollup -c", 36 | "build:analyze": "rollup -c rollup.analyze.js", 37 | "dev": "rollup -c -w", 38 | "lint": "eslint --ext .ts,.tsx,.vue,.js src/**", 39 | "lint:fix": "npm run lint -- --fix" 40 | }, 41 | "dependencies": { 42 | "vue-tsx-support": "^2.3.3" 43 | }, 44 | "devDependencies": { 45 | "@babel/core": "^7.1.6", 46 | "@babel/plugin-transform-runtime": "^7.11.5", 47 | "@babel/plugin-transform-typescript": "^7.11.0", 48 | "@babel/preset-env": "^7.11.5", 49 | "@rollup/plugin-babel": "^5.2.1", 50 | "@rollup/plugin-commonjs": "^13.0.0", 51 | "@rollup/plugin-node-resolve": "^7.1.3", 52 | "@types/node": "^13.11.1", 53 | "@typescript-eslint/eslint-plugin": "^3.0.1", 54 | "@typescript-eslint/parser": "^3.0.1", 55 | "@vue/babel-preset-app": "^4.5.6", 56 | "@vue/babel-preset-jsx": "^1.1.2", 57 | "babel-plugin-pure-calls-annotation": "^0.3.1", 58 | "babel-plugin-typescript-iife-enum": "^0.2.1", 59 | "core-js": "^3.6.5", 60 | "eslint": "^7.1.0", 61 | "eslint-config-prettier": "^6.11.0", 62 | "eslint-plugin-prettier": "^3.1.3", 63 | "eslint-plugin-vue": "^7.0.0-alpha.4", 64 | "minimist": "^1.2.0", 65 | "node-sass": "^4.14.1", 66 | "postcss": "^7.0.5", 67 | "prettier": "^2.1.2", 68 | "rollup": "^2.22.1", 69 | "rollup-plugin-analyzer": "^3.3.0", 70 | "rollup-plugin-postcss": "^3.1.2", 71 | "rollup-plugin-pure-annotation": "0.0.2", 72 | "rollup-plugin-size-snapshot": "^0.12.0", 73 | "rollup-plugin-terser": "^5.3.0", 74 | "typescript": "^3.8.3", 75 | "vue": "^2.5.22", 76 | "vue-eslint-parser": "^7.1.0", 77 | "vue-template-compiler": "^2.5.22" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | trailingComma: 'all', 4 | singleQuote: true, 5 | printWidth: 120, 6 | tabWidth: 2, 7 | vueIndentScriptAndStyle: false, 8 | }; 9 | -------------------------------------------------------------------------------- /rollup.analyze.js: -------------------------------------------------------------------------------- 1 | import anaylzer from 'rollup-plugin-analyzer'; 2 | import config from './rollup.config'; 3 | 4 | export default () => config().map(c => (c.plugins.push(anaylzer()), c)); 5 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import cjs from '@rollup/plugin-commonjs'; 2 | import { sizeSnapshot } from 'rollup-plugin-size-snapshot'; 3 | import { terser } from 'rollup-plugin-terser'; 4 | import css from './build/css'; 5 | import nodeResolve from './build/resolve'; 6 | import babel from './build/babel'; 7 | 8 | const isProduction = !process.env.ROLLUP_WATCH; 9 | 10 | export default () => { 11 | let config = [ 12 | { 13 | input: 'src/main.ts', 14 | output: { format: 'esm', file: 'dist/vectre.esm.js' }, 15 | external: (id) => { 16 | return /core|^vue$/.test(id); 17 | }, 18 | plugins: [nodeResolve(), cjs(), css(), babel()], 19 | }, 20 | { 21 | input: 'src/main.ts', 22 | output: { format: 'cjs', file: 'dist/vectre.cjs.js' }, 23 | external: (id) => { 24 | return /core|^vue$/.test(id); 25 | }, 26 | plugins: [nodeResolve(), cjs(), css(), babel()], 27 | }, 28 | { 29 | input: 'src/plugin.ts', 30 | output: { 31 | format: 'umd', 32 | file: 'dist/vectre.js', 33 | name: 'Vectre', 34 | exports: 'default', 35 | globals: { 36 | vue: 'Vue', 37 | }, 38 | }, 39 | external: ['vue'], 40 | plugins: [nodeResolve(), cjs(), css(), babel(false, true)], 41 | }, 42 | { 43 | input: 'src/plugin.ts', 44 | output: { 45 | format: 'umd', 46 | file: 'dist/vectre.legacy.js', 47 | name: 'Vectre', 48 | exports: 'default', 49 | globals: { 50 | vue: 'Vue', 51 | }, 52 | }, 53 | external: ['vue'], 54 | plugins: [nodeResolve(), cjs(), css(), babel(false, true, true)], 55 | }, 56 | ]; 57 | 58 | config.forEach((c) => c.plugins.push(sizeSnapshot())); 59 | 60 | config 61 | .filter((c) => c.output.format === 'umd') 62 | .forEach((c) => { 63 | config.push({ 64 | ...c, 65 | output: { ...c.output, file: c.output.file.replace(/\.js/g, '.min.js') }, 66 | plugins: [...c.plugins, terser()], 67 | }); 68 | }); 69 | 70 | if (!isProduction) { 71 | config = config.filter((c) => c.output.format === 'esm'); 72 | } 73 | 74 | return config; 75 | }; 76 | -------------------------------------------------------------------------------- /src/components/Accordion/Accordion.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { CreateElement, VNode } from 'vue'; 3 | import { uid } from '../../utils/uid'; 4 | import { Icon, IconType } from '../Icon'; 5 | import { AccordionEvents } from './Event'; 6 | 7 | export const Accordion = /*#__PURE__*/ tsx.componentFactoryOf().create({ 8 | name: 'Accordion', 9 | props: { 10 | items: { required: true, type: [Object, Array] as (() => Record | string[])[] }, 11 | checked: { type: [String, Number, Array], default: undefined }, 12 | name: { type: String, default: undefined }, 13 | multiple: { type: Boolean }, 14 | icon: { type: String as () => IconType, default: undefined }, 15 | }, 16 | computed: { 17 | $_name(): string { 18 | return this.name || 'accordion-' + uid(this); 19 | }, 20 | }, 21 | methods: { 22 | isChecked(key: string, index: number): boolean { 23 | if (!Array.isArray(this.checked)) { 24 | return !!this.checked && (this.checked === key || this.checked.toString() === index.toString()); 25 | } 26 | 27 | return this.checked.indexOf(index) !== -1 || this.checked.indexOf(key) !== -1; 28 | }, 29 | toggle(event: Event, key: string, index: number): void { 30 | if (!this.$listeners.check) return; 31 | 32 | if (!this.multiple) { 33 | this.$emit('check', key || index || 0); 34 | return; 35 | } 36 | 37 | let checked = Array.isArray(this.checked) ? [...this.checked] : this.checked !== undefined ? [this.checked] : []; 38 | 39 | if ((event.target as HTMLInputElement).checked) { 40 | checked.push(key || index || 0); 41 | } else { 42 | checked = checked.filter((item) => item !== index && item !== key); 43 | } 44 | 45 | this.$emit('check', checked); 46 | }, 47 | }, 48 | render(h: CreateElement): VNode { 49 | const items = Array.isArray(this.items) ? { ...this.items } : this.items; 50 | 51 | const accordionItems = Object.keys(items).map((key, index) => { 52 | const id = `${this.$_name}-${key}`; 53 | const type = this.multiple ? 'checkbox' : 'radio'; 54 | const headerSlot = this.$scopedSlots['header']; 55 | const bodySlot = this.$scopedSlots['body'] || this.$scopedSlots['default']; 56 | 57 | return ( 58 |
59 | this.toggle(e, key, index)} 65 | hidden 66 | /> 67 | 68 | 73 | 74 | {!bodySlot &&
} 75 | {bodySlot &&
{bodySlot({ header: key, item: items[key as any] })}
} 76 |
77 | ); 78 | }); 79 | 80 | return
{accordionItems}
; 81 | }, 82 | }); 83 | -------------------------------------------------------------------------------- /src/components/Accordion/Event.ts: -------------------------------------------------------------------------------- 1 | export interface AccordionEvents { 2 | onCheck: (value: string | number | string[] | number[]) => void; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/Accordion/index.ts: -------------------------------------------------------------------------------- 1 | import { Accordion } from './Accordion'; 2 | import { makePluggableComponents } from '../../utils/plugin'; 3 | 4 | export default makePluggableComponents({ Accordion }); 5 | export { Accordion }; 6 | -------------------------------------------------------------------------------- /src/components/Avatar/Avatar.tsx: -------------------------------------------------------------------------------- 1 | import { CreateElement, VNode } from 'vue'; 2 | import * as tsx from 'vue-tsx-support'; 3 | import { AvatarSize, AvatarSizes } from './Size'; 4 | import { AvatarPresence } from './Presence'; 5 | import './style.scss'; 6 | 7 | export const Avatar = tsx.component({ 8 | name: 'Avatar', 9 | functional: true, 10 | props: { 11 | size: { type: String as () => AvatarSize, default: undefined }, 12 | src: { type: String, default: undefined }, 13 | initials: { type: String, default: undefined }, 14 | background: { type: String, default: undefined }, 15 | color: { type: String, default: undefined }, 16 | alt: { type: String, default: undefined }, 17 | presence: { type: String as () => AvatarPresence, default: undefined }, 18 | icon: { type: String, default: undefined }, 19 | }, 20 | render(h: CreateElement, { props, data }): VNode { 21 | const cssClass = ['avatar', AvatarSizes[props.size] || props.size]; 22 | const cssStyles = { color: props.color, background: props.background }; 23 | const initials = props.initials && props.initials.trim().substring(0, 2); 24 | 25 | return ( 26 |
27 | {props.src && {props.alt}} 28 | {props.icon && } 29 | {props.presence && !props.icon && } 30 |
31 | ); 32 | }, 33 | }); 34 | -------------------------------------------------------------------------------- /src/components/Avatar/Presence.ts: -------------------------------------------------------------------------------- 1 | export enum AvatarPresences { 2 | online = 'online', 3 | busy = 'busy', 4 | away = 'away', 5 | offline = 'offline', 6 | } 7 | 8 | export type AvatarPresence = keyof typeof AvatarPresences; 9 | -------------------------------------------------------------------------------- /src/components/Avatar/Size.ts: -------------------------------------------------------------------------------- 1 | export enum AvatarSizes { 2 | xl = 'avatar-xl', 3 | lg = 'avatar-lg', 4 | sm = 'avatar-sm', 5 | xs = 'avatar-xs', 6 | } 7 | 8 | export type AvatarSize = keyof typeof AvatarSizes; 9 | -------------------------------------------------------------------------------- /src/components/Avatar/index.ts: -------------------------------------------------------------------------------- 1 | import { Avatar } from './Avatar'; 2 | import { makePluggableComponents } from '../../utils/plugin'; 3 | 4 | export default makePluggableComponents({ Avatar }); 5 | export { Avatar }; 6 | export * from './Size'; 7 | export * from './Presence'; 8 | -------------------------------------------------------------------------------- /src/components/Avatar/style.scss: -------------------------------------------------------------------------------- 1 | figure.avatar + figure.avatar { 2 | margin-left: 0.4rem; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/Bar/Bar.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { CreateElement, VNode } from 'vue'; 3 | 4 | export const Bar = tsx.component({ 5 | name: 'Bar', 6 | functional: true, 7 | props: { 8 | sm: { type: Boolean }, 9 | min: { type: Number, default: 0 }, 10 | max: { type: Number, default: 100 }, 11 | value: { type: Number, default: 0 }, 12 | tooltip: { 13 | type: [String, Function] as (() => string | ((value: number) => string))[], 14 | default: undefined, 15 | }, 16 | }, 17 | render(h: CreateElement, { props }): VNode { 18 | const barCssClass = ['bar', props.sm && 'bar-sm']; 19 | const barItemCssClass = ['bar-item', props.tooltip && 'tooltip']; 20 | const barItemStyles = { width: (props.value / props.max) * 100 + '%' }; 21 | 22 | return ( 23 |
24 |
33 |
34 | ); 35 | }, 36 | }); 37 | -------------------------------------------------------------------------------- /src/components/Bar/index.ts: -------------------------------------------------------------------------------- 1 | import { Bar } from './Bar'; 2 | import { makePluggableComponents } from '../../utils/plugin'; 3 | 4 | export default makePluggableComponents({ Bar }); 5 | export { Bar }; 6 | -------------------------------------------------------------------------------- /src/components/Breadcrumb/Breadcrumb.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { CreateElement, VNode } from 'vue'; 3 | import { Crumb } from './Crumb'; 4 | 5 | export const Breadcrumb = tsx.component({ 6 | name: 'Breadcrumb', 7 | functional: true, 8 | props: { 9 | crumbs: { type: Array as () => Crumb[], required: true }, 10 | }, 11 | render(h: CreateElement, { props, scopedSlots }): VNode { 12 | const crumbs = props.crumbs.map((crumb) => { 13 | const slot = scopedSlots.default && scopedSlots.default({ crumb }); 14 | const text = {crumb.title}; 15 | 16 | return
  • {slot || text}
  • ; 17 | }); 18 | 19 | return
      {crumbs}
    ; 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /src/components/Breadcrumb/Crumb.ts: -------------------------------------------------------------------------------- 1 | export interface Crumb { 2 | path: string; 3 | title: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/components/Breadcrumb/index.ts: -------------------------------------------------------------------------------- 1 | import { Breadcrumb } from './Breadcrumb'; 2 | import { makePluggableComponents } from '../../utils/plugin'; 3 | 4 | export default makePluggableComponents({ Breadcrumb }); 5 | export { Breadcrumb }; 6 | -------------------------------------------------------------------------------- /src/components/Btn/Btn.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import './styles.scss'; 3 | import { CreateElement, VNode } from 'vue'; 4 | import { BtnType, BtnTypes } from './Type'; 5 | import { BtnSizes, BtnSize } from './Size'; 6 | import { BtnState, BtnStates } from './State'; 7 | import { IconType } from '../Icon'; 8 | import { Icon } from '../Icon/Icon'; 9 | import { BtnEvents } from './Events'; 10 | import { mergeCss } from '../../utils/css'; 11 | 12 | export const Btn = /*#__PURE__*/ tsx.componentFactoryOf().create({ 13 | name: 'Btn', 14 | functional: true, 15 | props: { 16 | type: { type: String as () => BtnType }, 17 | size: { type: String as () => BtnSize }, 18 | icon: { type: String as () => IconType }, 19 | state: { type: String as () => BtnState }, 20 | tabindex: { type: [Number, String], default: undefined }, 21 | left: { type: Boolean }, 22 | circle: { type: Boolean }, 23 | action: { type: Boolean }, 24 | htmlTag: { 25 | type: String as () => 'a' | 'button', 26 | validator: (tag: 'a' | 'button'): boolean => ['a', 'button'].includes(tag), 27 | }, 28 | }, 29 | render(h: CreateElement, { props, data, slots }): VNode { 30 | const cssClass = mergeCss(data, 'btn', [ 31 | BtnTypes[props.type as BtnType] || props.type, 32 | BtnSizes[props.size] || props.size, 33 | BtnStates[props.state] || props.state, 34 | props.action && props.circle && 's-circle', 35 | props.action && 'btn-action', 36 | ]); 37 | 38 | const leftIcon = props.icon && props.left ? : ''; 39 | const rightIcon = props.icon && !props.left ? : ''; 40 | const content = !props.action && slots().default; 41 | const htmlTag = ['a', 'button'].includes(props.htmlTag) ? props.htmlTag : 'button'; 42 | 43 | return h(htmlTag, { ...data, class: cssClass, attrs: { tabindex: props.tabindex, ...data.attrs } }, [ 44 | leftIcon, 45 | content, 46 | rightIcon, 47 | ]); 48 | }, 49 | }); 50 | -------------------------------------------------------------------------------- /src/components/Btn/BtnGroup.tsx: -------------------------------------------------------------------------------- 1 | import { CreateElement, VNode } from 'vue'; 2 | import * as tsx from 'vue-tsx-support'; 3 | 4 | export const BtnGroup = tsx.component({ 5 | name: 'BtnGroup', 6 | functional: true, 7 | props: { 8 | block: { type: Boolean }, 9 | }, 10 | render(h: CreateElement, { props, data, slots }): VNode { 11 | const cssClass: string[] = ['btn-group', props.block && 'btn-group-block']; 12 | 13 | return ( 14 |
    15 | {slots().default} 16 |
    17 | ); 18 | }, 19 | }); 20 | -------------------------------------------------------------------------------- /src/components/Btn/Events.tsx: -------------------------------------------------------------------------------- 1 | export interface BtnEvents { 2 | onBlur: (event: Event) => void; 3 | onFocus: (event: Event) => void; 4 | onClick: (event: Event) => void; 5 | } 6 | -------------------------------------------------------------------------------- /src/components/Btn/Size.ts: -------------------------------------------------------------------------------- 1 | export enum BtnSizes { 2 | sm = 'btn-sm', 3 | lg = 'btn-lg', 4 | block = 'btn-block', 5 | } 6 | 7 | export type BtnSize = keyof typeof BtnSizes; 8 | -------------------------------------------------------------------------------- /src/components/Btn/State.ts: -------------------------------------------------------------------------------- 1 | export enum BtnStates { 2 | active = 'active', 3 | disabled = 'disabled', 4 | loading = 'loading', 5 | } 6 | 7 | export type BtnState = keyof typeof BtnStates; 8 | -------------------------------------------------------------------------------- /src/components/Btn/Type.ts: -------------------------------------------------------------------------------- 1 | export enum BtnTypes { 2 | primary = 'btn-primary', 3 | link = 'btn-link', 4 | success = 'btn-success', 5 | error = 'btn-error', 6 | clear = 'btn-clear', 7 | } 8 | 9 | export type BtnType = keyof typeof BtnTypes; 10 | -------------------------------------------------------------------------------- /src/components/Btn/index.ts: -------------------------------------------------------------------------------- 1 | import { Btn } from './Btn'; 2 | import { BtnGroup } from './BtnGroup'; 3 | import { makePluggableComponents } from '../../utils/plugin'; 4 | 5 | export default makePluggableComponents({ Btn, BtnGroup }); 6 | export { Btn, BtnGroup }; 7 | export * from './Size'; 8 | export * from './State'; 9 | export * from './Type'; 10 | -------------------------------------------------------------------------------- /src/components/Btn/styles.scss: -------------------------------------------------------------------------------- 1 | .btn + .btn { 2 | margin-left: 0.4rem; 3 | } 4 | 5 | .btn { 6 | &:not(.btn-action) .icon { 7 | margin: 0 0 0 0.2rem; 8 | 9 | &.left { 10 | margin: 0 0.2rem 0 0; 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/components/Card/Card.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { CreateElement, VNode } from 'vue'; 3 | import { mergeCss } from '../../utils/css'; 4 | 5 | export const Card = tsx.component({ 6 | name: 'Card', 7 | functional: true, 8 | render(h: CreateElement, { children, data }): VNode { 9 | const cssClass = mergeCss(data, 'card'); 10 | 11 | return ( 12 |
    13 | {children} 14 |
    15 | ); 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/Card/CardBody.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { CreateElement, VNode } from 'vue'; 3 | import { mergeCss } from '../../utils/css'; 4 | 5 | export const CardBody = tsx.component({ 6 | name: 'CardBody', 7 | functional: true, 8 | render(h: CreateElement, { children, data }): VNode { 9 | const cssClass = mergeCss(data, 'card-body'); 10 | 11 | return ( 12 |
    13 | {children} 14 |
    15 | ); 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/Card/CardFooter.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { CreateElement, VNode } from 'vue'; 3 | import { mergeCss } from '../../utils/css'; 4 | 5 | export const CardFooter = tsx.component({ 6 | name: 'CardFooter', 7 | functional: true, 8 | render(h: CreateElement, { children, data }): VNode { 9 | const cssClass = mergeCss(data, 'card-footer'); 10 | 11 | return ( 12 |
    13 | {children} 14 |
    15 | ); 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/Card/CardHeader.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { CreateElement, VNode } from 'vue'; 3 | import { mergeCss } from '../../utils/css'; 4 | 5 | export const CardHeader = tsx.component({ 6 | name: 'CardHeader', 7 | functional: true, 8 | render(h: CreateElement, { children, data }): VNode { 9 | const cssClass = mergeCss(data, 'card-header'); 10 | 11 | return ( 12 |
    13 | {children} 14 |
    15 | ); 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/Card/CardImage.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { CreateElement, VNode } from 'vue'; 3 | import { mergeCss } from '../../utils/css'; 4 | 5 | export const CardImage = tsx.component({ 6 | name: 'CardImage', 7 | functional: true, 8 | props: { 9 | img: { type: String, required: true }, 10 | }, 11 | render(h: CreateElement, { props, data }): VNode { 12 | const cssClass = mergeCss(data, 'card-image'); 13 | 14 | return ( 15 |
    16 | 17 |
    18 | ); 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /src/components/Card/Image.ts: -------------------------------------------------------------------------------- 1 | export enum CardImagePositions { 2 | before = 'before', 3 | after = 'after', 4 | } 5 | export type CardImagePosition = keyof typeof CardImagePositions; 6 | 7 | export enum CardImageSlots { 8 | header = 'header', 9 | body = 'body', 10 | footer = 'footer', 11 | } 12 | export type CardImageSlot = keyof typeof CardImageSlots; 13 | -------------------------------------------------------------------------------- /src/components/Card/index.ts: -------------------------------------------------------------------------------- 1 | import { Card } from './Card'; 2 | import { CardBody } from './CardBody'; 3 | import { CardFooter } from './CardFooter'; 4 | import { CardHeader } from './CardHeader'; 5 | import { CardImage } from './CardImage'; 6 | import { makePluggableComponents } from '../../utils/plugin'; 7 | 8 | export default makePluggableComponents({ Card, CardBody, CardFooter, CardHeader, CardImage }); 9 | export { Card, CardBody, CardFooter, CardHeader, CardImage }; 10 | export * from './Image'; 11 | -------------------------------------------------------------------------------- /src/components/Chip/Chip.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { CreateElement, VNode } from 'vue'; 3 | import { Avatar } from '../Avatar'; 4 | import { flattenListener } from '../../utils/listener'; 5 | import { ChipEvents } from './Event'; 6 | 7 | export const Chip = tsx.componentFactoryOf().create({ 8 | name: 'Chip', 9 | functional: true, 10 | props: { 11 | active: { type: Boolean }, 12 | text: { type: String, required: true }, 13 | avatar: { type: String, default: undefined }, 14 | initials: { type: String, default: undefined }, 15 | small: { type: Boolean }, 16 | }, 17 | render(h: CreateElement, { props, listeners }): VNode { 18 | const cssClass = ['chip', props.active && 'active']; 19 | 20 | const avatar = (props.avatar || props.initials) && ( 21 | 22 | ); 23 | const closeBtn = listeners.close && ( 24 | 25 | ); 26 | 27 | return ( 28 | 29 | {avatar} 30 | {props.text} 31 | {closeBtn} 32 | 33 | ); 34 | }, 35 | }); 36 | -------------------------------------------------------------------------------- /src/components/Chip/Event.ts: -------------------------------------------------------------------------------- 1 | export interface ChipEvents { 2 | onClose: () => void; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/Chip/index.ts: -------------------------------------------------------------------------------- 1 | import { Chip } from './Chip'; 2 | import { makePluggableComponents } from '../../utils/plugin'; 3 | 4 | export default makePluggableComponents({ Chip }); 5 | export { Chip }; 6 | -------------------------------------------------------------------------------- /src/components/Column/Column.tsx: -------------------------------------------------------------------------------- 1 | import { CreateElement, VNode } from 'vue'; 2 | import * as tsx from 'vue-tsx-support'; 3 | 4 | const sizeValidator = (size: number): boolean => size % 1 === 0 && size > 0 && size <= 12; 5 | const cssClass = (props: Record): string[] => { 6 | return [ 7 | 'column', 8 | props.mr ? 'col-mr-auto' : '', 9 | props.ml ? 'col-ml-auto' : '', 10 | props.mx ? 'col-mx-auto' : '', 11 | props.xs ? `col-xs-${props.xs}` : '', 12 | props.sm ? `col-sm-${props.sm}` : '', 13 | props.md ? `col-md-${props.md}` : '', 14 | props.lg ? `col-lg-${props.lg}` : '', 15 | props.xl ? `col-xl-${props.xl}` : '', 16 | props.col ? `col-${props.col}` : '', 17 | props.hide ? `hide-${props.hide}` : '', 18 | props.show ? `show-${props.show}` : '', 19 | ]; 20 | }; 21 | 22 | export const Column = tsx.createComponent({ 23 | name: 'Column', 24 | functional: true, 25 | props: { 26 | ml: { type: Boolean }, 27 | mx: { type: Boolean }, 28 | mr: { type: Boolean }, 29 | xs: { type: [Number, String], validator: sizeValidator }, 30 | sm: { type: [Number, String], validator: sizeValidator }, 31 | md: { 32 | type: [Number, String], 33 | validator: sizeValidator, 34 | }, 35 | lg: { type: [Number, String], validator: sizeValidator }, 36 | xl: { 37 | type: [Number, String], 38 | validator: sizeValidator, 39 | }, 40 | col: { type: [Number, String], validator: sizeValidator }, 41 | hide: { 42 | type: [Number, String], 43 | }, 44 | show: { type: [Number, String] }, 45 | }, 46 | render(h: CreateElement, { props, children, data }): VNode { 47 | return ( 48 |
    49 | {children} 50 |
    51 | ); 52 | }, 53 | }); 54 | -------------------------------------------------------------------------------- /src/components/Column/index.ts: -------------------------------------------------------------------------------- 1 | import { Column } from './Column'; 2 | import { makePluggableComponents } from '../../utils/plugin'; 3 | 4 | export default makePluggableComponents({ Column }); 5 | export { Column }; 6 | -------------------------------------------------------------------------------- /src/components/Columns/Columns.tsx: -------------------------------------------------------------------------------- 1 | import { CreateElement, VNode } from 'vue'; 2 | import * as tsx from 'vue-tsx-support'; 3 | 4 | export const Columns = tsx.createComponent({ 5 | name: 'Columns', 6 | functional: true, 7 | props: { 8 | gapless: Boolean, 9 | oneline: Boolean, 10 | }, 11 | render(h: CreateElement, { props, children, data }): VNode { 12 | const cssClasses = ['columns', props.gapless && 'col-gapless', props.oneline && 'col-oneline']; 13 | 14 | return ( 15 |
    16 | {children} 17 |
    18 | ); 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /src/components/Columns/index.ts: -------------------------------------------------------------------------------- 1 | import { Columns } from './Columns'; 2 | import { makePluggableComponents } from '../../utils/plugin'; 3 | 4 | export default makePluggableComponents({ Columns }); 5 | export { Columns }; 6 | -------------------------------------------------------------------------------- /src/components/Container/Container.tsx: -------------------------------------------------------------------------------- 1 | import { Grids, Grid } from './Grid'; 2 | import * as tsx from 'vue-tsx-support'; 3 | import { CreateElement, VNode } from 'vue'; 4 | 5 | export const Container = tsx.createComponent({ 6 | name: 'Container', 7 | functional: true, 8 | props: { 9 | grid: { type: String as () => Grid }, 10 | }, 11 | render(h: CreateElement, { props, children, data }): VNode { 12 | const cssClasses = ['container', Grids[props.grid as Grid]]; 13 | 14 | return ( 15 |
    16 | {children} 17 |
    18 | ); 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /src/components/Container/Grid.ts: -------------------------------------------------------------------------------- 1 | export enum Grids { 2 | xs = 'grid-xs', // 480px 3 | sm = 'grid-sm', // 600px 4 | md = 'grid-md', // 840px 5 | lg = 'grid-lg', // 960px 6 | xl = 'grid-xl', // 1280px 7 | } 8 | 9 | export type Grid = keyof typeof Grids; 10 | -------------------------------------------------------------------------------- /src/components/Container/index.ts: -------------------------------------------------------------------------------- 1 | import { Container } from './Container'; 2 | import { makePluggableComponents } from '../../utils/plugin'; 3 | 4 | export default makePluggableComponents({ Container }); 5 | export { Container }; 6 | export * from './Grid'; 7 | -------------------------------------------------------------------------------- /src/components/Divider/Divider.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { CreateElement, VNode } from 'vue'; 3 | 4 | export const Divider = tsx.component({ 5 | name: 'Divider', 6 | functional: true, 7 | props: { 8 | vert: { type: Boolean }, 9 | content: { type: String, default: undefined }, 10 | }, 11 | 12 | render(h: CreateElement, { props, slots, data }): VNode { 13 | const classes = ['text-center', props.vert && 'divider-vert', !props.vert && 'divider']; 14 | const dataContent = props.content || (slots().default && slots().default[0].text); 15 | 16 | return
    ; 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /src/components/Divider/index.ts: -------------------------------------------------------------------------------- 1 | import { Divider } from './Divider'; 2 | import { makePluggableComponents } from '../../utils/plugin'; 3 | 4 | export default makePluggableComponents({ Divider }); 5 | export { Divider }; 6 | -------------------------------------------------------------------------------- /src/components/DropdownMenu/DropdownMenu.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { CreateElement, VNode, VNodeDirective } from 'vue'; 3 | import { BtnType, BtnTypes, Btn, BtnState } from '../Btn'; 4 | import { IconType } from '../Icon'; 5 | import { VerticalMenu } from '../VerticalMenu'; 6 | import { ClickOutside } from '../../directives/ClickOutside'; 7 | import { flattenListener } from '../../utils/listener'; 8 | 9 | export const DropdownMenu = /*#__PURE__*/ tsx.component({ 10 | name: 'DropdownMenu', 11 | directives: { 12 | ClickOutside, 13 | }, 14 | props: { 15 | items: { type: [Object, Array], default: undefined }, 16 | right: { type: Boolean }, 17 | btnType: { type: String as () => BtnType, default: undefined }, 18 | btnText: { type: String, default: undefined }, 19 | btnIcon: { type: String as () => IconType, default: undefined }, 20 | state: { type: String as () => BtnState, default: undefined }, 21 | }, 22 | render(h: CreateElement): VNode { 23 | const onOpen = flattenListener(this.$listeners.opened); 24 | const onClose = flattenListener(this.$listeners.closed); 25 | const btnCssClass = [BtnTypes[this.btnType], 'dropdown-toggle']; 26 | const btn = ( 27 | 36 | {this.btnText} 37 | 38 | ); 39 | 40 | const directives: VNodeDirective[] = [ 41 | { 42 | name: 'click-outside', 43 | value: (): void => (btn.elm as HTMLElement).blur(), 44 | modifiers: { touch: false }, 45 | }, 46 | ]; 47 | 48 | return ( 49 |
    50 | {btn} 51 | 52 |
    53 | ); 54 | }, 55 | }); 56 | -------------------------------------------------------------------------------- /src/components/DropdownMenu/index.ts: -------------------------------------------------------------------------------- 1 | import { DropdownMenu } from './DropdownMenu'; 2 | import { makePluggableComponents } from '../../utils/plugin'; 3 | 4 | export default makePluggableComponents({ DropdownMenu }); 5 | export { DropdownMenu }; 6 | -------------------------------------------------------------------------------- /src/components/Empty/Empty.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { CreateElement, VNode } from 'vue'; 3 | import { IconType } from '../Icon'; 4 | import { EmptyTitle } from './EmptyTitle'; 5 | import { EmptySubtitle } from './EmptySubtitle'; 6 | import { EmptyIcon } from './EmptyIcon'; 7 | import { EmptyContent } from './EmptyContent'; 8 | import { EmptyAction } from './EmptyAction'; 9 | 10 | export const Empty = tsx.component({ 11 | name: 'Empty', 12 | functional: true, 13 | props: { 14 | icon: { type: String as () => IconType, default: undefined }, 15 | title: { type: String as () => IconType, default: undefined }, 16 | sub: { type: String as () => IconType, default: undefined }, 17 | }, 18 | render(h: CreateElement, { props, slots }): VNode { 19 | const title = props.title && {props.title}; 20 | const sub = props.sub && {props.sub}; 21 | const icon = props.icon && ; 22 | 23 | const _slots = slots(); 24 | const content = _slots.content && {_slots.content}; 25 | const actions = _slots.action && {_slots.action}; 26 | 27 | return ( 28 |
    29 | {icon} 30 | {title} 31 | {sub} 32 | {content} 33 | {actions} 34 | {_slots.default} 35 |
    36 | ); 37 | }, 38 | }); 39 | -------------------------------------------------------------------------------- /src/components/Empty/EmptyAction.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { CreateElement, VNode } from 'vue'; 3 | 4 | export const EmptyAction = tsx.component({ 5 | name: 'EmptyAction', 6 | functional: true, 7 | render(h: CreateElement, { children }): VNode { 8 | return

    {children}

    ; 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /src/components/Empty/EmptyContent.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { CreateElement, VNode } from 'vue'; 3 | 4 | export const EmptyContent = tsx.component({ 5 | name: 'EmptyContent', 6 | functional: true, 7 | render(h: CreateElement, { children }): VNode { 8 | return
    {children}
    ; 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /src/components/Empty/EmptyIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { CreateElement, VNode } from 'vue'; 3 | import { Icon, IconType } from '../Icon'; 4 | 5 | export const EmptyIcon = tsx.component({ 6 | name: 'EmptyIcon', 7 | functional: true, 8 | props: { 9 | icon: { type: String as () => IconType, required: true }, 10 | }, 11 | render(h: CreateElement, { props }): VNode { 12 | return ( 13 |
    14 | 15 |
    16 | ); 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /src/components/Empty/EmptySubtitle.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { CreateElement, VNode } from 'vue'; 3 | 4 | export const EmptySubtitle = tsx.component({ 5 | name: 'EmptySubtitle', 6 | functional: true, 7 | render(h: CreateElement, { children }): VNode { 8 | return

    {children}

    ; 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /src/components/Empty/EmptyTitle.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { CreateElement, VNode } from 'vue'; 3 | 4 | export const EmptyTitle = tsx.component({ 5 | name: 'EmptyTitle', 6 | functional: true, 7 | render(h: CreateElement, { children }): VNode { 8 | return

    {children}

    ; 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /src/components/Empty/index.ts: -------------------------------------------------------------------------------- 1 | import { Empty } from './Empty'; 2 | import { EmptyAction } from './EmptyAction'; 3 | import { EmptyContent } from './EmptyContent'; 4 | import { EmptyIcon } from './EmptyIcon'; 5 | import { EmptySubtitle } from './EmptySubtitle'; 6 | import { EmptyTitle } from './EmptyTitle'; 7 | import { makePluggableComponents } from '../../utils/plugin'; 8 | 9 | export default makePluggableComponents({ Empty, EmptyAction, EmptyContent, EmptyIcon, EmptySubtitle, EmptyTitle }); 10 | export { Empty, EmptyAction, EmptyContent, EmptyIcon, EmptySubtitle, EmptyTitle }; 11 | -------------------------------------------------------------------------------- /src/components/FormCheckbox/Checkbox.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { FormCheckboxType, FormCheckboxTypes } from './Type'; 3 | import { FormCheckboxSize, FormCheckboxSizes } from './Size'; 4 | import { CreateElement, VNode } from 'vue'; 5 | import { cachedListeners } from '../../mixins/cache'; 6 | import { FormCheckboxEvents } from './Event'; 7 | 8 | export const FormCheckbox = /*#__PURE__*/ tsx 9 | .componentFactoryOf() 10 | .mixin(cachedListeners) 11 | .create({ 12 | name: 'FormCheckbox', 13 | props: { 14 | checked: { type: Boolean }, 15 | disabled: { type: Boolean }, 16 | inline: { type: Boolean }, 17 | label: { type: [String, Number] }, 18 | model: { type: undefined as any }, 19 | value: { type: undefined as any }, 20 | size: { 21 | type: String as () => FormCheckboxSize, 22 | validator: (v: FormCheckboxSize): boolean => Object.keys(FormCheckboxSizes).includes(v), 23 | }, 24 | type: { 25 | type: String as () => FormCheckboxType, 26 | validator: (v: FormCheckboxType): boolean => Object.keys(FormCheckboxTypes).includes(v), 27 | }, 28 | error: { type: Boolean }, 29 | }, 30 | model: { 31 | prop: 'model', 32 | event: 'change', 33 | }, 34 | computed: { 35 | _checked(): boolean { 36 | if (!Array.isArray(this.model)) { 37 | return this.checked || (this.model && this.model === this.value); 38 | } 39 | 40 | return this.model.includes(this.value); 41 | }, 42 | }, 43 | methods: { 44 | onChange({ target: { checked } }: any): void { 45 | if (this.model === undefined || !Array.isArray(this.model)) { 46 | this.$emit('change', checked ? this.value || checked : false); 47 | return; 48 | } 49 | 50 | if (checked) { 51 | this.$emit('change', [...this.model, this.value]); 52 | } else { 53 | this.$emit( 54 | 'change', 55 | this.model.filter((option: any) => option !== this.value), 56 | ); 57 | } 58 | }, 59 | }, 60 | render(h: CreateElement): VNode { 61 | const cssClass = [ 62 | FormCheckboxTypes[this.type as FormCheckboxType] || 'form-checkbox', 63 | this.inline ? 'form-inline' : '', 64 | this.error ? 'is-error' : false, 65 | FormCheckboxSizes[this.size as FormCheckboxSize], 66 | ]; 67 | 68 | return ( 69 | 79 | ); 80 | }, 81 | }); 82 | -------------------------------------------------------------------------------- /src/components/FormCheckbox/Event.ts: -------------------------------------------------------------------------------- 1 | export interface FormCheckboxEvents { 2 | onChange: (value: any) => void; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/FormCheckbox/Group.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { FormCheckbox } from './Checkbox'; 3 | import { VNode, CreateElement, VNodeComponentOptions } from 'vue'; 4 | import { FormCheckboxType } from './Type'; 5 | import { FormCheckboxSize } from './Size'; 6 | import { FormCheckboxEvents } from './Event'; 7 | import { cachedListeners } from '../../mixins/cache'; 8 | 9 | interface NormalizedOption { 10 | label: string; 11 | value: any; 12 | } 13 | 14 | const normalizeOptions = (options: { [label: string]: any } | string[]): NormalizedOption[] => { 15 | if (Array.isArray(options)) { 16 | return options.reduce((normal, value) => [...normal, { value, label: value }], [] as any[]); 17 | } 18 | 19 | const normalized = []; 20 | for (const label of Object.keys(options)) { 21 | normalized.push({ label, value: options[label] }); 22 | } 23 | 24 | return normalized; 25 | }; 26 | 27 | const isCheckboxTag = (tag = ''): boolean => /^.*form-?(checkbox|switch)$/i.test(tag); 28 | 29 | export const FormCheckboxGroup = /*#__PURE__*/ tsx 30 | .componentFactoryOf() 31 | .mixin(cachedListeners) 32 | .create({ 33 | name: 'FormCheckboxGroup', 34 | model: { 35 | event: 'change', 36 | }, 37 | props: { 38 | options: { type: [Array, Object] as (() => { [label: string]: string } | string[])[] }, 39 | value: { type: [Array, Object], default: (): any[] => [] }, 40 | type: { type: String as () => FormCheckboxType, default: undefined }, 41 | size: { type: String as () => FormCheckboxSize, default: undefined }, 42 | inline: { type: Boolean }, 43 | disabled: { type: Boolean }, 44 | error: { type: Boolean }, 45 | }, 46 | methods: { 47 | onChange(value: any): void { 48 | this.$emit('change', value); 49 | }, 50 | }, 51 | render(h: CreateElement): VNode { 52 | let group; 53 | 54 | if (this.options) { 55 | group = normalizeOptions(this.options).map(({ label, value }) => { 56 | return ( 57 | 67 | ); 68 | }); 69 | } else { 70 | group = (this.$slots.default || []) 71 | .filter( 72 | ({ tag = '', componentOptions: { tag: componentTag = '' } }) => 73 | isCheckboxTag(tag) || isCheckboxTag(componentTag), 74 | ) 75 | .map((option: VNode) => { 76 | if (!option.componentOptions) { 77 | option.componentOptions = {} as VNodeComponentOptions; 78 | } 79 | if (!option.componentOptions.propsData) { 80 | option.componentOptions.propsData = {}; 81 | } 82 | const props = option.componentOptions.propsData as InstanceType; 83 | props.model = this.value; 84 | props.inline = this.inline || this.inline; 85 | props.type = this.type || this.type; 86 | props.size = this.size !== undefined ? this.size : this.size; 87 | props.disabled = this.disabled !== undefined ? this.disabled : this.disabled; 88 | props.error = this.error !== undefined ? this.error : this.error; 89 | 90 | const listeners = option.componentOptions.listeners as any; 91 | const change = [this.onChange]; 92 | if (listeners && listeners.change) { 93 | change.push(listeners.change); 94 | } 95 | 96 | option.componentOptions.listeners = { 97 | ...listeners, 98 | change, 99 | }; 100 | 101 | return option; 102 | }); 103 | } 104 | 105 | return
    {group}
    ; 106 | }, 107 | }); 108 | -------------------------------------------------------------------------------- /src/components/FormCheckbox/Size.ts: -------------------------------------------------------------------------------- 1 | export enum FormCheckboxSizes { 2 | sm = 'input-sm', 3 | lg = 'input-lg', 4 | } 5 | 6 | export type FormCheckboxSize = keyof typeof FormCheckboxSizes; 7 | -------------------------------------------------------------------------------- /src/components/FormCheckbox/Type.ts: -------------------------------------------------------------------------------- 1 | export enum FormCheckboxTypes { 2 | switch = 'form-switch', 3 | checkbox = 'form-checkbox', 4 | } 5 | 6 | export type FormCheckboxType = keyof typeof FormCheckboxTypes; 7 | -------------------------------------------------------------------------------- /src/components/FormCheckbox/index.ts: -------------------------------------------------------------------------------- 1 | import { FormCheckbox } from './Checkbox'; 2 | import { FormCheckboxGroup } from './Group'; 3 | import { makePluggableComponents } from '../../utils/plugin'; 4 | 5 | export default makePluggableComponents({ FormCheckbox, FormCheckboxGroup }); 6 | export { FormCheckbox, FormCheckboxGroup }; 7 | export * from './Type'; 8 | export * from './Size'; 9 | -------------------------------------------------------------------------------- /src/components/FormGroup/Group.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { VNode, CreateElement } from 'vue'; 3 | import { CommonOptions } from './Options'; 4 | 5 | const isFormTag = (tag = ''): boolean => 6 | /^.*form-?(label|input|select|switch|switch-?group|checkbox-?group|checkbox|radio-?group|radio|slider)$/i.test(tag); 7 | 8 | export const FormGroup = /*#__PURE__*/ tsx.createComponent({ 9 | name: 'FormGroup', 10 | functional: true, 11 | props: { 12 | size: { type: String as () => 'lg' | 'sm', validator: (v: string): boolean => !v || ['lg', 'sm'].includes(v) }, 13 | disabled: { type: Boolean }, 14 | error: { type: Boolean }, 15 | success: { type: Boolean }, 16 | }, 17 | render(h: CreateElement, { props, slots, data, children }): VNode { 18 | if (props.size) { 19 | children.map((v: VNode & CommonOptions) => { 20 | if (v.componentOptions && isFormTag(v.componentOptions.tag)) { 21 | if (!v.componentOptions.propsData) { 22 | v.componentOptions.propsData = {}; 23 | } 24 | 25 | v.componentOptions.propsData.size = v.componentOptions.propsData.size || props.size; 26 | } 27 | }); 28 | } 29 | 30 | if (props.disabled !== undefined) { 31 | children.map((v: VNode & CommonOptions) => { 32 | if (v.componentOptions && isFormTag(v.componentOptions.tag)) { 33 | if (!v.componentOptions.propsData) { 34 | v.componentOptions.propsData = {}; 35 | } 36 | 37 | v.componentOptions.propsData.disabled = props.disabled; 38 | } 39 | }); 40 | } 41 | 42 | const cssClass = ['form-group', props.error && 'has-error', props.success && 'has-success']; 43 | 44 | return ( 45 |
    46 | {slots().default} 47 |
    48 | ); 49 | }, 50 | }); 51 | -------------------------------------------------------------------------------- /src/components/FormGroup/Options.ts: -------------------------------------------------------------------------------- 1 | export interface CommonOptions { 2 | componentOptions?: { 3 | propsData?: { 4 | size?: 'sm' | 'lg'; 5 | disabled?: boolean; 6 | }; 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /src/components/FormGroup/index.ts: -------------------------------------------------------------------------------- 1 | import { FormGroup } from './Group'; 2 | import { makePluggableComponents } from '../../utils/plugin'; 3 | 4 | export default makePluggableComponents({ FormGroup }); 5 | export { FormGroup }; 6 | -------------------------------------------------------------------------------- /src/components/FormHint/Hint.tsx: -------------------------------------------------------------------------------- 1 | import './styles.scss'; 2 | import * as tsx from 'vue-tsx-support'; 3 | import { CreateElement, VNode } from 'vue'; 4 | 5 | export const FormHint = /*#__PURE__*/ tsx.createComponent({ 6 | name: 'FormHint', 7 | functional: true, 8 | props: { 9 | error: { type: Boolean }, 10 | success: { type: Boolean }, 11 | }, 12 | render(h: CreateElement, { props, children, data }): VNode { 13 | const cssClasses = ['form-input-hint', props.error && 'error', props.success && 'success']; 14 | 15 | return ( 16 |
    17 | {children} 18 |
    19 | ); 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /src/components/FormHint/index.ts: -------------------------------------------------------------------------------- 1 | import { FormHint } from './Hint'; 2 | import { makePluggableComponents } from '../../utils/plugin'; 3 | 4 | export default makePluggableComponents({ FormHint }); 5 | export { FormHint }; 6 | -------------------------------------------------------------------------------- /src/components/FormHint/styles.scss: -------------------------------------------------------------------------------- 1 | .form-input-hint.error { 2 | display: none; 3 | } 4 | 5 | // Hides all hints on error 6 | .form-group.has-error .form-input-hint { 7 | display: none; 8 | } 9 | 10 | // Except hints with errors 11 | .form-group.has-error .form-input-hint.error { 12 | display: initial; 13 | } 14 | 15 | .form-group.has-success .form-input-hint.success { 16 | display: initial; 17 | } 18 | -------------------------------------------------------------------------------- /src/components/FormHorizontal/Horizontal.tsx: -------------------------------------------------------------------------------- 1 | import { CreateElement, VNode } from 'vue'; 2 | import * as tsx from 'vue-tsx-support'; 3 | 4 | export const FormHorizontal = tsx.createComponent({ 5 | name: 'FormHorizontal', 6 | functional: true, 7 | render(h: CreateElement, { children, data }): VNode { 8 | return ( 9 |
    10 | {children} 11 |
    12 | ); 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /src/components/FormHorizontal/index.ts: -------------------------------------------------------------------------------- 1 | import { FormHorizontal } from './Horizontal'; 2 | import { makePluggableComponents } from '../../utils/plugin'; 3 | 4 | export default makePluggableComponents({ FormHorizontal }); 5 | export { FormHorizontal }; 6 | -------------------------------------------------------------------------------- /src/components/FormInput/Component.tsx: -------------------------------------------------------------------------------- 1 | import { VNode, CreateElement } from 'vue'; 2 | import * as tsx from 'vue-tsx-support'; 3 | import { FormInputSize, FormInputSizes } from './Size'; 4 | import { Input } from './Input'; 5 | import { Icon } from './Icon'; 6 | import { IconContainer, IconSide, IconSides } from './IconContainer'; 7 | import { Loading } from './Loading'; 8 | 9 | export interface FormInputEvents { 10 | onInput: (value: any) => void; 11 | } 12 | 13 | export const FormInput = /*#__PURE__*/ tsx.componentFactoryOf().create({ 14 | name: 'FormInput', 15 | props: { 16 | value: [String, Number], 17 | disabled: Boolean, 18 | error: Boolean, 19 | loading: Boolean, 20 | success: Boolean, 21 | icon: String, 22 | iconSide: { 23 | type: String as () => IconSide, 24 | validator: (side: IconSide): boolean => Object.keys(IconSides).includes(side), 25 | }, 26 | size: { 27 | type: String as () => FormInputSize, 28 | validator: (size: FormInputSize): boolean => Object.keys(FormInputSizes).includes(size), 29 | }, 30 | }, 31 | render(h: CreateElement): VNode { 32 | const input = ( 33 | 44 | ); 45 | 46 | if (this.icon || this.loading) { 47 | return ( 48 | 49 | {input} 50 | {this.loading && } 51 | {!this.loading && } 52 | 53 | ); 54 | } 55 | 56 | return input; 57 | }, 58 | }); 59 | -------------------------------------------------------------------------------- /src/components/FormInput/Icon.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { Icons, IconType } from '../Icon'; 3 | import { VNode, CreateElement } from 'vue'; 4 | 5 | export const Icon = tsx.component({ 6 | name: 'FormInputIcon', 7 | functional: true, 8 | props: { 9 | icon: { type: String, default: undefined }, 10 | }, 11 | render(h: CreateElement, { props }): VNode { 12 | return ; 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /src/components/FormInput/IconContainer.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { VNode, CreateElement } from 'vue'; 3 | 4 | export enum IconSides { 5 | left = 'has-icon-left', 6 | right = 'has-icon-right', 7 | } 8 | export type IconSide = keyof typeof IconSides; 9 | 10 | export const IconContainer = tsx.component({ 11 | name: 'FormInputIconContainer', 12 | functional: true, 13 | props: { 14 | side: { 15 | type: String as () => IconSide, 16 | validator: (side: IconSide): boolean => Object.keys(IconSides).includes(side), 17 | default: undefined, 18 | }, 19 | }, 20 | render(h: CreateElement, { props, children }): VNode { 21 | return
    {children}
    ; 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /src/components/FormInput/Input.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { CreateElement, VNode } from 'vue'; 3 | import { FormInputSize, FormInputSizes } from './Size'; 4 | 5 | interface InputEvents { 6 | onInput: (event: any) => void; 7 | } 8 | 9 | export const Input = /*#__PURE__*/ tsx.componentFactoryOf().create({ 10 | name: 'Input', 11 | functional: true, 12 | props: { 13 | size: { 14 | type: String as () => FormInputSize, 15 | validator: (size: FormInputSize): boolean => Object.keys(FormInputSizes).includes(size), 16 | }, 17 | error: { type: Boolean }, 18 | success: { type: Boolean }, 19 | value: { type: [String, Number] }, 20 | disabled: { type: Boolean }, 21 | }, 22 | render(h: CreateElement, { props, data, listeners }): VNode { 23 | const cssClass = [ 24 | 'form-input', 25 | props.error ? 'is-error' : false, 26 | props.success ? 'is-success' : false, 27 | FormInputSizes[props.size], 28 | ]; 29 | 30 | const onInput = (e: any): void => { 31 | const value = e.target.value; 32 | if (Array.isArray(listeners.input)) { 33 | return listeners.input.forEach((listener) => listener(value)); 34 | } 35 | 36 | if (listeners.input) { 37 | listeners.input(value); 38 | } 39 | }; 40 | 41 | return ( 42 | 51 | ); 52 | }, 53 | }); 54 | -------------------------------------------------------------------------------- /src/components/FormInput/Loading.tsx: -------------------------------------------------------------------------------- 1 | import Vue, { VNode, CreateElement } from 'vue'; 2 | 3 | export const Loading = Vue.extend({ 4 | name: 'FormInputLoading', 5 | functional: true, 6 | render(h: CreateElement): VNode { 7 | return ; 8 | }, 9 | }); 10 | -------------------------------------------------------------------------------- /src/components/FormInput/Size.ts: -------------------------------------------------------------------------------- 1 | export enum FormInputSizes { 2 | sm = 'input-sm', 3 | lg = 'input-lg', 4 | } 5 | 6 | export type FormInputSize = keyof typeof FormInputSizes; 7 | -------------------------------------------------------------------------------- /src/components/FormInput/index.ts: -------------------------------------------------------------------------------- 1 | import { FormInput } from './Component'; 2 | import { makePluggableComponents } from '../../utils/plugin'; 3 | 4 | export default makePluggableComponents({ FormInput }); 5 | export { FormInput }; 6 | export * from './Size'; 7 | -------------------------------------------------------------------------------- /src/components/FormLabel/Label.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { FormLabelSizes, FormLabelSize } from './LabelSize'; 3 | import { CreateElement, VNode } from 'vue'; 4 | 5 | export const FormLabel = /*#__PURE__*/ tsx.createComponent({ 6 | name: 'FormLabel', 7 | functional: true, 8 | props: { 9 | size: { 10 | type: String, 11 | default: undefined, 12 | validator: (v: string): boolean => !v || Object.keys(FormLabelSizes).includes(v), 13 | }, 14 | }, 15 | render(h: CreateElement, { props, children, data }): VNode { 16 | const cssClasses = ['form-label', FormLabelSizes[props.size as FormLabelSize]]; 17 | 18 | return ( 19 | 22 | ); 23 | }, 24 | }); 25 | -------------------------------------------------------------------------------- /src/components/FormLabel/LabelSize.ts: -------------------------------------------------------------------------------- 1 | export enum FormLabelSizes { 2 | sm = 'label-sm', 3 | lg = 'label-lg', 4 | } 5 | 6 | export type FormLabelSize = keyof typeof FormLabelSizes; 7 | -------------------------------------------------------------------------------- /src/components/FormLabel/index.ts: -------------------------------------------------------------------------------- 1 | import { FormLabel } from './Label'; 2 | import { makePluggableComponents } from '../../utils/plugin'; 3 | 4 | export default makePluggableComponents({ FormLabel }); 5 | export { FormLabel }; 6 | export * from './LabelSize'; 7 | -------------------------------------------------------------------------------- /src/components/FormRadio/Group.tsx: -------------------------------------------------------------------------------- 1 | import { VNode, CreateElement, VNodeComponentOptions } from 'vue'; 2 | import * as tsx from 'vue-tsx-support'; 3 | import { uid } from '../../utils/uid'; 4 | import { FormRadio } from './Radio'; 5 | import { FormRadioSize } from './Size'; 6 | 7 | interface NormalizedOption { 8 | value: any; 9 | label: string; 10 | } 11 | 12 | const normalizeOptions = (options: { [label: string]: any } | string[]): NormalizedOption[] => { 13 | if (Array.isArray(options)) { 14 | return options.reduce((normal, value) => [...normal, { value, label: value }], [] as any[]); 15 | } 16 | 17 | const normalized = []; 18 | for (const label of Object.keys(options)) { 19 | normalized.push({ label, value: options[label] }); 20 | } 21 | 22 | return normalized; 23 | }; 24 | 25 | export const FormRadioGroup = /*#__PURE__*/ tsx.componentFactoryOf().create({ 26 | name: 'FormRadioGroup', 27 | props: { 28 | name: { type: String }, 29 | options: { type: undefined }, 30 | value: { type: undefined }, 31 | size: { type: String as () => FormRadioSize, default: undefined }, 32 | inline: { type: Boolean }, 33 | disabled: { type: Boolean }, 34 | error: { type: Boolean }, 35 | }, 36 | methods: { 37 | update(value: any): void { 38 | this.$emit('input', value); 39 | }, 40 | }, 41 | render(h: CreateElement): VNode { 42 | const name = this.name || uid(this); 43 | let group: VNode[]; 44 | 45 | if (this.options) { 46 | group = normalizeOptions(this.options).map(({ label, value }) => { 47 | return ( 48 | 59 | ); 60 | }); 61 | } else { 62 | group = (this.$slots.default || []) 63 | .filter(({ componentOptions }) => { 64 | return componentOptions && componentOptions.tag && componentOptions.tag.includes('form-radio'); 65 | }) 66 | .map((option: VNode) => { 67 | if (!option.componentOptions) { 68 | option.componentOptions = {} as VNodeComponentOptions; 69 | } 70 | 71 | const props = (option.componentOptions.propsData || {}) as InstanceType; 72 | props.name = name; 73 | props.size = props.size !== undefined ? props.size : this.size; 74 | props.disabled = props.disabled !== undefined ? props.disabled : this.disabled; 75 | props.error = props.error !== undefined ? props.error : this.error; 76 | props.inline = this.inline || props.inline; 77 | props.model = this.value; 78 | 79 | option.componentOptions.listeners = { 80 | ...option.componentOptions.listeners, 81 | change: this.update, 82 | }; 83 | 84 | return option; 85 | }); 86 | } 87 | 88 | return
    {group}
    ; 89 | }, 90 | }); 91 | -------------------------------------------------------------------------------- /src/components/FormRadio/Radio.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { VNode, CreateElement } from 'vue'; 3 | import { cachedListeners } from '../../mixins/cache'; 4 | import { FormRadioSize, FormRadioSizes } from './Size'; 5 | 6 | export interface FormRadioEvents { 7 | onChange: (value: any) => void; 8 | } 9 | 10 | export const FormRadio = /*#__PURE__*/ tsx 11 | .componentFactoryOf() 12 | .mixin(cachedListeners) 13 | .create({ 14 | name: 'FormRadio', 15 | model: { 16 | prop: 'model', 17 | event: 'change', 18 | }, 19 | props: { 20 | checked: { type: Boolean }, 21 | disabled: { type: Boolean }, 22 | error: { type: Boolean }, 23 | inline: { type: Boolean }, 24 | label: { type: String }, 25 | name: { type: String }, 26 | size: { 27 | type: String as () => FormRadioSize, 28 | validator: (size: FormRadioSize): boolean => Object.keys(FormRadioSizes).includes(size), 29 | }, 30 | value: { type: undefined }, 31 | model: { type: undefined }, 32 | }, 33 | computed: { 34 | _label(): string | VNode | any { 35 | return this.$slots.default || this.label || this._value; 36 | }, 37 | _value(): any { 38 | return this.value || (this.$slots.default && this.$slots.default[0].text) || this.label; 39 | }, 40 | }, 41 | methods: { 42 | onChecked(): void { 43 | this.$emit('change', this._value); 44 | }, 45 | }, 46 | render(h: CreateElement): VNode { 47 | const cssClass = [ 48 | 'form-radio', 49 | this.inline ? 'form-inline' : false, 50 | this.error ? 'is-error' : false, 51 | FormRadioSizes[this.size], 52 | ]; 53 | 54 | return ( 55 | 65 | ); 66 | }, 67 | }); 68 | -------------------------------------------------------------------------------- /src/components/FormRadio/Size.ts: -------------------------------------------------------------------------------- 1 | export enum FormRadioSizes { 2 | sm = 'input-sm', 3 | lg = 'input-lg', 4 | } 5 | 6 | export type FormRadioSize = keyof typeof FormRadioSizes; 7 | -------------------------------------------------------------------------------- /src/components/FormRadio/index.ts: -------------------------------------------------------------------------------- 1 | import { FormRadioGroup } from './Group'; 2 | import { FormRadio } from './Radio'; 3 | import { makePluggableComponents } from '../../utils/plugin'; 4 | 5 | export default makePluggableComponents({ FormRadioGroup, FormRadio }); 6 | export { FormRadioGroup, FormRadio }; 7 | export * from './Size'; 8 | -------------------------------------------------------------------------------- /src/components/FormSelect/Option.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { VNode, CreateElement } from 'vue'; 3 | 4 | export interface FormSelectOptionProps { 5 | value?: string; 6 | label?: string; 7 | disabled?: boolean; 8 | selected?: boolean; 9 | } 10 | 11 | export const FormSelectOption = tsx.componentFactoryOf().create({ 12 | name: 'FormSelectOption', 13 | props: { 14 | disabled: { type: Boolean }, 15 | value: { type: [String, Number], default: '' }, 16 | label: { type: [String, Number] }, 17 | selected: { type: Boolean }, 18 | }, 19 | 20 | render(h: CreateElement): VNode { 21 | const { selected, disabled, value, label } = this.$props; 22 | 23 | return ( 24 | 27 | ); 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /src/components/FormSelect/Select.tsx: -------------------------------------------------------------------------------- 1 | import { VNode, CreateElement, VNodeComponentOptions } from 'vue'; 2 | import { FormSelectOption, FormSelectOptionProps } from './Option'; 3 | import { FormSelectSize, FormSelectSizes } from './Size'; 4 | import * as tsx from 'vue-tsx-support'; 5 | import { cachedListeners, cachedAttrs } from '../../mixins/cache'; 6 | 7 | export interface InputEvent { 8 | target: { 9 | value: string; 10 | selectedOptions: HTMLCollectionOf; 11 | }; 12 | } 13 | 14 | export interface NormalizedOption { 15 | label: string; 16 | value: any; 17 | } 18 | 19 | export interface FormSelectEvents { 20 | onInput: (value: string | string[] | number | number[]) => void; 21 | } 22 | 23 | export const FormSelect = /*#__PURE__*/ tsx 24 | .componentFactoryOf() 25 | .mixin(cachedListeners) 26 | .mixin(cachedAttrs) 27 | .create({ 28 | name: 'FormSelect', 29 | props: { 30 | options: { type: [Object, Array] as (() => { [label: string]: string } | string[])[] }, 31 | multiple: { type: Boolean }, 32 | placeholder: { type: String }, 33 | value: { type: [String, Number, Array as () => string[] | number[]], default: '' }, 34 | rows: { type: [String, Number] }, 35 | size: { 36 | type: String as () => FormSelectSize, 37 | validator: (size: FormSelectSize): boolean => Object.keys(FormSelectSizes).includes(size), 38 | }, 39 | error: { type: Boolean }, 40 | success: { type: Boolean }, 41 | disabled: { type: Boolean }, 42 | }, 43 | mounted(): void { 44 | if (!this.options && !this.$slots.default) { 45 | throw new TypeError('Component could not be created without options'); 46 | } 47 | }, 48 | methods: { 49 | onInput({ target: { selectedOptions } }: InputEvent): void { 50 | if (this.multiple) { 51 | const selected = [...selectedOptions].map((option: HTMLOptionElement) => { 52 | return option.value || option.innerHTML; 53 | }); 54 | this.$emit('input', selected); 55 | } else { 56 | this.$emit('input', selectedOptions[0].value); 57 | } 58 | }, 59 | isSelected( 60 | label: string | number | undefined, 61 | value: string | number | undefined, 62 | current?: string | string[] | number | number[], 63 | ): boolean { 64 | current = current || this.value; 65 | 66 | if (current instanceof Array) { 67 | return current.some((v: string | string[] | number | number[]) => this.isSelected(label, value, v)); 68 | } 69 | 70 | return ( 71 | (label !== undefined && current.toString() === label.toString()) || 72 | (value !== undefined && current.toString() === value.toString()) 73 | ); 74 | }, 75 | 76 | normalizeOptions(options: { [label: string]: any } | string[]): NormalizedOption[] { 77 | if (Array.isArray(options)) { 78 | return options.reduce((normal, value) => [...normal, { value, label: value }], [] as any[]); 79 | } 80 | 81 | const normalized = []; 82 | for (const label of Object.keys(options)) { 83 | normalized.push({ label, value: options[label] }); 84 | } 85 | 86 | return normalized; 87 | }, 88 | }, 89 | render(h: CreateElement): VNode { 90 | let options: VNode[] = []; 91 | 92 | if (this.options) { 93 | options = this.normalizeOptions(this.options).map(({ label, value }: NormalizedOption) => { 94 | return ; 95 | }); 96 | } else { 97 | options = (this.$slots.default || []) 98 | .filter(({ tag }) => { 99 | return tag && /^.*form-?select-?option$/i.test(tag); 100 | }) 101 | .map((option: VNode) => { 102 | if (!option.componentOptions) { 103 | option.componentOptions = { children: [] as VNode[] } as VNodeComponentOptions; 104 | } 105 | if (!option.componentOptions.propsData) { 106 | option.componentOptions.propsData = {}; 107 | } 108 | 109 | const props = option.componentOptions.propsData as FormSelectOptionProps; 110 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 111 | const value = props.value || (option.componentOptions.children![0] || {}).text; 112 | 113 | props.selected = props.selected !== undefined ? props.selected : this.isSelected(props.label, value); 114 | 115 | return option; 116 | }); 117 | } 118 | 119 | if (this.placeholder && !this.multiple) { 120 | options.unshift( 121 | 122 | {this.placeholder} 123 | , 124 | ); 125 | } 126 | 127 | const cssClass = [ 128 | 'form-select', 129 | FormSelectSizes[this.size], 130 | this.error ? 'is-error' : '', 131 | this.success ? 'is-success' : '', 132 | ]; 133 | 134 | return ( 135 | 145 | ); 146 | }, 147 | }); 148 | -------------------------------------------------------------------------------- /src/components/FormSelect/Size.ts: -------------------------------------------------------------------------------- 1 | export enum FormSelectSizes { 2 | sm = 'select-sm', 3 | lg = 'select-lg', 4 | } 5 | 6 | export type FormSelectSize = keyof typeof FormSelectSizes; 7 | -------------------------------------------------------------------------------- /src/components/FormSelect/index.ts: -------------------------------------------------------------------------------- 1 | import { FormSelect } from './Select'; 2 | import { FormSelectOption } from './Option'; 3 | import { makePluggableComponents } from '../../utils/plugin'; 4 | 5 | export default makePluggableComponents({ FormSelect, FormSelectOption }); 6 | export { FormSelect, FormSelectOption }; 7 | export * from './Size'; 8 | -------------------------------------------------------------------------------- /src/components/FormSlider/Events.ts: -------------------------------------------------------------------------------- 1 | export interface FormSliderEvents { 2 | onInput: (value: any) => void; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/FormSlider/Slider.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { CreateElement, VNode, VNodeDirective } from 'vue'; 3 | import { flattenListener } from '../../utils/listener'; 4 | import { Tooltip } from '../../directives'; 5 | import { FormSliderEvents } from './Events'; 6 | 7 | export const FormSlider = /*#__PURE__*/ tsx.componentFactoryOf().create({ 8 | name: 'FormSlider', 9 | directives: { 10 | Tooltip, 11 | }, 12 | props: { 13 | min: { type: [String, Number], default: 0 }, 14 | max: { type: [String, Number], default: 100 }, 15 | step: { type: [String, Number], default: 1 }, 16 | value: { type: [String, Number], default: undefined }, 17 | tooltip: { type: [Boolean, String, Function], default: false }, 18 | disabled: { type: Boolean, default: false }, 19 | }, 20 | computed: { 21 | tooltipText(): string | number { 22 | if (typeof this.tooltip === 'boolean') { 23 | return this.tooltip && this.value; 24 | } 25 | 26 | if (typeof this.tooltip === 'string') { 27 | return `${this.value}${this.tooltip}`; 28 | } 29 | 30 | if (typeof this.tooltip === 'function') { 31 | return this.tooltip(this.value); 32 | } 33 | 34 | throw new TypeError('Wrong type of tooltip'); 35 | }, 36 | }, 37 | render(h: CreateElement): VNode { 38 | const onInput = (e: Event): void => flattenListener(this.$listeners.input)((e.target as HTMLInputElement).value); 39 | const directives: VNodeDirective[] = []; 40 | 41 | if (this.tooltip) { 42 | directives.push({ 43 | name: 'tooltip', 44 | value: this.tooltipText, 45 | }); 46 | } 47 | 48 | return ( 49 | 61 | ); 62 | }, 63 | }); 64 | -------------------------------------------------------------------------------- /src/components/FormSlider/index.ts: -------------------------------------------------------------------------------- 1 | import { FormSlider } from './Slider'; 2 | import { makePluggableComponents } from '../../utils/plugin'; 3 | 4 | export default makePluggableComponents({ FormSlider }); 5 | export { FormSlider }; 6 | -------------------------------------------------------------------------------- /src/components/FormSwitch/Group.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { CreateElement, VNode } from 'vue'; 3 | import { FormCheckboxGroup } from '../FormCheckbox'; 4 | 5 | export const FormSwitchGroup = tsx.extendFrom(FormCheckboxGroup).create({ 6 | name: 'FormSwitchGroup', 7 | functional: true, 8 | render(h: CreateElement, { data, children, props }): VNode { 9 | return ( 10 | ), type: 'switch' } }}> 11 | {children} 12 | 13 | ); 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /src/components/FormSwitch/Switch.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { CreateElement, VNode } from 'vue'; 3 | import { FormCheckbox } from '../FormCheckbox'; 4 | 5 | export const FormSwitch = tsx.extendFrom(FormCheckbox).create({ 6 | name: 'FormSwitch', 7 | functional: true, 8 | render(h: CreateElement, { data, children, props }): VNode { 9 | return ( 10 | ), type: 'switch' } }}> 11 | {children} 12 | 13 | ); 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /src/components/FormSwitch/index.tsx: -------------------------------------------------------------------------------- 1 | import { FormSwitch } from './Switch'; 2 | import { FormSwitchGroup } from './Group'; 3 | import { makePluggableComponents } from '../../utils/plugin'; 4 | 5 | export default makePluggableComponents({ FormSwitch, FormSwitchGroup }); 6 | export { FormSwitch, FormSwitchGroup }; 7 | -------------------------------------------------------------------------------- /src/components/FormTextarea/Textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as tsx from 'vue-tsx-support'; 2 | import { cachedListeners, cachedAttrs } from '../../mixins/cache'; 3 | import { CreateElement, VNode } from 'vue'; 4 | 5 | export interface FormTextareaEvents { 6 | onInput: (value: any) => void; 7 | } 8 | 9 | export const FormTextarea = tsx 10 | .componentFactoryOf() 11 | .mixin(cachedListeners) 12 | .mixin(cachedAttrs) 13 | .create({ 14 | name: 'FormTextarea', 15 | props: { 16 | value: { type: String, default: undefined }, 17 | disabled: { type: Boolean, default: false }, 18 | }, 19 | computed: { 20 | placeholder(): string | undefined { 21 | return this.$attrs.placeholder || (this.$slots.default && this.$slots.default[0].text); 22 | }, 23 | }, 24 | methods: { 25 | onInput({ target: { value } }: any): void { 26 | this.$emit('input', value); 27 | }, 28 | }, 29 | render(h: CreateElement): VNode { 30 | return ( 31 |