├── .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 &&
}
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 |
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 ;
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 |
39 | );
40 | },
41 | });
42 |
--------------------------------------------------------------------------------
/src/components/FormTextarea/index.ts:
--------------------------------------------------------------------------------
1 | import { FormTextarea } from './Textarea';
2 | import { makePluggableComponents } from '../../utils/plugin';
3 |
4 | export default makePluggableComponents({ FormTextarea });
5 | export { FormTextarea };
6 |
--------------------------------------------------------------------------------
/src/components/Icon/Icon.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { IconSize, IconSizes } from './Size';
3 | import { IconType, Icons } from './Type';
4 | import { CreateElement, VNode } from 'vue';
5 |
6 | interface IconProps {
7 | type: IconType;
8 | }
9 |
10 | export const Icon = tsx.component({
11 | name: 'Icon',
12 | functional: true,
13 | props: {
14 | name: { type: String as () => IconType, required: true },
15 | size: { type: String as () => IconSize, default: undefined },
16 | },
17 | render(h: CreateElement, { props, data }): VNode {
18 | const styles = { 'font-size': IconSizes[props.size] || props.size };
19 | const classes = ['icon', IconSizes[props.size as IconSize], Icons[props.name] || props.name];
20 |
21 | return ;
22 | },
23 | });
24 |
--------------------------------------------------------------------------------
/src/components/Icon/Size.ts:
--------------------------------------------------------------------------------
1 | export enum IconSizes {
2 | x2 = 'icon-2x',
3 | x3 = 'icon-3x',
4 | x4 = 'icon-4x',
5 | }
6 |
7 | export type IconSize = keyof typeof IconSizes;
8 |
--------------------------------------------------------------------------------
/src/components/Icon/Type.ts:
--------------------------------------------------------------------------------
1 | enum IconNavigation {
2 | up = 'icon-arrow-up',
3 | down = 'icon-arrow-down',
4 | right = 'icon-arrow-right',
5 | left = 'icon-arrow-left',
6 |
7 | upward = 'icon-upward',
8 | forward = 'icon-forward',
9 | downward = 'icon-downward',
10 | back = 'icon-back',
11 |
12 | caret = 'icon-caret',
13 | menu = 'icon-menu',
14 | apps = 'icon-apps',
15 |
16 | hMore = 'icon-more-horiz',
17 | vMore = 'icon-more-vert',
18 | }
19 |
20 | enum IconAction {
21 | hResize = 'icon-resize-horiz',
22 | vResize = 'icon-resize-vert',
23 | plus = 'icon-plus',
24 | minus = 'icon-minus',
25 | cross = 'icon-cross',
26 | check = 'icon-check',
27 | stop = 'icon-stop',
28 | shutdown = 'icon-shutdown',
29 | refresh = 'icon-refresh',
30 | search = 'icon-search',
31 | flag = 'icon-flag',
32 | bookmark = 'icon-bookmark',
33 | edit = 'icon-edit',
34 | delete = 'icon-delete',
35 | share = 'icon-share',
36 | download = 'icon-download',
37 | upload = 'icon-upload',
38 | }
39 |
40 | enum IconObject {
41 | mail = 'icon-mail',
42 | people = 'icon-people',
43 | message = 'icon-message',
44 | photo = 'icon-photo',
45 | time = 'icon-time',
46 | location = 'icon-location',
47 | link = 'icon-link',
48 | emoji = 'icon-emoji',
49 | }
50 |
51 | const Icons = { ...IconNavigation, ...IconObject, ...IconAction };
52 |
53 | export type IconType = keyof typeof Icons;
54 | export { IconNavigation, IconAction, IconObject, Icons };
55 |
--------------------------------------------------------------------------------
/src/components/Icon/index.ts:
--------------------------------------------------------------------------------
1 | import { Icon } from './Icon';
2 | import { makePluggableComponents } from '../../utils/plugin';
3 |
4 | export default makePluggableComponents({ Icon });
5 | export { Icon };
6 | export * from './Size';
7 | export * from './Type';
8 |
--------------------------------------------------------------------------------
/src/components/Modal/Events.ts:
--------------------------------------------------------------------------------
1 | export interface ModalEvents {
2 | onClose: (event: Event) => void;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/Modal/Modal.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode } from 'vue';
3 | import { flattenListener } from '../../utils/listener';
4 | import { ModalSize, ModalSizes } from './Size';
5 | import { ModalHeader } from './ModalHeader';
6 | import { ModalBody } from './ModalBody';
7 | import { ModalFooter } from './ModalFooter';
8 | import { ModalEvents } from './Events';
9 | import { Overlay } from '../Overlay';
10 | import './styles.scss';
11 |
12 | export const Modal = /*#__PURE__*/ tsx.componentFactoryOf().create({
13 | name: 'Modal',
14 | props: {
15 | show: { type: Boolean },
16 | size: { type: String as () => ModalSize, default: undefined },
17 | overlay: { type: [Boolean, String, Number], default: true },
18 | closeBtn: { type: Boolean, default: true },
19 | closeOverlay: { type: Boolean, default: true },
20 | noScroll: { type: Boolean, default: true },
21 | },
22 | model: {
23 | prop: 'show',
24 | event: 'close',
25 | },
26 |
27 | render(h: CreateElement): VNode {
28 | const cssClass = ['modal', this.show && 'active', ModalSizes[this.size] || this.size];
29 | const opacity = typeof this.overlay !== 'boolean' ? this.overlay : undefined;
30 | const close = flattenListener(this.$listeners.close);
31 |
32 | return (
33 | this.closeOverlay && close(false)}
36 | noScroll={this.noScroll}
37 | opacity={opacity}
38 | class={cssClass}
39 | z-index="201"
40 | fullscreen
41 | >
42 |
43 | {this.$slots.header && {this.$slots.header}}
44 | {this.$slots.body && {this.$slots.body}}
45 | {this.$slots.footer && {this.$slots.footer}}
46 | {this.$slots.default}
47 |
48 |
49 | );
50 | },
51 | });
52 |
--------------------------------------------------------------------------------
/src/components/Modal/ModalBody.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode } from 'vue';
3 |
4 | export const ModalBody = tsx.component({
5 | name: 'ModalBody',
6 | functional: true,
7 | render(h: CreateElement, { children }): VNode {
8 | return (
9 |
12 | );
13 | },
14 | });
15 |
--------------------------------------------------------------------------------
/src/components/Modal/ModalFooter.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode } from 'vue';
3 |
4 | export const ModalFooter = tsx.component({
5 | name: 'ModalFooter',
6 | functional: true,
7 | render(h: CreateElement, { children }): VNode {
8 | return ;
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/src/components/Modal/ModalHeader.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode } from 'vue';
3 | import { flattenListener } from '../../utils/listener';
4 | import { Btn } from '../Btn';
5 | import { ModalEvents } from './Events';
6 |
7 | export const ModalHeader = /*#__PURE__*/ tsx.componentFactoryOf().create({
8 | name: 'ModalHeader',
9 | functional: true,
10 | render(h: CreateElement, { children, listeners }): VNode {
11 | const close = listeners.close && (
12 | flattenListener(listeners.close)(false)}
17 | />
18 | );
19 |
20 | return (
21 |
25 | );
26 | },
27 | });
28 |
--------------------------------------------------------------------------------
/src/components/Modal/Size.ts:
--------------------------------------------------------------------------------
1 | export enum ModalSizes {
2 | sm = 'modal-sm',
3 | lg = 'modal-lg',
4 | }
5 |
6 | export type ModalSize = keyof typeof ModalSizes;
7 |
--------------------------------------------------------------------------------
/src/components/Modal/index.ts:
--------------------------------------------------------------------------------
1 | import { Modal } from './Modal';
2 | import { ModalBody } from './ModalBody';
3 | import { ModalFooter } from './ModalFooter';
4 | import { ModalHeader } from './ModalHeader';
5 | import { makePluggableComponents } from '../../utils/plugin';
6 |
7 | export default makePluggableComponents({ Modal, ModalBody, ModalFooter, ModalHeader });
8 | export { Modal, ModalBody, ModalFooter, ModalHeader };
9 | export * from './Size';
10 |
--------------------------------------------------------------------------------
/src/components/Modal/styles.scss:
--------------------------------------------------------------------------------
1 | .modal-lg .overlay__shadow {
2 | background: #fff !important;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/Navigation/Navigation.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode } from 'vue';
3 | import { NavigationItem } from './NavigationItem';
4 |
5 | export const Navigation = /*#__PURE__*/ tsx.component({
6 | name: 'Navigation',
7 | functional: true,
8 | props: {
9 | items: { type: [Array, Object], default: (): any[] => [] },
10 | level: { type: [String, Number], default: -1 },
11 | },
12 | render(h: CreateElement, { props, scopedSlots, slots }): VNode {
13 | const items = Array.isArray(props.items) ? { ...props.items } : props.items;
14 | const navItems = Object.keys(items).map(
15 | (key: string | number): VNode => {
16 | const item = items[key];
17 |
18 | const sub = item.items && props.level != 0 && (
19 |
24 | );
25 | const slot = scopedSlots.default && scopedSlots.default({ item, index: key, level: props.level });
26 | const link = {item.text};
27 | const navItem = {slot || link};
28 |
29 | return (
30 |
31 | {navItem}
32 | {sub}
33 |
34 | );
35 | },
36 | );
37 |
38 | return {slots().default || navItems}
;
39 | },
40 | });
41 |
--------------------------------------------------------------------------------
/src/components/Navigation/NavigationItem.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode } from 'vue';
3 |
4 | export const NavigationItem = tsx.component({
5 | name: 'NavigationItem',
6 | functional: true,
7 | props: {
8 | active: { type: Boolean, default: false },
9 | },
10 | render(h: CreateElement, { props, children }): VNode {
11 | return (
12 |
13 | {children}
14 |
15 | );
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/src/components/Navigation/index.ts:
--------------------------------------------------------------------------------
1 | import { Navigation } from './Navigation';
2 | import { NavigationItem } from './NavigationItem';
3 | import { makePluggableComponents } from '../../utils/plugin';
4 |
5 | export default makePluggableComponents({ Navigation, NavigationItem });
6 | export { Navigation, NavigationItem };
7 |
--------------------------------------------------------------------------------
/src/components/OffCanvas/Events.ts:
--------------------------------------------------------------------------------
1 | export interface OffCanvasToggleEvents {
2 | onClick: (event: any) => void;
3 | }
4 |
5 | export interface OffCanvasOverlayEvents {
6 | onClick: (event: any) => void;
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/OffCanvas/OffCanvas.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode } from 'vue';
3 | import { IconType } from '../Icon';
4 | import { OffCanvasToggle } from './OffCanvasToggle';
5 | import { OffCanvasSidebar } from './OffCanvasSidebar';
6 | import { OffCanvasOverlay } from './OffCanvasOverlay';
7 | import { OffCanvasContent } from './OffCanvasContent';
8 |
9 | export const OffCanvas = tsx.component({
10 | name: 'OffCanvas',
11 | props: {
12 | icon: { type: String as () => IconType, default: 'menu' as IconType },
13 | sidebar: { type: Boolean, default: true },
14 | overlay: { type: [Number, String], default: 0.1 },
15 | closeOnOverlay: { type: Boolean, default: true },
16 | },
17 | data: () => ({
18 | active: false,
19 | }),
20 | methods: {
21 | showSidebar(): void {
22 | this.active = true;
23 | },
24 | hideSidebar(): void {
25 | this.active = false;
26 | },
27 | },
28 | render(h: CreateElement): VNode {
29 | const toggle = (
30 |
31 | {this.$slots.icon}
32 |
33 | );
34 |
35 | const sidebar = this.$slots.sidebar && (
36 | {this.$slots.sidebar}
37 | );
38 |
39 | const overlay = this.overlay && (
40 | {
43 | this.closeOnOverlay && this.hideSidebar();
44 | }}
45 | />
46 | );
47 |
48 | const content = this.$slots.content && {this.$slots.content};
49 |
50 | const cssClass = ['off-canvas', this.sidebar && 'off-canvas-sidebar-show'];
51 |
52 | const sloted = this.$slots.default && {this.$slots.default}
;
53 | const offCanvas = (
54 |
55 | {toggle}
56 | {sidebar}
57 | {overlay}
58 | {content}
59 |
60 | );
61 |
62 | return sloted || offCanvas;
63 | },
64 | });
65 |
--------------------------------------------------------------------------------
/src/components/OffCanvas/OffCanvasContent.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 OffCanvasContent = tsx.component({
6 | name: 'OffCanvasContent',
7 | functional: true,
8 | render(h: CreateElement, { data, children }): VNode {
9 | const cssClass = mergeCss(data, 'off-canvas-content');
10 |
11 | return (
12 |
13 | {children}
14 |
15 | );
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/src/components/OffCanvas/OffCanvasOverlay.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode } from 'vue';
3 | import { mergeCss } from '../../utils/css';
4 | import { flattenListener } from '../../utils/listener';
5 | import { OffCanvasOverlayEvents } from './Events';
6 |
7 | export const OffCanvasOverlay = tsx.componentFactoryOf().create({
8 | name: 'OffCanvasOverlay',
9 | functional: true,
10 | props: {
11 | opacity: { type: [Number, String], default: 0.1 },
12 | },
13 | render(h: CreateElement, { data, listeners, props }): VNode {
14 | const cssClass = mergeCss(data, 'off-canvas-overlay');
15 | const onClick = flattenListener(listeners.click);
16 |
17 | return ;
18 | },
19 | });
20 |
--------------------------------------------------------------------------------
/src/components/OffCanvas/OffCanvasSidebar.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 OffCanvasSidebar = tsx.component({
6 | name: 'OffCanvasSidebar',
7 | functional: true,
8 | props: {
9 | active: { type: Boolean, default: false },
10 | },
11 | render(h: CreateElement, { data, props, children }): VNode {
12 | const cssClass = mergeCss(data, 'off-canvas-sidebar', [props.active && 'active']);
13 |
14 | return (
15 |
16 | {children}
17 |
18 | );
19 | },
20 | });
21 |
--------------------------------------------------------------------------------
/src/components/OffCanvas/OffCanvasToggle.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode } from 'vue';
3 | import { mergeCss } from '../../utils/css';
4 | import { flattenListener } from '../../utils/listener';
5 | import { IconType, Icon } from '../Icon';
6 | import { OffCanvasToggleEvents } from './Events';
7 |
8 | export const OffCanvasToggle = tsx.componentFactoryOf().create({
9 | name: 'OffCanvasToggle',
10 | functional: true,
11 | props: {
12 | icon: { type: String as () => IconType, default: 'menu' as IconType },
13 | },
14 | render(h: CreateElement, { data, props, children = [], listeners }): VNode {
15 | const cssClass = mergeCss(data, 'off-canvas-toggle');
16 | const onClick = flattenListener(listeners.click);
17 |
18 | return (
19 |
24 | );
25 | },
26 | });
27 |
--------------------------------------------------------------------------------
/src/components/OffCanvas/index.ts:
--------------------------------------------------------------------------------
1 | import { OffCanvas } from './OffCanvas';
2 | import { OffCanvasContent } from './OffCanvasContent';
3 | import { OffCanvasOverlay } from './OffCanvasOverlay';
4 | import { OffCanvasSidebar } from './OffCanvasSidebar';
5 | import { OffCanvasToggle } from './OffCanvasToggle';
6 | import { makePluggableComponents } from '../../utils/plugin';
7 |
8 | export default makePluggableComponents({
9 | OffCanvas,
10 | OffCanvasContent,
11 | OffCanvasOverlay,
12 | OffCanvasSidebar,
13 | OffCanvasToggle,
14 | });
15 |
16 | export { OffCanvas, OffCanvasContent, OffCanvasOverlay, OffCanvasSidebar, OffCanvasToggle };
17 |
--------------------------------------------------------------------------------
/src/components/Overlay/Events.ts:
--------------------------------------------------------------------------------
1 | export interface OverlayEvents {
2 | onClick: (event: MouseEvent) => void;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/Overlay/Overlay.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode, VNodeDirective } from 'vue';
3 | import { overlay } from '../../directives/Overlay';
4 | import { OverlayEvents } from './Events';
5 | import './styles.scss';
6 |
7 | export const Overlay = /*#__PURE__*/ tsx.componentFactoryOf().create({
8 | name: 'Overlay',
9 | directives: {
10 | overlay,
11 | },
12 | props: {
13 | show: { type: Boolean, default: false },
14 | blur: { type: [String, Number], default: undefined },
15 | fullscreen: { type: Boolean, default: false },
16 | noScroll: { type: Boolean, default: false },
17 | opacity: { type: [String, Number], default: 75 },
18 | zIndex: { type: [String, Number], default: 1 },
19 | },
20 | computed: {
21 | styles(): Record {
22 | return { display: (!this.show && 'none') || 'flex', 'z-index': this.zIndex };
23 | },
24 | },
25 |
26 | render(h: CreateElement): VNode {
27 | const directives: VNodeDirective[] = [
28 | {
29 | name: 'overlay',
30 | modifiers: {
31 | fullscreen: this.fullscreen,
32 | noScroll: this.noScroll,
33 | },
34 | value: {
35 | blur: this.blur,
36 | show: this.show,
37 | opacity: this.opacity,
38 | onClick: this.$listeners.click,
39 | zIndex: 'auto',
40 | },
41 | },
42 | ];
43 |
44 | return (
45 |
51 |
52 | {this.$slots.default}
53 |
54 |
55 | );
56 | },
57 | });
58 |
--------------------------------------------------------------------------------
/src/components/Overlay/index.ts:
--------------------------------------------------------------------------------
1 | import { Overlay } from './Overlay';
2 | import { makePluggableComponents } from '../../utils/plugin';
3 |
4 | export default makePluggableComponents({ Overlay });
5 | export { Overlay };
6 |
--------------------------------------------------------------------------------
/src/components/Overlay/styles.scss:
--------------------------------------------------------------------------------
1 | .overlay {
2 | position: absolute;
3 | width: 100%;
4 | height: 100%;
5 | top: 0;
6 | left: 0;
7 | right: 0;
8 | bottom: 0;
9 |
10 | display: flex;
11 | align-items: center;
12 | justify-content: center;
13 |
14 | &__fullscreen {
15 | position: fixed;
16 | }
17 |
18 | &__content {
19 | position: relative;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/Pagination/Events.ts:
--------------------------------------------------------------------------------
1 | export interface PaginationEvents {
2 | onChange: (page: number | string) => void;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/Pagination/Pager.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode } from 'vue';
3 | import { PaginationEvents } from './Events';
4 | import { flattenListener } from '../../utils/listener';
5 |
6 | const SEPARATOR = ' ... ';
7 | export type PagerItem = number | string;
8 |
9 | const items = (pages: number, current: number, show: number): PagerItem[] => {
10 | const half = Math.round((show + 1) / 2);
11 |
12 | if (current <= half) {
13 | return [...Array.from({ length: show - 1 }, (v, i) => i + 1), ...[SEPARATOR, pages]];
14 | }
15 |
16 | if (current + half > pages) {
17 | return [...[1, SEPARATOR], ...Array.from({ length: show - 1 }, (v, i) => pages - show + 2 + i)];
18 | }
19 |
20 | const mediana = Math.floor((show - 4) / 2);
21 | return [
22 | ...[1, SEPARATOR],
23 | ...Array.from({ length: show - 3 }, (v, i) => current - mediana + i),
24 | ...[SEPARATOR, pages],
25 | ];
26 | };
27 |
28 | export const Pager = /*#__PURE__*/ tsx.componentFactoryOf().create({
29 | name: 'Pager',
30 | functional: true,
31 | props: {
32 | pages: { type: Number, required: true },
33 | current: { type: Number, default: 1 },
34 | show: { type: Number, default: 6 },
35 | },
36 | render(h: CreateElement, { props, listeners }): VNode {
37 | const change = (page: string | number) => (): void => flattenListener(listeners.change)(page);
38 |
39 | const pages = items(props.pages, props.current, props.show).map(
40 | (page: number | string): VNode => {
41 | return (
42 |
43 | {page === SEPARATOR && {page}}
44 | {page !== SEPARATOR && {page}}
45 |
46 | );
47 | },
48 | );
49 |
50 | return (
51 |
64 | );
65 | },
66 | });
67 |
--------------------------------------------------------------------------------
/src/components/Pagination/Pagination.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode } from 'vue';
3 | import { flattenListener } from '../../utils/listener';
4 | import { PaginationEvents } from './Events';
5 | import { SimplePager } from './SimplePager';
6 | import { Pager } from './Pager';
7 | import './styles.scss';
8 |
9 | export const Pagination = tsx.componentFactoryOf().create({
10 | name: 'Pagination',
11 | functional: true,
12 | model: {
13 | prop: 'current',
14 | event: 'change',
15 | },
16 | props: {
17 | pages: { type: [Number, Array] as (() => number | string[])[], required: true },
18 | current: { type: [Number, String], default: undefined },
19 | show: { type: Number, default: undefined },
20 | },
21 | render(h: CreateElement, { props, listeners }): VNode {
22 | const change = flattenListener(listeners.change);
23 |
24 | if (Array.isArray(props.pages)) {
25 | return ;
26 | }
27 |
28 | return ;
29 | },
30 | });
31 |
--------------------------------------------------------------------------------
/src/components/Pagination/SimplePager.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode } from 'vue';
3 | import { PaginationEvents } from './Events';
4 | import { flattenListener } from '../../utils/listener';
5 |
6 | export const SimplePager = /*#__PURE__*/ tsx.componentFactoryOf().create({
7 | name: 'SimplePager',
8 | functional: true,
9 | props: {
10 | pages: { type: Array as () => string[], required: true },
11 | current: { type: String, default: undefined },
12 | },
13 | render(h: CreateElement, { props, listeners }): VNode {
14 | const change = (page: string | number) => (): void => flattenListener(listeners.change)(page);
15 |
16 | const currentIndex = props.pages.indexOf(props.current);
17 | const previous = props.pages[currentIndex - 1];
18 | const next = props.pages[currentIndex + 1];
19 |
20 | return (
21 |
39 | );
40 | },
41 | });
42 |
--------------------------------------------------------------------------------
/src/components/Pagination/index.ts:
--------------------------------------------------------------------------------
1 | import { Pagination } from './Pagination';
2 | import { makePluggableComponents } from '../../utils/plugin';
3 |
4 | export default makePluggableComponents({ Pagination });
5 | export { Pagination };
6 |
--------------------------------------------------------------------------------
/src/components/Pagination/styles.scss:
--------------------------------------------------------------------------------
1 | .page-item-num {
2 | min-width: 1.4rem;
3 | }
4 |
5 | @media (min-width: 640px) {
6 | .page-item-num {
7 | min-width: 1.7rem;
8 | }
9 | }
10 |
11 | .page-item a {
12 | cursor: pointer;
13 | user-select: none;
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/Panel/Panel.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { mergeCss } from '../../utils/css';
3 | import { CreateElement, VNode } from 'vue';
4 | import { PanelHeader } from './PanelHeader';
5 | import { PanelNav } from './PanelNav';
6 | import { PanelBody } from './PanelBody';
7 | import { PanelFooter } from './PanelFooter';
8 |
9 | export const Panel = tsx.component({
10 | name: 'Panel',
11 | functional: true,
12 | render(h: CreateElement, { data, slots }): VNode {
13 | const cssClass = mergeCss(data, 'panel');
14 | const { header, nav, body, footer, default: _default } = slots();
15 |
16 | return (
17 |
18 | {header &&
{header}}
19 | {nav &&
{nav}}
20 | {body &&
{body}}
21 | {footer &&
{footer}}
22 | {_default}
23 |
24 | );
25 | },
26 | });
27 |
--------------------------------------------------------------------------------
/src/components/Panel/PanelBody.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 PanelBody = tsx.component({
6 | name: 'PanelBody',
7 | functional: true,
8 | render(h: CreateElement, { children, data }): VNode {
9 | const cssClass = mergeCss(data, 'panel-body');
10 |
11 | return (
12 |
13 | {children}
14 |
15 | );
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/src/components/Panel/PanelFooter.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 PanelFooter = tsx.component({
6 | name: 'PanelFooter',
7 | functional: true,
8 | render(h: CreateElement, { children, data }): VNode {
9 | const cssClass = mergeCss(data, 'panel-footer');
10 |
11 | return (
12 |
13 | {children}
14 |
15 | );
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/src/components/Panel/PanelHeader.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 PanelHeader = tsx.component({
6 | name: 'PanelHeader',
7 | functional: true,
8 | render(h: CreateElement, { children, data }): VNode {
9 | const cssClass = mergeCss(data, 'panel-header');
10 |
11 | return (
12 |
13 | {children}
14 |
15 | );
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/src/components/Panel/PanelNav.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 PanelNav = tsx.component({
6 | name: 'PanelNav',
7 | functional: true,
8 | render(h: CreateElement, { children, data }): VNode {
9 | const cssClass = mergeCss(data, 'panel-nav');
10 |
11 | return (
12 |
13 | {children}
14 |
15 | );
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/src/components/Panel/index.ts:
--------------------------------------------------------------------------------
1 | import { Panel } from './Panel';
2 | import { PanelHeader } from './PanelHeader';
3 | import { PanelBody } from './PanelBody';
4 | import { PanelFooter } from './PanelFooter';
5 | import { PanelNav } from './PanelNav';
6 | import { makePluggableComponents } from '../../utils/plugin';
7 |
8 | export default makePluggableComponents({ Panel, PanelBody, PanelHeader, PanelFooter, PanelNav });
9 | export { Panel, PanelBody, PanelHeader, PanelFooter, PanelNav };
10 |
--------------------------------------------------------------------------------
/src/components/Popover/Popover.tsx:
--------------------------------------------------------------------------------
1 | import { CreateElement, VNode } from 'vue';
2 | import * as tsx from 'vue-tsx-support';
3 | import { mergeCss } from '../../utils/css';
4 | import { PopoverSides, PopoverSide } from './Side';
5 |
6 | export const Popover = /*#__PURE__*/ tsx.component({
7 | name: 'Popover',
8 | functional: true,
9 | props: {
10 | side: {
11 | type: String as () => PopoverSide,
12 | default: undefined,
13 | validator: (side: PopoverSide): boolean => Object.keys(PopoverSides).includes(side),
14 | },
15 | },
16 | render(h: CreateElement, { data, props, children = [] }): VNode {
17 | const cssClass = mergeCss(data, 'popover', [PopoverSides[props.side]]);
18 | const activator = children.shift();
19 |
20 | return (
21 |
22 | {activator}
23 |
{children}
24 |
25 | );
26 | },
27 | });
28 |
--------------------------------------------------------------------------------
/src/components/Popover/Side.ts:
--------------------------------------------------------------------------------
1 | export enum PopoverSides {
2 | right = 'popover-right',
3 | left = 'popover-left',
4 | bottom = 'popover-bottom',
5 | top = '',
6 | }
7 |
8 | export type PopoverSide = keyof typeof PopoverSides;
9 |
--------------------------------------------------------------------------------
/src/components/Popover/index.ts:
--------------------------------------------------------------------------------
1 | import { Popover } from './Popover';
2 | import { makePluggableComponents } from '../../utils/plugin';
3 |
4 | export default makePluggableComponents({ Popover });
5 | export { Popover };
6 | export * from './Side';
7 |
--------------------------------------------------------------------------------
/src/components/Steps/Step.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 Step = tsx.component({
6 | name: 'Step',
7 | functional: true,
8 | props: {
9 | active: { type: Boolean },
10 | tooltip: { type: String, default: undefined },
11 | },
12 | render(h: CreateElement, { data, props, children }): VNode {
13 | const cssClass = mergeCss(data, '', ['step-item', props.active && 'active', props.tooltip && 'tooltip']);
14 |
15 | return (
16 |
17 | {children}
18 |
19 | );
20 | },
21 | });
22 |
--------------------------------------------------------------------------------
/src/components/Steps/Steps.tsx:
--------------------------------------------------------------------------------
1 | import { VNode, CreateElement } from 'vue';
2 | import * as tsx from 'vue-tsx-support';
3 | import { mergeCss } from '../../utils/css';
4 | import { Step } from './Step';
5 |
6 | export const Steps = /*#__PURE__*/ tsx.component({
7 | name: 'Steps',
8 | functional: true,
9 | props: {
10 | items: { type: [Array, Object], default: (): Record[] => [] },
11 | active: {
12 | type: [Number, String],
13 | default: 1,
14 | },
15 | },
16 | render(h: CreateElement, { data, props, children = [] }): VNode {
17 | const cssClass = mergeCss(data, 'step');
18 |
19 | const items = Array.isArray(props.items) ? { ...props.items } : props.items;
20 | const steps = Object.keys(items).map(
21 | (key, index): VNode => {
22 | const active = String(index) === key ? props.active == index + 1 : props.active == key;
23 |
24 | return (
25 |
26 | {items[key].name}
27 |
28 | );
29 | },
30 | );
31 |
32 | children.forEach((child: VNode, i: number): void => {
33 | if (i + 1 == props.active) {
34 | child.data.class.push('active');
35 | }
36 | });
37 |
38 | return (
39 |
40 | {(steps.length && steps) || children}
41 |
42 | );
43 | },
44 | });
45 |
--------------------------------------------------------------------------------
/src/components/Steps/index.ts:
--------------------------------------------------------------------------------
1 | import { Steps } from './Steps';
2 | import { Step } from './Step';
3 | import { makePluggableComponents } from '../../utils/plugin';
4 |
5 | export default makePluggableComponents({ Steps, Step });
6 | export { Steps, Step };
7 |
--------------------------------------------------------------------------------
/src/components/Tab/Events.ts:
--------------------------------------------------------------------------------
1 | export interface TabEvents {
2 | onClick: (event: any) => void;
3 | }
4 |
5 | export interface TabsEvents {
6 | onChange: (event: string | number) => void;
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/Tab/Tab.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode } from 'vue';
3 | import { mergeCss } from '../../utils/css';
4 | import { TabEvents } from './Events';
5 | import './tab.scss';
6 |
7 | export const Tab = tsx.componentFactoryOf().create({
8 | name: 'Tab',
9 | functional: true,
10 | props: {
11 | active: { type: Boolean, default: false },
12 | badge: { type: [String, Number], default: undefined },
13 | },
14 | render(h: CreateElement, { data, props, children }): VNode {
15 | const cssClass = mergeCss(data, 'tab-item', [props.active && 'active']);
16 |
17 | return (
18 |
19 |
20 | {children}
21 |
22 |
23 | );
24 | },
25 | });
26 |
--------------------------------------------------------------------------------
/src/components/Tab/TabAction.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 TabAction = tsx.component({
6 | name: 'TabAction',
7 | functional: true,
8 | render(h: CreateElement, { data, children }): VNode {
9 | const cssClass = mergeCss(data, 'tab-item tab-action');
10 |
11 | return (
12 |
13 | {children}
14 |
15 | );
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/src/components/Tab/Tabs.tsx:
--------------------------------------------------------------------------------
1 | import { VNode, CreateElement } from 'vue';
2 | import * as tsx from 'vue-tsx-support';
3 | import { flattenListener } from '../../utils/listener';
4 | import { Tab } from './Tab';
5 | import { mergeCss } from '../../utils/css';
6 | import { TabAction } from './TabAction';
7 | import { TabsEvents } from './Events';
8 |
9 | export const Tabs = /*#__PURE__*/ tsx.componentFactoryOf().create({
10 | name: 'Tabs',
11 | functional: true,
12 | model: {
13 | prop: 'current',
14 | event: 'change',
15 | },
16 | props: {
17 | current: {
18 | type: [String, Number],
19 | default: 0,
20 | },
21 | items: {
22 | type: Array as () => string[],
23 | default: (): string[] => [],
24 | },
25 | block: {
26 | type: Boolean,
27 | default: false,
28 | },
29 | },
30 |
31 | render(h: CreateElement, { data, slots, scopedSlots, props, listeners }): VNode {
32 | const { items, current, block } = props;
33 | const cssClass = mergeCss(data, 'tab', [block && 'tab-block']);
34 | const onChange = (tabIndex: string | number) => (): void => flattenListener(listeners.change)(tabIndex);
35 |
36 | const _slots = slots();
37 | const children = _slots.default;
38 |
39 | const tabs = items.map((item: string | any, index: number) => {
40 | const key = typeof item === 'string' ? item : item.key || index;
41 | const isActive = current === key;
42 |
43 | return (
44 |
45 | {scopedSlots.tab && scopedSlots.tab({ item, index })}
46 | {!scopedSlots.tab && item}
47 |
48 | );
49 | });
50 |
51 | (children || [])
52 | .filter((child: VNode) => {
53 | return child.data && child.data.class && child.data.class.includes('tab-item');
54 | })
55 | .forEach((tab: VNode, i: number) => {
56 | const isActive = i === current;
57 |
58 | if (tab.data) {
59 | tab.data.class = [...tab.data.class, isActive && 'active'];
60 | tab.data.on = {
61 | ...tab.data.on,
62 | click: [onChange(i), flattenListener(tab.data.on && tab.data.on.click)],
63 | };
64 | } else {
65 | tab.data.on = { click: onChange(tab.key || i) };
66 | }
67 |
68 | return tab;
69 | });
70 |
71 | const action = _slots.action && {_slots.action};
72 |
73 | return (
74 |
75 | {children || tabs}
76 | {action}
77 |
78 | );
79 | },
80 | });
81 |
--------------------------------------------------------------------------------
/src/components/Tab/index.ts:
--------------------------------------------------------------------------------
1 | import { Tabs } from './Tabs';
2 | import { Tab } from './Tab';
3 | import { TabAction } from './TabAction';
4 | import { makePluggableComponents } from '../../utils/plugin';
5 |
6 | export default makePluggableComponents({ Tabs, Tab, TabAction });
7 | export { Tabs, Tab, TabAction };
8 |
--------------------------------------------------------------------------------
/src/components/Tab/tab.scss:
--------------------------------------------------------------------------------
1 | .tab .tab-item {
2 | cursor: pointer;
3 | user-select: none;
4 | }
5 |
--------------------------------------------------------------------------------
/src/components/Tag/Tag.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { TagType, TagTypes } from './Type';
3 | import './styles.scss';
4 |
5 | export const Tag = /*#__PURE__*/ tsx.component({
6 | name: 'Tag',
7 | functional: true,
8 | props: {
9 | type: { type: String as () => TagType, default: undefined },
10 | rounded: { type: Boolean },
11 | },
12 | render(h, { slots, props, data }) {
13 | const classes = ['label', TagTypes[props.type], props.rounded && 'label-rounded'];
14 |
15 | return (
16 |
17 | {slots().default}
18 |
19 | );
20 | },
21 | });
22 |
--------------------------------------------------------------------------------
/src/components/Tag/Type.ts:
--------------------------------------------------------------------------------
1 | export enum TagTypes {
2 | primary = 'label-primary',
3 | secondary = 'label-secondary',
4 | success = 'label-success',
5 | warning = 'label-warning',
6 | error = 'label-error',
7 | }
8 |
9 | export type TagType = keyof typeof TagTypes;
10 |
--------------------------------------------------------------------------------
/src/components/Tag/index.ts:
--------------------------------------------------------------------------------
1 | import { Tag } from './Tag';
2 | import { makePluggableComponents } from '../../utils/plugin';
3 |
4 | export default makePluggableComponents({ Tag });
5 | export { Tag };
6 | export * from './Type';
7 |
--------------------------------------------------------------------------------
/src/components/Tag/styles.scss:
--------------------------------------------------------------------------------
1 | .label + .label {
2 | margin-left: 0.3rem;
3 | }
--------------------------------------------------------------------------------
/src/components/Tile/Tile.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode } from 'vue';
3 | import { mergeCss } from '../../utils/css';
4 | import { IconType } from '@components/Icon';
5 | import { TileIcon } from './TileIcon';
6 | import { TileTitle } from './TileTitle';
7 | import { TileSubtitle } from './TileSubtitle';
8 | import { TileContent } from './TileContent';
9 | import { TileAction } from './TileAction';
10 |
11 | export const Tile = tsx.component({
12 | name: 'Tile',
13 | functional: true,
14 | props: {
15 | compact: { type: Boolean },
16 | title: { type: String, default: undefined },
17 | subtitle: { type: String, default: undefined },
18 | icon: { type: String as () => IconType, default: undefined },
19 | avatar: { type: String, default: undefined },
20 | initials: { type: String, default: undefined },
21 | },
22 | render(h: CreateElement, { data, slots, props }): VNode {
23 | const _slots = slots() || [];
24 | const cssClass = mergeCss(data, 'tile', { 'tile-centered': props.compact });
25 | const icon = (props.icon || props.avatar || props.initials || _slots.icon) && (
26 |
27 | {_slots.icon}
28 |
29 | );
30 |
31 | const title = props.title && ;
32 | const subtitle = props.subtitle && ;
33 | const content = (_slots.content || title || subtitle) && (
34 |
35 | {!_slots.content && title}
36 | {!_slots.content && subtitle}
37 | {_slots.content}
38 |
39 | );
40 |
41 | const actions = _slots.actions && {_slots.actions};
42 |
43 | return (
44 |
45 | {icon}
46 | {content}
47 | {actions}
48 | {_slots.default}
49 |
50 | );
51 | },
52 | });
53 |
--------------------------------------------------------------------------------
/src/components/Tile/TileAction.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 TileAction = tsx.component({
6 | name: 'TileAction',
7 | functional: true,
8 | render(h: CreateElement, { data, children }): VNode {
9 | const cssClass = mergeCss(data, 'tile-action');
10 |
11 | return (
12 |
13 | {children}
14 |
15 | );
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/src/components/Tile/TileContent.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 TileContent = tsx.component({
6 | name: 'TileContent',
7 | functional: true,
8 | render(h: CreateElement, { data, children }): VNode {
9 | const cssClass = mergeCss(data, 'tile-content');
10 |
11 | return (
12 |
13 | {children}
14 |
15 | );
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/src/components/Tile/TileIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode } from 'vue';
3 | import { IconType, Icon } from '../Icon';
4 | import { mergeCss } from '../../utils/css';
5 | import { Avatar } from '../Avatar';
6 | import './icon-styles.scss';
7 |
8 | export const TileIcon = tsx.component({
9 | name: 'TileIcon',
10 | functional: true,
11 | props: {
12 | icon: { type: String as () => IconType, default: undefined },
13 | avatar: { type: String, default: undefined },
14 | initials: { type: String, default: undefined },
15 | },
16 | render(h: CreateElement, { data, props, children = [] }): VNode {
17 | const cssClass = mergeCss(data, 'tile-icon');
18 | const avatar = (props.avatar || props.initials) && (
19 |
20 | );
21 | const icon = props.icon && ;
22 |
23 | return (
24 |
25 | {children}
26 | {!children.length && avatar}
27 | {!children.length && icon}
28 |
29 | );
30 | },
31 | });
32 |
--------------------------------------------------------------------------------
/src/components/Tile/TileSubtitle.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 TileSubtitle = tsx.component({
6 | name: 'TileSubtitle',
7 | functional: true,
8 | props: {
9 | compact: { type: Boolean },
10 | },
11 | render(h: CreateElement, { data, children, props }): VNode {
12 | const cssClass = mergeCss(data, 'tile-subtitle');
13 |
14 | return props.compact ? (
15 |
16 | {children}
17 |
18 | ) : (
19 |
20 | {children}
21 |
22 | );
23 | },
24 | });
25 |
--------------------------------------------------------------------------------
/src/components/Tile/TileTitle.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 TileTitle = tsx.component({
6 | name: 'TileTitle',
7 | functional: true,
8 | render(h: CreateElement, { data, children }): VNode {
9 | const cssClass = mergeCss(data, 'tile-title');
10 |
11 | return (
12 |
13 | {children}
14 |
15 | );
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/src/components/Tile/icon-styles.scss:
--------------------------------------------------------------------------------
1 | .tile .tile-icon .icon {
2 | display: flex;
3 | height: 2rem;
4 | width: 2.4rem;
5 | }
6 |
--------------------------------------------------------------------------------
/src/components/Tile/index.ts:
--------------------------------------------------------------------------------
1 | import { Tile } from './Tile';
2 | import { TileAction } from './TileAction';
3 | import { TileIcon } from './TileIcon';
4 | import { TileContent } from './TileContent';
5 | import { TileSubtitle } from './TileSubtitle';
6 | import { TileTitle } from './TileTitle';
7 | import { makePluggableComponents } from '../../utils/plugin';
8 |
9 | export default makePluggableComponents({ Tile, TileAction, TileIcon, TileContent, TileSubtitle, TileTitle });
10 | export { Tile, TileAction, TileIcon, TileContent, TileSubtitle, TileTitle };
11 |
--------------------------------------------------------------------------------
/src/components/Toast/Toast.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode } from 'vue';
3 | import { IconType } from '../Icon';
4 | import { ToastType, ToastTypes } from './Type';
5 | import { ToastIcon } from './ToastIcon';
6 | import { ToastAction } from './ToastAction';
7 | import { ToastBody } from './ToastBody';
8 | import { ToastContent } from './ToastContent';
9 | import { ToastTitle } from './ToastTitle';
10 | import './styles.scss';
11 |
12 | export const Toast = /*#__PURE__*/ tsx.component({
13 | name: 'Toast',
14 | props: {
15 | title: { type: String, default: undefined },
16 | content: { type: String, default: undefined },
17 | type: {
18 | type: String as () => ToastType,
19 | default: undefined,
20 | validator: (side: ToastType): boolean => Object.keys(ToastTypes).includes(side),
21 | },
22 | autoclose: { type: [Number, String], default: 0 },
23 | closeable: { type: Boolean, default: false },
24 | icon: { type: String as () => IconType, default: undefined },
25 | },
26 | data: () => ({
27 | shown: true,
28 | }),
29 | mounted() {
30 | if (this.autoclose) {
31 | setTimeout(this.close, +this.autoclose);
32 | }
33 | },
34 | methods: {
35 | close(): void {
36 | this.shown = false;
37 | this.$emit('closed');
38 | },
39 | toggle(): void {
40 | this.shown = !this.shown;
41 | },
42 | },
43 | render(h: CreateElement): VNode {
44 | const title = (this.$slots.title || this.title) && (
45 | {this.$slots.title}
46 | );
47 | const content = (this.$slots.content || this.content) && (
48 | {this.$slots.content}
49 | );
50 | const icon = this.icon && ;
51 | const body = (title || content) && (
52 |
53 | {title}
54 | {content}
55 |
56 | );
57 |
58 | const closeBtn = this.closeable && ;
59 | const action = (
60 |
61 | {closeBtn}
62 | {this.$slots.action}
63 |
64 | );
65 |
66 | const sloted = this.$slots.default && (
67 |
68 | {this.$slots.default}
69 |
70 | );
71 |
72 | const toast = (
73 |
74 | {icon}
75 | {body}
76 | {action}
77 |
78 | );
79 |
80 | return {this.shown && (sloted || toast)};
81 | },
82 | });
83 |
--------------------------------------------------------------------------------
/src/components/Toast/ToastAction.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 ToastAction = tsx.component({
6 | name: 'ToastAction',
7 | functional: true,
8 | render(h: CreateElement, { data, children }): VNode {
9 | const cssClass = mergeCss(data, 'toast-action');
10 |
11 | return (
12 |
13 | {children}
14 |
15 | );
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/src/components/Toast/ToastBody.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 ToastBody = tsx.component({
6 | name: 'ToastBody',
7 | functional: true,
8 | render(h: CreateElement, { data, children }): VNode {
9 | const cssClass = mergeCss(data, 'toast-body');
10 |
11 | return (
12 |
13 | {children}
14 |
15 | );
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/src/components/Toast/ToastContent.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 ToastContent = tsx.component({
6 | name: 'ToastContent',
7 | functional: true,
8 | render(h: CreateElement, { data, children }): VNode {
9 | const cssClass = mergeCss(data, 'toast-content');
10 |
11 | return (
12 |
13 | {children}
14 |
15 | );
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/src/components/Toast/ToastIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode } from 'vue';
3 | import { IconType, Icon } from '../Icon';
4 | import { mergeCss } from '../../utils/css';
5 |
6 | export const ToastIcon = tsx.component({
7 | name: 'ToastIcon',
8 | functional: true,
9 | props: {
10 | icon: { type: String as () => IconType, required: true },
11 | large: { type: Boolean, default: false },
12 | },
13 | render(h: CreateElement, { data, props }): VNode {
14 | const cssClass = mergeCss(data, 'toast-icon', [props.large && 'large']);
15 |
16 | return (
17 |
18 |
19 |
20 | );
21 | },
22 | });
23 |
--------------------------------------------------------------------------------
/src/components/Toast/ToastTitle.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 ToastTitle = tsx.component({
6 | name: 'ToastTitle',
7 | functional: true,
8 | render(h: CreateElement, { data, children }): VNode {
9 | const cssClass = mergeCss(data, 'toast-title');
10 |
11 | return (
12 |
13 | {children}
14 |
15 | );
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/src/components/Toast/Type.ts:
--------------------------------------------------------------------------------
1 | export enum ToastTypes {
2 | primary = 'toast-primary',
3 | success = 'toast-success',
4 | warning = 'toast-warning',
5 | error = 'toast-error',
6 | }
7 |
8 | export type ToastType = keyof typeof ToastTypes;
9 |
--------------------------------------------------------------------------------
/src/components/Toast/index.ts:
--------------------------------------------------------------------------------
1 | import { Toast } from './Toast';
2 | import { ToastAction } from './ToastAction';
3 | import { ToastBody } from './ToastBody';
4 | import { ToastContent } from './ToastContent';
5 | import { ToastIcon } from './ToastIcon';
6 | import { ToastTitle } from './ToastTitle';
7 | import { makePluggableComponents } from '../../utils/plugin';
8 |
9 | export default makePluggableComponents({ Toast, ToastAction, ToastBody, ToastContent, ToastIcon, ToastTitle });
10 | export { Toast, ToastAction, ToastBody, ToastContent, ToastIcon, ToastTitle };
11 |
--------------------------------------------------------------------------------
/src/components/Toast/styles.scss:
--------------------------------------------------------------------------------
1 | .toast-fade-enter-active,
2 | .toast-fade-leave-active {
3 | transition: opacity 0.5s;
4 | }
5 | .toast-fade-enter,
6 | .toast-fade-leave-to {
7 | opacity: 0;
8 | }
9 |
10 | .toast {
11 | display: flex;
12 |
13 | &-body {
14 | flex-grow: 4;
15 | }
16 |
17 | &-icon {
18 | align-self: flex-start;
19 | margin: 0 0.3rem 0 0.2rem;
20 |
21 | &.large {
22 | align-self: center;
23 | margin: 0 0.75rem 0 0.5rem;
24 | }
25 | }
26 |
27 | &-action {
28 | display: flex;
29 | flex-flow: row-reverse wrap;
30 | align-items: center;
31 | align-content: flex-start;
32 |
33 | .btn {
34 | &-clear {
35 | margin: 0;
36 | }
37 |
38 | &.btn-link {
39 | color: #fff;
40 | }
41 |
42 | &.btn-action {
43 | width: 1rem;
44 | height: 1rem;
45 | padding: 0.1rem;
46 | line-height: 0.8rem;
47 |
48 | &:hover {
49 | background: rgba(247, 248, 249, 0.5);
50 | opacity: 0.95;
51 | }
52 | }
53 |
54 | &.btn-link:hover {
55 | color: rgba(255, 255, 255, 0.81);
56 | }
57 |
58 | // & + .btn {
59 | // margin-top: 1rem;
60 | // }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/components/VerticalMenu/VerticalMenu.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode } from 'vue';
3 | import { VerticalMenuItem } from './VerticalMenuItem';
4 | import { VerticalMenuDivider } from './VerticalMenuDivider';
5 |
6 | export const VerticalMenu = /*#__PURE__*/ tsx.component({
7 | name: 'VerticalMenu',
8 | functional: true,
9 | props: {
10 | items: { type: [Array, Object], default: (): [] => [] },
11 | active: { type: [Number, String], default: '' },
12 | },
13 | render(h: CreateElement, { props, slots, scopedSlots, data }): VNode {
14 | if (!props.items) {
15 | throw new TypeError('Items cannot be empty');
16 | }
17 |
18 | const items = Array.isArray(props.items) ? { ...props.items } : props.items;
19 | const menuItems = Object.keys(items).map((key: string | number) => {
20 | if (items[key].divider) {
21 | return ;
22 | }
23 |
24 | return (
25 |
31 | {scopedSlots.default && scopedSlots.default({ item: items[key], index: key })}
32 |
33 | );
34 | });
35 |
36 | return (
37 |
41 | );
42 | },
43 | });
44 |
--------------------------------------------------------------------------------
/src/components/VerticalMenu/VerticalMenuDivider.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode } from 'vue';
3 |
4 | const normalizeDivider = (divider: string | boolean): string => {
5 | return typeof divider === 'string' ? divider : '';
6 | };
7 |
8 | export const VerticalMenuDivider = tsx.component({
9 | name: 'VerticalMenuDivider',
10 | functional: true,
11 | props: {
12 | text: { type: [String, Boolean], default: undefined },
13 | },
14 | render(h: CreateElement, { props }): VNode {
15 | return ;
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/src/components/VerticalMenu/VerticalMenuItem.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode } from 'vue';
3 | import { VerticalMenuItemBadge } from './VerticalMenuItemBadge';
4 |
5 | export const VerticalMenuItem = tsx.component({
6 | name: 'VerticalMenuItem',
7 | functional: true,
8 | props: {
9 | active: { type: Boolean },
10 | badge: { type: [String, Number], default: undefined },
11 | text: { type: String, default: undefined },
12 | path: { type: String, default: undefined },
13 | },
14 | render(h: CreateElement, { props, children }): VNode {
15 | if (children && children.length) {
16 | return {children};
17 | }
18 |
19 | const badge = props.badge && {props.badge};
20 | const link = (
21 |
22 | {props.text}
23 |
24 | );
25 |
26 | return (
27 |
28 | {badge} {link}
29 |
30 | );
31 | },
32 | });
33 |
--------------------------------------------------------------------------------
/src/components/VerticalMenu/VerticalMenuItemBadge.tsx:
--------------------------------------------------------------------------------
1 | import * as tsx from 'vue-tsx-support';
2 | import { CreateElement, VNode } from 'vue';
3 | import { Tag, TagType } from '../Tag';
4 |
5 | export const VerticalMenuItemBadge = tsx.component({
6 | name: 'VerticalMenuItemBadge',
7 | functional: true,
8 | props: {
9 | type: { type: String as () => TagType, default: undefined },
10 | },
11 | render(h: CreateElement, { props, children }): VNode {
12 | return (
13 |
16 | );
17 | },
18 | });
19 |
--------------------------------------------------------------------------------
/src/components/VerticalMenu/index.ts:
--------------------------------------------------------------------------------
1 | import { VerticalMenu } from './VerticalMenu';
2 | import { VerticalMenuDivider } from './VerticalMenuDivider';
3 | import { VerticalMenuItem } from './VerticalMenuItem';
4 | import { VerticalMenuItemBadge } from './VerticalMenuItemBadge';
5 | import { makePluggableComponents } from '../../utils/plugin';
6 |
7 | export default makePluggableComponents({ VerticalMenu, VerticalMenuDivider, VerticalMenuItem, VerticalMenuItemBadge });
8 | export { VerticalMenu, VerticalMenuDivider, VerticalMenuItem, VerticalMenuItemBadge };
9 |
--------------------------------------------------------------------------------
/src/components/index.ts:
--------------------------------------------------------------------------------
1 | import AvatarComponents from './Avatar';
2 | import AccordionComponents from './Accordion';
3 | import CardComponents from './Card';
4 | import ColumnComponents from './Column';
5 | import ColumnsComponents from './Columns';
6 | import ContainerComponents from './Container';
7 | import BarComponents from './Bar';
8 | import BreadcrumbComponents from './Breadcrumb';
9 | import BtnComponents from './Btn';
10 | import ChipComponents from './Chip';
11 | import DividerComponents from './Divider';
12 | import DropdownMenuComponents from './DropdownMenu';
13 | import VerticalMenuComponents from './VerticalMenu';
14 | import EmptyComponents from './Empty';
15 | import IconComponents from './Icon';
16 | import TagComponents from './Tag';
17 | import ModalComponents from './Modal';
18 | import NavigationComponents from './Navigation';
19 | import OffCanvasComponents from './OffCanvas';
20 | import OverlayComponents from './Overlay';
21 | import PaginationComponents from './Pagination';
22 | import PanelComponents from './Panel';
23 | import PopoverComponents from './Popover';
24 | import StepComponents from './Steps';
25 | import TabComponents from './Tab';
26 | import TileComponents from './Tile';
27 | import ToasComponents from './Toast';
28 |
29 | import FormCheckboxComponents from './FormCheckbox';
30 | import FormGroupComponents from './FormGroup';
31 | import FormHintComponents from './FormHint';
32 | import FormHorizontalComponents from './FormHorizontal';
33 | import FormInputComponents from './FormInput';
34 | import FormLabelComponents from './FormLabel';
35 | import FormRadioComponents from './FormRadio';
36 | import FormSelectComponents from './FormSelect';
37 | import FormSliderComponents from './FormSlider';
38 | import FormSwitchComponents from './FormSwitch';
39 | import FormTextareaComponents from './FormTextarea';
40 |
41 | export * from './Avatar';
42 | export * from './Accordion';
43 | export * from './Bar';
44 | export * from './Breadcrumb';
45 | export * from './Card';
46 | export * from './Chip';
47 | export * from './Column';
48 | export * from './Columns';
49 | export * from './Container';
50 | export * from './Divider';
51 | export * from './DropdownMenu';
52 | export * from './Empty';
53 | export * from './VerticalMenu';
54 | export * from './OffCanvas';
55 | export * from './Overlay';
56 | export * from './Pagination';
57 | export * from './Panel';
58 | export * from './Steps';
59 | export * from './Tab';
60 | export * from './Tile';
61 | export * from './Toast';
62 | export * from './Navigation';
63 | export * from './Btn';
64 |
65 | export * from './FormCheckbox';
66 | export * from './FormGroup';
67 | export * from './FormHint';
68 | export * from './FormHorizontal';
69 | export * from './FormInput';
70 | export * from './FormLabel';
71 | export * from './FormRadio';
72 | export * from './FormSelect';
73 | export * from './FormSlider';
74 | export * from './FormSwitch';
75 | export * from './FormTextarea';
76 | export * from './Icon';
77 | export * from './Tag';
78 | export * from './Modal';
79 | export * from './Popover';
80 |
81 | // // Default is all components
82 | export default {
83 | AvatarComponents,
84 | AccordionComponents,
85 | BtnComponents,
86 | BarComponents,
87 | BreadcrumbComponents,
88 | CardComponents,
89 | ChipComponents,
90 | ColumnComponents,
91 | ColumnsComponents,
92 | ContainerComponents,
93 | DividerComponents,
94 | DropdownMenuComponents,
95 | EmptyComponents,
96 | IconComponents,
97 | ModalComponents,
98 | NavigationComponents,
99 | OffCanvasComponents,
100 | OverlayComponents,
101 | PaginationComponents,
102 | PanelComponents,
103 | PopoverComponents,
104 | StepComponents,
105 | TabComponents,
106 | TileComponents,
107 | ToasComponents,
108 | TagComponents,
109 | VerticalMenuComponents,
110 | FormCheckboxComponents,
111 | FormGroupComponents,
112 | FormInputComponents,
113 | FormLabelComponents,
114 | FormHintComponents,
115 | FormHorizontalComponents,
116 | FormRadioComponents,
117 | FormSelectComponents,
118 | FormSliderComponents,
119 | FormSwitchComponents,
120 | FormTextareaComponents,
121 | };
122 |
--------------------------------------------------------------------------------
/src/directives/Badge/Badge.ts:
--------------------------------------------------------------------------------
1 | import { DirectiveFunction } from 'vue';
2 |
3 | export const Badge: DirectiveFunction = /*#__PURE__*/ (el: HTMLElement, { value }): void => {
4 | if (value === undefined) return;
5 |
6 | el.classList.add('badge');
7 | el.setAttribute('data-badge', value);
8 | };
9 |
--------------------------------------------------------------------------------
/src/directives/Badge/index.ts:
--------------------------------------------------------------------------------
1 | import { Badge } from './Badge';
2 | import { makePluggableDirectives } from '../../utils/plugin';
3 |
4 | export default makePluggableDirectives({ Badge });
5 | export { Badge };
6 |
--------------------------------------------------------------------------------
/src/directives/ClickOutside/ClickOutside.ts:
--------------------------------------------------------------------------------
1 | import { DirectiveOptions } from 'vue';
2 |
3 | const listeners = new WeakMap();
4 |
5 | const getEvent = (touch = false): 'touchstart' | 'click' => {
6 | if (!touch) return 'click';
7 |
8 | return 'ontouchstart' in window || navigator.msMaxTouchPoints ? 'touchstart' : 'click';
9 | };
10 |
11 | const getListener = (
12 | el: HTMLElement,
13 | callback?: (e: Event, el: HTMLElement) => void,
14 | ): ((e: TouchEvent | MouseEvent) => void) => {
15 | if (!listeners.has(el)) {
16 | listeners.set(el, (e: TouchEvent | MouseEvent): void => {
17 | if (!el.contains(e.target as Node)) {
18 | callback(e, el);
19 | }
20 | });
21 | }
22 |
23 | return listeners.get(el);
24 | };
25 |
26 | export const ClickOutside: DirectiveOptions = /*#__PURE__*/ {
27 | bind(el, { value, modifiers }) {
28 | document.addEventListener(getEvent(modifiers.touch), getListener(el, value));
29 | },
30 | unbind(el) {
31 | document.removeEventListener(getEvent(), getListener(el));
32 | },
33 | };
34 |
--------------------------------------------------------------------------------
/src/directives/ClickOutside/index.ts:
--------------------------------------------------------------------------------
1 | import { ClickOutside } from './ClickOutside';
2 | import { makePluggableDirectives } from '../../utils/plugin';
3 |
4 | export default makePluggableDirectives({ ClickOutside });
5 | export { ClickOutside };
6 |
--------------------------------------------------------------------------------
/src/directives/Loading/Loading.ts:
--------------------------------------------------------------------------------
1 | import { DirectiveFunction } from 'vue';
2 |
3 | const add = (el: HTMLElement, cssClasses: string[]): void => {
4 | el.classList.add(...cssClasses);
5 | };
6 |
7 | const remove = (el: HTMLElement, cssClasses: string[]): void => {
8 | el.classList.remove(...cssClasses);
9 | };
10 |
11 | export const Loading: DirectiveFunction = /*#__PURE__*/ (el: HTMLElement, { value = true, modifiers }): void => {
12 | const cssClasses = ['loading'];
13 |
14 | if (modifiers.lg) {
15 | cssClasses.push('loading-lg');
16 | }
17 |
18 | if (value instanceof Promise) {
19 | add(el, cssClasses);
20 | value.then(() => remove(el, cssClasses));
21 |
22 | return;
23 | }
24 |
25 | if (value === undefined || !!value) {
26 | add(el, cssClasses);
27 |
28 | return;
29 | }
30 |
31 | remove(el, cssClasses);
32 | };
33 |
--------------------------------------------------------------------------------
/src/directives/Loading/index.ts:
--------------------------------------------------------------------------------
1 | import { Loading } from './Loading';
2 | import { makePluggableDirectives } from '../../utils/plugin';
3 |
4 | export default makePluggableDirectives({ Loading });
5 | export { Loading };
6 |
--------------------------------------------------------------------------------
/src/directives/Overlay/Overlay.ts:
--------------------------------------------------------------------------------
1 | import { DirectiveOptions } from 'vue';
2 | import { OverlayBinding, OverlayConfiguration } from './type';
3 | import './styles.scss';
4 |
5 | const findOverlay = (el: HTMLElement): HTMLDivElement =>
6 | [].slice.call(el.children).find((n) => n.className === 'overlay__shadow');
7 |
8 | const findRootElement = (el: HTMLElement): HTMLElement => {
9 | let parent = el.parentElement;
10 |
11 | while (parent.parentElement !== document.body) {
12 | parent = parent.parentElement;
13 | }
14 |
15 | return parent;
16 | };
17 |
18 | const onClickStub = (): void => {
19 | /* NOOP */
20 | };
21 |
22 | const normalizeBinding = ({ value, arg, modifiers }: OverlayBinding): OverlayConfiguration => {
23 | const configuration: OverlayConfiguration = {};
24 |
25 | if (typeof value === 'object') {
26 | configuration.opacity = String(value.opacity || 75).padStart(2, '0');
27 | configuration.zIndex = value.zIndex || 1;
28 | configuration.text = value.text || '';
29 | configuration.blur = value.blur || modifiers.blur;
30 | configuration.onClick = value.onClick || onClickStub;
31 | configuration.fullscreen = value.fullscreen || modifiers.fullscreen;
32 | configuration.noScroll = configuration.fullscreen && (value.noScroll || modifiers.noScroll);
33 | configuration.show = value.show;
34 | } else {
35 | configuration.opacity = String(arg || 75).padStart(2, '0');
36 | configuration.zIndex = 1;
37 | configuration.text = typeof value === 'string' ? value : '';
38 | configuration.blur = modifiers.blur;
39 | configuration.onClick = typeof value === 'function' ? value : onClickStub;
40 | configuration.fullscreen = modifiers.fullscreen;
41 | configuration.noScroll = configuration.fullscreen && modifiers.noScroll;
42 | configuration.show = !!value;
43 | }
44 |
45 | return configuration;
46 | };
47 |
48 | const updateOverlay = (overlayEl: HTMLElement, configuration: OverlayConfiguration): void => {
49 | overlayEl.style.setProperty('z-index', String(configuration.zIndex));
50 | overlayEl.style.setProperty('background', `rgba(247, 248, 249, 0.${configuration.opacity})`);
51 |
52 | if (blur) {
53 | const blurLevel = configuration.blur === true ? '2px' : `${configuration.blur}px`;
54 | overlayEl.style.setProperty('backdrop-filter', `blur(${blurLevel})`);
55 | }
56 |
57 | if (configuration.text) {
58 | overlayEl.innerHTML = configuration.text;
59 | }
60 | };
61 |
62 | const createOverlay = (container: HTMLElement, configuration: OverlayConfiguration): HTMLDivElement => {
63 | const overlayEl = document.createElement('div');
64 | overlayEl.className = 'overlay__shadow';
65 |
66 | if (configuration.fullscreen) {
67 | overlayEl.style.setProperty('position', 'fixed');
68 | }
69 |
70 | overlayEl.addEventListener('click', configuration.onClick);
71 | container.insertBefore(overlayEl, container.firstChild);
72 |
73 | return overlayEl;
74 | };
75 |
76 | const disableScroll = (container: HTMLElement): void => {
77 | const root = findRootElement(container);
78 | root.style.setProperty('top', `-${window.scrollY}px`);
79 | root.style.setProperty('position', 'fixed');
80 | };
81 |
82 | const enableScroll = (container: HTMLElement): void => {
83 | const root = findRootElement(container);
84 | root.style.position = '';
85 | window.scrollTo({ top: +root.style.top.match(/\d+/)[0] });
86 | };
87 |
88 | export const overlay: DirectiveOptions = {
89 | inserted: /*#__PURE__*/ (el: HTMLElement, binding: OverlayBinding): void => {
90 | const configuration = normalizeBinding(binding);
91 | let overlayEl = findOverlay(el);
92 |
93 | if (configuration.show && !overlayEl) {
94 | overlayEl = createOverlay(el, configuration);
95 | updateOverlay(overlayEl, configuration);
96 | }
97 | },
98 | update: /*#__PURE__*/ (el, binding: OverlayBinding) => {
99 | let overlayEl = findOverlay(el);
100 | const configuration = normalizeBinding(binding);
101 | const oldValue = typeof binding.oldValue === 'object' ? binding.oldValue.show : !!binding.oldValue;
102 |
103 | if (configuration.show) {
104 | if (!overlayEl) {
105 | overlayEl = createOverlay(el, configuration);
106 | }
107 |
108 | if (configuration.noScroll) {
109 | disableScroll(el);
110 | }
111 |
112 | updateOverlay(overlayEl, configuration);
113 | } else if (oldValue !== configuration.show) {
114 | if (configuration.noScroll) {
115 | enableScroll(el);
116 | }
117 | if (overlayEl) {
118 | el.removeChild(overlayEl);
119 | }
120 | }
121 | },
122 | };
123 |
--------------------------------------------------------------------------------
/src/directives/Overlay/index.ts:
--------------------------------------------------------------------------------
1 | import { overlay } from './overlay';
2 | import { makePluggableDirectives } from '../../utils/plugin';
3 |
4 | export default makePluggableDirectives({ overlay });
5 | export { overlay };
6 |
--------------------------------------------------------------------------------
/src/directives/Overlay/styles.scss:
--------------------------------------------------------------------------------
1 | .overlay__shadow {
2 | height: 100%;
3 | width: 100%;
4 | position: absolute;
5 | right: 0;
6 | top: 0;
7 | bottom: 0;
8 | left: 0;
9 | will-change: opacity;
10 | align-items: center;
11 | justify-content: center;
12 | display: flex;
13 | }
14 |
--------------------------------------------------------------------------------
/src/directives/Overlay/type.ts:
--------------------------------------------------------------------------------
1 | import { VNodeDirective } from 'vue';
2 |
3 | export interface OverlayConfiguration {
4 | onClick?: OverlayOnClickHandler;
5 | zIndex?: string | number;
6 | opacity?: string | number;
7 | text?: string;
8 | show?: boolean;
9 | blur?: boolean | string | number;
10 | noScroll?: boolean;
11 | fullscreen?: boolean;
12 | }
13 |
14 | export type OverlayOnClickHandler = (event: MouseEvent) => void;
15 |
16 | export type OverlayValue = string | OverlayOnClickHandler | OverlayConfiguration;
17 |
18 | export interface OverlayBinding extends VNodeDirective {
19 | value?: OverlayValue;
20 | oldValue?: OverlayValue;
21 | }
22 |
--------------------------------------------------------------------------------
/src/directives/Tooltip/Side.ts:
--------------------------------------------------------------------------------
1 | export enum TooltipSides {
2 | top = '',
3 | bottom = 'tooltip-bottom',
4 | right = 'tooltip-right',
5 | left = 'tooltip-left',
6 | }
7 |
8 | export type TooltipSide = keyof typeof TooltipSides;
9 |
--------------------------------------------------------------------------------
/src/directives/Tooltip/Tooltip.ts:
--------------------------------------------------------------------------------
1 | import { DirectiveFunction } from 'vue';
2 | import { TooltipSides, TooltipSide } from './Side';
3 |
4 | export const Tooltip: DirectiveFunction = /*#__PURE__*/ (el, { value, modifiers }) => {
5 | if (typeof value !== 'undefined') {
6 | el.classList.add('tooltip');
7 | el.setAttribute('data-tooltip', value);
8 |
9 | Object.keys(TooltipSides).map((side: string) => {
10 | if (modifiers[side]) el.classList.add(TooltipSides[side as TooltipSide]);
11 | });
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/src/directives/Tooltip/index.ts:
--------------------------------------------------------------------------------
1 | import { Tooltip } from './Tooltip';
2 | import { makePluggableDirectives } from '../../utils/plugin';
3 |
4 | export default makePluggableDirectives({ Tooltip });
5 | export { Tooltip };
6 | export * from './Side';
7 |
--------------------------------------------------------------------------------
/src/directives/index.ts:
--------------------------------------------------------------------------------
1 | import BadgeDirectives from './Badge';
2 | import ClickOutside from './ClickOutside';
3 | import LoadingDirectives from './Loading';
4 | import OverlayDirectives from './Overlay';
5 | import TooltipDirectives from './Tooltip';
6 |
7 | // Directives
8 | export default {
9 | BadgeDirectives,
10 | ClickOutside,
11 | LoadingDirectives,
12 | OverlayDirectives,
13 | TooltipDirectives,
14 | };
15 |
16 | export * from './Badge';
17 | export * from './ClickOutside';
18 | export * from './Loading';
19 | export * from './Overlay';
20 | export * from './Tooltip';
21 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import 'vue-tsx-support/enable-check';
2 |
3 | export * from './components';
4 | export * from './directives';
5 |
6 | export { default as VectrePlugin } from './plugin';
7 |
--------------------------------------------------------------------------------
/src/mixins/cache.ts:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import { ExtendedVue } from 'vue/types/vue';
3 |
4 | type cached = { (data: string): void }[];
5 |
6 | export function cachedProp>(
7 | origin: string,
8 | cached: CachedKey,
9 | ): ExtendedVue<
10 | Vue,
11 | { [P in CachedKey]: T },
12 | Record,
13 | { [P in CachedKey]: () => any },
14 | Record
15 | > {
16 | return Vue.extend({
17 | data() {
18 | return { [cached]: {} };
19 | },
20 | watch: {
21 | [origin]: {
22 | immediate: true,
23 | handler(value: Record, old: Record): void {
24 | if (old !== void 0) {
25 | for (const prop in old) {
26 | if (Object.prototype.hasOwnProperty.call(value, prop) !== true) {
27 | this.$delete(this[cached], prop);
28 | }
29 | }
30 | }
31 |
32 | for (const prop in value) {
33 | this.$set(this.$data[cached], prop, value[prop]);
34 | }
35 | },
36 | },
37 | },
38 | created() {
39 | this[cached] = { ...this[origin] };
40 | },
41 | });
42 | }
43 |
44 | export const cachedListeners = cachedProp<'__listeners', cached>('$listeners', '__listeners');
45 | export const cachedAttrs = cachedProp<'__attrs', cached>('$attrs', '__attrs');
46 |
--------------------------------------------------------------------------------
/src/plugin.ts:
--------------------------------------------------------------------------------
1 | import { PluginFunction } from 'vue';
2 | import components from './components';
3 | import directives from './directives';
4 |
5 | export interface PluginOptions {
6 | prefix?: string;
7 | }
8 |
9 | const VectrePlugin: PluginFunction = (vue, options = { prefix: '' }): void => {
10 | Object.values(components).forEach((c) => vue.use(c, options));
11 | Object.values(directives).forEach((c) => vue.use(c, options));
12 | };
13 |
14 | export default VectrePlugin;
15 |
--------------------------------------------------------------------------------
/src/shims-vue.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.vue' {
2 | import Vue from 'vue';
3 | export default Vue;
4 | }
5 |
--------------------------------------------------------------------------------
/src/utils/css.ts:
--------------------------------------------------------------------------------
1 | import { VNodeData } from 'vue';
2 |
3 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
4 | export const mergeCss = (data: VNodeData, staticClass?: string, cssClass?: any): any[] => {
5 | return [data.class, data.staticClass, cssClass, staticClass];
6 | };
7 |
8 | // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
9 | export const mergeStyles = (data: VNodeData, staticStyle?: string, style?: any): any[] => {
10 | return [data.staticStyle, data.style, staticStyle, style];
11 | };
12 |
--------------------------------------------------------------------------------
/src/utils/listener.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/ban-types */
2 | export const flattenListener = (listener?: Function | Function[]): ((event: any) => void) => {
3 | let flatten: Function[];
4 | if (listener) {
5 | flatten = Array.isArray(listener) ? listener : [listener];
6 | } else {
7 | flatten = [
8 | (): void => {
9 | /* noop */
10 | },
11 | ];
12 | }
13 |
14 | return (event: any): void => flatten.forEach((l) => l(event));
15 | };
16 |
--------------------------------------------------------------------------------
/src/utils/plugin.ts:
--------------------------------------------------------------------------------
1 | import { VueConstructor, PluginFunction, DirectiveFunction, DirectiveOptions } from 'vue';
2 | import { addPrefix } from './prefix';
3 | import { capitalize, uncapitalize } from './string';
4 |
5 | export const makePluggableComponents = /*#__PURE__*/ (
6 | components = {} as Record>,
7 | ): PluginFunction<{ prefix?: string }> => {
8 | return (vue: VueConstructor, options = { prefix: '' }): void => {
9 | Object.keys(components).forEach((name) =>
10 | vue.component(addPrefix(capitalize(name), options.prefix), components[name]),
11 | );
12 | };
13 | };
14 |
15 | export const makePluggableDirectives = /*#__PURE__*/ (
16 | directives = {} as Record,
17 | ): PluginFunction<{ prefix?: string }> => {
18 | return (vue: VueConstructor, options = { prefix: '' }): void => {
19 | Object.keys(directives).forEach((name) =>
20 | vue.directive(addPrefix(uncapitalize(name), options.prefix, '-'), directives[name]),
21 | );
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/src/utils/prefix.ts:
--------------------------------------------------------------------------------
1 | export const addPrefix = (source: string, prefix = '', suffix = ''): string => {
2 | const prefixWithSuffix = prefix ? prefix + suffix : prefix;
3 |
4 | return prefixWithSuffix + source;
5 | };
6 |
--------------------------------------------------------------------------------
/src/utils/string.ts:
--------------------------------------------------------------------------------
1 | export const capitalize = (s: string): string => {
2 | if (typeof s !== 'string') {
3 | throw new TypeError(`Argument should be a string. Given: ${typeof s}`);
4 | }
5 |
6 | return s.charAt(0).toUpperCase() + s.slice(1);
7 | };
8 |
9 | export const uncapitalize = (s: string): string => {
10 | if (typeof s !== 'string') {
11 | throw new TypeError(`Argument should be a string. Given: ${typeof s}`);
12 | }
13 |
14 | return s.charAt(0).toLowerCase() + s.slice(1);
15 | };
16 |
--------------------------------------------------------------------------------
/src/utils/uid.ts:
--------------------------------------------------------------------------------
1 | const uids = new WeakMap();
2 |
3 | export const uid = (component: Record): string => {
4 | if (uids.has(component)) {
5 | return uids.get(component);
6 | }
7 |
8 | uids.set(component, String(~~(Math.random() * 1000000000)));
9 |
10 | return uid(component);
11 | };
12 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "target": "es6",
5 | "module": "esnext",
6 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"],
7 | "paths": {
8 | "@components/*": ["src/components/*"]
9 | },
10 | "allowSyntheticDefaultImports": true,
11 | "importHelpers": true,
12 | "moduleResolution": "node",
13 | "jsx": "preserve",
14 | "strict": true,
15 | "esModuleInterop": true,
16 | "experimentalDecorators": true,
17 | "emitDecoratorMetadata": true,
18 | "noLib": false,
19 | "strictPropertyInitialization": false,
20 | "downlevelIteration": true,
21 | "declaration": true,
22 | "declarationDir": "./types",
23 | "noImplicitAny": true,
24 | "strictNullChecks": false,
25 | "noErrorTruncation": true
26 | },
27 | "exclude": ["node_modules/*"],
28 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "node_modules/vue-tsx-support/enable-check.d.ts"]
29 | }
30 |
--------------------------------------------------------------------------------