├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github ├── .git_commit_msg.txt ├── CONTRIBUTING.md └── ISSUE_TEMPLATE.md ├── .gitignore ├── .npmignore ├── .travis.yml ├── BACKERS.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── build ├── babel-transform-stylus-paths.js ├── config.js ├── parse-npm-tag.js ├── release.sh ├── webpack.base.config.js ├── webpack.dev.config.js └── webpack.prod.config.js ├── dev ├── App.vue ├── Playground.vue ├── index.html ├── index.js ├── prepare-commit-message.js ├── router.js └── symbol.js ├── dist ├── vuetify.css ├── vuetify.css.map ├── vuetify.js ├── vuetify.js.map ├── vuetify.min.css ├── vuetify.min.css.map └── vuetify.min.js ├── index.d.ts ├── package-lock.json ├── package.json ├── postcss.config.js ├── src ├── components │ ├── VAlert │ │ ├── VAlert.js │ │ ├── VAlert.spec.js │ │ ├── __snapshots__ │ │ │ └── VAlert.spec.js.snap │ │ └── index.js │ ├── VApp │ │ ├── VApp.js │ │ ├── VApp.spec.js │ │ ├── __snapshots__ │ │ │ └── VApp.spec.js.snap │ │ └── index.js │ ├── VAvatar │ │ ├── VAvatar.js │ │ ├── VAvatar.spec.js │ │ ├── __snapshots__ │ │ │ └── VAvatar.spec.js.snap │ │ └── index.js │ ├── VBadge │ │ ├── VBadge.js │ │ └── index.js │ ├── VBottomNav │ │ ├── VBottomNav.js │ │ ├── VBottomNav.spec.js │ │ ├── __snapshots__ │ │ │ └── VBottomNav.spec.js.snap │ │ └── index.js │ ├── VBottomSheet │ │ ├── VBottomSheet.js │ │ └── index.js │ ├── VBreadcrumbs │ │ ├── VBreadcrumbs.js │ │ ├── VBreadcrumbs.spec.js │ │ ├── VBreadcrumbsItem.js │ │ ├── VBreadcrumbsItem.spec.js │ │ ├── __snapshots__ │ │ │ └── VBreadcrumbs.spec.js.snap │ │ └── index.js │ ├── VBtn │ │ ├── VBtn.js │ │ ├── VBtn.spec.js │ │ ├── __snapshots__ │ │ │ └── VBtn.spec.js.snap │ │ └── index.js │ ├── VBtnToggle │ │ ├── VBtnToggle.js │ │ ├── VBtnToggle.spec.js │ │ ├── __snapshots__ │ │ │ └── VBtnToggle.spec.js.snap │ │ └── index.js │ ├── VCard │ │ ├── VCard.js │ │ ├── VCard.spec.js │ │ ├── VCardMedia.js │ │ ├── VCardMedia.spec.js │ │ ├── VCardTitle.js │ │ ├── VCardTitle.spec.js │ │ ├── __snapshots__ │ │ │ ├── VCard.spec.js.snap │ │ │ ├── VCardMedia.spec.js.snap │ │ │ └── VCardTitle.spec.js.snap │ │ └── index.js │ ├── VCarousel │ │ ├── VCarousel.js │ │ ├── VCarousel.spec.js │ │ ├── VCarouselItem.js │ │ ├── VCarouselItem.spec.js │ │ ├── __snapshots__ │ │ │ ├── VCarousel.spec.js.snap │ │ │ └── VCarouselItem.spec.js.snap │ │ └── index.js │ ├── VCheckbox │ │ ├── VCheckbox.js │ │ ├── VCheckbox.spec.js │ │ └── index.js │ ├── VChip │ │ ├── VChip.js │ │ ├── VChip.spec.js │ │ ├── __snapshots__ │ │ │ └── VChip.spec.js.snap │ │ └── index.js │ ├── VDataTable │ │ ├── VDataTable.js │ │ ├── VDataTable.spec.js │ │ ├── VEditDialog.js │ │ ├── __snapshots__ │ │ │ └── VDataTable.spec.js.snap │ │ ├── index.js │ │ └── mixins │ │ │ ├── body.js │ │ │ ├── foot.js │ │ │ ├── head.js │ │ │ └── progress.js │ ├── VDatePicker │ │ ├── VDatePicker.js │ │ ├── VDatePicker.spec.js │ │ ├── __snapshots__ │ │ │ └── VDatePicker.spec.js.snap │ │ ├── index.js │ │ └── mixins │ │ │ ├── date-header.js │ │ │ ├── date-table.js │ │ │ ├── date-title.js │ │ │ ├── date-years.js │ │ │ └── month-table.js │ ├── VDialog │ │ ├── VDialog.js │ │ ├── VDialog.spec.js │ │ ├── __snapshots__ │ │ │ └── VDialog.spec.js.snap │ │ └── index.js │ ├── VDivider │ │ ├── VDivider.js │ │ ├── VDivider.spec.js │ │ ├── __snapshots__ │ │ │ └── VDivider.spec.js.snap │ │ └── index.js │ ├── VEditor │ │ ├── VEditor.vue │ │ ├── emoji.json │ │ ├── index.js │ │ └── tool.js │ ├── VExpansionPanel │ │ ├── VExpansionPanel.js │ │ ├── VExpansionPanel.spec.js │ │ ├── VExpansionPanelContent.js │ │ ├── VExpansionPanelContent.spec.js │ │ └── index.js │ ├── VFooter │ │ ├── VFooter.js │ │ ├── VFooter.spec.js │ │ └── index.js │ ├── VForm │ │ ├── VForm.js │ │ ├── VForm.spec.js │ │ └── index.js │ ├── VGrid │ │ ├── VContainer.js │ │ ├── VContent.js │ │ ├── VFlex.js │ │ ├── VGrid.spec.js │ │ ├── VLayout.js │ │ ├── grid.js │ │ └── index.js │ ├── VIcon │ │ ├── VIcon.js │ │ ├── VIcon.spec.js │ │ └── index.js │ ├── VList │ │ ├── VList.js │ │ ├── VList.spec.js │ │ ├── VListGroup.js │ │ ├── VListGroup.spec.js │ │ ├── VListTile.js │ │ ├── VListTile.spec.js │ │ ├── VListTileAction.js │ │ ├── VListTileAction.spec.js │ │ ├── __snapshots__ │ │ │ ├── VList.spec.js.snap │ │ │ ├── VListGroup.spec.js.snap │ │ │ ├── VListTile.spec.js.snap │ │ │ └── VListTileAction.spec.js.snap │ │ └── index.js │ ├── VMenu │ │ ├── VMenu.js │ │ ├── VMenu.spec.js │ │ ├── __snapshots__ │ │ │ └── VMenu.spec.js.snap │ │ ├── index.js │ │ └── mixins │ │ │ ├── menu-activator.js │ │ │ ├── menu-generators.js │ │ │ ├── menu-keyable.js │ │ │ └── menu-position.js │ ├── VNavigationDrawer │ │ ├── VNavigationDrawer.js │ │ ├── VNavigationDrawer.spec.js │ │ ├── __snapshots__ │ │ │ └── VNavigationDrawer.spec.js.snap │ │ └── index.js │ ├── VPagination │ │ ├── VPagination.js │ │ ├── VPagination.spec.js │ │ ├── __snapshots__ │ │ │ └── VPagination.spec.js.snap │ │ └── index.js │ ├── VParallax │ │ ├── VParallax.js │ │ ├── VParallax.spec.js │ │ ├── __snapshots__ │ │ │ └── VParallax.spec.js.snap │ │ └── index.js │ ├── VProgressCircular │ │ ├── VProgressCircular.js │ │ └── index.js │ ├── VProgressLinear │ │ ├── VProgressLinear.js │ │ ├── VProgressLinear.spec.js │ │ ├── __snapshots__ │ │ │ └── VProgressLinear.spec.js.snap │ │ └── index.js │ ├── VRadioGroup │ │ ├── VRadio.js │ │ ├── VRadio.spec.js │ │ ├── VRadioGroup.js │ │ ├── VRadioGroup.spec.js │ │ ├── __snapshots__ │ │ │ ├── VRadio.spec.js.snap │ │ │ └── VRadioGroup.spec.js.snap │ │ └── index.js │ ├── VSelect │ │ ├── VSelect-autocomplete.spec.js │ │ ├── VSelect-combobox.spec.js │ │ ├── VSelect-tags.spec.js │ │ ├── VSelect.js │ │ ├── VSelect.spec.js │ │ ├── VSelect2.spec.js │ │ ├── __snapshots__ │ │ │ ├── VSelect.spec.js.snap │ │ │ └── VSelect2.spec.js.snap │ │ ├── index.js │ │ └── mixins │ │ │ ├── select-autocomplete.js │ │ │ └── select-generators.js │ ├── VSlider │ │ ├── VSlider.js │ │ ├── VSlider.spec.js │ │ ├── __snapshots__ │ │ │ └── VSlider.spec.js.snap │ │ └── index.js │ ├── VSnackbar │ │ ├── VSnackbar.js │ │ ├── VSnackbar.spec.js │ │ └── index.js │ ├── VSpeedDial │ │ ├── VSpeedDial.js │ │ ├── VSpeedDial.spec.js │ │ ├── __snapshots__ │ │ │ └── VSpeedDial.spec.js.snap │ │ └── index.js │ ├── VStepper │ │ ├── VStepper.js │ │ ├── VStepperContent.js │ │ ├── VStepperStep.js │ │ └── index.js │ ├── VSubheader │ │ ├── VSubheader.js │ │ └── index.js │ ├── VSwitch │ │ ├── VSwitch.js │ │ └── index.js │ ├── VSystemBar │ │ ├── VSystemBar.js │ │ ├── VSystemBar.spec.js │ │ └── index.js │ ├── VTabs │ │ ├── VTabs.js │ │ ├── VTabs.spec.js │ │ ├── VTabsBar.js │ │ ├── VTabsContent.js │ │ ├── VTabsItem.js │ │ ├── VTabsItems.js │ │ ├── VTabsSlider.js │ │ ├── VTabsSlider.spec.js │ │ └── index.js │ ├── VTextField │ │ ├── VTextField.js │ │ ├── VTextField.spec.js │ │ ├── __snapshots__ │ │ │ └── VTextField.spec.js.snap │ │ └── index.js │ ├── VTimePicker │ │ ├── VTimePicker.js │ │ ├── VTimePicker.spec.js │ │ ├── __snapshots__ │ │ │ └── VTimePicker.spec.js.snap │ │ ├── index.js │ │ └── mixins │ │ │ ├── time-body.js │ │ │ └── time-title.js │ ├── VToolbar │ │ ├── VToolbar.js │ │ ├── VToolbar.spec.js │ │ ├── VToolbarSideIcon.js │ │ ├── VToolbarSideIcon.spec.js │ │ ├── __snapshots__ │ │ │ └── VToolbarSideIcon.spec.js.snap │ │ └── index.js │ ├── VTooltip │ │ ├── VTooltip.js │ │ └── index.js │ ├── Vuetify │ │ └── index.js │ ├── index.js │ └── transitions │ │ ├── expand-transition.js │ │ └── index.js ├── directives │ ├── click-outside.js │ ├── index.js │ ├── resize.js │ ├── ripple.js │ ├── ripple.spec.js │ ├── scroll.js │ └── touch.js ├── index.js ├── mixins │ ├── applicationable.js │ ├── bootable.js │ ├── button-group.js │ ├── colorable.js │ ├── contextualable.js │ ├── delayable.js │ ├── dependent.js │ ├── detachable.js │ ├── filterable.js │ ├── input.js │ ├── loadable.js │ ├── maskable.js │ ├── menuable.js │ ├── overlayable.js │ ├── picker.js │ ├── positionable.js │ ├── rippleable.js │ ├── routable.js │ ├── selectable.js │ ├── stackable.js │ ├── tab-focusable.js │ ├── themeable.js │ ├── toggleable.js │ ├── transitionable.js │ ├── translatable.js │ └── validatable.js ├── stylus │ ├── app.styl │ ├── bootstrap.styl │ ├── components │ │ ├── _alerts.styl │ │ ├── _app.styl │ │ ├── _avatars.styl │ │ ├── _badges.styl │ │ ├── _bottom-navs.styl │ │ ├── _bottom-sheets.styl │ │ ├── _breadcrumbs.styl │ │ ├── _button-toggle.styl │ │ ├── _buttons.styl │ │ ├── _cards.styl │ │ ├── _carousel.styl │ │ ├── _chips.styl │ │ ├── _content.styl │ │ ├── _data-table.styl │ │ ├── _date-picker.styl │ │ ├── _dialogs.styl │ │ ├── _dividers.styl │ │ ├── _editor.styl │ │ ├── _expansion-panel.styl │ │ ├── _footer.styl │ │ ├── _grid.styl │ │ ├── _icons.styl │ │ ├── _input-groups.styl │ │ ├── _lists.styl │ │ ├── _menus.styl │ │ ├── _navigation-drawer.styl │ │ ├── _overlay.styl │ │ ├── _pagination.styl │ │ ├── _parallax.styl │ │ ├── _pickers.styl │ │ ├── _progress-circular.styl │ │ ├── _progress-linear.styl │ │ ├── _radio-group.styl │ │ ├── _ripples.styl │ │ ├── _select.styl │ │ ├── _selection-controls.styl │ │ ├── _sliders.styl │ │ ├── _small-dialog.styl │ │ ├── _snackbars.styl │ │ ├── _speed-dial.styl │ │ ├── _steppers.styl │ │ ├── _subheaders.styl │ │ ├── _switch.styl │ │ ├── _system-bars.styl │ │ ├── _tables.styl │ │ ├── _tabs.styl │ │ ├── _text-fields.styl │ │ ├── _time-picker.styl │ │ ├── _toolbar.styl │ │ └── _tooltips.styl │ ├── elements │ │ ├── _blockquote.styl │ │ ├── _code.styl │ │ ├── _global.styl │ │ ├── _headings.styl │ │ ├── _lists.styl │ │ └── _typography.styl │ ├── generic │ │ ├── _bootstrap.styl │ │ ├── _reset.styl │ │ └── _transitions.styl │ ├── main.styl │ ├── settings │ │ ├── _colors.styl │ │ ├── _elevations.styl │ │ ├── _theme.styl │ │ └── _variables.styl │ ├── theme.styl │ ├── tools │ │ ├── _animations.styl │ │ ├── _colors.styl │ │ └── _elevations.styl │ └── trumps │ │ ├── _display.styl │ │ ├── _helpers.styl │ │ ├── _spacing.styl │ │ └── _text.styl └── util │ ├── breakpoint.js │ ├── breakpoint.spec.js │ ├── helpers.d.ts │ ├── helpers.js │ ├── load.js │ ├── mask.js │ ├── mask.spec.js │ ├── testing.js │ ├── to-have-been-warned.js │ └── touchSupport.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "presets": [ 5 | ["es2015"], 6 | ["stage-2"] 7 | ], 8 | "plugins": [ 9 | "transform-runtime", 10 | ["module-resolver", { 11 | "root": ["./src"], 12 | "alias": { 13 | "~components": "components", 14 | "~directives": "directives", 15 | "~mixins": "mixins", 16 | "~stylus": "stylus", 17 | "~util": "util" 18 | } 19 | }] 20 | ] 21 | }, 22 | "es5": { 23 | "presets": [ 24 | ["es2015", {"modules": false}], 25 | ["stage-2"] 26 | ], 27 | "plugins": ["./build/babel-transform-stylus-paths.js"] 28 | }, 29 | "development": { 30 | "presets": [ 31 | ["es2015", {"modules": false}], 32 | ["stage-2"] 33 | ], 34 | "plugins": ["add-filehash"] 35 | }, 36 | "production": { 37 | "presets": [ 38 | ["es2015", {"modules": false}], 39 | ["stage-2"] 40 | ] 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | dev/* 3 | src/stylus/*.styl 4 | src/util/testing.js 5 | src/util/to-have-been-warned.js 6 | *.spec.js 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: 'babel-eslint', 4 | parserOptions: { 5 | sourceType: 'module' 6 | }, 7 | // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style 8 | extends: 'vue', 9 | // required to lint *.vue files 10 | plugins: [ 11 | 'vue', 12 | 'pug', 13 | 'html' 14 | ], 15 | env: { 16 | browser: true 17 | }, 18 | globals: { 19 | 'expect': true, 20 | 'describe': true, 21 | 'it': true, 22 | 'jest': true 23 | }, 24 | // add your custom rules here 25 | 'rules': { 26 | // allow paren-less arrow functions 27 | 'arrow-parens': 0, 28 | // allow async-await 29 | 'generator-star-spacing': 0, 30 | // set maximum line characters 31 | 'max-len': [2, 140, 4, {'ignoreUrls': true, 'ignoreTemplateLiterals': true, 'ignoreStrings': true}], 32 | // allow debugger during development 33 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 34 | 'no-return-assign': 0, 35 | // disallow indentation using both tabs and spaces 36 | 'no-mixed-spaces-and-tabs': 2, 37 | // ensure consistent 2 space indentation and indent cases under switch 38 | 'indent': [2, 2, {'SwitchCase': 1}], 39 | 'object-curly-spacing': [2, 'always'], 40 | 'max-statements': [2, 24] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.github/.git_commit_msg.txt: -------------------------------------------------------------------------------- 1 | # : (If applied, this commit will...) (Max 50 char) 2 | # |<---- Using a Maximum Of 50 Characters ---->| Hard limit to 72 -->| 3 | 4 | 5 | # Explain why this change is being made 6 | # |<---- Try To Limit Each Line to a Maximum Of 72 Characters ---->| 7 | 8 | # Provide links to any relevant issues, articles, commits, or other 9 | # pull requests 10 | # Example: See #23, fixes #58 11 | 12 | # --- COMMIT END --- 13 | # can be 14 | # feat (new feature) 15 | # fix (bug fix) 16 | # refactor (refactoring production code) 17 | # style (formatting, missing semi colons, etc; no code change) 18 | # test (adding or refactoring tests; no production code change) 19 | # chore (updating npm scripts etc; no production code change) 20 | # -------------------- 21 | # Remember to 22 | # Capitalize the subject line 23 | # Use the imperative mood in the subject line 24 | # Do not end the subject line with a period 25 | # Separate subject from body with a blank line (comments don't count) 26 | # Use the body to explain what and why vs. how 27 | # Can use multiple lines with "-" for bullet points in body 28 | # 29 | # If you can't summarize your changes in a single line, they should 30 | # probably be split into multiple commits 31 | # -------------------- 32 | # For more information about this template, check out 33 | # https://gist.github.com/adeekshith/cd4c95a064977cdc6c50 34 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Vuetify.js Contributing Guide 2 | 3 | Hello and thank you for interest in helping make Vuetify better. Please take a few moments to review the following guidelines. 4 | 5 | ## Reporting Issues 6 | * The issue list of this repo is exclusively for bug reports and feature requests. Non-conforming issues will be closed immediately. 7 | * For general questions, please join the Discord chat room. 8 | * Try to search for your issue, it may have been answered. 9 | * See if the error is reproduceable with the latest version. 10 | * If reproduceable, please provide a simple www.jsfiddle.com or repository that can be cloned to produce the expected behavior. 11 | 12 | ## Pull Requests 13 | * All PR's should be made to the ```dev``` branch of Vuetify. 14 | * For changes and feature requests, please include an example of what you are trying to solve and an example of the markup 15 | * For bug fixes please reference the issue # that the PR resolves 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | 24 | ### Steps to reproduce 25 | 26 | ### Versions 27 | 28 | 29 | ### What is expected ? 30 | 31 | 32 | ### What is actually happening ? 33 | 34 | 35 | ### Reproduction Link 36 | 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | .vscode/ 4 | npm-debug.log 5 | .idea/ 6 | coverage/ 7 | report.html 8 | .vs/ 9 | /es5 10 | /release 11 | /dev/main.css 12 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .github/ 2 | .vscode/ 3 | npm-debug.log 4 | .idea/ 5 | coverage/ 6 | report.html 7 | .vs/ 8 | dev/ 9 | build/ 10 | release/ 11 | **/*.spec.js 12 | /.babelrc 13 | /.editorconfig 14 | /.eslintignore 15 | /.eslintrc.js 16 | /.travis.yml 17 | /package-lock.json 18 | /postcss.config.js 19 | /yarn.lock 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | 5 | script: 6 | - yarn 7 | - yarn run lint 8 | - yarn run test 9 | - yarn run build 10 | 11 | cache: 12 | yarn: true 13 | 14 | before_deploy: 15 | - rm -rf release 16 | - mkdir -p release 17 | - for file in ./dist/*; do cp "$file" "${file/dist\/vuetify/release\/vuetify-${TRAVIS_TAG}}"; done 18 | 19 | deploy: 20 | - provider: npm 21 | email: "john.j.leider@gmail.com" 22 | api_key: $NPM_API_KEY 23 | tag: $(node ./build/parse-npm-tag.js) 24 | skip_cleanup: true 25 | on: 26 | repo: vuetifyjs/vuetify 27 | tags: true 28 | - provider: releases 29 | api_key: $GITHUB_API_KEY 30 | files: 31 | - "release/vuetify-${TRAVIS_TAG}.css" 32 | - "release/vuetify-${TRAVIS_TAG}.js" 33 | - "release/vuetify-${TRAVIS_TAG}.min.css" 34 | - "release/vuetify-${TRAVIS_TAG}.min.js" 35 | skip_cleanup: true 36 | on: 37 | tags: true 38 | -------------------------------------------------------------------------------- /BACKERS.md: -------------------------------------------------------------------------------- 1 | # Backers 2 | 3 | You can join them in supporting Vuetify.js by [pledging on Patreon](https://www.patreon.com/vuetify) 4 | 5 | ### $500+ 6 | - [LMAX Exchange](https://www.lmax.com/) 7 | 8 | ### $250+ 9 | - Jake Arnold 10 | 11 | ### $100+ 12 | - [Anthony Gherghetta](https://gorilladash.com/) 13 | 14 | ### $50+ 15 | - [Xavier Escoté](http://www.deister.net/) 16 | - [Steve Gehrman](https://cocoatech.com/) 17 | - [Cycloid](https://www.cycloid.io/) 18 | - [LiveCasino](https://livecasino.com/) 19 | - Brad Stewart 20 | 21 | ### $20+ 22 | - Tibor Nagy 23 | - [Jaime Olmo](https://www.jaimeolmo.com) 24 | - Maxim Bogdanov 25 | - Michael Marti 26 | - [Aeroden](www.aeroden.com) 27 | - Sam Mitchell 28 | - David Hess 29 | - Roger Vilaseca 30 | - Max Matteo Staack 31 | - Jay Blanchard 32 | - Christo Crampton 33 | 34 | ### $10+ 35 | 36 | - Kevin Pilard 37 | - Espen Bratberg 38 | - Ivan Vedernikov 39 | - Jérôme Pott 40 | - Sebastian Mares 41 | - Chava Sobreyra 42 | - Dima Shmakov 43 | - Costa Huang 44 | - Ryan Mortier 45 | - Roger Vilaseca 46 | - Paul 47 | - Asif Mehedi 48 | - Jussi Pesonen 49 | - Jordan Wood 50 | - Alexey Toroshchin 51 | - Phillippe Genois 52 | - Dominik Narożny 53 | - Jim O`Quinn 54 | - Sam Bosell 55 | - Cliff Hess 56 | - Antonio Luis Gil Rodríguez 57 | - Omar Spira 58 | - Jiří Žižka 59 | - Dejan Kovac 60 | - Anythony Estebe 61 | - Luis F Rocha 62 | - HelloWorld 63 | - Eeico Wentrup 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 John Jeremy Leider 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /build/babel-transform-stylus-paths.js: -------------------------------------------------------------------------------- 1 | var types = require('babel-types'); 2 | var pathLib = require('path'); 3 | var wrapListener = require('babel-plugin-detective/wrap-listener'); 4 | 5 | module.exports = wrapListener(listener, 'transform-stylus-paths'); 6 | 7 | function listener(path, file, opts) { 8 | const regex = /((?:\.\.\/)+)/gi 9 | if (path.isLiteral() && path.node.value.endsWith('.styl')) { 10 | const matches = regex.exec(path.node.value) 11 | if (!matches) return 12 | 13 | path.node.value = path.node.value.replace(matches[0], `${matches[0]}../src/`) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /build/parse-npm-tag.js: -------------------------------------------------------------------------------- 1 | const semver = require('semver') 2 | 3 | const prerelease = semver.prerelease(process.env.TRAVIS_TAG) 4 | const prereleaseTag = (prerelease || [])[0] || '' 5 | 6 | console.log(/^[a-zA-Z]+$/.test(prereleaseTag) ? prereleaseTag : 'latest') 7 | -------------------------------------------------------------------------------- /build/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | BRANCH=$(git symbolic-ref --short HEAD) 6 | LATEST=$(npm view vuetify version) 7 | echo # 8 | echo "Current branch is $BRANCH" 9 | echo "Last git version was $(git describe --abbrev=0 --tags)" 10 | echo "Latest npm version is $LATEST" 11 | echo # 12 | 13 | if [ "$BRANCH" != 'dev' ]; then 14 | echo "Releasing on a branch other than 'dev'" 15 | echo "This may have unintended side-effects" 16 | options=("Switch to dev" "Continue anyway") 17 | select opt in "${options[@]}"; do 18 | if [ "$opt" = "${options[0]}" ]; then 19 | echo # 20 | git checkout dev 21 | BRANCH=$(git symbolic-ref --short HEAD) 22 | break 23 | elif [ "$opt" = "${options[1]}" ]; then 24 | break 25 | fi 26 | done 27 | fi 28 | 29 | echo # 30 | 31 | read -e -p "Enter release version: " VERSION 32 | 33 | TAG=$(node -e "v=(require('semver').prerelease('$VERSION')||[])[0]||'';console.log(/^[a-zA-Z]+$/.test(v)?v:'latest')") 34 | 35 | echo # 36 | 37 | echo "Releasing $VERSION on $BRANCH" 38 | echo "Tag: $TAG" 39 | read -p "Are you sure? [Y/n]" -n 1 -r 40 | echo # 41 | [ ${REPLY,,} != "y" ] && exit 42 | 43 | echo "Releasing $VERSION ..." 44 | 45 | npm run lint 46 | npm run test -i 47 | 48 | npm_config_commit_hooks=false 49 | npm version $VERSION --message "[release] $VERSION" 50 | 51 | git push --no-verify --follow-tags 52 | 53 | if [ "$BRANCH" == 'dev' ] && [ "$TAG" == 'latest' ]; then 54 | echo "Fast-forwarding 'master'..." 55 | echo # 56 | git fetch . dev:master 57 | git push origin master --no-verify 58 | fi 59 | -------------------------------------------------------------------------------- /build/webpack.base.config.js: -------------------------------------------------------------------------------- 1 | const ProgressBarPlugin = require('progress-bar-webpack-plugin') 2 | const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin') 3 | 4 | const resolve = file => require('path').resolve(__dirname, file) 5 | 6 | module.exports = { 7 | resolve: { 8 | extensions: ['*', '.js', '.json', '.vue'], 9 | alias: { 10 | '~components': resolve('../src/components'), 11 | '~directives': resolve('../src/directives'), 12 | '~mixins': resolve('../src/mixins'), 13 | '~util': resolve('../src/util'), 14 | 'stylus': resolve('../src/stylus') 15 | } 16 | }, 17 | node: { 18 | fs: 'empty' 19 | }, 20 | plugins: [ 21 | new FriendlyErrorsWebpackPlugin({ 22 | clearConsole: true 23 | }) 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /build/webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | const merge = require('webpack-merge') 2 | const baseWebpackConfig = require('./webpack.base.config') 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | 5 | var extractPlugin = ExtractTextPlugin.extract({ 6 | use: ['css-loader', 'postcss-loader', 'stylus-loader'] 7 | }) 8 | 9 | // Helpers 10 | const resolve = file => require('path').resolve(__dirname, file) 11 | 12 | module.exports = merge(baseWebpackConfig, { 13 | devtool: '#source-map', 14 | entry: { 15 | app: './src/index.js' 16 | }, 17 | output: { 18 | path: resolve('../dist'), 19 | publicPath: '/dist/', 20 | library: 'Vuetify' 21 | }, 22 | module: { 23 | noParse: /es6-promise\.js$/, // avoid webpack shimming process 24 | rules: [ 25 | { 26 | test: /\.vue$/, 27 | use: [ 28 | { 29 | loader: 'vue-loader', 30 | options: { 31 | loaders: { 32 | stylus: extractPlugin 33 | } 34 | } 35 | }, 36 | 'eslint-loader' 37 | ], 38 | exclude: /node_modules/ 39 | }, 40 | { 41 | test: /\.js$/, 42 | loaders: ['babel-loader', 'eslint-loader'], 43 | exclude: /node_modules/ 44 | }, 45 | { 46 | test: /\.styl$/, 47 | use: extractPlugin, 48 | exclude: /node_modules/ 49 | } 50 | ] 51 | }, 52 | performance: { 53 | hints: false 54 | } 55 | }) 56 | -------------------------------------------------------------------------------- /dev/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 14 | -------------------------------------------------------------------------------- /dev/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Welcome to Vuetify 5 | 6 | 7 | 8 | 9 | 10 | 11 | 26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /dev/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App' 3 | require('./symbol') 4 | 5 | import Vuetify from 'vuetify' 6 | Vue.use(Vuetify) 7 | 8 | import VueRouter from 'vue-router' 9 | Vue.use(VueRouter) 10 | 11 | import router from './router' 12 | 13 | new Vue({ 14 | render: h => h(App), 15 | router 16 | }).$mount('#app') 17 | -------------------------------------------------------------------------------- /dev/prepare-commit-message.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | const [ 4 | messageFile, 5 | commitType, 6 | commitHash 7 | ] = process.env.GIT_PARAMS.split(' ') 8 | 9 | if (commitType == null) { 10 | const currentMessage = fs.readFileSync(messageFile) 11 | const newMessage = fs.readFileSync('.github/.git_commit_msg.txt') 12 | fs.writeFileSync(messageFile, newMessage) 13 | fs.appendFileSync(messageFile, currentMessage) 14 | } 15 | -------------------------------------------------------------------------------- /dev/router.js: -------------------------------------------------------------------------------- 1 | import VueRouter from 'vue-router' 2 | 3 | const component1 = { 4 | template: `
Page 1
` 5 | } 6 | const component2 = { 7 | template: `
Page 2
` 8 | } 9 | 10 | const router = new VueRouter({ 11 | routes: [ 12 | { 13 | path: '/page1', 14 | name: 'Page 1', 15 | component: component1 16 | }, 17 | { 18 | path: '/page2', 19 | name: 'Page 2', 20 | component: component2 21 | }, 22 | { path: '*', redirect: '/page1' } 23 | ] 24 | }) 25 | 26 | export default router 27 | -------------------------------------------------------------------------------- /dist/vuetify.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"vuetify.css","sourceRoot":""} -------------------------------------------------------------------------------- /dist/vuetify.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":[],"names":[],"mappings":"","file":"vuetify.min.css","sourceRoot":""} -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import './src/util/helpers' 2 | import { PluginFunction } from 'vue' 3 | 4 | declare class Vuetify { 5 | static install: PluginFunction 6 | } 7 | 8 | export = Vuetify 9 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const autoprefixer = require('autoprefixer') 2 | const mqpacker = require('css-mqpacker') 3 | const postcss = require('postcss') 4 | 5 | module.exports = (ctx) => ({ 6 | plugins: [ 7 | autoprefixer({ 8 | browsers: ['ie >= 11', 'safari >= 9', 'last 2 versions', '> 1%'] 9 | }), 10 | mqpacker() 11 | ] 12 | }) 13 | -------------------------------------------------------------------------------- /src/components/VAlert/VAlert.js: -------------------------------------------------------------------------------- 1 | require('../../stylus/components/_alerts.styl') 2 | 3 | import VIcon from '../VIcon' 4 | 5 | import Colorable from '../../mixins/colorable' 6 | import Toggleable from '../../mixins/toggleable' 7 | import Transitionable from '../../mixins/transitionable' 8 | 9 | export default { 10 | name: 'v-alert', 11 | 12 | components: { 13 | VIcon 14 | }, 15 | 16 | mixins: [Colorable, Toggleable, Transitionable], 17 | 18 | props: { 19 | dismissible: Boolean, 20 | icon: String 21 | }, 22 | 23 | data: () => ({ 24 | defaultColor: 'error' 25 | }), 26 | 27 | computed: { 28 | classes () { 29 | return this.addBackgroundColorClassChecks({ 30 | 'alert--dismissible': this.dismissible 31 | }) 32 | } 33 | }, 34 | 35 | render (h) { 36 | const children = [h('div', this.$slots.default)] 37 | 38 | if (this.icon) { 39 | children.unshift(h('v-icon', { 40 | 'class': 'alert__icon' 41 | }, this.icon)) 42 | } 43 | 44 | if (this.dismissible) { 45 | const close = h('a', { 46 | 'class': 'alert__dismissible', 47 | domProps: { href: 'javascript:;' }, 48 | on: { click: () => this.$emit('input', false) } 49 | }, [ 50 | h(VIcon, { 51 | props: { 52 | right: true 53 | } 54 | }, 'cancel') 55 | ]) 56 | 57 | children.push(close) 58 | } 59 | 60 | const alert = h('div', { 61 | staticClass: 'alert', 62 | 'class': this.classes, 63 | directives: [{ 64 | name: 'show', 65 | value: this.isActive 66 | }], 67 | on: this.$listeners 68 | }, children) 69 | 70 | if (!this.transition) return alert 71 | 72 | return h('transition', { 73 | props: { 74 | name: this.transition, 75 | origin: this.origin, 76 | mode: this.mode 77 | } 78 | }, [alert]) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/components/VAlert/VAlert.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from '~util/testing' 2 | import VAlert from '~components/VAlert' 3 | import VIcon from '~components/VIcon' 4 | 5 | test('VAlert.vue', ({ mount }) => { 6 | it('should be closed by default', async () => { 7 | const wrapper = mount(VAlert) 8 | 9 | expect(wrapper.vm.isActive).toBe(false) 10 | expect(wrapper.html()).toMatchSnapshot() 11 | }) 12 | 13 | it('should have a close icon', async () => { 14 | const wrapper = mount(VAlert, { 15 | propsData: { dismissible: true } 16 | }) 17 | 18 | expect(wrapper.html()).toMatchSnapshot() 19 | }) 20 | 21 | it('should be dismissible', async () => { 22 | const wrapper = mount(VAlert, { 23 | propsData: { 24 | value: true, 25 | dismissible: true 26 | } 27 | }) 28 | 29 | const icon = wrapper.find('.alert__dismissible')[0] 30 | 31 | const input = jest.fn(value => wrapper.setProps({ value })) 32 | wrapper.vm.$on('input', input) 33 | 34 | icon.trigger('click') 35 | expect(input).toBeCalledWith(false) 36 | expect(wrapper.html()).toMatchSnapshot() 37 | }) 38 | 39 | it('should have a custom icon', async () => { 40 | const wrapper = mount(VAlert, { 41 | propsData: { 42 | value: true, 43 | icon: 'list' 44 | } 45 | }) 46 | 47 | const icon = wrapper.find('.alert__icon')[0] 48 | 49 | expect(icon.text()).toBe('list') 50 | }) 51 | 52 | it('should have no icon', () => { 53 | const wrapper = mount(VAlert) 54 | 55 | expect(wrapper.contains('.icon')).toBe(false) 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /src/components/VAlert/__snapshots__/VAlert.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`VAlert.vue should be closed by default 1`] = ` 4 | 5 | 11 | 12 | `; 13 | 14 | exports[`VAlert.vue should be dismissible 1`] = ` 15 | 16 | 29 | 30 | `; 31 | 32 | exports[`VAlert.vue should have a close icon 1`] = ` 33 | 34 | 47 | 48 | `; 49 | -------------------------------------------------------------------------------- /src/components/VAlert/index.js: -------------------------------------------------------------------------------- 1 | import VAlert from './VAlert' 2 | 3 | VAlert.install = function install (Vue) { 4 | Vue.component(VAlert.name, VAlert) 5 | } 6 | 7 | export default VAlert 8 | -------------------------------------------------------------------------------- /src/components/VApp/VApp.js: -------------------------------------------------------------------------------- 1 | require('../../stylus/components/_app.styl') 2 | 3 | import Breakpoint from '../../util/breakpoint' 4 | import Themeable from '../../mixins/themeable' 5 | import TouchSupport from '../../util/touchSupport' 6 | 7 | import Resize from '../../directives/resize' 8 | 9 | export default { 10 | name: 'v-app', 11 | 12 | mixins: [Breakpoint, Themeable, TouchSupport], 13 | 14 | directives: { 15 | Resize 16 | }, 17 | 18 | data: () => ({ 19 | resizeTimeout: {} 20 | }), 21 | 22 | props: { 23 | id: { 24 | type: String, 25 | default: 'app' 26 | } 27 | }, 28 | 29 | mounted () { 30 | this.$vuetify.breakpoint = this.breakpoint 31 | window.addEventListener('load', this.runCallbacks) 32 | }, 33 | 34 | methods: { 35 | // Run all load callbacks created 36 | // from the load helper utility 37 | runCallbacks () { 38 | // For unit tests 39 | if (!document._loadCallbacks) return 40 | 41 | while (document._loadCallbacks.length) { 42 | document._loadCallbacks.pop()() 43 | } 44 | } 45 | }, 46 | 47 | render (h) { 48 | const data = { 49 | staticClass: 'application', 50 | 'class': { 51 | 'application--dark': this.dark, 52 | 'application--light': !this.dark 53 | }, 54 | attrs: { 'data-app': true }, 55 | domProps: { id: this.id }, 56 | directives: [{ 57 | name: 'resize', 58 | value: this.onResize 59 | }] 60 | } 61 | 62 | return h('div', data, this.$slots.default) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/components/VApp/VApp.spec.js: -------------------------------------------------------------------------------- 1 | import VApp from '~components/VApp' 2 | import { test } from '~util/testing' 3 | 4 | test('VApp.js', ({ mount }) => { 5 | it('should have an application class', () => { 6 | const wrapper = mount(VApp) 7 | 8 | expect(wrapper.hasClass('application')).toBe(true) 9 | expect(wrapper.html()).toMatchSnapshot() 10 | }) 11 | 12 | it('should have data-app attribute', () => { 13 | const wrapper = mount(VApp) 14 | 15 | expect(wrapper.getAttribute('data-app')).toBe('true') 16 | expect(wrapper.html()).toMatchSnapshot() 17 | }) 18 | 19 | it('should allow a custom id', () => { 20 | const wrapper = mount(VApp, { 21 | propsData: { 22 | id: 'inspire' 23 | } 24 | }) 25 | 26 | expect(wrapper.getAttribute('id')).toBe('inspire') 27 | expect(wrapper.html()).toMatchSnapshot() 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /src/components/VApp/__snapshots__/VApp.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`VApp.js should allow a custom id 1`] = ` 4 | 5 |
9 |
10 | 11 | `; 12 | 13 | exports[`VApp.js should have an application class 1`] = ` 14 | 15 |
19 |
20 | 21 | `; 22 | 23 | exports[`VApp.js should have data-app attribute 1`] = ` 24 | 25 |
29 |
30 | 31 | `; 32 | -------------------------------------------------------------------------------- /src/components/VApp/index.js: -------------------------------------------------------------------------------- 1 | import VApp from './VApp' 2 | 3 | VApp.install = function install (Vue) { 4 | Vue.component(VApp.name, VApp) 5 | } 6 | 7 | export default VApp 8 | -------------------------------------------------------------------------------- /src/components/VAvatar/VAvatar.js: -------------------------------------------------------------------------------- 1 | require('../../stylus/components/_avatars.styl') 2 | 3 | export default { 4 | name: 'v-avatar', 5 | 6 | functional: true, 7 | 8 | props: { 9 | size: { 10 | type: String, 11 | default: '48px' 12 | }, 13 | tile: Boolean 14 | }, 15 | 16 | render (h, { data, props, children }) { 17 | data.staticClass = (`avatar ${data.staticClass || ''}`).trim() 18 | data.style = data.style || {} 19 | 20 | if (props.tile) data.staticClass += ' avatar--tile' 21 | 22 | data.style.height = props.size 23 | data.style.width = props.size 24 | 25 | return h('div', data, children) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/components/VAvatar/VAvatar.spec.js: -------------------------------------------------------------------------------- 1 | import VAvatar from '~components/VAvatar' 2 | import { test } from '~util/testing' 3 | 4 | test('VAvatar.vue', ({ mount, functionalContext }) => { 5 | it('should have an avatar class', () => { 6 | const wrapper = mount(VAvatar, functionalContext()) 7 | 8 | expect(wrapper.hasClass('avatar')).toBe(true) 9 | expect(wrapper.html()).toMatchSnapshot() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /src/components/VAvatar/__snapshots__/VAvatar.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`VAvatar.vue should have an avatar class 1`] = ` 4 | 5 |
8 |
9 | 10 | `; 11 | -------------------------------------------------------------------------------- /src/components/VAvatar/index.js: -------------------------------------------------------------------------------- 1 | import VAvatar from './VAvatar' 2 | 3 | VAvatar.install = function install (Vue) { 4 | Vue.component(VAvatar.name, VAvatar) 5 | } 6 | 7 | export default VAvatar 8 | -------------------------------------------------------------------------------- /src/components/VBadge/VBadge.js: -------------------------------------------------------------------------------- 1 | require('../../stylus/components/_badges.styl') 2 | 3 | // Mixins 4 | import Colorable from '../../mixins/colorable' 5 | import Toggleable from '../../mixins/toggleable' 6 | 7 | export default { 8 | name: 'v-badge', 9 | 10 | mixins: [Colorable, Toggleable], 11 | 12 | props: { 13 | bottom: Boolean, 14 | color: { 15 | type: String, 16 | default: 'primary' 17 | }, 18 | left: Boolean, 19 | overlap: Boolean, 20 | transition: { 21 | type: String, 22 | default: 'fab-transition' 23 | }, 24 | value: { 25 | default: true 26 | } 27 | }, 28 | 29 | computed: { 30 | classes () { 31 | return { 32 | 'badge--bottom': this.bottom, 33 | 'badge--left': this.left, 34 | 'badge--overlap': this.overlap 35 | } 36 | } 37 | }, 38 | 39 | render (h) { 40 | const badge = this.$slots.badge ? [h('span', { 41 | staticClass: 'badge__badge', 42 | 'class': this.addBackgroundColorClassChecks(), 43 | attrs: this.attrs, 44 | directives: [{ 45 | name: 'show', 46 | value: this.isActive 47 | }] 48 | }, this.$slots.badge)] : null 49 | 50 | return h('span', { 51 | staticClass: 'badge', 52 | 'class': this.classes 53 | }, [ 54 | this.$slots.default, 55 | h('transition', { 56 | props: { 57 | name: this.transition 58 | } 59 | }, badge) 60 | ]) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/components/VBadge/index.js: -------------------------------------------------------------------------------- 1 | import VBadge from './VBadge' 2 | 3 | VBadge.install = function install (Vue) { 4 | Vue.component(VBadge.name, VBadge) 5 | } 6 | 7 | export default VBadge 8 | -------------------------------------------------------------------------------- /src/components/VBottomNav/VBottomNav.js: -------------------------------------------------------------------------------- 1 | require('../../stylus/components/_bottom-navs.styl') 2 | 3 | import ButtonGroup from '../../mixins/button-group' 4 | 5 | export default { 6 | name: 'v-bottom-nav', 7 | 8 | mixins: [ButtonGroup], 9 | 10 | props: { 11 | absolute: Boolean, 12 | active: [Number, String], 13 | shift: Boolean, 14 | value: { required: false } 15 | }, 16 | 17 | watch: { 18 | active () { 19 | this.update() 20 | } 21 | }, 22 | 23 | computed: { 24 | classes () { 25 | return { 26 | 'bottom-nav': true, 27 | 'bottom-nav--absolute': this.absolute, 28 | 'bottom-nav--shift': this.shift, 29 | 'bottom-nav--active': this.value 30 | } 31 | } 32 | }, 33 | 34 | methods: { 35 | isSelected (i) { 36 | const item = this.getValue(i) 37 | return this.active === item 38 | }, 39 | updateValue (i) { 40 | const item = this.getValue(i) 41 | this.$emit('update:active', item) 42 | } 43 | }, 44 | 45 | render (h) { 46 | return h('div', { 47 | class: this.classes 48 | }, this.$slots.default) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/components/VBottomNav/VBottomNav.spec.js: -------------------------------------------------------------------------------- 1 | import VBottomNav from './VBottomNav' 2 | import VBtn from '../VBtn' 3 | import { test } from '~util/testing' 4 | 5 | test('VBottomNav.js', ({ mount }) => { 6 | it('should have a bottom-nav class', () => { 7 | const wrapper = mount(VBottomNav, { 8 | slots: { 9 | default: [VBtn, VBtn] 10 | } 11 | }) 12 | 13 | expect(wrapper.hasClass('bottom-nav')).toBe(true) 14 | expect(wrapper.html()).toMatchSnapshot() 15 | }) 16 | 17 | it('should have prop classes', () => { 18 | const wrapper = mount(VBottomNav, { 19 | propsData: { 20 | absolute: true, 21 | shift: true 22 | }, 23 | slots: { 24 | default: [VBtn, VBtn] 25 | } 26 | }) 27 | 28 | expect(wrapper.hasClass('bottom-nav--absolute')).toBe(true) 29 | expect(wrapper.hasClass('bottom-nav--shift')).toBe(true) 30 | expect(wrapper.html()).toMatchSnapshot() 31 | }) 32 | 33 | it('should be hidden with a false value', () => { 34 | const wrapper = mount(VBottomNav, { 35 | propsData: { value: false }, 36 | slots: { 37 | default: [VBtn, VBtn] 38 | } 39 | }) 40 | 41 | expect(wrapper.hasClass('bottom-nav--active')).toBe(false) 42 | expect(wrapper.html()).toMatchSnapshot() 43 | }) 44 | 45 | it('should be visible with a true value', () => { 46 | const wrapper = mount(VBottomNav, { 47 | propsData: { value: true }, 48 | slots: { 49 | default: [VBtn, VBtn] 50 | } 51 | }) 52 | 53 | expect(wrapper.hasClass('bottom-nav--active')).toBe(true) 54 | expect(wrapper.html()).toMatchSnapshot() 55 | }) 56 | 57 | it('should output active btn when clicked', () => { 58 | const wrapper = mount(VBottomNav, { 59 | propsData: { value: true, active: 1 }, 60 | slots: { 61 | default: [VBtn, VBtn] 62 | } 63 | }) 64 | 65 | const btn = wrapper.find('.btn')[0] 66 | 67 | const change = jest.fn() 68 | wrapper.instance().$on('update:active', change) 69 | 70 | btn.trigger('click') 71 | expect(change).toBeCalledWith(0) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /src/components/VBottomNav/__snapshots__/VBottomNav.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`VBottomNav.js should be hidden with a false value 1`] = ` 4 | 5 |
6 | 13 | 20 |
21 | 22 | `; 23 | 24 | exports[`VBottomNav.js should be visible with a true value 1`] = ` 25 | 26 |
27 | 34 | 41 |
42 | 43 | `; 44 | 45 | exports[`VBottomNav.js should have a bottom-nav class 1`] = ` 46 | 47 |
48 | 55 | 62 |
63 | 64 | `; 65 | 66 | exports[`VBottomNav.js should have prop classes 1`] = ` 67 | 68 |
69 | 76 | 83 |
84 | 85 | `; 86 | -------------------------------------------------------------------------------- /src/components/VBottomNav/index.js: -------------------------------------------------------------------------------- 1 | import VBottomNav from './VBottomNav' 2 | 3 | VBottomNav.install = function install (Vue) { 4 | Vue.component(VBottomNav.name, VBottomNav) 5 | } 6 | 7 | export default VBottomNav 8 | -------------------------------------------------------------------------------- /src/components/VBottomSheet/VBottomSheet.js: -------------------------------------------------------------------------------- 1 | require('../../stylus/components/_bottom-sheets.styl') 2 | 3 | import VDialog from '../VDialog/VDialog' 4 | 5 | export default { 6 | name: 'v-bottom-sheet', 7 | 8 | components: { 9 | VDialog 10 | }, 11 | 12 | props: { 13 | inset: Boolean, 14 | value: null 15 | }, 16 | 17 | render (h) { 18 | const activator = h('template', { 19 | slot: 'activator' 20 | }, this.$slots.activator) 21 | 22 | const contentClass = [ 23 | 'bottom-sheet', 24 | this.inset ? 'bottom-sheet--inset' : '' 25 | ].join(' ') 26 | 27 | return h(VDialog, { 28 | attrs: { 29 | ...this.$attrs 30 | }, 31 | on: { 32 | ...this.$listeners 33 | }, 34 | props: { 35 | contentClass: contentClass, 36 | transition: 'bottom-sheet-transition', 37 | value: this.value 38 | } 39 | }, [activator, this.$slots.default]) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/VBottomSheet/index.js: -------------------------------------------------------------------------------- 1 | import VBottomSheet from './VBottomSheet' 2 | 3 | VBottomSheet.install = function install (Vue) { 4 | Vue.component(VBottomSheet.name, VBottomSheet) 5 | } 6 | 7 | export default VBottomSheet 8 | -------------------------------------------------------------------------------- /src/components/VBreadcrumbs/VBreadcrumbs.js: -------------------------------------------------------------------------------- 1 | require('../../stylus/components/_breadcrumbs.styl') 2 | 3 | export default { 4 | name: 'v-breadcrumbs', 5 | 6 | props: { 7 | divider: { 8 | type: String, 9 | default: '/' 10 | }, 11 | large: Boolean, 12 | justifyCenter: Boolean, 13 | justifyEnd: Boolean 14 | }, 15 | 16 | computed: { 17 | classes () { 18 | return { 19 | 'breadcrumbs--large': this.large 20 | } 21 | }, 22 | computedDivider () { 23 | return this.$slots.divider 24 | ? this.$slots.divider 25 | : this.divider 26 | }, 27 | styles () { 28 | const justify = this.justifyCenter 29 | ? 'center' 30 | : this.justifyEnd 31 | ? 'flex-end' 32 | : 'flex-start' 33 | 34 | return { 35 | 'justify-content': justify 36 | } 37 | } 38 | }, 39 | 40 | methods: { 41 | /** 42 | * Add dividers between 43 | * v-breadcrumbs-item 44 | * 45 | * @return {array} 46 | */ 47 | genChildren () { 48 | if (!this.$slots.default) return null 49 | 50 | const children = [] 51 | const dividerData = { staticClass: 'breadcrumbs__divider' } 52 | const length = this.$slots.default.length 53 | 54 | this.$slots.default.forEach((elm, i) => { 55 | children.push(elm) 56 | 57 | if (!elm.componentOptions || 58 | elm.componentOptions.tag !== 'v-breadcrumbs-item' || 59 | i === length - 1 60 | ) return 61 | 62 | children.push(this.$createElement('li', dividerData, this.computedDivider)) 63 | }) 64 | 65 | return children 66 | } 67 | }, 68 | 69 | render (h) { 70 | return h('ul', { 71 | staticClass: 'breadcrumbs', 72 | 'class': this.classes, 73 | style: this.styles 74 | }, this.genChildren()) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/components/VBreadcrumbs/VBreadcrumbs.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from '~util/testing' 2 | import { 3 | VBreadcrumbs, 4 | VBreadcrumbsItem 5 | } from '~components/VBreadcrumbs' 6 | 7 | test('VBreadcrumbs.js', ({ mount }) => { 8 | it('should have breadcrumbs classes', () => { 9 | const wrapper = mount(VBreadcrumbs) 10 | 11 | expect(wrapper.hasClass('breadcrumbs')).toBe(true) 12 | expect(wrapper.html()).toMatchSnapshot() 13 | }) 14 | 15 | // TODO: Return to this 16 | it.skip('should inject slot to children', () => { 17 | const wrapper = mount(VBreadcrumbs, { 18 | attachToDocument: true, 19 | slots: { 20 | default: [ 21 | VBreadcrumbsItem, 22 | VBreadcrumbsItem, 23 | VBreadcrumbsItem, 24 | VBreadcrumbsItem 25 | ] 26 | } 27 | }) 28 | 29 | const item = wrapper.find('.breadcrumbs__divider')[0] 30 | // console.log(wrapper.html()) 31 | // expect(item.html()).toBe('/') 32 | expect(wrapper.html()).toMatchSnapshot() 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /src/components/VBreadcrumbs/VBreadcrumbsItem.js: -------------------------------------------------------------------------------- 1 | import Routable from '../../mixins/routable' 2 | 3 | export default { 4 | name: 'v-breadcrumbs-item', 5 | 6 | mixins: [Routable], 7 | 8 | props: { 9 | // In a breadcrumb, the currently 10 | // active item should be dimmed 11 | activeClass: { 12 | type: String, 13 | default: 'breadcrumbs__item--disabled' 14 | } 15 | }, 16 | 17 | computed: { 18 | classes () { 19 | return { 20 | 'breadcrumbs__item': true, 21 | [this.activeClass]: this.disabled 22 | } 23 | } 24 | }, 25 | 26 | render (h) { 27 | const { tag, data } = this.generateRouteLink() 28 | 29 | return h('li', [ 30 | h(tag, data, this.$slots.default) 31 | ]) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/VBreadcrumbs/VBreadcrumbsItem.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from '~util/testing' 2 | import { VBreadcrumbsItem } from '~components/VBreadcrumbs' 3 | 4 | // TODO: Enable when Vue has optional injects 5 | test.skip('VBreadcrumbsItem.js', ({ mount }) => { 6 | it('should render component and match snapshot', () => { 7 | const wrapper = mount(VBreadcrumbsItem) 8 | 9 | expect(wrapper.html()).toMatchSnapshot() 10 | }) 11 | 12 | // TODO: Use vue-router or nuxt in tests 13 | it.skip('should have a custom active class', () => { 14 | const wrapper = mount(VBreadcrumbsItem, { 15 | propsData: { 16 | activeClass: 'breadcrumbs-item--active', 17 | to: '/' 18 | } 19 | }) 20 | 21 | expect(wrapper.html()).toMatchSnapshot() 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /src/components/VBreadcrumbs/__snapshots__/VBreadcrumbs.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`VBreadcrumbs.js should have breadcrumbs classes 1`] = ` 4 | 5 | 7 | 8 | `; 9 | 10 | exports[`VBreadcrumbs.js should inject slot to children 1`] = ` 11 | 12 | 39 | 40 | `; 41 | -------------------------------------------------------------------------------- /src/components/VBreadcrumbs/index.js: -------------------------------------------------------------------------------- 1 | import VBreadcrumbs from './VBreadcrumbs' 2 | import VBreadcrumbsItem from './VBreadcrumbsItem' 3 | 4 | export { VBreadcrumbs, VBreadcrumbsItem } 5 | 6 | VBreadcrumbs.install = function install (Vue) { 7 | Vue.component(VBreadcrumbs.name, VBreadcrumbs) 8 | Vue.component(VBreadcrumbsItem.name, VBreadcrumbsItem) 9 | } 10 | 11 | export default VBreadcrumbs 12 | -------------------------------------------------------------------------------- /src/components/VBtn/VBtn.spec.js: -------------------------------------------------------------------------------- 1 | import { mount } from 'avoriaz' 2 | import Vue from 'vue' 3 | import VBtn from '~components/VBtn' 4 | 5 | const stub = { 6 | name: 'router-link', 7 | render: h => h('button') 8 | } 9 | 10 | describe('VBtn.js', () => { 11 | it('should render component and match snapshot', () => { 12 | const wrapper = mount(VBtn) 13 | 14 | expect(wrapper.html()).toMatchSnapshot() 15 | }) 16 | 17 | it('should render an tag when using href prop', () => { 18 | const wrapper = mount(VBtn, { 19 | propsData: { 20 | href: 'http://www.google.com' 21 | } 22 | }) 23 | 24 | expect(wrapper.is('a')).toBe(true) 25 | expect(wrapper.getAttribute('href')).toBe('http://www.google.com') 26 | expect(wrapper.html()).toMatchSnapshot() 27 | }) 28 | 29 | it('should render a 9 | 10 | `; 11 | 12 | exports[`VBtn.js should render an tag when using href prop 1`] = ` 13 | 14 | 18 |
19 |
20 |
21 | 22 | `; 23 | 24 | exports[`VBtn.js should render component and match snapshot 1`] = ` 25 | 26 | 33 | 34 | `; 35 | 36 | exports[`VBtn.js should render specified tag when using tag prop 1`] = ` 37 | 38 | 42 |
43 |
44 |
45 | 46 | `; 47 | -------------------------------------------------------------------------------- /src/components/VBtn/index.js: -------------------------------------------------------------------------------- 1 | import VBtn from './VBtn' 2 | 3 | VBtn.install = function install (Vue) { 4 | Vue.component(VBtn.name, VBtn) 5 | } 6 | 7 | export default VBtn 8 | -------------------------------------------------------------------------------- /src/components/VBtnToggle/index.js: -------------------------------------------------------------------------------- 1 | import VBtnToggle from './VBtnToggle' 2 | 3 | VBtnToggle.install = function install (Vue) { 4 | Vue.component(VBtnToggle.name, VBtnToggle) 5 | } 6 | 7 | export default VBtnToggle 8 | -------------------------------------------------------------------------------- /src/components/VCard/VCard.js: -------------------------------------------------------------------------------- 1 | require('../../stylus/components/_cards.styl') 2 | 3 | import Colorable from '../../mixins/colorable' 4 | import Routable from '../../mixins/routable' 5 | import Themeable from '../../mixins/themeable' 6 | 7 | export default { 8 | name: 'v-card', 9 | 10 | mixins: [Colorable, Routable, Themeable], 11 | 12 | props: { 13 | flat: Boolean, 14 | height: { 15 | type: String, 16 | default: 'auto' 17 | }, 18 | hover: Boolean, 19 | img: String, 20 | raised: Boolean, 21 | tag: { 22 | type: String, 23 | default: 'div' 24 | }, 25 | tile: Boolean 26 | }, 27 | 28 | computed: { 29 | classes () { 30 | return this.addBackgroundColorClassChecks({ 31 | 'card': true, 32 | 'card--flat': this.flat, 33 | 'card--horizontal': this.horizontal, 34 | 'card--hover': this.hover, 35 | 'card--raised': this.raised, 36 | 'card--tile': this.tile, 37 | 'theme--light': this.light, 38 | 'theme--dark': this.dark 39 | }) 40 | }, 41 | styles () { 42 | const style = { 43 | height: isNaN(this.height) ? this.height : `${this.height}px` 44 | } 45 | 46 | if (this.img) { 47 | style.background = `url(${this.img}) center center / cover no-repeat` 48 | } 49 | 50 | return style 51 | } 52 | }, 53 | 54 | render (h) { 55 | const { tag, data } = this.generateRouteLink() 56 | 57 | data.style = this.styles 58 | 59 | return h(tag, data, this.$slots.default) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/components/VCard/VCard.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from '~util/testing' 2 | import { mount } from 'avoriaz' 3 | import VCard from '~components/VCard' 4 | 5 | test('VCard.vue', () => { 6 | it('should render component and match snapshot', () => { 7 | const wrapper = mount(VCard) 8 | 9 | expect(wrapper.html()).toMatchSnapshot() 10 | }) 11 | 12 | it('should render a flat card', () => { 13 | const wrapper = mount(VCard, { 14 | propsData: { 15 | flat: true 16 | } 17 | }) 18 | 19 | expect(wrapper.html()).toMatchSnapshot() 20 | }) 21 | 22 | it('should render a raised card', () => { 23 | const wrapper = mount(VCard, { 24 | propsData: { 25 | raised: true 26 | } 27 | }) 28 | 29 | expect(wrapper.html()).toMatchSnapshot() 30 | }) 31 | 32 | it('should render a colored card', () => { 33 | const wrapper = mount(VCard, { 34 | propsData: { 35 | color: 'blue lighten-1' 36 | } 37 | }) 38 | 39 | expect(wrapper.element.classList).toContain('blue') 40 | expect(wrapper.element.classList).toContain('lighten-1') 41 | }) 42 | 43 | it('should render a tile card', () => { 44 | const wrapper = mount(VCard, { 45 | propsData: { 46 | tile: true 47 | } 48 | }) 49 | 50 | expect(wrapper.html()).toMatchSnapshot() 51 | }) 52 | 53 | it('should render a card with custom height', () => { 54 | const heightpx = '400px' 55 | const wrapper = mount(VCard, { 56 | propsData: { 57 | height: heightpx 58 | } 59 | }) 60 | 61 | expect(wrapper.hasStyle('height', heightpx)).toBe(true) 62 | expect(wrapper.html()).toMatchSnapshot() 63 | }) 64 | 65 | it('should render a tile card', () => { 66 | const wrapper = mount(VCard, { 67 | propsData: { 68 | tile: true 69 | } 70 | }) 71 | 72 | expect(wrapper.hasClass('card--tile')).toBe(true) 73 | expect(wrapper.html()).toMatchSnapshot() 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /src/components/VCard/VCardMedia.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'v-card-media', 3 | 4 | props: { 5 | contain: Boolean, 6 | height: { 7 | type: [Number, String], 8 | default: 'auto' 9 | }, 10 | src: { 11 | type: String 12 | } 13 | }, 14 | 15 | render (h) { 16 | const data = { 17 | 'class': 'card__media', 18 | style: { 19 | height: !isNaN(this.height) ? `${this.height}px` : this.height 20 | }, 21 | on: this.$listeners 22 | } 23 | 24 | const children = [] 25 | 26 | if (this.src) { 27 | children.push(h('div', { 28 | 'class': 'card__media__background', 29 | style: { 30 | background: `url(${this.src}) center center / ${this.contain ? 'contain' : 'cover'} no-repeat` 31 | } 32 | })) 33 | } 34 | 35 | children.push(h('div', { 36 | 'class': 'card__media__content' 37 | }, this.$slots.default)) 38 | 39 | return h('div', data, children) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/VCard/VCardMedia.spec.js: -------------------------------------------------------------------------------- 1 | import { mount } from 'avoriaz' 2 | import { VCardMedia } from '~components/VCard' 3 | 4 | describe('VCardMedia.js', () => { 5 | it('should render component and match snapshot', () => { 6 | const wrapper = mount(VCardMedia) 7 | 8 | expect(wrapper.html()).toMatchSnapshot() 9 | }) 10 | 11 | it('should render component with contained background and match snapshot', () => { 12 | const wrapper = mount(VCardMedia, { 13 | propsData: { 14 | contain: true 15 | } 16 | }) 17 | 18 | expect(wrapper.html()).toMatchSnapshot() 19 | }) 20 | 21 | it('should render component with custom height (string) and match snapshot', () => { 22 | const wrapper = mount(VCardMedia, { 23 | propsData: { 24 | height: '100px' 25 | } 26 | }) 27 | 28 | expect(wrapper.html()).toMatchSnapshot() 29 | }) 30 | 31 | it('should render component with custom height (number) and match snapshot', () => { 32 | const wrapper = mount(VCardMedia, { 33 | propsData: { 34 | height: 100 35 | } 36 | }) 37 | 38 | expect(wrapper.html()).toMatchSnapshot() 39 | }) 40 | 41 | it('should render matching components with custom height', () => { 42 | const wrapper1 = mount(VCardMedia, { 43 | propsData: { 44 | height: '100px' 45 | } 46 | }) 47 | const wrapper2 = mount(VCardMedia, { 48 | propsData: { 49 | height: 100 50 | } 51 | }) 52 | 53 | expect(wrapper1.html()).toEqual(wrapper2.html()) 54 | }) 55 | 56 | it('should render component with custom background image and match snapshot', () => { 57 | const wrapper = mount(VCardMedia, { 58 | propsData: { 59 | src: 'https://vuetifyjs.com/static/doc-images/cards/sunshine.jpg' 60 | } 61 | }) 62 | 63 | expect(wrapper.html()).toMatchSnapshot() 64 | }) 65 | }) 66 | -------------------------------------------------------------------------------- /src/components/VCard/VCardTitle.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'v-card-title', 3 | 4 | functional: true, 5 | 6 | props: { 7 | primaryTitle: Boolean 8 | }, 9 | 10 | render (h, { data, props, children }) { 11 | data.staticClass = (`card__title ${data.staticClass || ''}`).trim() 12 | 13 | if (props.primaryTitle) data.staticClass += ' card__title--primary' 14 | 15 | return h('div', data, children) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/VCard/VCardTitle.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from '~util/testing' 2 | import { VCardTitle } from '~components/VCard' 3 | 4 | test('VCardTitle.js', ({ mount, functionalContext }) => { 5 | it('should render component and match snapshot', () => { 6 | const wrapper = mount(VCardTitle, functionalContext()) 7 | 8 | expect(wrapper.html()).toMatchSnapshot() 9 | }) 10 | 11 | it('should render component with specific padding applied', () => { 12 | const wrapper = mount(VCardTitle, functionalContext({ 13 | props: { 14 | 'primary-title': true 15 | } 16 | })) 17 | 18 | expect(wrapper.html()).toMatchSnapshot() 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /src/components/VCard/__snapshots__/VCard.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`VCard.vue should render a card with custom height 1`] = ` 4 | 5 |
9 |
10 | 11 | `; 12 | 13 | exports[`VCard.vue should render a flat card 1`] = ` 14 | 15 |
19 |
20 | 21 | `; 22 | 23 | exports[`VCard.vue should render a raised card 1`] = ` 24 | 25 |
29 |
30 | 31 | `; 32 | 33 | exports[`VCard.vue should render a tile card 1`] = ` 34 | 35 |
39 |
40 | 41 | `; 42 | 43 | exports[`VCard.vue should render a tile card 2`] = ` 44 | 45 |
49 |
50 | 51 | `; 52 | 53 | exports[`VCard.vue should render component and match snapshot 1`] = ` 54 | 55 |
59 |
60 | 61 | `; 62 | -------------------------------------------------------------------------------- /src/components/VCard/__snapshots__/VCardMedia.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`VCardMedia.js should render component and match snapshot 1`] = ` 4 | 5 |
8 |
9 |
10 |
11 | 12 | `; 13 | 14 | exports[`VCardMedia.js should render component with contained background and match snapshot 1`] = ` 15 | 16 |
19 |
20 |
21 |
22 | 23 | `; 24 | 25 | exports[`VCardMedia.js should render component with custom background image and match snapshot 1`] = ` 26 | 27 |
30 |
31 |
32 |
33 |
34 |
35 | 36 | `; 37 | 38 | exports[`VCardMedia.js should render component with custom height (number) and match snapshot 1`] = ` 39 | 40 |
43 |
44 |
45 |
46 | 47 | `; 48 | 49 | exports[`VCardMedia.js should render component with custom height (string) and match snapshot 1`] = ` 50 | 51 |
54 |
55 |
56 |
57 | 58 | `; 59 | -------------------------------------------------------------------------------- /src/components/VCard/__snapshots__/VCardTitle.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`VCardTitle.js should render component and match snapshot 1`] = ` 4 | 5 |
6 |
7 | 8 | `; 9 | 10 | exports[`VCardTitle.js should render component with specific padding applied 1`] = ` 11 | 12 |
13 |
14 | 15 | `; 16 | -------------------------------------------------------------------------------- /src/components/VCard/index.js: -------------------------------------------------------------------------------- 1 | import { createSimpleFunctional } from '../../util/helpers' 2 | import VCard from './VCard' 3 | import VCardMedia from './VCardMedia' 4 | import VCardTitle from './VCardTitle' 5 | 6 | export { VCard, VCardMedia, VCardTitle } 7 | 8 | VCard.install = function install (Vue) { 9 | const VCardActions = createSimpleFunctional('card__actions') 10 | const VCardText = createSimpleFunctional('card__text') 11 | 12 | Vue.component(VCard.name, VCard) 13 | Vue.component(VCardMedia.name, VCardMedia) 14 | Vue.component(VCardTitle.name, VCardTitle) 15 | Vue.component('v-card-actions', VCardActions) 16 | Vue.component('v-card-text', VCardText) 17 | } 18 | 19 | export default VCard 20 | -------------------------------------------------------------------------------- /src/components/VCarousel/VCarouselItem.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'v-carousel-item', 3 | 4 | data () { 5 | return { 6 | active: false, 7 | reverse: false 8 | } 9 | }, 10 | 11 | props: { 12 | src: { 13 | type: String, 14 | required: true 15 | }, 16 | 17 | transition: { 18 | type: String, 19 | default: 'tab-transition' 20 | }, 21 | 22 | reverseTransition: { 23 | type: String, 24 | default: 'tab-reverse-transition' 25 | } 26 | }, 27 | 28 | computed: { 29 | computedTransition () { 30 | return this.reverse ? this.reverseTransition : this.transition 31 | }, 32 | 33 | styles () { 34 | return { 35 | backgroundImage: `url(${this.src})` 36 | } 37 | } 38 | }, 39 | 40 | methods: { 41 | open (id, reverse) { 42 | this.active = this._uid === id 43 | this.reverse = reverse 44 | } 45 | }, 46 | 47 | render (h) { 48 | const item = h('div', { 49 | class: { 50 | 'carousel__item': true, 51 | 'reverse': this.reverse 52 | }, 53 | style: this.styles, 54 | on: this.$listeners, 55 | directives: [ 56 | { 57 | name: 'show', 58 | value: this.active 59 | } 60 | ] 61 | }, [this.$slots.default]) 62 | 63 | return h('transition', { props: { name: this.computedTransition } }, [item]) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/components/VCarousel/VCarouselItem.spec.js: -------------------------------------------------------------------------------- 1 | import { mount } from 'avoriaz' 2 | import { VCarouselItem } from '~components/VCarousel' 3 | 4 | const imageSrc = 'https://vuetifyjs.com/static/doc-images/cards/sunshine.jpg' 5 | 6 | describe('VCarouselItem.js', () => { 7 | it('should render component and match snapshot', () => { 8 | const wrapper = mount(VCarouselItem, { 9 | propsData: { 10 | src: imageSrc 11 | } 12 | }) 13 | 14 | expect(wrapper.html()).toMatchSnapshot() 15 | }) 16 | 17 | it('should render component with custom transition and match snapshot', () => { 18 | const wrapper = mount(VCarouselItem, { 19 | propsData: { 20 | src: imageSrc, 21 | transition: 'slide-y-transition' 22 | } 23 | }) 24 | 25 | expect(wrapper.html()).toMatchSnapshot() 26 | }) 27 | 28 | it('should render component with custom reverse transition and match snapshot', () => { 29 | const wrapper = mount(VCarouselItem, { 30 | propsData: { 31 | src: imageSrc, 32 | 'reverse-ransition': 'slide-y-reverse-transition' 33 | } 34 | }) 35 | 36 | expect(wrapper.html()).toMatchSnapshot() 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /src/components/VCarousel/__snapshots__/VCarouselItem.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`VCarouselItem.js should render component and match snapshot 1`] = ` 4 | 5 | 9 | 10 | `; 11 | 12 | exports[`VCarouselItem.js should render component with custom reverse transition and match snapshot 1`] = ` 13 | 14 | 18 | 19 | `; 20 | 21 | exports[`VCarouselItem.js should render component with custom transition and match snapshot 1`] = ` 22 | 23 | 27 | 28 | `; 29 | -------------------------------------------------------------------------------- /src/components/VCarousel/index.js: -------------------------------------------------------------------------------- 1 | import VCarousel from './VCarousel' 2 | import VCarouselItem from './VCarouselItem' 3 | 4 | export { VCarousel, VCarouselItem } 5 | 6 | VCarousel.install = function install (Vue) { 7 | Vue.component(VCarousel.name, VCarousel) 8 | Vue.component(VCarouselItem.name, VCarouselItem) 9 | } 10 | 11 | export default VCarousel 12 | -------------------------------------------------------------------------------- /src/components/VCheckbox/index.js: -------------------------------------------------------------------------------- 1 | import VCheckbox from './VCheckbox' 2 | 3 | VCheckbox.install = function install (Vue) { 4 | Vue.component(VCheckbox.name, VCheckbox) 5 | } 6 | 7 | export default VCheckbox 8 | -------------------------------------------------------------------------------- /src/components/VChip/VChip.js: -------------------------------------------------------------------------------- 1 | require('../../stylus/components/_chips.styl') 2 | 3 | import VIcon from '../VIcon' 4 | import Colorable from '../../mixins/colorable' 5 | import Themeable from '../../mixins/themeable' 6 | import Toggleable from '../../mixins/toggleable' 7 | 8 | export default { 9 | name: 'v-chip', 10 | 11 | components: { 12 | VIcon 13 | }, 14 | 15 | mixins: [Colorable, Themeable, Toggleable], 16 | 17 | props: { 18 | close: Boolean, 19 | disabled: Boolean, 20 | label: Boolean, 21 | outline: Boolean, 22 | // Used for selects/tagging 23 | selected: Boolean, 24 | small: Boolean, 25 | textColor: String, 26 | value: { 27 | type: Boolean, 28 | default: true 29 | } 30 | }, 31 | 32 | computed: { 33 | classes () { 34 | const classes = this.addBackgroundColorClassChecks({ 35 | 'chip': true, 36 | 'chip--disabled': this.disabled, 37 | 'chip--selected': this.selected, 38 | 'chip--label': this.label, 39 | 'chip--outline': this.outline, 40 | 'chip--small': this.small, 41 | 'chip--removable': this.close, 42 | 'theme--light': this.light, 43 | 'theme--dark': this.dark 44 | }) 45 | 46 | return (this.textColor || this.outline) 47 | ? this.addTextColorClassChecks(classes, this.textColor ? 'textColor' : 'color') 48 | : classes 49 | } 50 | }, 51 | 52 | render (h) { 53 | const children = [this.$slots.default] 54 | const data = { 55 | 'class': this.classes, 56 | attrs: { tabindex: this.disabled ? -1 : 0 }, 57 | directives: [{ 58 | name: 'show', 59 | value: this.isActive 60 | }], 61 | on: this.$listeners 62 | } 63 | 64 | if (this.close) { 65 | const data = { 66 | staticClass: 'chip__close', 67 | on: { 68 | click: e => { 69 | e.stopPropagation() 70 | 71 | this.$emit('input', false) 72 | } 73 | } 74 | } 75 | 76 | children.push(h('div', data, [ 77 | h(VIcon, { props: { right: true } }, 'cancel') 78 | ])) 79 | } 80 | 81 | return h('span', data, children) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/components/VChip/VChip.spec.js: -------------------------------------------------------------------------------- 1 | import VChip from '~components/VChip' 2 | import { mount } from 'avoriaz' 3 | import { test } from '~util/testing' 4 | 5 | test('VChip.vue', () => { 6 | it('should have a chip class', () => { 7 | const wrapper = mount(VChip) 8 | 9 | expect(wrapper.hasClass('chip')).toBe(true) 10 | expect(wrapper.html()).toMatchSnapshot() 11 | }) 12 | 13 | it('should be removable', () => { 14 | const wrapper = mount(VChip, { 15 | propsData: { close: true } 16 | }) 17 | 18 | const close = wrapper.find('.chip__close')[0] 19 | 20 | const input = jest.fn(value => wrapper.setProps({ value })) 21 | wrapper.vm.$on('input', input) 22 | 23 | expect(wrapper.html()).toMatchSnapshot() 24 | 25 | close.trigger('click') 26 | expect(input).toBeCalledWith(false) 27 | expect(wrapper.html()).toMatchSnapshot() 28 | }) 29 | 30 | it('should render a colored chip', () => { 31 | const wrapper = mount(VChip, { 32 | propsData: { 33 | color: 'blue', 34 | textColor: 'green' 35 | } 36 | }) 37 | 38 | expect(wrapper.element.classList).toContain('blue') 39 | expect(wrapper.element.classList).toContain('green--text') 40 | }) 41 | 42 | it('should render a colored outline chip', () => { 43 | const wrapper = mount(VChip, { 44 | propsData: { 45 | outline: true, 46 | color: 'blue' 47 | } 48 | }) 49 | 50 | expect(wrapper.element.classList).toContain('blue') 51 | expect(wrapper.element.classList).toContain('blue--text') 52 | }) 53 | 54 | it('should render a colored outline chip with text color', () => { 55 | const wrapper = mount(VChip, { 56 | propsData: { 57 | outline: true, 58 | color: 'blue', 59 | textColor: 'green' 60 | } 61 | }) 62 | 63 | expect(wrapper.element.classList).toContain('blue') 64 | expect(wrapper.element.classList).toContain('green--text') 65 | }) 66 | }) 67 | 68 | -------------------------------------------------------------------------------- /src/components/VChip/__snapshots__/VChip.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`VChip.vue should be removable 1`] = ` 4 | 5 | 8 |
9 | 10 | cancel 11 | 12 |
13 |
14 | 15 | `; 16 | 17 | exports[`VChip.vue should be removable 2`] = ` 18 | 19 | 29 | 30 | `; 31 | 32 | exports[`VChip.vue should have a chip class 1`] = ` 33 | 34 | 37 | 38 | 39 | `; 40 | -------------------------------------------------------------------------------- /src/components/VChip/index.js: -------------------------------------------------------------------------------- 1 | import VChip from './VChip' 2 | 3 | VChip.install = function install (Vue) { 4 | Vue.component(VChip.name, VChip) 5 | } 6 | 7 | export default VChip 8 | -------------------------------------------------------------------------------- /src/components/VDataTable/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | createSimpleFunctional 3 | } from '../../util/helpers' 4 | 5 | import VDataTable from './VDataTable' 6 | import VEditDialog from './VEditDialog' 7 | 8 | VDataTable.install = function install (Vue) { 9 | const VTableOverflow = createSimpleFunctional('table__overflow') 10 | 11 | Vue.component(VDataTable.name, VDataTable) 12 | Vue.component(VEditDialog.name, VEditDialog) 13 | Vue.component('v-table-overflow', VTableOverflow) 14 | } 15 | 16 | export default VDataTable 17 | -------------------------------------------------------------------------------- /src/components/VDataTable/mixins/progress.js: -------------------------------------------------------------------------------- 1 | export default { 2 | methods: { 3 | genTProgress () { 4 | const col = this.$createElement('th', { 5 | staticClass: 'column', 6 | attrs: { 7 | colspan: '100%' 8 | } 9 | }, [this.genProgress()]) 10 | 11 | return this.genTR([col], { 12 | staticClass: 'datatable__progress' 13 | }) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/VDatePicker/index.js: -------------------------------------------------------------------------------- 1 | import VDatePicker from './VDatePicker' 2 | 3 | VDatePicker.install = function install (Vue) { 4 | Vue.component(VDatePicker.name, VDatePicker) 5 | } 6 | 7 | export default VDatePicker 8 | -------------------------------------------------------------------------------- /src/components/VDatePicker/mixins/date-title.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | methods: { 4 | genYearIcon () { 5 | return this.yearIcon 6 | ? this.$createElement('v-icon', { 7 | props: { 8 | dark: true 9 | } 10 | }, this.yearIcon) 11 | : null 12 | }, 13 | 14 | getYearBtn () { 15 | return this.$createElement('div', { 16 | 'class': { 17 | 'picker--date__title-year': true, 18 | 'active': this.activePicker === 'YEAR' 19 | }, 20 | on: { 21 | click: e => { 22 | e.stopPropagation() 23 | this.activePicker = 'YEAR' 24 | } 25 | } 26 | }, [ 27 | this.yearFormat(`${this.year}`, this.locale), 28 | this.genYearIcon() 29 | ]) 30 | }, 31 | 32 | genTitleText (title) { 33 | return this.$createElement('transition', { 34 | props: { 35 | name: 'slide-y-reverse-transition', 36 | mode: 'out-in' 37 | } 38 | }, [ 39 | this.$createElement('div', { 40 | domProps: { innerHTML: title }, 41 | key: title 42 | }) 43 | ]) 44 | }, 45 | 46 | genTitleDate (title) { 47 | return this.$createElement('div', { 48 | staticClass: 'picker--date__title-date', 49 | 'class': { 50 | 'active': this.activePicker === this.type.toUpperCase() 51 | }, 52 | on: { 53 | click: e => { 54 | e.stopPropagation() 55 | this.activePicker = this.type.toUpperCase() 56 | } 57 | } 58 | }, [this.genTitleText(title)]) 59 | }, 60 | 61 | genTitle (title) { 62 | return this.genPickerTitle([ 63 | this.getYearBtn(), 64 | this.genTitleDate(title) 65 | ]) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/components/VDatePicker/mixins/date-years.js: -------------------------------------------------------------------------------- 1 | export default { 2 | methods: { 3 | genYears () { 4 | return this.$createElement('ul', { 5 | staticClass: 'picker--date__years', 6 | key: 'year', 7 | ref: 'years' 8 | }, this.genYearItems()) 9 | }, 10 | yearClick (year) { 11 | if (this.type === 'year') { 12 | this.inputDate = `${year}` 13 | this.$nextTick(() => (this.autosave && this.save())) 14 | } else if (this.type === 'month') { 15 | const date = this.sanitizeDateString(`${year}-${this.month + 1}`, 'month') 16 | if (this.isAllowed(date)) this.inputDate = date 17 | this.tableDate = `${year}` 18 | this.activePicker = 'MONTH' 19 | } else { 20 | const date = this.sanitizeDateString(`${year}-${this.tableMonth + 1}-${this.day}`, 'date') 21 | if (this.isAllowed(date)) this.inputDate = date 22 | this.tableDate = `${year}-${this.tableMonth + 1}` 23 | this.inputDate = date 24 | this.activePicker = 'MONTH' 25 | } 26 | }, 27 | genYearItems () { 28 | const children = [] 29 | for (let year = this.year + 100, length = this.year - 100; year > length; year--) { 30 | const buttonText = this.yearFormat(`${year}`, this.locale) 31 | 32 | children.push(this.$createElement('li', { 33 | 'class': this.year === year 34 | ? this.addTextColorClassChecks({ active: true }) 35 | : {}, 36 | on: { 37 | click: () => this.yearClick(year) 38 | } 39 | }, buttonText)) 40 | } 41 | return children 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/VDialog/index.js: -------------------------------------------------------------------------------- 1 | import VDialog from './VDialog' 2 | 3 | VDialog.install = function install (Vue) { 4 | Vue.component(VDialog.name, VDialog) 5 | } 6 | 7 | export default VDialog 8 | -------------------------------------------------------------------------------- /src/components/VDivider/VDivider.js: -------------------------------------------------------------------------------- 1 | require('../../stylus/components/_dividers.styl') 2 | 3 | import Themeable from '../../mixins/themeable' 4 | 5 | export default { 6 | name: 'v-divider', 7 | 8 | functional: true, 9 | 10 | mixins: [Themeable], 11 | 12 | props: { 13 | inset: Boolean 14 | }, 15 | 16 | render (h, { props, data, children }) { 17 | data.staticClass = (`divider ${data.staticClass || ''}`).trim() 18 | 19 | if (props.inset) data.staticClass += ' divider--inset' 20 | if (props.light) data.staticClass += ' theme--light' 21 | if (props.dark) data.staticClass += ' theme--dark' 22 | 23 | return h('hr', data) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/VDivider/VDivider.spec.js: -------------------------------------------------------------------------------- 1 | import { mount } from 'avoriaz' 2 | import VDivider from '~components/VDivider' 3 | import { test, functionalContext } from '~util/testing' 4 | 5 | test('VDivider.js', () => { 6 | it('should render component and match snapshot', () => { 7 | const wrapper = mount(VDivider, functionalContext()) 8 | 9 | expect(wrapper.html()).toMatchSnapshot() 10 | }) 11 | 12 | it('should render an inset component and match snapshot', () => { 13 | const wrapper = mount(VDivider, functionalContext({ 14 | propsData: { 15 | inset: true 16 | } 17 | })) 18 | 19 | expect(wrapper.html()).toMatchSnapshot() 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /src/components/VDivider/__snapshots__/VDivider.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`VDivider.js should render an inset component and match snapshot 1`] = ` 4 | 5 |
6 | 7 | `; 8 | 9 | exports[`VDivider.js should render component and match snapshot 1`] = ` 10 | 11 |
12 | 13 | `; 14 | -------------------------------------------------------------------------------- /src/components/VDivider/index.js: -------------------------------------------------------------------------------- 1 | import VDivider from './VDivider' 2 | 3 | VDivider.install = function install (Vue) { 4 | Vue.component(VDivider.name, VDivider) 5 | } 6 | 7 | export default VDivider 8 | -------------------------------------------------------------------------------- /src/components/VEditor/index.js: -------------------------------------------------------------------------------- 1 | import VEditor from './VEditor.vue' 2 | 3 | VEditor.install = function install (Vue) { 4 | Vue.component(VEditor.name, VEditor) 5 | } 6 | 7 | export default VEditor 8 | -------------------------------------------------------------------------------- /src/components/VExpansionPanel/VExpansionPanel.js: -------------------------------------------------------------------------------- 1 | require('../../stylus/components/_expansion-panel.styl') 2 | 3 | import Themeable from '../../mixins/themeable' 4 | 5 | export default { 6 | name: 'v-expansion-panel', 7 | 8 | mixins: [Themeable], 9 | 10 | provide () { 11 | return { 12 | panelClick: this.panelClick, 13 | focusable: this.focusable 14 | } 15 | }, 16 | 17 | props: { 18 | expand: Boolean, 19 | focusable: Boolean, 20 | inset: Boolean, 21 | popout: Boolean 22 | }, 23 | 24 | methods: { 25 | getChildren () { 26 | return this.$children.filter(c => { 27 | if (!c.$options) return 28 | 29 | return c.$options._componentTag === 'v-expansion-panel-content' 30 | }) 31 | }, 32 | panelClick (uid) { 33 | if (!this.expand) { 34 | return this.getChildren() 35 | .forEach(e => e.toggle(uid)) 36 | } 37 | 38 | const panel = this.$children.find(e => e._uid === uid) 39 | 40 | panel && panel.toggle(uid) 41 | } 42 | }, 43 | 44 | render (h) { 45 | return h('ul', { 46 | staticClass: 'expansion-panel', 47 | 'class': { 48 | 'expansion-panel--focusable': this.focusable, 49 | 'expansion-panel--popout': this.popout, 50 | 'expansion-panel--inset': this.inset, 51 | ...this.themeClasses 52 | } 53 | }, this.$slots.default) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/components/VExpansionPanel/VExpansionPanel.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from '~util/testing' 2 | import { mount } from 'avoriaz' 3 | import VExpansionPanel from '~components/VExpansionPanel' 4 | 5 | // TODO: Fix when Vue has optional injects 6 | test.skip('VExpansionPanel.js', () => { 7 | it('should render component and match snapshot', () => { 8 | const wrapper = mount(VExpansionPanel) 9 | 10 | expect(wrapper.html()).toMatchSnapshot() 11 | }) 12 | 13 | it('should render an expanded component and match snapshot', () => { 14 | const wrapper = mount(VExpansionPanel, { 15 | propsData: { 16 | expand: true 17 | } 18 | }) 19 | 20 | expect(wrapper.html()).toMatchSnapshot() 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /src/components/VExpansionPanel/VExpansionPanelContent.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from '~util/testing' 2 | import { mount } from 'avoriaz' 3 | import VExpansionPanelContent from './VExpansionPanelContent' 4 | 5 | // TODO: Fix when Vue has optional injects 6 | test.skip('VExpansionPanelContent.js', () => { 7 | it('should render component and match snapshot', () => { 8 | const wrapper = mount(VExpansionPanelContent) 9 | 10 | expect(wrapper.html()).toMatchSnapshot() 11 | }) 12 | 13 | it('should render an expanded component and match snapshot', () => { 14 | const wrapper = mount(VExpansionPanelContent, { 15 | propsData: { 16 | ripple: true 17 | } 18 | }) 19 | 20 | expect(wrapper.html()).toMatchSnapshot() 21 | }) 22 | 23 | it('should render an expanded component with lazy prop and match snapshot', () => { 24 | const wrapper = mount(VExpansionPanelContent, { 25 | propsData: { 26 | lazy: true 27 | } 28 | }) 29 | 30 | expect(wrapper.html()).toMatchSnapshot() 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /src/components/VExpansionPanel/index.js: -------------------------------------------------------------------------------- 1 | import VExpansionPanel from './VExpansionPanel' 2 | import VExpansionPanelContent from './VExpansionPanelContent' 3 | 4 | VExpansionPanel.install = function install (Vue) { 5 | Vue.component(VExpansionPanel.name, VExpansionPanel) 6 | Vue.component(VExpansionPanelContent.name, VExpansionPanelContent) 7 | } 8 | 9 | export default VExpansionPanel 10 | -------------------------------------------------------------------------------- /src/components/VFooter/VFooter.js: -------------------------------------------------------------------------------- 1 | require('../../stylus/components/_footer.styl') 2 | 3 | import Applicationable from '../../mixins/applicationable' 4 | import Colorable from '../../mixins/colorable' 5 | import Themeable from '../../mixins/themeable' 6 | 7 | export default { 8 | name: 'v-footer', 9 | 10 | mixins: [Applicationable, Colorable, Themeable], 11 | 12 | props: { 13 | absolute: Boolean, 14 | fixed: Boolean 15 | }, 16 | 17 | computed: { 18 | paddingLeft () { 19 | return this.fixed || !this.app 20 | ? 0 21 | : this.$vuetify.application.left 22 | }, 23 | paddingRight () { 24 | return this.fixed || !this.app 25 | ? 0 26 | : this.$vuetify.application.right 27 | } 28 | }, 29 | 30 | destroyed () { 31 | if (this.app) this.$vuetify.application.bottom = 0 32 | }, 33 | 34 | methods: { 35 | updateApplication () { 36 | if (!this.app) return 37 | 38 | this.$vuetify.application.bottom = this.fixed 39 | ? this.$el && this.$el.clientHeight 40 | : 0 41 | } 42 | }, 43 | 44 | mounted () { 45 | this.updateApplication() 46 | }, 47 | 48 | render (h) { 49 | this.updateApplication() 50 | 51 | const data = { 52 | staticClass: 'footer', 53 | 'class': this.addBackgroundColorClassChecks({ 54 | 'footer--absolute': this.absolute, 55 | 'footer--fixed': this.fixed, 56 | 'theme--dark': this.dark, 57 | 'theme--light': this.light 58 | }), 59 | style: { 60 | paddingLeft: `${this.paddingLeft}px`, 61 | paddingRight: `${this.paddingRight}px` 62 | } 63 | } 64 | 65 | return h('footer', data, this.$slots.default) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/components/VFooter/VFooter.spec.js: -------------------------------------------------------------------------------- 1 | import { test, functionalContext } from '~util/testing' 2 | import { mount } from 'avoriaz' 3 | import VFooter from './VFooter' 4 | 5 | test('VFooter.js', () => { 6 | it('should render component and match snapshot', () => { 7 | const wrapper = mount(VFooter) 8 | 9 | expect(wrapper.element.classList).toContain('footer') 10 | }) 11 | 12 | it('should render a colored footer', () => { 13 | const wrapper = mount(VFooter, { 14 | propsData: { 15 | color: 'blue lighten-1' 16 | } 17 | }) 18 | 19 | expect(wrapper.element.classList).toContain('blue') 20 | expect(wrapper.element.classList).toContain('lighten-1') 21 | }) 22 | 23 | it('should render an absolute positioned component and match snapshot', () => { 24 | const wrapper = mount(VFooter, { 25 | propsData: { 26 | absolute: true 27 | } 28 | }) 29 | 30 | expect(wrapper.element.classList).toContain('footer--absolute') 31 | }) 32 | 33 | it('should render a fixed positioned component and match snapshot', () => { 34 | const wrapper = mount(VFooter, { 35 | propsData: { 36 | fixed: true 37 | } 38 | }) 39 | 40 | expect(wrapper.element.classList).toContain('footer--fixed') 41 | }) 42 | 43 | it('should render a fixed and absolute positioned and match snapshot', () => { 44 | const wrapper = mount(VFooter, { 45 | propsData: { 46 | absolute: true, 47 | fixed: true 48 | } 49 | }) 50 | 51 | expect(wrapper.element.classList).toContain('footer--absolute') 52 | expect(wrapper.element.classList).toContain('footer--fixed') 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /src/components/VFooter/index.js: -------------------------------------------------------------------------------- 1 | import VFooter from './VFooter' 2 | 3 | VFooter.install = function install (Vue) { 4 | Vue.component(VFooter.name, VFooter) 5 | } 6 | 7 | export default VFooter 8 | -------------------------------------------------------------------------------- /src/components/VForm/VForm.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { mount } from 'avoriaz' 3 | import { test } from '~util/testing' 4 | import VTextField from '~components/VTextField' 5 | import VBtn from '~components/VBtn' 6 | import VForm from './VForm' 7 | 8 | const inputOne = Vue.component('input-one', { 9 | render (h) { 10 | return h(VTextField, { 11 | propsData: [(v) => !!v || 'Required'] 12 | }) 13 | } 14 | }) 15 | 16 | test('VForm.js', () => { 17 | it('should pass on listeners to form element', async () => { 18 | const submit = jest.fn() 19 | const component = Vue.component('test', { 20 | render (h) { 21 | return h(VForm, { 22 | on: { 23 | submit 24 | } 25 | }, [ 26 | h(VBtn, { 27 | props: { 28 | type: 'submit' 29 | }, 30 | slot: 'default' 31 | }, ['Submit']) 32 | ]) 33 | } 34 | }) 35 | 36 | const wrapper = mount(component) 37 | 38 | const btn = wrapper.find('button')[0] 39 | 40 | btn.trigger('click') 41 | 42 | expect(submit).toBeCalled() 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /src/components/VForm/index.js: -------------------------------------------------------------------------------- 1 | import VForm from './VForm' 2 | 3 | VForm.install = function install (Vue) { 4 | Vue.component(VForm.name, VForm) 5 | } 6 | 7 | export default VForm 8 | -------------------------------------------------------------------------------- /src/components/VGrid/VContainer.js: -------------------------------------------------------------------------------- 1 | require('../../stylus/components/_grid.styl') 2 | 3 | import Grid from './grid' 4 | 5 | export default Grid('container') 6 | -------------------------------------------------------------------------------- /src/components/VGrid/VContent.js: -------------------------------------------------------------------------------- 1 | require('../../stylus/components/_content.styl') 2 | 3 | export default { 4 | name: 'v-content', 5 | 6 | computed: { 7 | styles () { 8 | const { 9 | bar, top, right, bottom, left 10 | } = this.$vuetify.application 11 | 12 | return { 13 | paddingTop: `${top + bar}px`, 14 | paddingRight: `${right}px`, 15 | paddingBottom: `${bottom}px`, 16 | paddingLeft: `${left}px` 17 | } 18 | } 19 | }, 20 | 21 | mounted () { 22 | // TODO: Deprecate 23 | if (this.$el.parentElement.tagName === 'MAIN') { 24 | console.warn('v-content no longer needs to be wrapped in a
tag', this.$el.parentElement) 25 | } 26 | }, 27 | 28 | render (h) { 29 | const data = { 30 | staticClass: 'content', 31 | style: this.styles 32 | } 33 | 34 | return h('main', data, this.$slots.default) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/components/VGrid/VFlex.js: -------------------------------------------------------------------------------- 1 | require('../../stylus/components/_grid.styl') 2 | 3 | import Grid from './grid' 4 | 5 | export default Grid('flex') 6 | -------------------------------------------------------------------------------- /src/components/VGrid/VGrid.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from '~util/testing' 2 | import VFlex from '~components/VGrid/VFlex' 3 | 4 | test('VFlex', ({ mount, functionalContext }) => { 5 | it('should conditionally apply if boolean is used', () => { 6 | const wrapper = mount(VFlex, functionalContext({ 7 | attrs: { 8 | md6: false 9 | } 10 | })) 11 | 12 | expect(wrapper.hasClass('md6')).toBe(false) 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /src/components/VGrid/VLayout.js: -------------------------------------------------------------------------------- 1 | require('../../stylus/components/_grid.styl') 2 | 3 | import Grid from './grid' 4 | 5 | export default Grid('layout') 6 | -------------------------------------------------------------------------------- /src/components/VGrid/grid.js: -------------------------------------------------------------------------------- 1 | export default function Grid (name) { 2 | return { 3 | name: `v-${name}`, 4 | 5 | functional: true, 6 | 7 | props: { 8 | id: String, 9 | tag: { 10 | type: String, 11 | default: 'div' 12 | } 13 | }, 14 | 15 | render: (h, { props, data, children }) => { 16 | data.staticClass = (`${name} ${data.staticClass || ''}`).trim() 17 | 18 | if (data.attrs) { 19 | const classes = [] 20 | 21 | Object.keys(data.attrs).forEach(key => { 22 | const value = data.attrs[key] 23 | 24 | if (typeof value === 'string') classes.push(key) 25 | else if (value) classes.push(key) 26 | }) 27 | 28 | if (classes.length) data.staticClass += ` ${classes.join(' ')}` 29 | delete data.attrs 30 | } 31 | 32 | if (props.id) { 33 | data.domProps = data.domProps || {} 34 | data.domProps.id = props.id 35 | } 36 | 37 | return h(props.tag, data, children) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/VGrid/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | createSimpleFunctional 3 | } from '../../util/helpers' 4 | import VContent from './VContent' 5 | import VContainer from './VContainer' 6 | import VFlex from './VFlex' 7 | import VLayout from './VLayout' 8 | 9 | export const VSpacer = createSimpleFunctional('spacer') 10 | export { 11 | VContainer, 12 | VContent, 13 | VFlex, 14 | VLayout 15 | } 16 | 17 | const VGrid = {} 18 | 19 | VGrid.install = function install (Vue) { 20 | Vue.component(VContent.name, VContent) 21 | Vue.component(VContainer.name, VContainer) 22 | Vue.component(VFlex.name, VFlex) 23 | Vue.component(VLayout.name, VLayout) 24 | Vue.component(VSpacer.name, VSpacer) 25 | } 26 | 27 | export default VGrid 28 | -------------------------------------------------------------------------------- /src/components/VIcon/index.js: -------------------------------------------------------------------------------- 1 | import VIcon from './VIcon' 2 | 3 | VIcon.install = function install (Vue) { 4 | Vue.component(VIcon.name, VIcon) 5 | } 6 | 7 | export default VIcon 8 | -------------------------------------------------------------------------------- /src/components/VList/VList.js: -------------------------------------------------------------------------------- 1 | require('../../stylus/components/_lists.styl') 2 | 3 | import Themeable from '../../mixins/themeable' 4 | 5 | export default { 6 | name: 'v-list', 7 | 8 | provide () { 9 | return { 10 | listClick: this.listClick, 11 | listClose: this.listClose 12 | } 13 | }, 14 | 15 | mixins: [Themeable], 16 | 17 | data () { 18 | return { 19 | uid: null, 20 | groups: [] 21 | } 22 | }, 23 | 24 | props: { 25 | dense: Boolean, 26 | subheader: Boolean, 27 | threeLine: Boolean, 28 | twoLine: Boolean 29 | }, 30 | 31 | computed: { 32 | classes () { 33 | return { 34 | 'list': true, 35 | 'list--two-line': this.twoLine, 36 | 'list--dense': this.dense, 37 | 'list--three-line': this.threeLine, 38 | 'list--subheader': this.subheader, 39 | 'theme--dark dark--bg': this.dark, 40 | 'theme--light light--bg': this.light 41 | } 42 | } 43 | }, 44 | 45 | watch: { 46 | uid () { 47 | this.$children.filter(i => i.$options._componentTag === 'v-list-group').forEach(i => i.toggle(this.uid)) 48 | } 49 | }, 50 | 51 | methods: { 52 | listClick (uid, force) { 53 | if (force) { 54 | this.uid = uid 55 | } else { 56 | this.uid = this.uid === uid ? null : uid 57 | } 58 | }, 59 | 60 | listClose (uid) { 61 | if (this.uid === uid) { 62 | this.uid = null 63 | } 64 | } 65 | }, 66 | 67 | render (h) { 68 | const data = { 69 | 'class': this.classes, 70 | attrs: { 'data-uid': this._uid } 71 | } 72 | 73 | return h('ul', data, [this.$slots.default]) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/components/VList/VList.spec.js: -------------------------------------------------------------------------------- 1 | import VList from '~components/VList' 2 | import { test } from '~util/testing' 3 | 4 | // TODO: Test actual behaviour instead of classes 5 | test('VList.js', ({ mount }) => { 6 | it('should render component and match snapshot', () => { 7 | const wrapper = mount(VList) 8 | 9 | expect(wrapper.html()).toMatchSnapshot() 10 | }) 11 | 12 | it('should render a dense component and match snapshot', () => { 13 | const wrapper = mount(VList, { 14 | propsData: { 15 | dense: true 16 | } 17 | }) 18 | 19 | expect(wrapper.html()).toMatchSnapshot() 20 | }) 21 | 22 | it('should render a subheader component and match snapshot', () => { 23 | const wrapper = mount(VList, { 24 | propsData: { 25 | subheader: true 26 | } 27 | }) 28 | 29 | expect(wrapper.html()).toMatchSnapshot() 30 | }) 31 | 32 | it('should render a threeLine component and match snapshot', () => { 33 | const wrapper = mount(VList, { 34 | propsData: { 35 | threeLine: true 36 | } 37 | }) 38 | 39 | expect(wrapper.html()).toMatchSnapshot() 40 | }) 41 | 42 | it('should render a twoLine component and match snapshot', () => { 43 | const wrapper = mount(VList, { 44 | propsData: { 45 | twoLine: true 46 | } 47 | }) 48 | 49 | expect(wrapper.html()).toMatchSnapshot() 50 | }) 51 | }) 52 | -------------------------------------------------------------------------------- /src/components/VList/VListGroup.spec.js: -------------------------------------------------------------------------------- 1 | import { VList, VListGroup } from '~components/VList' 2 | import { test } from '~util/testing' 3 | 4 | // TODO: Test actual behaviour instead of classes 5 | test('VListGroup.js', ({ mount }) => { 6 | it('should render component and match snapshot', () => { 7 | const wrapper = mount(VList, { 8 | slots: { 9 | default: [VListGroup] 10 | } 11 | }) 12 | 13 | expect(wrapper.html()).toMatchSnapshot() 14 | }) 15 | 16 | it('should render a lazy component and match snapshot', () => { 17 | const wrapper = mount(VList, { 18 | propsData: { 19 | lazy: true 20 | }, 21 | slots: { 22 | default: [VListGroup] 23 | } 24 | }) 25 | 26 | expect(wrapper.html()).toMatchSnapshot() 27 | }) 28 | 29 | it('should render a component with no padding for action icon and match snapshot', () => { 30 | const wrapper = mount(VList, { 31 | propsData: { 32 | noAction: true 33 | }, 34 | slots: { 35 | default: [VListGroup] 36 | } 37 | }) 38 | 39 | expect(wrapper.html()).toMatchSnapshot() 40 | }) 41 | 42 | it('should render a component with route namespace and match snapshot', () => { 43 | const $route = { path: '' } 44 | const wrapper = mount(VList, { 45 | propsData: { 46 | group: 'listGroup' 47 | }, 48 | slots: { 49 | default: [VListGroup] 50 | }, 51 | globals: { 52 | $route 53 | } 54 | }) 55 | 56 | expect(wrapper.html()).toMatchSnapshot() 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /src/components/VList/VListTile.js: -------------------------------------------------------------------------------- 1 | import Routable from '../../mixins/routable' 2 | import Toggleable from '../../mixins/toggleable' 3 | import Ripple from '../../directives/ripple' 4 | 5 | export default { 6 | name: 'v-list-tile', 7 | 8 | mixins: [Routable, Toggleable], 9 | 10 | directives: { 11 | Ripple 12 | }, 13 | 14 | inheritAttrs: false, 15 | 16 | data: () => ({ 17 | proxyClass: 'list__tile--active' 18 | }), 19 | 20 | props: { 21 | activeClass: { 22 | type: String, 23 | default: 'primary--text' 24 | }, 25 | avatar: Boolean, 26 | inactive: Boolean, 27 | tag: String 28 | }, 29 | 30 | computed: { 31 | classes () { 32 | return { 33 | 'list__tile': true, 34 | 'list__tile--link': this.isLink && !this.inactive, 35 | 'list__tile--avatar': this.avatar, 36 | 'list__tile--disabled': this.disabled, 37 | 'list__tile--active': !this.to && this.isActive, 38 | [this.activeClass]: this.isActive 39 | } 40 | }, 41 | isLink () { 42 | return this.href || this.to || 43 | (this.$listeners && (this.$listeners.click || this.$listeners['!click'])) 44 | } 45 | }, 46 | 47 | render (h) { 48 | const isRouteLink = !this.inactive && this.isLink 49 | const { tag, data } = isRouteLink ? this.generateRouteLink() : { 50 | tag: this.tag || 'div', 51 | data: { 52 | class: this.classes 53 | } 54 | } 55 | 56 | data.attrs = Object.assign({}, data.attrs, this.$attrs) 57 | 58 | return h('li', { 59 | attrs: { 60 | disabled: this.disabled 61 | }, 62 | on: { 63 | ...this.$listeners 64 | } 65 | }, [h(tag, data, this.$slots.default)]) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/components/VList/VListTileAction.js: -------------------------------------------------------------------------------- 1 | export default { 2 | functional: true, 3 | 4 | name: 'v-list-tile-action', 5 | 6 | render (h, { data, children }) { 7 | data.staticClass = data.staticClass ? `list__tile__action ${data.staticClass || ''}` : 'list__tile__action' 8 | if ((children || []).length > 1) data.staticClass += ' list__tile__action--stack' 9 | 10 | return h('div', data, children) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/VList/VListTileAction.spec.js: -------------------------------------------------------------------------------- 1 | import { VListTileAction } from '~components/VList' 2 | import { test } from '~util/testing' 3 | 4 | test('VListTileAction.js', ({ mount, functionalContext }) => { 5 | it('should render component and match snapshot', () => { 6 | const wrapper = mount(VListTileAction, functionalContext()) 7 | 8 | expect(wrapper.html()).toMatchSnapshot() 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /src/components/VList/__snapshots__/VList.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`VList.js should render a dense component and match snapshot 1`] = ` 4 | 5 |
    8 |
9 | 10 | `; 11 | 12 | exports[`VList.js should render a subheader component and match snapshot 1`] = ` 13 | 14 |
    17 |
18 | 19 | `; 20 | 21 | exports[`VList.js should render a threeLine component and match snapshot 1`] = ` 22 | 23 |
    26 |
27 | 28 | `; 29 | 30 | exports[`VList.js should render a twoLine component and match snapshot 1`] = ` 31 | 32 |
    35 |
36 | 37 | `; 38 | 39 | exports[`VList.js should render component and match snapshot 1`] = ` 40 | 41 |
    44 |
45 | 46 | `; 47 | -------------------------------------------------------------------------------- /src/components/VList/__snapshots__/VListGroup.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`VListGroup.js should render a component with no padding for action icon and match snapshot 1`] = ` 4 | 5 |
    8 |
    9 |
    10 |
    11 | 15 |
    16 |
17 | 18 | `; 19 | 20 | exports[`VListGroup.js should render a component with route namespace and match snapshot 1`] = ` 21 | 22 |
    25 |
    26 |
    27 |
    28 | 32 |
    33 |
34 | 35 | `; 36 | 37 | exports[`VListGroup.js should render a lazy component and match snapshot 1`] = ` 38 | 39 |
    42 |
    43 |
    44 |
    45 | 49 |
    50 |
51 | 52 | `; 53 | 54 | exports[`VListGroup.js should render component and match snapshot 1`] = ` 55 | 56 |
    59 |
    60 |
    61 |
    62 | 66 |
    67 |
68 | 69 | `; 70 | -------------------------------------------------------------------------------- /src/components/VList/__snapshots__/VListTile.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`VListTile.vue should render
  • with when using href prop 1`] = ` 4 | 5 |
  • 6 | 10 | 11 |
  • 12 | 13 | `; 14 | 15 | exports[`VListTile.vue should render
  • with 22 |
  • 23 | 24 | `; 25 | 26 | exports[`VListTile.vue should render with a div when href and to are not used 1`] = ` 27 | 28 |
  • 29 |
    30 |
    31 |
  • 32 | 33 | `; 34 | -------------------------------------------------------------------------------- /src/components/VList/__snapshots__/VListTileAction.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`VListTileAction.js should render component and match snapshot 1`] = ` 4 | 5 |
    6 |
    7 | 8 | `; 9 | -------------------------------------------------------------------------------- /src/components/VList/index.js: -------------------------------------------------------------------------------- 1 | import { createSimpleFunctional } from '../../util/helpers' 2 | 3 | import VList from './VList' 4 | import VListGroup from './VListGroup' 5 | import VListTile from './VListTile' 6 | import VListTileAction from './VListTileAction' 7 | 8 | export { VList, VListGroup, VListTile, VListTileAction } 9 | export const VListTileActionText = createSimpleFunctional('list__tile__action-text', 'span') 10 | export const VListTileAvatar = createSimpleFunctional('list__tile__avatar', 'v-avatar') 11 | export const VListTileContent = createSimpleFunctional('list__tile__content', 'div') 12 | export const VListTileTitle = createSimpleFunctional('list__tile__title', 'div') 13 | export const VListTileSubTitle = createSimpleFunctional('list__tile__sub-title', 'div') 14 | 15 | VList.install = function install (Vue) { 16 | Vue.component(VList.name, VList) 17 | Vue.component(VListGroup.name, VListGroup) 18 | Vue.component(VListTile.name, VListTile) 19 | Vue.component(VListTileAction.name, VListTileAction) 20 | Vue.component('v-list-tile-action-text', VListTileActionText) 21 | Vue.component('v-list-tile-avatar', VListTileAvatar) 22 | Vue.component('v-list-tile-content', VListTileContent) 23 | Vue.component('v-list-tile-sub-title', VListTileSubTitle) 24 | Vue.component('v-list-tile-title', VListTileTitle) 25 | } 26 | 27 | export default VList 28 | -------------------------------------------------------------------------------- /src/components/VMenu/index.js: -------------------------------------------------------------------------------- 1 | import VMenu from './VMenu' 2 | 3 | VMenu.install = function install (Vue) { 4 | Vue.component(VMenu.name, VMenu) 5 | } 6 | 7 | export default VMenu 8 | -------------------------------------------------------------------------------- /src/components/VMenu/mixins/menu-activator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Menu activator 3 | * 4 | * @mixin 5 | * 6 | * Handles the click and hover activation 7 | * Supports slotted and detached activators 8 | */ 9 | export default { 10 | methods: { 11 | activatorClickHandler (e) { 12 | if (this.disabled) return 13 | if (this.openOnClick && !this.isActive) { 14 | this.getActivator().focus() 15 | this.isActive = true 16 | this.absoluteX = e.clientX 17 | this.absoluteY = e.clientY 18 | } else if (this.closeOnClick && this.isActive) { 19 | this.getActivator().blur() 20 | this.isActive = false 21 | } 22 | }, 23 | mouseEnterHandler (e) { 24 | this.runDelay('open', () => { 25 | if (this.hasJustFocused) return 26 | 27 | this.hasJustFocused = true 28 | this.isActive = true 29 | }) 30 | }, 31 | mouseLeaveHandler (e) { 32 | // Prevent accidental re-activation 33 | this.runDelay('close', () => { 34 | if (this.$refs.content.contains(e.relatedTarget)) return 35 | 36 | requestAnimationFrame(() => { 37 | this.isActive = false 38 | this.callDeactivate() 39 | }) 40 | }) 41 | }, 42 | addActivatorEvents (activator = null) { 43 | if (!activator) return 44 | activator.addEventListener('click', this.activatorClickHandler) 45 | }, 46 | removeActivatorEvents (activator = null) { 47 | if (!activator) return 48 | activator.removeEventListener('click', this.activatorClickHandler) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/components/VMenu/mixins/menu-keyable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Menu keyable 3 | * 4 | * @mixin 5 | * 6 | * Primarily used to support VSelect 7 | * Handles opening and closing of VMenu from keystrokes 8 | * Will conditionally highlight VListTiles for VSelect 9 | */ 10 | export default { 11 | data: () => ({ 12 | listIndex: -1, 13 | tiles: [] 14 | }), 15 | 16 | watch: { 17 | isActive (val) { 18 | if (!val) this.listIndex = -1 19 | }, 20 | listIndex (next, prev) { 21 | // For infinite scroll and autocomplete, re-evaluate children 22 | this.getTiles() 23 | 24 | if (next in this.tiles) { 25 | this.tiles[next].classList.add('list__tile--highlighted') 26 | this.$refs.content.scrollTop = next * 48 27 | } 28 | 29 | prev in this.tiles && 30 | this.tiles[prev].classList.remove('list__tile--highlighted') 31 | } 32 | }, 33 | 34 | methods: { 35 | changeListIndex (e) { 36 | // Up, Down, Enter, Space 37 | if ([40, 38, 13].includes(e.keyCode) || 38 | e.keyCode === 32 && !this.isActive 39 | ) { 40 | e.preventDefault() 41 | } 42 | 43 | // Esc, Tab 44 | if ([27, 9].includes(e.keyCode)) return this.isActive = false 45 | else if (!this.isActive && 46 | // Enter, Space 47 | [13, 32].includes(e.keyCode) && 48 | this.openOnClick 49 | ) { 50 | return this.isActive = true 51 | } 52 | 53 | // Down 54 | if (e.keyCode === 40 && this.listIndex < this.tiles.length - 1) { 55 | this.listIndex++ 56 | // Up 57 | } else if (e.keyCode === 38 && this.listIndex > 0) { 58 | this.listIndex-- 59 | // Enter 60 | } else if (e.keyCode === 13 && this.listIndex !== -1) { 61 | this.tiles[this.listIndex].click() 62 | } 63 | }, 64 | getTiles () { 65 | this.tiles = this.$refs.content.querySelectorAll('.list__tile') 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/components/VNavigationDrawer/index.js: -------------------------------------------------------------------------------- 1 | import VNavigationDrawer from './VNavigationDrawer' 2 | 3 | VNavigationDrawer.install = function install (Vue) { 4 | Vue.component(VNavigationDrawer.name, VNavigationDrawer) 5 | } 6 | 7 | export default VNavigationDrawer 8 | -------------------------------------------------------------------------------- /src/components/VPagination/index.js: -------------------------------------------------------------------------------- 1 | import VPagination from './VPagination' 2 | 3 | VPagination.install = function install (Vue) { 4 | Vue.component(VPagination.name, VPagination) 5 | } 6 | 7 | export default VPagination 8 | -------------------------------------------------------------------------------- /src/components/VParallax/VParallax.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from '~util/testing' 2 | import VParallax from '~components/VParallax' 3 | 4 | test('VParallax.js', ({ mount }) => { 5 | it('should render', async () => { 6 | const wrapper = mount(VParallax, { 7 | attachToDocument: true 8 | }) 9 | 10 | expect(wrapper.html()).toMatchSnapshot() 11 | }) 12 | 13 | it('should use alt tag when supplied', async () => { 14 | const wrapper = mount(VParallax, { 15 | attachToDocument: true, 16 | propsData: { 17 | alt: 'name' 18 | } 19 | }) 20 | 21 | const img = wrapper.find('img')[0] 22 | expect(img.getAttribute('alt')).toBe('name') 23 | expect(wrapper.html()).toMatchSnapshot() 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /src/components/VParallax/__snapshots__/VParallax.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`VParallax.js should render 1`] = ` 4 | 5 |
    8 |
    9 | 12 |
    13 |
    14 |
    15 |
    16 | 17 | `; 18 | 19 | exports[`VParallax.js should use alt tag when supplied 1`] = ` 20 | 21 |
    24 |
    25 | name 29 |
    30 |
    31 |
    32 |
    33 | 34 | `; 35 | -------------------------------------------------------------------------------- /src/components/VParallax/index.js: -------------------------------------------------------------------------------- 1 | import VParallax from './VParallax' 2 | 3 | VParallax.install = function install (Vue) { 4 | Vue.component(VParallax.name, VParallax) 5 | } 6 | 7 | export default VParallax 8 | -------------------------------------------------------------------------------- /src/components/VProgressCircular/index.js: -------------------------------------------------------------------------------- 1 | import VProgressCircular from './VProgressCircular' 2 | 3 | VProgressCircular.install = function install (Vue) { 4 | Vue.component(VProgressCircular.name, VProgressCircular) 5 | } 6 | 7 | export default VProgressCircular 8 | -------------------------------------------------------------------------------- /src/components/VProgressLinear/index.js: -------------------------------------------------------------------------------- 1 | import VProgressLinear from './VProgressLinear' 2 | 3 | VProgressLinear.install = function install (Vue) { 4 | Vue.component(VProgressLinear.name, VProgressLinear) 5 | } 6 | 7 | export default VProgressLinear 8 | -------------------------------------------------------------------------------- /src/components/VRadioGroup/VRadioGroup.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from '~util/testing' 2 | import { VRadioGroup /*, VRadio */ } from '~components/VRadioGroup' 3 | 4 | test('VRadioGroup.vue', ({ mount }) => { 5 | it('should render role on radio group', () => { 6 | const wrapper = mount(VRadioGroup) 7 | 8 | expect(wrapper.html()).toMatchSnapshot() 9 | 10 | const radioGroup = wrapper.find('.radio-group')[0] 11 | expect(radioGroup.getAttribute('role')).toBe('radiogroup') 12 | }) 13 | 14 | // TODO: Test ability to toggle multiple data types 15 | }) 16 | -------------------------------------------------------------------------------- /src/components/VRadioGroup/__snapshots__/VRadioGroup.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`VRadioGroup.vue should render role on radio group 1`] = ` 4 | 5 |
    8 |
    9 |
    10 |
    11 |
    12 |
    13 |
    14 |
    15 | 16 | `; 17 | -------------------------------------------------------------------------------- /src/components/VRadioGroup/index.js: -------------------------------------------------------------------------------- 1 | import VRadioGroup from './VRadioGroup' 2 | import VRadio from './VRadio' 3 | 4 | export { VRadioGroup, VRadio } 5 | 6 | VRadioGroup.install = function install (Vue) { 7 | Vue.component(VRadioGroup.name, VRadioGroup) 8 | Vue.component(VRadio.name, VRadio) 9 | } 10 | 11 | export default VRadioGroup 12 | -------------------------------------------------------------------------------- /src/components/VSelect/VSelect-combobox.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from '~util/testing' 2 | import { mount } from 'avoriaz' 3 | import VSelect from '~components/VSelect' 4 | import VMenu from '~components/VMenu' 5 | 6 | test('VSelect - combobox', () => { 7 | const backspace = new Event('keydown') 8 | backspace.keyCode = 8 9 | 10 | it('should emit custom value on blur', async () => { 11 | const wrapper = mount(VSelect, { 12 | attachToDocument: true, 13 | propsData: { 14 | combobox: true, 15 | value: null 16 | } 17 | }) 18 | 19 | const input = wrapper.find('input')[0] 20 | 21 | const change = jest.fn() 22 | wrapper.vm.$on('input', change) 23 | 24 | input.trigger('focus') 25 | await wrapper.vm.$nextTick() 26 | 27 | input.element.value = 'foo' 28 | input.trigger('input') 29 | await wrapper.vm.$nextTick() 30 | 31 | input.trigger('blur') 32 | await wrapper.vm.$nextTick() 33 | 34 | expect(change).toHaveBeenCalledWith('foo') 35 | expect('Application is missing component.').toHaveBeenTipped() 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /src/components/VSelect/index.js: -------------------------------------------------------------------------------- 1 | import VSelect from './VSelect' 2 | 3 | VSelect.install = function install (Vue) { 4 | Vue.component(VSelect.name, VSelect) 5 | } 6 | 7 | export default VSelect 8 | -------------------------------------------------------------------------------- /src/components/VSlider/VSlider.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from '~util/testing' 2 | import VSlider from './VSlider' 3 | 4 | const warning = 'The v-slider component requires the presence of v-app or a non-body wrapping element with the [data-app] attribute.' 5 | 6 | test('Vslider.vue', ({ mount }) => { 7 | it('should match a snapshot', () => { 8 | const wrapper = mount(VSlider) 9 | 10 | expect(wrapper.html()).toMatchSnapshot() 11 | expect(warning).toHaveBeenTipped() 12 | }) 13 | 14 | it('should not allow values outside of min/max', async () => { 15 | const wrapper = mount(VSlider, { 16 | propsData: { 17 | min: 2, 18 | max: 4 19 | } 20 | }) 21 | 22 | const input = jest.fn() 23 | wrapper.instance().$on('input', input) 24 | 25 | wrapper.setProps({ value: 0 }) 26 | await wrapper.vm.$nextTick() 27 | expect(input).toBeCalledWith(2) 28 | 29 | wrapper.setProps({ value: 5 }) 30 | await wrapper.vm.$nextTick() 31 | expect(input).toBeCalledWith(4) 32 | 33 | expect(warning).toHaveBeenTipped() 34 | }) 35 | 36 | it('should adjust value if min/max props change', async () => { 37 | const wrapper = mount(VSlider, { 38 | propsData: { 39 | value: 5, 40 | min: 0, 41 | max: 10 42 | } 43 | }) 44 | 45 | const input = jest.fn() 46 | wrapper.instance().$on('input', input) 47 | 48 | wrapper.setProps({ min: 6 }) 49 | await wrapper.vm.$nextTick() 50 | expect(input).toBeCalledWith(6) 51 | 52 | wrapper.setProps({ max: 4 }) 53 | await wrapper.vm.$nextTick() 54 | expect(input).toBeCalledWith(4) 55 | 56 | expect(warning).toHaveBeenTipped() 57 | }) 58 | 59 | it('should be focused when active', async () => { 60 | const wrapper = mount(VSlider, { 61 | propsData: { 62 | value: 5, 63 | min: 0, 64 | max: 10 65 | } 66 | }) 67 | 68 | wrapper.setData({ isActive: true }) 69 | 70 | await wrapper.vm.$nextTick() 71 | 72 | expect(wrapper.vm.isFocused).toBe(true) 73 | expect(wrapper.html()).toMatchSnapshot() 74 | expect(warning).toHaveBeenTipped() 75 | }) 76 | }) 77 | -------------------------------------------------------------------------------- /src/components/VSlider/__snapshots__/VSlider.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Vslider.vue should be focused when active 1`] = ` 4 | 5 |
    9 |
    10 |
    11 |
    12 |
    13 |
    14 |
    15 |
    16 |
    17 |
    20 |
    21 |
    22 |
    23 |
    24 |
    25 |
    26 |
    27 |
    28 |
    29 |
    30 | 31 | `; 32 | 33 | exports[`Vslider.vue should match a snapshot 1`] = ` 34 | 35 |
    39 |
    40 |
    41 |
    42 |
    43 |
    44 |
    45 |
    46 |
    47 |
    48 |
    49 |
    50 |
    51 |
    52 |
    53 |
    54 |
    55 |
    56 |
    57 |
    58 | 59 | `; 60 | -------------------------------------------------------------------------------- /src/components/VSlider/index.js: -------------------------------------------------------------------------------- 1 | import VSlider from './VSlider' 2 | 3 | VSlider.install = function install (Vue) { 4 | Vue.component(VSlider.name, VSlider) 5 | } 6 | 7 | export default VSlider 8 | -------------------------------------------------------------------------------- /src/components/VSnackbar/index.js: -------------------------------------------------------------------------------- 1 | import VSnackbar from './VSnackbar' 2 | 3 | VSnackbar.install = function install (Vue) { 4 | Vue.component(VSnackbar.name, VSnackbar) 5 | } 6 | 7 | export default VSnackbar 8 | -------------------------------------------------------------------------------- /src/components/VSpeedDial/VSpeedDial.js: -------------------------------------------------------------------------------- 1 | require('../../stylus/components/_speed-dial.styl') 2 | 3 | import Toggleable from '../../mixins/toggleable' 4 | import Positionable from '../../mixins/positionable' 5 | 6 | import ClickOutside from '../../directives/click-outside' 7 | 8 | export default { 9 | name: 'v-speed-dial', 10 | 11 | mixins: [Positionable, Toggleable], 12 | 13 | directives: { ClickOutside }, 14 | 15 | props: { 16 | direction: { 17 | type: String, 18 | default: 'top', 19 | validator: (val) => { 20 | return ['top', 'right', 'bottom', 'left'].includes(val) 21 | } 22 | }, 23 | hover: Boolean, 24 | transition: { 25 | type: String, 26 | default: 'scale-transition' 27 | } 28 | }, 29 | 30 | computed: { 31 | classes () { 32 | return { 33 | 'speed-dial': true, 34 | 'speed-dial--top': this.top, 35 | 'speed-dial--right': this.right, 36 | 'speed-dial--bottom': this.bottom, 37 | 'speed-dial--left': this.left, 38 | 'speed-dial--absolute': this.absolute, 39 | 'speed-dial--fixed': this.fixed, 40 | [`speed-dial--direction-${this.direction}`]: true 41 | } 42 | } 43 | }, 44 | 45 | render (h) { 46 | let children = [] 47 | const data = { 48 | 'class': this.classes, 49 | directives: [{ 50 | name: 'click-outside' 51 | }], 52 | on: { 53 | click: () => (this.isActive = !this.isActive) 54 | } 55 | } 56 | 57 | if (this.hover) { 58 | data.on.mouseenter = () => (this.isActive = true) 59 | data.on.mouseleave = () => (this.isActive = false) 60 | } 61 | 62 | if (this.isActive) { 63 | children = (this.$slots.default || []).map((b, i) => { 64 | b.key = i 65 | 66 | return b 67 | }) 68 | } 69 | 70 | const list = h('transition-group', { 71 | 'class': 'speed-dial__list', 72 | props: { 73 | name: this.transition, 74 | tag: 'div' 75 | } 76 | }, children) 77 | 78 | return h('div', data, [this.$slots.activator, list]) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/components/VSpeedDial/VSpeedDial.spec.js: -------------------------------------------------------------------------------- 1 | import VSpeedDial from '~components/VSpeedDial' 2 | import { test } from '~util/testing' 3 | 4 | test('VSpeedDial.js', ({ mount }) => { 5 | it('should render component and match snapshot', () => { 6 | const wrapper = mount(VSpeedDial) 7 | 8 | expect(wrapper.html()).toMatchSnapshot() 9 | }) 10 | 11 | it('should render component with custom direction and match snapshot', () => { 12 | const wrapper = mount(VSpeedDial, { 13 | propsData: { 14 | direction: 'right' 15 | } 16 | }) 17 | 18 | expect(wrapper.html()).toMatchSnapshot() 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /src/components/VSpeedDial/__snapshots__/VSpeedDial.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`VSpeedDial.js should render component and match snapshot 1`] = ` 4 | 5 |
    6 |
    7 |
    8 |
    9 | 10 | `; 11 | 12 | exports[`VSpeedDial.js should render component with custom direction and match snapshot 1`] = ` 13 | 14 |
    15 |
    16 |
    17 |
    18 | 19 | `; 20 | -------------------------------------------------------------------------------- /src/components/VSpeedDial/index.js: -------------------------------------------------------------------------------- 1 | import VSpeedDial from './VSpeedDial' 2 | 3 | VSpeedDial.install = function install (Vue) { 4 | Vue.component(VSpeedDial.name, VSpeedDial) 5 | } 6 | 7 | export default VSpeedDial 8 | -------------------------------------------------------------------------------- /src/components/VStepper/index.js: -------------------------------------------------------------------------------- 1 | import { createSimpleFunctional } from '../../util/helpers' 2 | import VStepper from './VStepper' 3 | import VStepperStep from './VStepperStep' 4 | import VStepperContent from './VStepperContent' 5 | 6 | VStepper.install = function install (Vue) { 7 | const VStepperHeader = createSimpleFunctional('stepper__header') 8 | const VStepperItems = createSimpleFunctional('stepper__items') 9 | 10 | Vue.component(VStepper.name, VStepper) 11 | Vue.component(VStepperContent.name, VStepperContent) 12 | Vue.component(VStepperStep.name, VStepperStep) 13 | Vue.component(VStepperHeader.name, VStepperHeader) 14 | Vue.component(VStepperItems.name, VStepperItems) 15 | } 16 | 17 | export default VStepper 18 | -------------------------------------------------------------------------------- /src/components/VSubheader/VSubheader.js: -------------------------------------------------------------------------------- 1 | require('../../stylus/components/_subheaders.styl') 2 | 3 | import Themeable from '../../mixins/themeable' 4 | 5 | export default { 6 | name: 'v-subheader', 7 | 8 | functional: true, 9 | 10 | mixins: [Themeable], 11 | 12 | props: { 13 | inset: Boolean 14 | }, 15 | 16 | render (h, { data, children, props }) { 17 | data.staticClass = (`subheader ${data.staticClass || ''}`).trim() 18 | 19 | if (props.inset) data.staticClass += ' subheader--inset' 20 | if (props.light) data.staticClass += ' theme--light' 21 | if (props.dark) data.staticClass += ' theme--dark' 22 | 23 | return h('li', data, children) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/VSubheader/index.js: -------------------------------------------------------------------------------- 1 | import VSubheader from './VSubheader' 2 | 3 | VSubheader.install = function install (Vue) { 4 | Vue.component(VSubheader.name, VSubheader) 5 | } 6 | 7 | export default VSubheader 8 | -------------------------------------------------------------------------------- /src/components/VSwitch/VSwitch.js: -------------------------------------------------------------------------------- 1 | require('../../stylus/components/_input-groups.styl') 2 | require('../../stylus/components/_selection-controls.styl') 3 | require('../../stylus/components/_switch.styl') 4 | 5 | import Rippleable from '../../mixins/rippleable' 6 | import Selectable from '../../mixins/selectable' 7 | 8 | export default { 9 | name: 'v-switch', 10 | 11 | mixins: [Rippleable, Selectable], 12 | 13 | computed: { 14 | classes () { 15 | const classes = { 16 | 'input-group--selection-controls switch': true 17 | } 18 | 19 | if (this.hasError) { 20 | classes['error--text'] = true 21 | } else { 22 | return this.addTextColorClassChecks(classes) 23 | } 24 | 25 | return classes 26 | }, 27 | rippleClasses () { 28 | return { 29 | 'input-group--selection-controls__ripple': true, 30 | 'input-group--selection-controls__ripple--active': this.isActive 31 | } 32 | }, 33 | containerClasses () { 34 | return { 35 | 'input-group--selection-controls__container': true, 36 | 'input-group--selection-controls__container--light': this.light, 37 | 'input-group--selection-controls__container--disabled': this.disabled 38 | } 39 | }, 40 | toggleClasses () { 41 | return { 42 | 'input-group--selection-controls__toggle': true, 43 | 'input-group--selection-controls__toggle--active': this.isActive 44 | } 45 | } 46 | }, 47 | 48 | render (h) { 49 | const container = h('div', { 50 | 'class': this.containerClasses 51 | }, [ 52 | h('div', { 'class': this.toggleClasses }), 53 | this.genRipple() 54 | ]) 55 | 56 | return this.genInputGroup([container]) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/components/VSwitch/index.js: -------------------------------------------------------------------------------- 1 | import VSwitch from './VSwitch' 2 | 3 | VSwitch.install = function install (Vue) { 4 | Vue.component(VSwitch.name, VSwitch) 5 | } 6 | 7 | export default VSwitch 8 | -------------------------------------------------------------------------------- /src/components/VSystemBar/VSystemBar.js: -------------------------------------------------------------------------------- 1 | require('../../stylus/components/_system-bars.styl') 2 | 3 | import Applicationable from '../../mixins/applicationable' 4 | import Colorable from '../../mixins/colorable' 5 | import Themeable from '../../mixins/themeable' 6 | 7 | export default { 8 | name: 'v-system-bar', 9 | 10 | mixins: [Applicationable, Colorable, Themeable], 11 | 12 | props: { 13 | absolute: Boolean, 14 | fixed: Boolean, 15 | lightsOut: Boolean, 16 | status: Boolean, 17 | window: Boolean 18 | }, 19 | 20 | computed: { 21 | classes () { 22 | return this.addBackgroundColorClassChecks(Object.assign({ 23 | 'system-bar--lights-out': this.lightsOut, 24 | 'system-bar--absolute': this.absolute, 25 | 'system-bar--fixed': this.fixed, 26 | 'system-bar--status': this.status, 27 | 'system-bar--window': this.window 28 | }, this.themeClasses)) 29 | }, 30 | computedHeight () { 31 | return this.window ? 32 : 24 32 | } 33 | }, 34 | 35 | watch: { 36 | window () { 37 | this.updateApplication() 38 | }, 39 | fixed () { 40 | this.updateApplication() 41 | }, 42 | absolute () { 43 | this.updateApplication() 44 | } 45 | }, 46 | 47 | methods: { 48 | updateApplication () { 49 | if (this.app && this.$vuetify) { 50 | this.$vuetify.application.bar = (this.fixed || this.absolute) ? this.computedHeight : 0 51 | } 52 | } 53 | }, 54 | 55 | destroyed () { 56 | if (this.app && this.$vuetify) this.$vuetify.application.bar = 0 57 | }, 58 | 59 | render (h) { 60 | const data = { 61 | staticClass: 'system-bar', 62 | 'class': this.classes, 63 | style: { 64 | height: `${this.computedHeight}px` 65 | } 66 | } 67 | 68 | return h('div', data, this.$slots.default) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/components/VSystemBar/VSystemBar.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from '~util/testing' 2 | import { mount } from 'avoriaz' 3 | import VSystemBar from '~components/VSystemBar' 4 | 5 | test('VSystemBar.vue', () => { 6 | it('should render a colored system bar', () => { 7 | const wrapper = mount(VSystemBar, { 8 | propsData: { 9 | color: 'blue lighten-1' 10 | } 11 | }) 12 | 13 | expect(wrapper.element.classList).toContain('blue') 14 | expect(wrapper.element.classList).toContain('lighten-1') 15 | }) 16 | 17 | it('should render system bar with fixed prop', () => { 18 | const wrapper = mount(VSystemBar, { 19 | propsData: { 20 | fixed: true 21 | } 22 | }) 23 | 24 | expect(wrapper.element.classList).toContain('system-bar--fixed') 25 | }) 26 | 27 | it('should render system bar with absolute prop', () => { 28 | const wrapper = mount(VSystemBar, { 29 | propsData: { 30 | absolute: true 31 | } 32 | }) 33 | 34 | expect(wrapper.element.classList).toContain('system-bar--absolute') 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /src/components/VSystemBar/index.js: -------------------------------------------------------------------------------- 1 | import VSystemBar from './VSystemBar' 2 | 3 | VSystemBar.install = function install (Vue) { 4 | Vue.component(VSystemBar.name, VSystemBar) 5 | } 6 | 7 | export default VSystemBar 8 | -------------------------------------------------------------------------------- /src/components/VTabs/VTabsContent.js: -------------------------------------------------------------------------------- 1 | import Bootable from '../../mixins/bootable' 2 | 3 | import { 4 | VTabTransition, 5 | VTabReverseTransition 6 | } from '../transitions' 7 | 8 | import Touch from '../../directives/touch' 9 | 10 | export default { 11 | name: 'v-tabs-content', 12 | 13 | mixins: [Bootable], 14 | 15 | inject: ['registerContent', 'unregisterContent'], 16 | 17 | components: { 18 | VTabTransition, 19 | VTabReverseTransition 20 | }, 21 | 22 | directives: { 23 | Touch 24 | }, 25 | 26 | data () { 27 | return { 28 | isActive: false, 29 | reverse: false 30 | } 31 | }, 32 | 33 | props: { 34 | id: { 35 | type: String, 36 | required: true 37 | }, 38 | transition: { 39 | type: [Boolean, String], 40 | default: 'tab-transition' 41 | }, 42 | reverseTransition: { 43 | type: [Boolean, String], 44 | default: 'tab-reverse-transition' 45 | } 46 | }, 47 | 48 | computed: { 49 | computedTransition () { 50 | return this.reverse ? this.reverseTransition : this.transition 51 | } 52 | }, 53 | 54 | methods: { 55 | toggle (target, reverse, showTransition) { 56 | this.$el.style.transition = !showTransition ? 'none' : null 57 | this.reverse = reverse 58 | this.isActive = this.id === target 59 | } 60 | }, 61 | 62 | mounted () { 63 | this.registerContent(this.id, this.toggle) 64 | }, 65 | 66 | beforeDestroy () { 67 | this.unregisterContent(this.id) 68 | }, 69 | 70 | render (h) { 71 | const data = { 72 | staticClass: 'tabs__content', 73 | directives: [{ 74 | name: 'show', 75 | value: this.isActive 76 | }], 77 | on: this.$listeners 78 | } 79 | 80 | if (this.id) data.domProps = { id: this.id } 81 | 82 | const div = h('div', data, this.showLazyContent(this.$slots.default)) 83 | 84 | if (!this.computedTransition) return div 85 | 86 | return h('transition', { 87 | props: { name: this.computedTransition } 88 | }, [div]) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/components/VTabs/VTabsItems.js: -------------------------------------------------------------------------------- 1 | import Touch from '../../directives/touch' 2 | 3 | export default { 4 | name: 'v-tabs-items', 5 | 6 | directives: { Touch }, 7 | 8 | inject: ['next', 'prev'], 9 | 10 | props: { 11 | cycle: Boolean, 12 | touchless: Boolean 13 | }, 14 | 15 | methods: { 16 | swipeLeft () { 17 | this.next(this.cycle) 18 | }, 19 | swipeRight () { 20 | this.prev(this.cycle) 21 | } 22 | }, 23 | 24 | render (h) { 25 | const data = { 26 | staticClass: 'tabs__items', 27 | directives: [] 28 | } 29 | 30 | !this.touchless && data.directives.push({ 31 | name: 'touch', 32 | value: { 33 | left: this.swipeLeft, 34 | right: this.swipeRight 35 | } 36 | }) 37 | 38 | return h('div', data, this.$slots.default) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/components/VTabs/VTabsSlider.js: -------------------------------------------------------------------------------- 1 | import Colorable from '../../mixins/colorable' 2 | 3 | export default { 4 | name: 'v-tabs-slider', 5 | 6 | mixins: [Colorable], 7 | 8 | render (h) { 9 | return h('li', { 10 | staticClass: 'tabs__slider', 11 | class: this.addBackgroundColorClassChecks() 12 | }) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/VTabs/VTabsSlider.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from '~util/testing' 2 | import { mount } from 'avoriaz' 3 | import VTabsSlider from '~components/VTabs/VTabsSlider.js' 4 | 5 | test('VTabsSlider.vue', () => { 6 | it('should render a tabs slider', () => { 7 | const wrapper = mount(VTabsSlider, { 8 | propsData: { 9 | color: 'blue lighten-1' 10 | } 11 | }) 12 | 13 | expect(wrapper.element.classList).toContain('blue') 14 | expect(wrapper.element.classList).toContain('lighten-1') 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /src/components/VTabs/index.js: -------------------------------------------------------------------------------- 1 | import VTabs from './VTabs' 2 | import VTabsBar from './VTabsBar' 3 | import VTabsContent from './VTabsContent' 4 | import VTabsItem from './VTabsItem' 5 | import VTabsItems from './VTabsItems' 6 | import VTabsSlider from './VTabsSlider' 7 | 8 | VTabs.install = function install (Vue) { 9 | Vue.component(VTabs.name, VTabs) 10 | Vue.component(VTabsBar.name, VTabsBar) 11 | Vue.component(VTabsContent.name, VTabsContent) 12 | Vue.component(VTabsItem.name, VTabsItem) 13 | Vue.component(VTabsItems.name, VTabsItems) 14 | Vue.component(VTabsSlider.name, VTabsSlider) 15 | } 16 | 17 | export default VTabs 18 | -------------------------------------------------------------------------------- /src/components/VTextField/index.js: -------------------------------------------------------------------------------- 1 | import VTextField from './VTextField' 2 | 3 | VTextField.install = function install (Vue) { 4 | Vue.component(VTextField.name, VTextField) 5 | } 6 | 7 | export default VTextField 8 | -------------------------------------------------------------------------------- /src/components/VTimePicker/index.js: -------------------------------------------------------------------------------- 1 | import VTimePicker from './VTimePicker' 2 | 3 | VTimePicker.install = function install (Vue) { 4 | Vue.component(VTimePicker.name, VTimePicker) 5 | } 6 | 7 | export default VTimePicker 8 | -------------------------------------------------------------------------------- /src/components/VTimePicker/mixins/time-title.js: -------------------------------------------------------------------------------- 1 | export default { 2 | methods: { 3 | genTitle () { 4 | const children = [this.genTime()] 5 | 6 | if (this.format === 'ampm') { 7 | children.push(this.genAMPM()) 8 | } 9 | 10 | return this.genPickerTitle(children) 11 | }, 12 | genTime () { 13 | let hour = this.hour 14 | 15 | if (this.is24hr && hour < 10) { 16 | hour = `0${hour}` 17 | } 18 | 19 | return this.$createElement('div', { 20 | 'class': 'picker--time__title' 21 | }, [ 22 | this.$createElement('span', { 23 | 'class': { active: this.selectingHour }, 24 | on: { 25 | click: () => (this.selectingHour = true) 26 | } 27 | }, hour), 28 | this.$createElement('span', { 29 | 'class': { active: !this.selectingHour }, 30 | on: { 31 | click: () => (this.selectingHour = false) 32 | } 33 | }, `:${this.minute}`) 34 | ]) 35 | }, 36 | genAMPM () { 37 | return this.$createElement('div', [ 38 | this.genPeriod('am'), 39 | this.genPeriod('pm') 40 | ]) 41 | }, 42 | genPeriod (period) { 43 | return this.$createElement('span', { 44 | 'class': { active: this.period === period }, 45 | on: { click: () => (this.period = period) } 46 | }, period.toUpperCase()) 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/components/VToolbar/VToolbar.spec.js: -------------------------------------------------------------------------------- 1 | import { test } from '~util/testing' 2 | import { mount } from 'avoriaz' 3 | import VToolbar from '~components/VToolbar' 4 | 5 | test('VToolbar.vue', () => { 6 | it('should render a colored toolbar', () => { 7 | const wrapper = mount(VToolbar, { 8 | propsData: { 9 | color: 'blue lighten-1' 10 | } 11 | }) 12 | 13 | expect(wrapper.element.classList).toContain('blue') 14 | expect(wrapper.element.classList).toContain('lighten-1') 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /src/components/VToolbar/VToolbarSideIcon.js: -------------------------------------------------------------------------------- 1 | import VBtn from '../../components/VBtn' 2 | import VIcon from '../../components/VIcon' 3 | 4 | export default { 5 | name: 'v-toolbar-side-icon', 6 | 7 | functional: true, 8 | 9 | render (h, { slots, listeners, props, data }) { 10 | const classes = data.staticClass 11 | ? `${data.staticClass} toolbar__side-icon` 12 | : 'toolbar__side-icon' 13 | 14 | const d = Object.assign(data, { 15 | staticClass: classes, 16 | props: Object.assign(props, { 17 | icon: true 18 | }), 19 | on: listeners 20 | }) 21 | 22 | const defaultSlot = slots().default 23 | 24 | return h(VBtn, d, defaultSlot || [h(VIcon, 'menu')]) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/components/VToolbar/__snapshots__/VToolbarSideIcon.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`VToolbarSideIcon.js should create default icon when no slot used 1`] = ` 4 | 5 | 15 | 16 | `; 17 | 18 | exports[`VToolbarSideIcon.js should create slot icon when present 1`] = ` 19 | 20 | 29 | 30 | `; 31 | -------------------------------------------------------------------------------- /src/components/VToolbar/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | createSimpleFunctional 3 | } from '../../util/helpers' 4 | 5 | import VToolbar from './VToolbar' 6 | import VToolbarSideIcon from './VToolbarSideIcon' 7 | 8 | export { VToolbar, VToolbarSideIcon } 9 | export const VToolbarTitle = createSimpleFunctional('toolbar__title') 10 | export const VToolbarItems = createSimpleFunctional('toolbar__items') 11 | 12 | VToolbar.install = function install (Vue) { 13 | Vue.component('v-toolbar', VToolbar) 14 | Vue.component('v-toolbar-items', VToolbarItems) 15 | Vue.component('v-toolbar-title', VToolbarTitle) 16 | Vue.component('v-toolbar-side-icon', VToolbarSideIcon) 17 | } 18 | 19 | export default VToolbar 20 | -------------------------------------------------------------------------------- /src/components/VTooltip/index.js: -------------------------------------------------------------------------------- 1 | import VTooltip from './VTooltip' 2 | 3 | VTooltip.install = function install (Vue) { 4 | Vue.component(VTooltip.name, VTooltip) 5 | } 6 | 7 | export default VTooltip 8 | -------------------------------------------------------------------------------- /src/components/Vuetify/index.js: -------------------------------------------------------------------------------- 1 | import load from '../../util/load' 2 | 3 | const Vuetify = { 4 | install (Vue, opts = {}) { 5 | const $vuetify = { 6 | load, 7 | application: { 8 | bar: 0, 9 | top: 0, 10 | bottom: 0, 11 | left: 0, 12 | right: 0 13 | }, 14 | breakpoint: {}, 15 | touchSupport: false 16 | } 17 | 18 | Vue.util.defineReactive({}, 'breakpoint', $vuetify) 19 | Vue.util.defineReactive({}, 'application', $vuetify) 20 | 21 | Vue.prototype.$vuetify = $vuetify 22 | 23 | if (opts.transitions) { 24 | Object.keys(opts.transitions).forEach(key => { 25 | const t = opts.transitions[key] 26 | if (t.name !== undefined && t.name.startsWith('v-')) { 27 | Vue.component(t.name, t) 28 | } 29 | }) 30 | } 31 | 32 | if (opts.directives) { 33 | Object.keys(opts.directives).forEach(key => { 34 | const d = opts.directives[key] 35 | Vue.directive(d.name, d) 36 | }) 37 | } 38 | 39 | if (opts.components) { 40 | Object.keys(opts.components).forEach(key => { 41 | const c = opts.components[key] 42 | Vue.use(c) 43 | }) 44 | } 45 | } 46 | } 47 | 48 | export default Vuetify 49 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Vuetify } from './Vuetify' 2 | export { default as VApp } from './VApp' 3 | export { default as VBtn } from './VBtn' 4 | export { default as VForm } from './VForm' 5 | export { default as VIcon } from './VIcon' 6 | export { default as VList } from './VList' 7 | export { default as VMenu } from './VMenu' 8 | export { default as VPagination } from './VPagination' 9 | export { default as VSelect } from './VSelect' 10 | export { default as VSnackbar } from './VSnackbar' 11 | export { default as VTextField } from './VTextField' 12 | export { default as VToolbar } from './VToolbar' 13 | export { default as VChip } from './VChip' 14 | export { default as VDivider } from './VDivider' 15 | export { default as VCarousel } from './VCarousel' 16 | -------------------------------------------------------------------------------- /src/components/transitions/expand-transition.js: -------------------------------------------------------------------------------- 1 | import { addOnceEventListener } from '../../util/helpers' 2 | 3 | export default function (expandedParentClass = '') { 4 | return { 5 | enter (el, done) { 6 | el._parent = el.parentNode 7 | 8 | addOnceEventListener(el, 'transitionend', done) 9 | 10 | // Get height that is to be scrolled 11 | el.style.overflow = 'hidden' 12 | el.style.height = 0 13 | el.style.display = 'block' 14 | expandedParentClass && el._parent.classList.add(expandedParentClass) 15 | 16 | setTimeout(() => (el.style.height = `${el.scrollHeight}px`), 0) 17 | }, 18 | 19 | afterEnter (el) { 20 | el.style.overflow = null 21 | el.style.height = null 22 | }, 23 | 24 | leave (el, done) { 25 | // Remove initial transition 26 | addOnceEventListener(el, 'transitionend', done) 27 | 28 | // Set height before we transition to 0 29 | el.style.overflow = 'hidden' 30 | el.style.height = `${el.offsetHeight}px` 31 | 32 | setTimeout(() => (el.style.height = 0), 100) 33 | }, 34 | 35 | afterLeave (el) { 36 | expandedParentClass && el._parent.classList.remove(expandedParentClass) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/directives/index.js: -------------------------------------------------------------------------------- 1 | import Ripple from './ripple' 2 | 3 | export { 4 | Ripple 5 | } 6 | 7 | export default function install (Vue) { 8 | Vue.directive('ripple', Ripple) 9 | } 10 | -------------------------------------------------------------------------------- /src/directives/resize.js: -------------------------------------------------------------------------------- 1 | function inserted (el, binding) { 2 | let cb = binding.value 3 | let debounce = 200 4 | 5 | if (typeof binding.value !== 'function') { 6 | cb = binding.value.value 7 | debounce = binding.value.debounce 8 | } 9 | 10 | let debounceTimeout = setTimeout(cb, debounce) 11 | const onResize = () => { 12 | clearTimeout(debounceTimeout) 13 | debounceTimeout = setTimeout(cb, debounce) 14 | } 15 | 16 | window.addEventListener('resize', onResize, { passive: true }) 17 | el._onResize = onResize 18 | 19 | onResize() 20 | } 21 | 22 | function unbind (el, binding) { 23 | window.removeEventListener('resize', el._onResize) 24 | } 25 | 26 | export default { 27 | name: 'resize', 28 | inserted, 29 | unbind 30 | } 31 | -------------------------------------------------------------------------------- /src/directives/ripple.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import { test } from '~util/testing' 3 | import { mount } from 'avoriaz' 4 | import Ripple from '~directives/ripple' 5 | 6 | test('VRipple', () => { 7 | it('Ripple with no value should render data attribute true', () => { 8 | const testComponent = Vue.component('test', { 9 | directives: { 10 | Ripple 11 | }, 12 | render (h){ 13 | const data = { 14 | directives: [{ 15 | name: 'ripple' 16 | }] 17 | } 18 | return h('div', data) 19 | } 20 | }) 21 | 22 | const wrapper = mount(testComponent) 23 | 24 | const div = wrapper.find('div')[0] 25 | expect(div.getAttribute('data-ripple')).toBe('true') 26 | }) 27 | 28 | it('Ripple should update data attribute reactively', () => { 29 | const testComponent = Vue.component('test', { 30 | directives: { 31 | Ripple 32 | }, 33 | props: { 34 | ripple: Boolean, 35 | default: false 36 | }, 37 | render (h){ 38 | const data = { 39 | directives: [{ 40 | name: 'ripple', 41 | value: this.ripple 42 | }] 43 | } 44 | return h('div', data) 45 | } 46 | }) 47 | 48 | const wrapper = mount(testComponent, { 49 | propsData: { 50 | ripple: true 51 | } 52 | }) 53 | 54 | const div = wrapper.find('div')[0] 55 | expect(div.getAttribute('data-ripple')).toBe('true') 56 | 57 | wrapper.setProps({ ripple: false }) 58 | expect(div.getAttribute('data-ripple')).toBe('false') 59 | 60 | wrapper.setProps({ ripple: true }) 61 | expect(div.getAttribute('data-ripple')).toBe('true') 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /src/directives/scroll.js: -------------------------------------------------------------------------------- 1 | function inserted (el, binding) { 2 | const callback = typeof binding.value === 'function' 3 | ? binding.value 4 | : binding.value.callback 5 | const options = binding.value.options || { passive: true } 6 | let target = binding.value.target || window 7 | if (target === 'undefined') return 8 | 9 | if (target !== window) { 10 | target = document.querySelector(target) 11 | } 12 | 13 | target.addEventListener('scroll', callback, options) 14 | 15 | el._onScroll = { 16 | target, 17 | options 18 | } 19 | } 20 | 21 | function unbind (el, binding) { 22 | const { target, options } = el._onScroll 23 | 24 | target.removeEventListener('scroll', binding.callback, options) 25 | } 26 | 27 | export default { 28 | name: 'scroll', 29 | inserted, 30 | unbind 31 | } 32 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | require('./stylus/app.styl') 2 | import Semver from 'semver' 3 | import { devDependencies, version } from '../package.json' 4 | import * as components from './components' 5 | import * as directives from './directives' 6 | 7 | function Vuetify (Vue) { 8 | const Vuetify = components.Vuetify 9 | 10 | Vue.use(Vuetify, { 11 | components, 12 | directives 13 | }) 14 | } 15 | 16 | Vuetify.version = version 17 | 18 | function checkVueVersion () { 19 | const vueDep = devDependencies.vue 20 | if (!Semver.satisfies(window.Vue.version, vueDep)) { 21 | console.warn(`Vuetify requires Vue version ${vueDep}`) 22 | } 23 | } 24 | 25 | if (typeof window !== 'undefined' && window.Vue) { 26 | window.Vue.version && checkVueVersion() 27 | window.Vue.use(Vuetify) 28 | } 29 | 30 | export default Vuetify 31 | -------------------------------------------------------------------------------- /src/mixins/applicationable.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | app: Boolean 4 | }, 5 | 6 | watch: { 7 | app () { 8 | this.updateApplication() 9 | } 10 | }, 11 | 12 | created () { 13 | this.updateApplication() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/mixins/bootable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Bootable 3 | * @mixin 4 | * 5 | * Used to add lazy content functionality to components 6 | * Looks for change in "isActive" to automatically boot 7 | * Otherwise can be set manually 8 | */ 9 | export default { 10 | data: () => ({ 11 | isBooted: false 12 | }), 13 | 14 | props: { 15 | lazy: Boolean 16 | }, 17 | 18 | watch: { 19 | isActive () { 20 | this.isBooted = true 21 | } 22 | }, 23 | 24 | methods: { 25 | showLazyContent (content) { 26 | return (this.isBooted || !this.lazy) 27 | ? content 28 | : null 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/mixins/button-group.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data () { 3 | return { 4 | buttons: [], 5 | listeners: [] 6 | } 7 | }, 8 | 9 | methods: { 10 | getValue (i) { 11 | if (this.buttons[i].value != null) { 12 | return this.buttons[i].value 13 | } 14 | 15 | // Fix for testing, this should always be false in the browser 16 | if (this.buttons[i].$el.value != null && this.buttons[i].$el.value !== '') { 17 | return this.buttons[i].$el.value 18 | } 19 | 20 | return i 21 | }, 22 | update () { 23 | const selected = [] 24 | 25 | this.buttons 26 | .forEach((button, i) => { 27 | const elm = button.$el 28 | 29 | // Fix for testing, dataset does not exist on elm? 30 | if (!elm.dataset) elm.dataset = {} 31 | 32 | elm.removeAttribute('data-only-child') 33 | 34 | if (this.isSelected(i)) { 35 | elm.setAttribute('data-selected', true) 36 | 37 | if (!elm.classList.contains('btn--router')) { 38 | elm.classList.add('btn--active') 39 | } 40 | 41 | selected.push(i) 42 | } else { 43 | elm.removeAttribute('data-selected') 44 | 45 | if (!elm.classList.contains('btn--router')) { 46 | elm.classList.remove('btn--active') 47 | } 48 | } 49 | 50 | elm.dataset.index = i 51 | }) 52 | 53 | if (selected.length === 1) { 54 | this.buttons[selected[0]].$el.setAttribute('data-only-child', true) 55 | } 56 | } 57 | }, 58 | 59 | mounted () { 60 | this.$vuetify.load(() => { 61 | this.buttons = this.$children 62 | 63 | this.buttons.forEach((button, i) => { 64 | this.listeners.push(this.updateValue.bind(this, i)) 65 | button.$on('click', this.listeners[i]) 66 | }) 67 | 68 | this.update() 69 | }) 70 | }, 71 | 72 | beforeDestroy () { 73 | this.buttons.forEach((button, i) => { 74 | button.$off('click', this.listeners[i]) 75 | }) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/mixins/colorable.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | color: String 4 | }, 5 | 6 | data () { 7 | return { 8 | defaultColor: null 9 | } 10 | }, 11 | 12 | computed: { 13 | computedColor () { 14 | return this.color || this.defaultColor 15 | } 16 | }, 17 | 18 | methods: { 19 | addBackgroundColorClassChecks (classes = {}, prop = 'computedColor') { 20 | if (prop && this[prop]) { 21 | classes[this[prop]] = true 22 | } 23 | return classes 24 | }, 25 | addTextColorClassChecks (classes = {}, prop = 'computedColor') { 26 | if (prop && this[prop]) { 27 | const parts = this[prop].trim().split(' ') 28 | 29 | let color = parts[0] + '--text' 30 | 31 | if (parts.length > 1) color += ' text--' + parts[1] 32 | 33 | classes[color] = !!this[prop] 34 | } 35 | 36 | return classes 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/mixins/contextualable.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | primary: Boolean, 4 | secondary: Boolean, 5 | success: Boolean, 6 | info: Boolean, 7 | warning: Boolean, 8 | error: Boolean 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/mixins/delayable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Delayable 3 | * 4 | * @mixin 5 | * 6 | * Changes the open or close 7 | * delay time for elements 8 | */ 9 | export default { 10 | data: () => ({ 11 | openTimeout: null, 12 | closeTimeout: null 13 | }), 14 | 15 | props: { 16 | openDelay: { 17 | type: [Number, String], 18 | default: 0 19 | }, 20 | closeDelay: { 21 | type: [Number, String], 22 | default: 200 23 | } 24 | }, 25 | 26 | methods: { 27 | /** 28 | * Clear any pending delay 29 | * timers from executing 30 | * 31 | * @return {void} 32 | */ 33 | clearDelay () { 34 | clearTimeout(this.openTimeout) 35 | clearTimeout(this.closeTimeout) 36 | }, 37 | /** 38 | * Runs callback after 39 | * a specified delay 40 | * 41 | * @param {String} type 42 | * @param {Function} cb 43 | * 44 | * @return {void} 45 | */ 46 | runDelay (type, cb) { 47 | this.clearDelay() 48 | 49 | const delay = parseInt(this[`${type}Delay`], 10) 50 | 51 | this[`${type}Timeout`] = setTimeout(cb, delay) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/mixins/dependent.js: -------------------------------------------------------------------------------- 1 | function searchChildren (children) { 2 | const results = [] 3 | for (const child of children) { 4 | if (child.isActive && child.isDependent) { 5 | results.push(child) 6 | } else { 7 | results.push(...searchChildren(child.$children)) 8 | } 9 | } 10 | 11 | return results 12 | } 13 | 14 | export default { 15 | data () { 16 | return { 17 | closeDependents: true, 18 | isDependent: true 19 | } 20 | }, 21 | 22 | methods: { 23 | getOpenDependents () { 24 | if (this.closeDependents) return searchChildren(this.$children) 25 | 26 | return [] 27 | }, 28 | getOpenDependentElements () { 29 | const result = [] 30 | 31 | for (const dependent of this.getOpenDependents()) { 32 | result.push(...dependent.getClickableDependentElements()) 33 | } 34 | 35 | return result 36 | }, 37 | getClickableDependentElements () { 38 | const result = [this.$el] 39 | if (this.$refs.content) result.push(this.$refs.content) 40 | result.push(...this.getOpenDependentElements()) 41 | 42 | return result 43 | } 44 | }, 45 | 46 | watch: { 47 | isActive (val) { 48 | if (val) return 49 | 50 | for (const dependent of this.getOpenDependents()) { 51 | dependent.isActive = false 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/mixins/detachable.js: -------------------------------------------------------------------------------- 1 | import Bootable from './bootable' 2 | 3 | export default { 4 | mixins: [Bootable], 5 | 6 | props: { 7 | contentClass: { 8 | default: '' 9 | } 10 | }, 11 | 12 | mounted () { 13 | this.$vuetify.load(this.initDetach) 14 | }, 15 | 16 | beforeDestroy () { 17 | if (!this.$refs.content) return 18 | 19 | // IE11 Fix 20 | try { 21 | this.$refs.content.parentNode.removeChild(this.$refs.content) 22 | } catch (e) {} 23 | }, 24 | 25 | methods: { 26 | initDetach () { 27 | if (this._isDestroyed) return 28 | 29 | const app = document.querySelector('[data-app]') 30 | 31 | if (!app) { 32 | return console.warn('Application is missing component.') 33 | } 34 | 35 | // If child has already been removed, bail 36 | if (!this.$refs.content) return 37 | 38 | app.insertBefore( 39 | this.$refs.content, 40 | app.firstChild 41 | ) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/mixins/filterable.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | noDataText: { 4 | type: String, 5 | default: 'No data available' 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/mixins/loadable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Loadable 3 | * 4 | * @mixin 5 | * 6 | * Used to add linear progress bar to components 7 | * Can use a default bar with a specific color 8 | * or designate a custom progress linear bar 9 | */ 10 | export default { 11 | props: { 12 | loading: { 13 | type: [Boolean, String], 14 | default: false 15 | } 16 | }, 17 | 18 | methods: { 19 | genProgress () { 20 | if (this.loading === false) return null 21 | 22 | return this.$slots.progress || this.$createElement('v-progress-linear', { 23 | props: { 24 | color: (this.loading === true || this.loading === '') 25 | ? (this.color || 'primary') 26 | : this.loading, 27 | height: 2, 28 | indeterminate: true 29 | } 30 | }) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/mixins/picker.js: -------------------------------------------------------------------------------- 1 | import Colorable from './colorable' 2 | import Themeable from './themeable' 3 | 4 | export default { 5 | mixins: [Colorable, Themeable], 6 | 7 | data () { 8 | return { 9 | defaultColor: 'accent', 10 | isSaving: false 11 | } 12 | }, 13 | 14 | props: { 15 | actions: Boolean, 16 | autosave: Boolean, 17 | headerColor: String, 18 | landscape: Boolean, 19 | noTitle: Boolean, 20 | scrollable: Boolean, 21 | value: { 22 | required: true 23 | } 24 | }, 25 | 26 | computed: { 27 | titleColor () { 28 | // If no headerColor/color is set then the default 29 | // title color will be taken from styles 30 | return this.headerColor || this.color 31 | } 32 | }, 33 | 34 | methods: { 35 | save () {}, 36 | cancel () {}, 37 | genSlot () { 38 | return this.$scopedSlots.default({ 39 | save: this.save, 40 | cancel: this.cancel 41 | }) 42 | }, 43 | genPickerTitle (children) { 44 | return this.$createElement('div', { 45 | staticClass: 'picker__title', 46 | 'class': this.addBackgroundColorClassChecks({}, 'titleColor') 47 | }, children) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/mixins/positionable.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | absolute: Boolean, 4 | bottom: Boolean, 5 | fixed: Boolean, 6 | left: Boolean, 7 | right: Boolean, 8 | top: Boolean 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/mixins/rippleable.js: -------------------------------------------------------------------------------- 1 | import Ripple from '../directives/ripple' 2 | 3 | /** @mixin */ 4 | export default { 5 | directives: { Ripple }, 6 | 7 | methods: { 8 | genRipple () { 9 | return this.$createElement('div', { 10 | 'class': this.rippleClasses || 'input-group--selection-controls__ripple', 11 | on: Object.assign({}, { 12 | click: this.toggle 13 | }, this.$listeners), 14 | directives: [{ 15 | name: 'ripple', 16 | value: !this.disabled && { center: true } 17 | }] 18 | }) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/mixins/routable.js: -------------------------------------------------------------------------------- 1 | import Ripple from '../directives/ripple' 2 | 3 | export default { 4 | directives: { 5 | Ripple 6 | }, 7 | 8 | props: { 9 | activeClass: String, 10 | append: Boolean, 11 | disabled: Boolean, 12 | exact: Boolean, 13 | exactActiveClass: String, 14 | href: [String, Object], 15 | to: [String, Object], 16 | nuxt: Boolean, 17 | replace: Boolean, 18 | ripple: Boolean, 19 | tag: String, 20 | target: String 21 | }, 22 | 23 | methods: { 24 | click () {}, 25 | generateRouteLink () { 26 | let exact = this.exact 27 | let tag 28 | 29 | const data = { 30 | attrs: { disabled: this.disabled }, 31 | class: this.classes, 32 | props: {}, 33 | directives: [{ 34 | name: 'ripple', 35 | value: this.ripple || false 36 | }], 37 | on: { 38 | ...(this.$listeners || {}), 39 | click: this.click 40 | } 41 | } 42 | 43 | if (typeof this.exact === 'undefined') { 44 | exact = this.to === '/' || 45 | (this.to === Object(this.to) && this.to.path === '/') 46 | } 47 | 48 | if (this.to) { 49 | // Add a special activeClass hook 50 | // for component level styles 51 | let activeClass = this.activeClass 52 | let exactActiveClass = this.exactActiveClass || activeClass 53 | 54 | if (this.proxyClass) { 55 | activeClass += ' ' + this.proxyClass 56 | exactActiveClass += ' ' + this.proxyClass 57 | } 58 | 59 | tag = this.nuxt ? 'nuxt-link' : 'router-link' 60 | data.props.to = this.to 61 | data.props.exact = exact 62 | data.props.activeClass = activeClass 63 | data.props.exactActiveClass = exactActiveClass 64 | data.props.append = this.append 65 | data.props.replace = this.replace 66 | } else { 67 | tag = this.href && 'a' || this.tag || 'a' 68 | 69 | if (tag === 'a') { 70 | data.attrs.href = this.href || 'javascript:;' 71 | if (this.target) data.attrs.target = this.target 72 | } 73 | } 74 | 75 | return { tag, data } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/mixins/selectable.js: -------------------------------------------------------------------------------- 1 | import Colorable from './colorable' 2 | import Input from './input' 3 | 4 | export default { 5 | mixins: [Input, Colorable], 6 | 7 | model: { 8 | prop: 'inputValue', 9 | event: 'change' 10 | }, 11 | 12 | data: () => ({ 13 | defaultColor: 'accent' 14 | }), 15 | 16 | props: { 17 | id: String, 18 | inputValue: null, 19 | falseValue: null, 20 | trueValue: null 21 | }, 22 | 23 | computed: { 24 | isActive () { 25 | if ((Array.isArray(this.inputValue)) 26 | ) { 27 | return this.inputValue.indexOf(this.value) !== -1 28 | } 29 | 30 | if (!this.trueValue || !this.falseValue) { 31 | return this.value 32 | ? this.value === this.inputValue 33 | : Boolean(this.inputValue) 34 | } 35 | 36 | return this.inputValue === this.trueValue 37 | }, 38 | isDirty () { 39 | return this.isActive 40 | } 41 | }, 42 | 43 | watch: { 44 | indeterminate (val) { 45 | this.inputIndeterminate = val 46 | } 47 | }, 48 | 49 | methods: { 50 | genLabel () { 51 | return this.$createElement('label', { 52 | on: { click: this.toggle }, 53 | attrs: { 54 | for: this.id 55 | } 56 | }, this.$slots.label || this.label) 57 | }, 58 | toggle () { 59 | if (this.disabled) { 60 | return 61 | } 62 | 63 | let input = this.inputValue 64 | if (Array.isArray(input)) { 65 | input = input.slice() 66 | const i = input.indexOf(this.value) 67 | 68 | if (i === -1) { 69 | input.push(this.value) 70 | } else { 71 | input.splice(i, 1) 72 | } 73 | } else if (this.trueValue || this.falseValue) { 74 | input = input === this.trueValue ? this.falseValue : this.trueValue 75 | } else if (this.value) { 76 | input = this.value === this.inputValue 77 | ? null 78 | : this.value 79 | } else { 80 | input = !input 81 | } 82 | 83 | this.validate(true, input) 84 | 85 | this.$emit('change', input) 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/mixins/stackable.js: -------------------------------------------------------------------------------- 1 | import { getZIndex } from '../util/helpers' 2 | 3 | export default { 4 | data () { 5 | return { 6 | stackBase: null, 7 | stackClass: 'unpecified', 8 | stackElement: null, 9 | stackExclude: null, 10 | stackMinZIndex: 0 11 | } 12 | }, 13 | computed: { 14 | activeZIndex () { 15 | const content = this.stackElement || this.$refs.content 16 | // Return current zindex if not active 17 | if (!this.isActive) return getZIndex(content) 18 | 19 | // Return max current z-index (excluding self) + 2 20 | // (2 to leave room for an overlay below, if needed) 21 | return this.getMaxZIndex(this.stackExclude || [content]) + 2 22 | } 23 | }, 24 | methods: { 25 | getMaxZIndex (exclude = []) { 26 | const base = this.stackBase || this.$el 27 | // Start with lowest allowed z-index or z-index of 28 | // base component's element, whichever is greater 29 | const zis = [this.stackMinZIndex, getZIndex(base)] 30 | // Convert the NodeList to an array to 31 | // prevent an Edge bug with Symbol.iterator 32 | // https://github.com/vuetifyjs/vuetify/issues/2146 33 | const activeElements = [...document.getElementsByClassName(this.stackClass)] 34 | 35 | // Get z-index for all active dialogs 36 | for (const activeElement of activeElements) { 37 | if (!exclude.includes(activeElement)) { 38 | zis.push(getZIndex(activeElement)) 39 | } 40 | } 41 | 42 | return Math.max(...zis) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/mixins/tab-focusable.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data () { 3 | return { 4 | tabFocused: false 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/mixins/themeable.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | dark: Boolean, 4 | light: Boolean 5 | }, 6 | 7 | computed: { 8 | themeClasses () { 9 | return { 10 | 'theme--light': this.light, 11 | 'theme--dark': this.dark 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/mixins/toggleable.js: -------------------------------------------------------------------------------- 1 | export function factory (prop = 'value', event = 'input') { 2 | return { 3 | model: { prop, event }, 4 | 5 | props: { 6 | [prop]: { required: false } 7 | }, 8 | 9 | data () { 10 | return { 11 | isActive: !!this[prop] 12 | } 13 | }, 14 | 15 | watch: { 16 | [prop] (val) { 17 | this.isActive = !!val 18 | }, 19 | isActive (val) { 20 | !!val !== this[prop] && this.$emit(event, val) 21 | } 22 | } 23 | } 24 | } 25 | 26 | const Toggleable = factory() 27 | 28 | export default Toggleable 29 | -------------------------------------------------------------------------------- /src/mixins/transitionable.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | mode: String, 4 | origin: String, 5 | transition: String 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/mixins/translatable.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data () { 3 | return { 4 | parallax: null, 5 | parallaxDist: null, 6 | percentScrolled: null, 7 | scrollTop: null, 8 | windowHeight: null, 9 | windowBottom: null 10 | } 11 | }, 12 | 13 | computed: { 14 | normalizedHeight () { 15 | if (this.jumbotron) { 16 | return isNaN(this.height) ? this.height : `${this.height}px` 17 | } 18 | 19 | return Number(this.height.toString().replace(/(^[0-9]*$)/, '$1')) 20 | }, 21 | 22 | imgHeight () { 23 | return this.objHeight() 24 | } 25 | }, 26 | 27 | mounted () { 28 | this.$vuetify.load(this.init) 29 | }, 30 | 31 | beforeDestroy () { 32 | window.removeEventListener('scroll', this.translate, false) 33 | window.removeEventListener('resize', this.translate, false) 34 | }, 35 | 36 | methods: { 37 | listeners () { 38 | window.addEventListener('scroll', this.translate, false) 39 | window.addEventListener('resize', this.translate, false) 40 | }, 41 | 42 | translate () { 43 | this.calcDimensions() 44 | 45 | this.percentScrolled = ( 46 | (this.windowBottom - this.elOffsetTop) / 47 | (this.normalizedHeight + this.windowHeight) 48 | ) 49 | 50 | this.parallax = Math.round(this.parallaxDist * this.percentScrolled) 51 | 52 | if (this.translated) { 53 | this.translated() 54 | } 55 | }, 56 | 57 | calcDimensions () { 58 | const offset = this.$el.getBoundingClientRect() 59 | 60 | this.scrollTop = window.pageYOffset 61 | this.parallaxDist = this.imgHeight - this.normalizedHeight 62 | this.elOffsetTop = offset.top + this.scrollTop 63 | this.windowHeight = window.innerHeight 64 | this.windowBottom = this.scrollTop + this.windowHeight 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/stylus/app.styl: -------------------------------------------------------------------------------- 1 | @require './settings/*' 2 | @require './generic/_transitions.styl' 3 | @require './elements/*' 4 | @require './tools/*' 5 | @require './trumps/*' 6 | @require './components/_tooltips.styl' 7 | -------------------------------------------------------------------------------- /src/stylus/bootstrap.styl: -------------------------------------------------------------------------------- 1 | @import './settings/*' 2 | -------------------------------------------------------------------------------- /src/stylus/components/_alerts.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | 3 | .alert 4 | border-radius: $alert-border-radius 5 | border-width: $alert-border-width 6 | border-style: solid 7 | color: #fff 8 | display: flex 9 | font-size: $alert-font-size 10 | margin: $alert-margin 11 | padding: $alert-padding 12 | position: relative 13 | transition: $primary-transition 14 | 15 | .alert__icon.icon, 16 | &__dismissible .icon 17 | align-self: center 18 | color: $alert-icon-color 19 | font-size: $alert-icon-font-size 20 | 21 | &__icon 22 | margin-right: $alert-padding 23 | 24 | &__dismissible 25 | align-self: flex-start 26 | margin-left: $alert-padding 27 | margin-right: 0 28 | text-decoration: none 29 | transition: $primary-transition 30 | 31 | &:hover 32 | color: lighten($alert-icon-color, 10%) 33 | 34 | &--no-icon 35 | .alert__icon 36 | display: none 37 | 38 | > div 39 | flex: 1 40 | 41 | @media screen and (max-width: $grid-breakpoints.sm) 42 | &__icon 43 | display: none 44 | 45 | // Double .alert is intended - it increases css specificity 46 | // to properly set the border color where alert has set color 47 | // with modifier (for example "red lighten-2") 48 | .alert.alert 49 | border-color: $material-light.dividers !important 50 | -------------------------------------------------------------------------------- /src/stylus/components/_app.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | @import '../theme' 3 | 4 | .application 5 | -webkit-backface-visibility: hidden 6 | display: flex 7 | min-height: 100vh 8 | flex-direction: column 9 | 10 | // TODO: Deprecate 11 | > main:not(.content) 12 | flex: 1 0 auto 13 | display: flex 14 | flex-direction: column 15 | 16 | application($material) 17 | background: $material.background 18 | color: $material.text.primary 19 | 20 | a 21 | color: $material.text.link 22 | 23 | app(application) 24 | -------------------------------------------------------------------------------- /src/stylus/components/_avatars.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | 3 | .avatar 4 | align-items: center 5 | border-radius: 50% 6 | display: inline-flex 7 | justify-content: center 8 | text-align: center 9 | vertical-align: middle 10 | 11 | img, 12 | .icon 13 | border-radius: 50% 14 | height: inherit 15 | width: inherit 16 | 17 | &--tile 18 | border-radius: 0 19 | 20 | img, 21 | .icon 22 | border-radius: 0 -------------------------------------------------------------------------------- /src/stylus/components/_badges.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | 3 | .badge 4 | display: inline-block 5 | position: relative 6 | 7 | &__badge 8 | color: $badge-color 9 | display: flex 10 | position: absolute 11 | font-size: $badge-font-size 12 | top: -($badge-height / 2) 13 | right: -($badge-width) 14 | border-radius: $badge-border-radius 15 | height: $badge-height 16 | width: $badge-width 17 | justify-content: center 18 | align-items: center 19 | flex-direction: row 20 | flex-wrap: wrap 21 | transition: $primary-transition 22 | 23 | .icon 24 | font-size: $badge-font-size 25 | 26 | &--overlap 27 | .badge__badge 28 | top: -8px 29 | right: -8px 30 | 31 | &.badge--left 32 | .badge__badge 33 | left: -8px 34 | right: initial 35 | 36 | &.badge--bottom 37 | .badge__badge 38 | bottom: -8px 39 | top: initial 40 | 41 | &--left 42 | .badge__badge 43 | left: -($badge-width) 44 | 45 | &--bottom 46 | .badge__badge 47 | bottom: -($badge-height / 2) 48 | top: initial; 49 | -------------------------------------------------------------------------------- /src/stylus/components/_bottom-navs.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | 3 | .bottom-nav 4 | background: $theme.primary 5 | bottom: 0 6 | box-shadow: 0 3px 14px 2px rgba(#000, .12) 7 | display: flex 8 | height: 56px 9 | justify-content: center 10 | position: fixed 11 | transform: translate3d(0, 60px, 0) 12 | transition: all .4s $transition.swing 13 | width: 100% 14 | z-index: 4 15 | 16 | &--absolute 17 | position: absolute 18 | 19 | &--active 20 | transform: translate3d(0, 0, 0) 21 | 22 | .btn 23 | background: transparent !important 24 | border-radius: 0 25 | box-shadow: none !important 26 | font-weight: 400 27 | height: 100% 28 | margin: 0 29 | max-width: 168px 30 | min-width: 80px 31 | padding: 0px 32 | text-transform: none 33 | opacity: .5 34 | width: 100% 35 | 36 | .btn__content 37 | height: 100% 38 | flex-direction: column-reverse 39 | height: 56px 40 | font-size: 12px 41 | transform: scale3d(1, 1, 1) translate3d(0, 0, 0) 42 | white-space: nowrap 43 | will-change: font-size 44 | 45 | i.icon 46 | color: inherit 47 | transition: all .4s cubic-bezier(.25, .8, .50, 1) 48 | 49 | &--active 50 | opacity: 1 51 | 52 | .btn__content 53 | font-size: 14px 54 | 55 | &:before 56 | opacity: 0 57 | 58 | .icon 59 | transform: none 60 | 61 | &:not(.btn--active) 62 | filter: grayscale(100%) 63 | 64 | &--shift 65 | .btn__content 66 | font-size: 14px 67 | 68 | span 69 | height: 21px 70 | 71 | .btn 72 | transition: all .3s 73 | min-width: 56px 74 | max-width: 96px 75 | 76 | &--active 77 | min-width: 96px 78 | max-width: 168px 79 | 80 | .bottom-nav--shift 81 | .btn:not(.btn--active) .btn__content 82 | .icon 83 | transform: scale3d(1, 1, 1) translate3d(0, 10px, 0) 84 | 85 | span 86 | color: transparent 87 | -------------------------------------------------------------------------------- /src/stylus/components/_bottom-sheets.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | @import '../theme' 3 | 4 | .bottom-sheet.dialog 5 | align-self: flex-end 6 | border-radius: 0 7 | flex: 1 0 100% 8 | margin: 0 9 | min-width: 100% 10 | overflow: visible 11 | transition: .4s $transition.swing 12 | 13 | &.bottom-sheet--inset 14 | max-width: 70% 15 | min-width: 0 16 | 17 | @media $display-breakpoints.xs-only 18 | max-width: none 19 | -------------------------------------------------------------------------------- /src/stylus/components/_breadcrumbs.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | @import '../theme' 3 | 4 | breadcrumbs($material) 5 | li.breadcrumbs__divider, 6 | li:last-child .breadcrumbs__item, 7 | li .breadcrumbs__item--disabled 8 | color: $material.text.disabled 9 | 10 | theme(breadcrumbs, "breadcrumbs") 11 | 12 | .breadcrumbs 13 | align-items: center 14 | display: flex 15 | flex-wrap: wrap 16 | flex: $breadcrumbs-flex 17 | list-style-type: none 18 | margin: $breadcrumbs-margin 19 | padding: $breadcrumbs-padding 20 | 21 | li 22 | align-items: center 23 | display: inline-flex 24 | font-size: $breadcrumbs-item-font-size 25 | 26 | .icon 27 | font-size: $breadcrumbs-item-large-font-size 28 | 29 | &:last-child a 30 | cursor: default 31 | pointer-events: none 32 | 33 | &:nth-child(even) 34 | padding: $breadcrumbs-even-child-padding 35 | 36 | &--large 37 | li 38 | font-size: $breadcrumbs-item-large-font-size 39 | 40 | .icon 41 | font-size: $breadcrumbs-item-large-font-size 42 | 43 | &__item 44 | align-items: center 45 | display: inline-flex 46 | text-decoration: none 47 | transition: $primary-transition 48 | 49 | &--disabled 50 | pointer-events: none 51 | -------------------------------------------------------------------------------- /src/stylus/components/_button-toggle.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | @import '../theme' 3 | 4 | /** Themes */ 5 | 6 | btn-toggle($material) 7 | background: $material.cards 8 | 9 | .btn 10 | color: $material.text.primary 11 | 12 | &[data-selected]:not(:last-child):not([data-only-child]) 13 | border-right-color: $material.buttons.disabled 14 | 15 | theme(btn-toggle, "btn-toggle") 16 | 17 | .btn-toggle 18 | display: inline-flex 19 | border-radius: 2px 20 | transition: $primary-transition 21 | will-change: background, box-shadow 22 | 23 | .btn 24 | justify-content: center 25 | min-width: auto 26 | width: auto 27 | padding: 0 8px 28 | margin: 0 29 | opacity: .4 30 | border-radius: 0 31 | 32 | &:not(:last-child) 33 | border-right: 1px solid transparent 34 | 35 | &:after 36 | display: none 37 | 38 | &[data-selected] 39 | opacity: 1 40 | 41 | &__content 42 | padding: 0 43 | 44 | span + .icon 45 | font-size: initial 46 | margin-left: 10px 47 | 48 | &:first-child 49 | border-radius: 2px 0 0 2px 50 | 51 | &:last-child 52 | border-radius: 0 2px 2px 0 53 | 54 | &--selected 55 | elevation(2) 56 | -------------------------------------------------------------------------------- /src/stylus/components/_cards.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | @import '../theme' 3 | 4 | /* Themes */ 5 | card($material) 6 | background-color: $material.cards 7 | 8 | theme(card, "card") 9 | 10 | .card 11 | display: block 12 | border-radius: $card-border-radius 13 | min-width: 0 14 | position: relative 15 | text-decoration: none 16 | elevation(2) 17 | 18 | > *:first-child 19 | border-top-left-radius: inherit 20 | border-top-right-radius: inherit 21 | 22 | > *:last-child 23 | border-bottom-left-radius: inherit 24 | border-bottom-right-radius: inherit 25 | 26 | &--raised 27 | elevation(3) 28 | 29 | &--tile 30 | border-radius: 0 31 | 32 | &--flat 33 | elevation(0) 34 | 35 | &--hover 36 | cursor: pointer 37 | transition: all .4s cubic-bezier(.25, .8, .25, 1) 38 | transition-property: box-shadow 39 | 40 | &:hover 41 | elevation(8) 42 | 43 | &__title 44 | align-items: center 45 | display: flex 46 | flex-wrap: wrap 47 | padding: 16px 48 | 49 | &--primary 50 | padding-top: 24px 51 | 52 | &__text 53 | padding: 16px 54 | width: 100% 55 | 56 | &__media 57 | display: flex 58 | overflow: hidden 59 | position: relative 60 | 61 | img 62 | width: 100% 63 | 64 | &__background 65 | border-radius: inherit 66 | position: absolute 67 | left: 0 68 | top: 0 69 | width: 100% 70 | height: 100% 71 | 72 | &__content 73 | display: flex 74 | flex: 1 1 auto 75 | position: relative 76 | 77 | &__actions 78 | align-items: center 79 | display: flex 80 | padding: 8px 4px 81 | 82 | > *, 83 | .btn 84 | margin: 0 4px 85 | -------------------------------------------------------------------------------- /src/stylus/components/_carousel.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | 3 | .carousel 4 | height: 180px 5 | width: 100% 6 | position: relative 7 | overflow: hidden 8 | elevation(2) 9 | 10 | &__item 11 | display: flex 12 | align-items: center 13 | justify-content: center 14 | flex: 1 0 100% 15 | height: 100% 16 | width: 100% 17 | background-size: cover 18 | background-position: center center 19 | transition: .2s ease-out 20 | 21 | &__left, &__right 22 | position: absolute 23 | top: 50% 24 | z-index: 1 25 | transform: translateY(-50%) 26 | 27 | .btn 28 | color: #FFFFFF 29 | margin: 0 !important 30 | height: 30px 31 | width: 30px 32 | 33 | .icon 34 | margin: 0 35 | 36 | &:hover 37 | background: none 38 | 39 | &__left 40 | left: 5px 41 | 42 | &__right 43 | right: 5px 44 | 45 | &__controls 46 | background: rgba(0, 0, 0, .5) 47 | align-items: center 48 | bottom: 0 49 | display: flex 50 | justify-content: center 51 | left: 0 52 | position: absolute 53 | height: 50px 54 | list-style-type: none 55 | width: 100% 56 | z-index: 1 57 | 58 | &__item 59 | color: #FFFFFF 60 | margin: 0 1rem !important 61 | 62 | .icon 63 | opacity: .5 64 | transition: $primary-transition 65 | margin 0 66 | 67 | &--active 68 | .icon 69 | opacity: 1 70 | vertical-align: middle 71 | height: 18px 72 | width: 18px 73 | 74 | &:hover 75 | background: none 76 | 77 | .icon 78 | opacity: .8 79 | -------------------------------------------------------------------------------- /src/stylus/components/_content.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | 3 | .content 4 | flex: 1 0 auto 5 | max-width: 100% 6 | transition: .3s padding $transition.swing 7 | will-change: padding 8 | -------------------------------------------------------------------------------- /src/stylus/components/_dialogs.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | 3 | .dialog 4 | elevation(24) 5 | border-radius: $card-border-radius 6 | margin: 24px 7 | overflow-y: auto 8 | pointer-events: auto 9 | transition: .3s ease-in-out 10 | width: 100% 11 | 12 | &__content 13 | align-items: center 14 | display: flex 15 | height: 100% 16 | justify-content: center 17 | left: 0 18 | pointer-events: none 19 | position: fixed 20 | top: 0 21 | transition: .3s ease-in-out 22 | width: 100% 23 | z-index: 6 24 | outline: none 25 | 26 | &:not(.dialog--fullscreen) 27 | max-height: 90% 28 | 29 | &__container 30 | display: inline-block 31 | vertical-align: middle 32 | 33 | &--fullscreen 34 | margin 0 35 | height 100% 36 | position fixed 37 | overflow-y: auto 38 | top: 0 39 | left 0 40 | 41 | > 42 | .card 43 | min-height 100% 44 | min-width 100% 45 | margin 0 !important 46 | padding 0 !important 47 | 48 | &--scrollable 49 | display: flex 50 | overflow: hidden 51 | 52 | > 53 | .card 54 | display: flex 55 | flex: 1 1 auto 56 | flex-direction: column 57 | 58 | .card__title 59 | .card__actions 60 | flex: 1 0 auto 61 | 62 | .card__text 63 | overflow-y: auto 64 | backface-visibility: hidden 65 | -------------------------------------------------------------------------------- /src/stylus/components/_dividers.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | @import '../theme' 3 | 4 | divider($material) 5 | background-color: $material.dividers 6 | 7 | theme(divider, "divider") 8 | 9 | .divider 10 | border: none 11 | display: block 12 | height: 1px 13 | min-height: 1px 14 | flex: 1 15 | width: 100% 16 | 17 | &--inset 18 | margin-left: 72px 19 | width: calc(100% - 72px) -------------------------------------------------------------------------------- /src/stylus/components/_expansion-panel.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | @import '../theme' 3 | 4 | /** Theme */ 5 | expansion-panel($material) 6 | .expansion-panel__container 7 | border-top: 1px solid rgba($material.fg-color, $material.divider-percent) 8 | background-color: $material.cards 9 | color: $material.text.primary 10 | 11 | .expansion-panel__header 12 | .icon 13 | color: $material.icons.active 14 | 15 | &--focusable 16 | .expansion-panel__container 17 | &:focus 18 | background-color: $material.expansion-panels.focus 19 | 20 | theme(expansion-panel, "expansion-panel") 21 | 22 | .expansion-panel 23 | display: flex 24 | flex-wrap: wrap 25 | justify-content: center 26 | list-style-type: none 27 | padding: 0 28 | text-align: left 29 | width: 100% 30 | elevation(2) 31 | 32 | &__container 33 | flex: 1 0 100% 34 | outline: none 35 | transition: .3s $transition.swing 36 | 37 | &:first-child 38 | border-top: none !important 39 | 40 | .header__icon 41 | margin-left: auto 42 | 43 | .icon 44 | // Edge rendering issue TODO: revisit later 45 | transition: none 46 | 47 | &--active 48 | > .expansion-panel__header 49 | .header__icon .icon 50 | transform: rotate(-180deg) 51 | 52 | &__header 53 | display: flex 54 | cursor: pointer 55 | align-items: center 56 | position: relative 57 | padding: 12px 24px 58 | 59 | > *:not(.header__icon) 60 | flex: 1 1 auto 61 | 62 | &__body 63 | transition: $primary-transition 64 | 65 | .card 66 | border-radius: 0 67 | elevation(0) 68 | 69 | &--popout, &--inset 70 | elevation(0) 71 | 72 | .expansion-panel__container--active 73 | margin: $spacers.three.x 74 | elevation(3) 75 | 76 | &--popout, &--inset 77 | .expansion-panel__container 78 | max-width: 95% 79 | 80 | &--popout 81 | .expansion-panel__container--active 82 | max-width: 100% 83 | 84 | &--inset 85 | .expansion-panel__container--active 86 | max-width: 85% 87 | -------------------------------------------------------------------------------- /src/stylus/components/_footer.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | @import '../theme' 3 | 4 | footer($material) 5 | background: $material.app-bar 6 | 7 | theme(footer, "footer") 8 | 9 | .footer 10 | align-items: center 11 | display: flex 12 | flex: 0 1 auto !important // Don't let devs break their code 13 | min-height: $footer-height 14 | transition: .3s $transition.swing 15 | 16 | &--absolute, &--fixed 17 | bottom: 0 18 | left: 0 19 | width: 100% 20 | z-index: 3 21 | 22 | &--absolute 23 | position: absolute 24 | 25 | &--fixed 26 | position: fixed 27 | 28 | > *:first-child 29 | margin-left: $grid-gutters.md 30 | 31 | > *:last-child 32 | margin-right: $grid-gutters.md 33 | 34 | @media $display-breakpoints.xs-only 35 | > *:first-child 36 | margin-left: $grid-gutters.lg 37 | 38 | > *:last-child 39 | margin-right: $grid-gutters.lg 40 | 41 | -------------------------------------------------------------------------------- /src/stylus/components/_icons.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | @import '../theme' 3 | 4 | /* Themes */ 5 | icon($material) 6 | color: $material.icons.active 7 | 8 | &.icon--disabled:not(.input-group__append-icon) 9 | color: $material.icons.inactive !important 10 | 11 | theme(icon, "icon") 12 | 13 | .icon 14 | align-items: center 15 | display: inline-flex 16 | font-size: 24px 17 | justify-content: center 18 | line-height: 1 19 | transition: $primary-transition 20 | vertical-align: middle 21 | 22 | &.icon--large 23 | font-size: 2.5rem 24 | 25 | &.icon--medium 26 | font-size: 2rem 27 | 28 | &.icon--x-large 29 | font-size: 3rem 30 | 31 | &.icon--disabled 32 | cursor: default 33 | -------------------------------------------------------------------------------- /src/stylus/components/_menus.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | 3 | .menu 4 | display: inline-block 5 | position: relative 6 | vertical-align: middle 7 | 8 | &--disabled 9 | cursor: default 10 | 11 | .menu__activator, 12 | .menu__activator > * 13 | cursor: default 14 | pointer-events: none 15 | 16 | &__activator 17 | align-items: center 18 | cursor: pointer 19 | position: relative 20 | 21 | input[readonly] 22 | cursor: pointer 23 | 24 | .toolbar__side-icon 25 | margin: 0 26 | 27 | &--active .toolbar__title .icon 28 | transform: rotate(-180deg) 29 | 30 | &__content 31 | position: absolute 32 | display: inline-block 33 | border-radius: 2px 34 | max-width: 80% 35 | overflow-y: auto 36 | overflow-x: hidden 37 | transition: .3s $transition.swing 38 | contain: content 39 | elevation(8) 40 | 41 | &--active 42 | pointer-events: none 43 | 44 | &--dropdown 45 | border-top-left-radius: 0 46 | border-top-right-radius: 0 47 | border-top: 1px solid $material-light.dividers 48 | 49 | & > .card 50 | contain: content 51 | backface-visibility: hidden 52 | 53 | .list__tile--link 54 | height: 40px 55 | 56 | &-transition 57 | &-enter 58 | .list__tile 59 | min-width: 0 60 | transition-delay: .4s 61 | opacity: 0 62 | transform: translateY(-15px) 63 | pointer-events: none 64 | 65 | &-enter-to 66 | .list__tile 67 | pointer-events: auto 68 | opacity: 1 69 | 70 | .list__tile--active 71 | transform: none !important 72 | 73 | &-leave-to 74 | transform: translateY(-10px) 75 | 76 | &-leave-active, &-leave-to 77 | pointer-events: none 78 | 79 | &-enter, &-leave-to 80 | opacity: 0 81 | 82 | &-enter-to, &-leave 83 | opacity: 1 84 | 85 | &-enter-active, &-leave-active 86 | transition: all .5s $transition.swing 87 | 88 | .menu-transition-enter 89 | &.menu__content--auto 90 | .list__tile--active 91 | opacity: 1 92 | transform: none !important 93 | pointer-events: auto 94 | -------------------------------------------------------------------------------- /src/stylus/components/_overlay.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | 3 | .overlay 4 | position: fixed 5 | top: 0 6 | left: 0 7 | right: 0 8 | bottom: 0 9 | pointer-events: none 10 | // The overlay has a dynamically set 11 | // z-index, we want the transition 12 | // timing to affect its changing 13 | // https://github.com/vuetifyjs/vuetify/issues/2146 14 | transition: .5s $transition.swing 15 | // This is the standard index 16 | z-index: 5 17 | 18 | &--absolute 19 | position: absolute 20 | 21 | &:before 22 | background-color: rgba(33, 33, 33, 1) 23 | bottom: 0 24 | content: '' 25 | height: 100% 26 | left: 0 27 | opacity: 0 28 | position: absolute 29 | right: 0 30 | top: 0 31 | transition: inherit 32 | // Delay the transition to avoid a 33 | // rendering bug that is visible 34 | // within Edge and Firefox 35 | // https://github.com/vuetifyjs/vuetify/issues/2146 36 | transition-delay: 150ms 37 | width: 100% 38 | 39 | &--active 40 | pointer-events: auto 41 | touch-action: none 42 | 43 | &:before 44 | opacity: .46 45 | -------------------------------------------------------------------------------- /src/stylus/components/_pagination.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | @import '../theme' 3 | 4 | pagination($material) 5 | &__item 6 | background: $material.cards 7 | color: $material.fg-color 8 | 9 | &--active 10 | color: $material.text.theme 11 | background: $theme.primary 12 | 13 | &__navigation 14 | background: $material.cards 15 | 16 | .icon 17 | color: rgba($material.fg-color, $material.active-icon-percent) 18 | 19 | theme(pagination, "pagination") 20 | 21 | .pagination 22 | align-items: center 23 | display: inline-flex 24 | list-style-type: none 25 | height: 40px 26 | margin: 0 27 | max-width: 100% 28 | padding: 0 29 | 30 | > li 31 | align-items: center 32 | display: flex 33 | 34 | a 35 | transition: .3s $transition.linear-out-slow-in 36 | 37 | &:hover 38 | elevation(4) 39 | text-decoration: none 40 | 41 | &--circle 42 | .pagination__item, 43 | .pagination__more, 44 | .pagination__navigation 45 | border-radius: 50% 46 | 47 | &--disabled 48 | pointer-events: none 49 | opacity: .6 50 | 51 | &__item 52 | elevation(2) 53 | border-radius: 4px 54 | display: inline-flex 55 | justify-content: center 56 | align-items: center 57 | height: 34px 58 | width: 34px 59 | margin: .3rem 60 | text-decoration: none 61 | 62 | &--active 63 | elevation(4) 64 | background-color: $theme.primary 65 | color: #fff 66 | 67 | &__navigation 68 | elevation(2) 69 | display: inline-flex 70 | justify-content: center 71 | align-items: center 72 | text-decoration: none 73 | height: 2rem 74 | 75 | border-radius: 4px 76 | width: 2rem 77 | margin: .3rem 10px 78 | 79 | .icon 80 | font-size: 2rem 81 | transition: $secondary-transition 82 | vertical-align: middle 83 | 84 | &--disabled 85 | opacity: .6 86 | pointer-events: none 87 | 88 | &__more 89 | margin: .3rem 90 | display: inline-flex 91 | align-items: flex-end 92 | justify-content: center 93 | height: 2rem 94 | width: 2rem 95 | 96 | @media (max-width: 768px) 97 | .pagination a 98 | margin: 2px 99 | -------------------------------------------------------------------------------- /src/stylus/components/_parallax.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | 3 | .parallax 4 | position: relative 5 | overflow: hidden 6 | z-index: 0 7 | 8 | &__image-container 9 | position: absolute 10 | top: 0 11 | left: 0 12 | right: 0 13 | bottom: 0 14 | z-index: 1 15 | 16 | &__image 17 | position: absolute 18 | bottom: 0 19 | left: 50% 20 | min-width: 100% 21 | min-height: 100% 22 | display: none 23 | transform: translate3d(-50%, 0, 0) 24 | transition: .3s opacity $transition.swing 25 | z-index: 1 26 | 27 | &__content 28 | color: #FFFFFF 29 | height: 100% 30 | z-index: 2 31 | position: relative 32 | display: flex 33 | flex-direction: column 34 | justify-content: center 35 | padding: 0 1rem -------------------------------------------------------------------------------- /src/stylus/components/_pickers.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | @import '../theme' 3 | 4 | /* Themes */ 5 | 6 | picker($material) 7 | color: $material.text.primary 8 | 9 | .picker__body 10 | background: $material.picker.body 11 | 12 | .picker__title 13 | background: $material.picker.title 14 | 15 | theme(picker, "picker") 16 | 17 | .picker 18 | border-radius: $card-border-radius 19 | display: flex 20 | flex-direction: column 21 | width: 290px 22 | 23 | .card__row--actions 24 | border: none 25 | margin-top: -20px 26 | 27 | &__title 28 | color: #fff 29 | border-top-left-radius: $card-border-radius 30 | border-top-right-radius: $card-border-radius 31 | height: 105px 32 | padding: 16px 33 | 34 | &__body 35 | height: 290px 36 | overflow: hidden 37 | position: relative 38 | 39 | &--landscape 40 | flex-direction: row 41 | flex-wrap: wrap 42 | width: 500px 43 | 44 | .picker__title 45 | border-top-right-radius: 0 46 | border-bottom-right-radius: 0 47 | flex: 0 1 170px 48 | width: 170px 49 | height: auto 50 | position: absolute 51 | top: 0 52 | left: 0 53 | height: 100% 54 | z-index: 1 55 | 56 | .picker__body 57 | flex: 1 0 58 | width: 330px 59 | margin-left: 170px 60 | 61 | .card__row--actions 62 | margin-left: 170px 63 | width: 330px 64 | -------------------------------------------------------------------------------- /src/stylus/components/_progress-circular.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | 3 | .progress-circular 4 | position: relative 5 | display: inline-flex 6 | 7 | &--indeterminate 8 | svg 9 | animation: $progress-circular-rotate-animation 10 | transform-origin: center center 11 | width: 100% 12 | height: 100% 13 | margin: auto 14 | position: absolute 15 | top: 0 16 | bottom: 0 17 | left: 0 18 | right: 0 19 | transition: $process-circular-intermediate-svg-transition 20 | z-index: 0 21 | 22 | .progress-circular__overlay 23 | animation: $progress-circular-rotate-dash 24 | stroke-linecap: round 25 | stroke-dasharray: 80,200 26 | stroke-dashoffset: 0px 27 | 28 | &__underlay 29 | stroke: $progress-circular-underlay-stroke 30 | z-index: 1 31 | 32 | &__overlay 33 | stroke: currentColor 34 | z-index: 2 35 | transition: $progress-circular-overlay-transition 36 | 37 | &__info 38 | position: absolute 39 | top: 50% 40 | left: 50% 41 | transform: translate3d(-50%, -50%, 0) 42 | 43 | @keyframes progress-circular-dash 44 | 0% 45 | stroke-dasharray: 1,200 46 | stroke-dashoffset: 0px 47 | 48 | 50% 49 | stroke-dasharray: 100,200 50 | stroke-dashoffset: -15px 51 | 52 | 100% 53 | stroke-dasharray: 100,200 54 | stroke-dashoffset: -125px 55 | 56 | @keyframes progress-circular-rotate 57 | 100% 58 | transform: rotate(360deg) 59 | -------------------------------------------------------------------------------- /src/stylus/components/_radio-group.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | @import '../theme' 3 | 4 | radiogroup($material) 5 | .input-group--selection-controls 6 | // 7 | 8 | theme(radiogroup, "radio-group") 9 | 10 | .radio-group 11 | .input-group__details:before, .input-group__details:after 12 | display: none 13 | 14 | .input-group 15 | padding: 0 16 | 17 | &--column 18 | .input-group__input 19 | display: block 20 | 21 | &--row 22 | .input-group__input 23 | flex-direction: row 24 | -------------------------------------------------------------------------------- /src/stylus/components/_ripples.styl: -------------------------------------------------------------------------------- 1 | .ripple 2 | &__container 3 | color: inherit 4 | border-radius: inherit 5 | position: absolute 6 | width: 100% 7 | height: 100% 8 | left: 0 9 | top: 0 10 | overflow: hidden 11 | z-index: 0 12 | pointer-events: none 13 | 14 | &__animation 15 | color: inherit 16 | position: absolute 17 | top: 0 18 | left: 0 19 | border-radius: 50% 20 | background: currentColor 21 | opacity: 0 22 | transition: $ripple-animation-transition 23 | pointer-events: none 24 | overflow: hidden 25 | will-change: opacity 26 | 27 | &--enter 28 | transition: none 29 | 30 | &--visible 31 | opacity: $ripple-animation-visible-opacity -------------------------------------------------------------------------------- /src/stylus/components/_small-dialog.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | @import '../theme' 3 | 4 | small-dialog($material) 5 | &__content 6 | background: $material.cards 7 | 8 | &__actions 9 | background: $material.cards 10 | 11 | a 12 | color: rgba($material.fg-color, $material.primary-text-percent) 13 | 14 | theme(small-dialog, "small-dialog") 15 | 16 | .small-dialog 17 | display: block 18 | height: 100% 19 | 20 | &__content 21 | padding: 0 24px 22 | 23 | &__actions 24 | text-align: right 25 | 26 | a 27 | display: flex 28 | align-items: center 29 | 30 | height: 100% 31 | text-decoration: none 32 | 33 | > * 34 | width: 100% 35 | 36 | .menu__activator 37 | height: 100% 38 | -------------------------------------------------------------------------------- /src/stylus/components/_speed-dial.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | 3 | .speed-dial 4 | position: relative 5 | 6 | &--absolute 7 | position: absolute 8 | 9 | &--fixed 10 | position: fixed 11 | 12 | &--top 13 | &:not(.speed-dial--absolute) 14 | top: $grid-gutters.lg 15 | 16 | &.speed-dial--absolute 17 | top: 50% 18 | transform: translateY(-50%) 19 | 20 | &--bottom 21 | &:not(.speed-dial--absolute) 22 | bottom: $grid-gutters.lg 23 | 24 | &.speed-dial--absolute 25 | bottom: 50% 26 | transform: translateY(50%) 27 | 28 | &--left 29 | left: $grid-gutters.lg 30 | 31 | &--right 32 | right: $grid-gutters.lg 33 | 34 | &--direction 35 | &-left, 36 | &-right 37 | .speed-dial__list 38 | height: 100% 39 | top: 0 40 | 41 | &-top, 42 | &-bottom 43 | .speed-dial__list 44 | left: 0 45 | width: 100% 46 | 47 | &-top 48 | .speed-dial__list 49 | flex-direction: column-reverse 50 | bottom: 100% 51 | 52 | &-right 53 | .speed-dial__list 54 | flex-direction: row 55 | left: 100% 56 | 57 | &-bottom 58 | .speed-dial__list 59 | flex-direction: column 60 | top: 100% 61 | 62 | &-left 63 | .speed-dial__list 64 | flex-direction: row-reverse 65 | right: 100% 66 | 67 | /** Elements */ 68 | .speed-dial__list 69 | align-items: center 70 | display: flex 71 | justify-content: center 72 | position: absolute 73 | 74 | .btn 75 | for n in (1..7) 76 | &:nth-child({n}) 77 | transition-delay: "%ss" % (n * .05) 78 | 79 | -------------------------------------------------------------------------------- /src/stylus/components/_subheaders.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | @import '../theme' 3 | 4 | subheader($material) 5 | color: $material.text.primary 6 | 7 | theme(subheader, "subheader") 8 | 9 | .subheader 10 | height: $list-item-single-height 11 | display: flex 12 | align-items: center 13 | font-size: $subheader-height-font-size 14 | font-weight: $subheader-font-weight 15 | padding: 0 $list-right-padding 0 $list-left-padding 16 | 17 | &--inset 18 | margin-left: $subheader-inset-margin -------------------------------------------------------------------------------- /src/stylus/components/_system-bars.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | @import '../theme' 3 | 4 | /* Theme */ 5 | system-bar($material) 6 | background-color: $material.status-bar.regular 7 | color: $material.text.secondary 8 | 9 | .icon 10 | color: $material.text.secondary 11 | 12 | &--lights-out 13 | background-color: $material.status-bar.lights-out !important 14 | 15 | theme(system-bar, "system-bar") 16 | 17 | .system-bar 18 | align-items: center 19 | display: flex 20 | font-size: $headings.body-2.size 21 | font-weight: $headings.body-2.weight 22 | padding: 0 8px 23 | 24 | .icon 25 | font-size: $headings.subheading.size 26 | 27 | &--fixed, &--absolute 28 | left: 0 29 | top: 0 30 | width: 100% 31 | z-index: 3 32 | 33 | &--fixed 34 | position: fixed 35 | 36 | &--absolute 37 | position: absolute 38 | 39 | &--status 40 | .icon 41 | margin-right: 4px 42 | 43 | &--window 44 | .icon 45 | font-size: $headings.h6.size 46 | margin-right: 8px 47 | -------------------------------------------------------------------------------- /src/stylus/components/_tooltips.styl: -------------------------------------------------------------------------------- 1 | @import '../bootstrap' 2 | 3 | .tooltip 4 | position: relative 5 | 6 | &__content 7 | background: $grey.darken-2 8 | border-radius: 2px 9 | color: #FFFFFF 10 | font-size: 12px 11 | display: inline-block 12 | padding: 5px 8px 13 | position: absolute 14 | text-transform: initial 15 | transition: .15s $transition.swing 16 | width: auto 17 | elevation(2) 18 | 19 | &[class*="-active"] { 20 | pointer-events: none 21 | } 22 | 23 | @media $display-breakpoints.sm-and-down 24 | .tooltip__content 25 | padding: 10px 16px 26 | -------------------------------------------------------------------------------- /src/stylus/elements/_blockquote.styl: -------------------------------------------------------------------------------- 1 | blockquote 2 | border-left: 5px solid $theme.primary 3 | padding: $spacers.three.y 0 $spacers.three.y $spacers.four.x 4 | font-size: 18px 5 | font-weight: 300 -------------------------------------------------------------------------------- /src/stylus/elements/_code.styl: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/stylus/elements/_global.styl: -------------------------------------------------------------------------------- 1 | html, body 2 | height: 100% 3 | min-height: 100% 4 | position: relative 5 | width: 100% 6 | 7 | html 8 | font-size: $font-size-root 9 | text-rendering: optimizeLegibility 10 | -webkit-font-smoothing: antialiased 11 | -moz-osx-font-smoothing: grayscale 12 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0) 13 | overflow-x: hidden 14 | 15 | body 16 | font-family: $body-font-family 17 | line-height: $line-height-root 18 | 19 | // TODO: deprecate 20 | header 21 | transition: .3s padding $transition.swing 22 | width: 100% 23 | z-index: 1 24 | 25 | ::-ms-clear 26 | ::-ms-reveal 27 | display: none 28 | -------------------------------------------------------------------------------- /src/stylus/elements/_headings.styl: -------------------------------------------------------------------------------- 1 | heading($material) 2 | color: $material.text.primary 3 | 4 | theme(heading, "heading") 5 | 6 | for heading, options in $headings 7 | {heading} 8 | font-size: options.size 9 | font-weight: options.weight 10 | if options.line-height 11 | line-height: options.line-height 12 | if options.letter-spacing 13 | letter-spacing: options.letter-spacing 14 | margin-bottom: $spacers.three.y 15 | 16 | @media screen and (max-width: $grid-breakpoints.sm) 17 | font-size: options.size * .6 -------------------------------------------------------------------------------- /src/stylus/elements/_lists.styl: -------------------------------------------------------------------------------- 1 | ul, ol 2 | padding-left: $spacers.four.x -------------------------------------------------------------------------------- /src/stylus/elements/_typography.styl: -------------------------------------------------------------------------------- 1 | .display-4 2 | font-size: $headings.h1.size !important 3 | font-weight: $headings.h1.weight 4 | line-height: $headings.h1.line-height !important 5 | letter-spacing: $headings.h1.letter-spacing !important 6 | 7 | .display-3 8 | font-size: $headings.h2.size !important 9 | font-weight: $headings.h2.weight 10 | line-height: $headings.h2.line-height !important 11 | letter-spacing: $headings.h2.letter-spacing !important 12 | 13 | .display-2 14 | font-size: $headings.h3.size !important 15 | font-weight: $headings.h3.weight 16 | line-height: $headings.h3.line-height !important 17 | letter-spacing: $headings.h3.letter-spacing !important 18 | 19 | .display-1 20 | font-size: $headings.h4.size !important 21 | font-weight: $headings.h4.weight 22 | line-height: $headings.h4.line-height !important 23 | letter-spacing: $headings.h4.letter-spacing !important 24 | 25 | .headline 26 | font-size: $headings.h5.size !important 27 | font-weight: $headings.h5.weight 28 | line-height: $headings.h5.line-height !important 29 | letter-spacing: $headings.h5.letter-spacing !important 30 | 31 | .title 32 | font-size: $headings.h6.size !important 33 | font-weight: $headings.h6.weight 34 | line-height: $headings.h6.line-height !important 35 | letter-spacing: $headings.h6.letter-spacing !important 36 | 37 | .subheading 38 | font-size: $headings.subheading.size !important 39 | font-weight: $headings.subheading.weight 40 | 41 | .body-2 42 | font-size: $headings.body-2.size !important 43 | font-weight: $headings.body-2.weight 44 | 45 | .body-1 46 | font-size: $headings.body-1.size !important 47 | font-weight: $headings.body-1.weight 48 | 49 | .caption 50 | font-size: $headings.caption.size !important 51 | font-weight: $headings.caption.weight 52 | 53 | p 54 | margin-bottom: $spacers.three.y -------------------------------------------------------------------------------- /src/stylus/generic/_bootstrap.styl: -------------------------------------------------------------------------------- 1 | global-color(color_name, color_value) 2 | .{color_name} 3 | background-color color_value !important 4 | border-color: color_value !important 5 | 6 | .{color_name}--text 7 | color color_value !important 8 | 9 | input, 10 | textarea 11 | caret-color: color_value !important 12 | 13 | .{color_name}--after 14 | &:after 15 | background: color_value !important 16 | 17 | global-color-accent(color_name, color_value) 18 | .{color_name} 19 | &.{color_type} 20 | background-color color_value !important 21 | border-color: color_value !important 22 | 23 | &.{color_type}--after 24 | &:after 25 | background-color: color_value !important 26 | 27 | .{color_name}--text 28 | &.text--{color_type} 29 | color: color_value !important 30 | 31 | input, 32 | textarea 33 | caret-color: color_value !important 34 | 35 | 36 | for color_name, color_value in $theme 37 | global-color(color_name, color_value) 38 | 39 | for color_name, color_value in $shades 40 | global-color(color_name, color_value) 41 | 42 | if ($color-pack) 43 | for color_name, color_color in $colors 44 | for color_type, color_value in color_color 45 | if color_type == 'base' 46 | global-color(color_name, color_value) 47 | 48 | else if color_type != 'shades' 49 | global-color-accent(color_name, color_value) 50 | -------------------------------------------------------------------------------- /src/stylus/main.styl: -------------------------------------------------------------------------------- 1 | @import './app' 2 | @import './components/*' 3 | -------------------------------------------------------------------------------- /src/stylus/theme.styl: -------------------------------------------------------------------------------- 1 | @import './settings/_theme.styl' 2 | 3 | app($component) 4 | 5 | theme($component, $name) 6 | light($component, $name) 7 | 8 | light($component, $name) 9 | .{$name} 10 | $component($material-light) 11 | 12 | dark($component, $name) 13 | .application--dark .{$name} 14 | $component($material-dark) 15 | .application .theme--dark.{$name} 16 | $component($material-dark) 17 | -------------------------------------------------------------------------------- /src/stylus/tools/_animations.styl: -------------------------------------------------------------------------------- 1 | @keyframes shake 2 | 59% 3 | margin-left: 0 4 | 5 | 60%, 80% 6 | margin-left: 2px 7 | 8 | 70%, 90% 9 | margin-left: -2px 10 | -------------------------------------------------------------------------------- /src/stylus/tools/_colors.styl: -------------------------------------------------------------------------------- 1 | global-color(color_name, color_value) 2 | .{color_name} 3 | background-color color_value !important 4 | border-color: color_value !important 5 | 6 | .{color_name}--text 7 | color color_value !important 8 | 9 | global-color-accent(color_name, color_value) 10 | .{color_name} 11 | &.{color_type} 12 | background-color color_value !important 13 | border-color: color_value !important 14 | 15 | .{color_name}--text 16 | &.text--{color_type} 17 | color: color_value !important 18 | -------------------------------------------------------------------------------- /src/stylus/tools/_elevations.styl: -------------------------------------------------------------------------------- 1 | @import '../settings/_elevations' 2 | 3 | // GENERATE HELPER CLASSES 4 | for z in (0..24) 5 | .elevation-{z} 6 | elevation(z, true) 7 | 8 | -------------------------------------------------------------------------------- /src/stylus/trumps/_display.styl: -------------------------------------------------------------------------------- 1 | for size, query in $display-breakpoints 2 | .hidden 3 | &-{size} 4 | @media query 5 | display: none !important 6 | 7 | .overflow-hidden 8 | overflow: hidden 9 | 10 | .overflow-x-hidden 11 | overflow-x: hidden 12 | 13 | .overflow-y-hidden 14 | overflow-y: hidden 15 | -------------------------------------------------------------------------------- /src/stylus/trumps/_helpers.styl: -------------------------------------------------------------------------------- 1 | .right 2 | float: right !important 3 | 4 | .left 5 | float: left !important -------------------------------------------------------------------------------- /src/stylus/trumps/_spacing.styl: -------------------------------------------------------------------------------- 1 | $i = 0 2 | for level, spacer in $spacers 3 | .mt-{$i} 4 | margin-top: spacer.y !important 5 | .mr-{$i} 6 | margin-right: spacer.x !important 7 | .mb-{$i} 8 | margin-bottom: spacer.y !important 9 | .ml-{$i} 10 | margin-left: spacer.x !important 11 | .mx-{$i} 12 | margin-left: spacer.x !important 13 | margin-right: spacer.x !important 14 | .my-{$i} 15 | margin-top: spacer.y !important 16 | margin-bottom: spacer.y !important 17 | .ma-{$i} 18 | margin: spacer.y spacer.x !important 19 | 20 | .pt-{$i} 21 | padding-top: spacer.y !important 22 | .pr-{$i} 23 | padding-right: spacer.x !important 24 | .pb-{$i} 25 | padding-bottom: spacer.y !important 26 | .pl-{$i} 27 | padding-left: spacer.x !important 28 | .px-{$i} 29 | padding-left: spacer.x !important 30 | padding-right: spacer.x !important 31 | .py-{$i} 32 | padding-top: spacer.y !important 33 | padding-bottom: spacer.y !important 34 | .pa-{$i} 35 | padding: spacer.y spacer.x !important 36 | 37 | $i = $i + 1 -------------------------------------------------------------------------------- /src/stylus/trumps/_text.styl: -------------------------------------------------------------------------------- 1 | for size, width in $grid-breakpoints 2 | @media all and (min-width: width) 3 | .text-{size}-left 4 | text-align: left !important 5 | 6 | .text-{size}-center 7 | text-align: center !important 8 | 9 | .text-{size}-right 10 | text-align: right !important 11 | 12 | .text-{size}-justify 13 | text-align: justify !important -------------------------------------------------------------------------------- /src/util/helpers.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vuetify/src/util/helpers' { 2 | import { FunctionalComponentOptions, VNodeDirective } from 'vue' 3 | 4 | export function createSimpleFunctional(c: string, el: string): FunctionalComponentOptions 5 | export function createSimpleTransition(name: string, origin: string, mode: string): FunctionalComponentOptions 6 | 7 | export interface EventHandlersDict { 8 | [event: string]: Function 9 | } 10 | export function createJavaScriptTransition(name: string, functions: EventHandlersDict, css: boolean, mode: string): FunctionalComponentOptions 11 | 12 | export interface BindingConfig { 13 | arg: VNodeDirective['arg'], 14 | modifiers: VNodeDirective['modifiers'], 15 | value: VNodeDirective['value'] 16 | } 17 | export function directiveConfig(binding: BindingConfig, defaults: object): VNodeDirective 18 | export function addOnceEventListener(el: EventTarget, event: string, cb: () => void): void 19 | export function getObjectValueByPath(obj: object, path: string): any 20 | export function createRange(length: number): Array 21 | } -------------------------------------------------------------------------------- /src/util/load.js: -------------------------------------------------------------------------------- 1 | function load (cb, i = 0) { 2 | if (!document._loadCallbacks) { 3 | document._loadCallbacks = [] 4 | } 5 | 6 | if (document.readyState === 'complete') { 7 | return cb() 8 | } 9 | 10 | document._loadCallbacks.push(cb) 11 | } 12 | 13 | export default load 14 | -------------------------------------------------------------------------------- /src/util/to-have-been-warned.js: -------------------------------------------------------------------------------- 1 | // From Vue, slightly modified 2 | 3 | function noop() { } 4 | 5 | if (typeof console === 'undefined') { 6 | window.console = { 7 | warn: noop, 8 | error: noop 9 | } 10 | } 11 | 12 | // avoid info messages during test 13 | console.info = noop 14 | 15 | const asserted = [] 16 | 17 | function createCompareFn (spy) { 18 | const hasWarned = msg => { 19 | for (const args of spy.calls.allArgs()) { 20 | if (args.some(arg => ( 21 | arg.toString().includes(msg) 22 | ))) return true 23 | } 24 | } 25 | 26 | return { 27 | compare: msg => { 28 | asserted.push(msg) 29 | const warned = Array.isArray(msg) 30 | ? msg.some(hasWarned) 31 | : hasWarned(msg) 32 | return { 33 | pass: warned, 34 | message: warned 35 | ? `Expected message "${msg}" not to have been warned` 36 | : `Expected message "${msg}" to have been warned` 37 | } 38 | } 39 | } 40 | } 41 | 42 | function toHaveBeenWarnedInit() { 43 | // define custom matcher for warnings 44 | beforeEach(() => { 45 | asserted.length = 0 46 | spyOn(console, 'warn') 47 | spyOn(console, 'error') 48 | jasmine.addMatchers({ 49 | toHaveBeenWarned: () => createCompareFn(console.error), 50 | toHaveBeenTipped: () => createCompareFn(console.warn) 51 | }) 52 | }) 53 | 54 | afterEach(done => { 55 | for (const type of ['error', 'warn']) { 56 | const warned = msg => asserted.some(assertedMsg => msg.toString().includes(assertedMsg)) 57 | for (const args of console[type].calls.allArgs()) { 58 | if (!warned(args[0])) { 59 | done.fail(`Unexpected console.${type} message: ${args[0]}`) 60 | return 61 | } 62 | } 63 | } 64 | done() 65 | }) 66 | } 67 | 68 | export default toHaveBeenWarnedInit 69 | -------------------------------------------------------------------------------- /src/util/touchSupport.js: -------------------------------------------------------------------------------- 1 | import { addOnceEventListener } from './helpers' 2 | 3 | /** 4 | * @mixin 5 | */ 6 | export default { 7 | mounted () { 8 | addOnceEventListener(window, 'touchstart', () => { 9 | this.$vuetify.touchSupport = true 10 | }) 11 | } 12 | } 13 | --------------------------------------------------------------------------------