├── .npmignore ├── .browserslistrc ├── site ├── assets │ ├── scss │ │ ├── base │ │ │ ├── _img.scss │ │ │ ├── _icons.scss │ │ │ ├── _headings.scss │ │ │ ├── _base.scss │ │ │ ├── _focus-visible.scss │ │ │ └── _general.scss │ │ ├── bootstrap │ │ │ ├── utilities │ │ │ │ ├── _position.scss │ │ │ │ ├── _sizes.scss │ │ │ │ ├── _transforms.scss │ │ │ │ ├── _backgrounds.scss │ │ │ │ ├── _transition.scss │ │ │ │ ├── _font-weights.scss │ │ │ │ ├── _text.scss │ │ │ │ ├── _links.scss │ │ │ │ ├── _font-sizes.scss │ │ │ │ └── _width.scss │ │ │ ├── _buttons.scss │ │ │ ├── _page-row.scss │ │ │ ├── _dropdowns.scss │ │ │ ├── _row-gutters.scss │ │ │ ├── _general.scss │ │ │ ├── bootstrap.scss │ │ │ ├── _cards.scss │ │ │ └── _variable-overrides.scss │ │ ├── components │ │ │ ├── configurator │ │ │ │ ├── _configurator-zone-editor.scss │ │ │ │ ├── _configurator-tabs.scss │ │ │ │ ├── _configurator-shape-picker.scss │ │ │ │ ├── _configurator-canvas.scss │ │ │ │ └── _configurator-controls.scss │ │ │ ├── _carousel.scss │ │ │ ├── _address-grid.scss │ │ │ ├── _icon-label.scss │ │ │ ├── _case-thumb.scss │ │ │ ├── _link-block.scss │ │ │ ├── _snackbar.scss │ │ │ ├── _alert.scss │ │ │ ├── _scroll-menu.scss │ │ │ ├── _title-border.scss │ │ │ ├── _grid.scss │ │ │ ├── _global-messages.scss │ │ │ ├── _components.scss │ │ │ ├── _social-icons-list.scss │ │ │ ├── _dropdown.scss │ │ │ ├── _news-item.scss │ │ │ ├── _image-block.scss │ │ │ ├── _pagination.scss │ │ │ ├── _product-thumb.scss │ │ │ ├── _popper.scss │ │ │ ├── _transitions.scss │ │ │ ├── _dialog.scss │ │ │ └── _prism.scss │ │ ├── helpers │ │ │ ├── _object-fit.scss │ │ │ ├── _helpers.scss │ │ │ ├── _type.scss │ │ │ └── _opacity.scss │ │ ├── global.scss │ │ └── private │ │ │ ├── variables.scss │ │ │ └── mixins.scss │ ├── images │ │ └── svgs │ │ │ └── icons │ │ │ ├── knob.svg │ │ │ ├── logo.svg │ │ │ └── twitter.svg │ └── README.md ├── layouts │ ├── default.vue │ └── README.md ├── .prettierrc ├── static │ ├── demo.gif │ ├── icon.png │ ├── favicon.png │ ├── favicons │ │ ├── favicon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── apple-touch-icon.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── apple-touch-icon-120x120.png │ │ ├── apple-touch-icon-152x152.png │ │ └── apple-touch-icon-180x180.png │ └── README.md ├── plugins │ ├── vue-prism.js │ ├── README.md │ └── ga.js ├── main.js ├── components │ ├── README.md │ ├── Logo.vue │ └── ResizeContent.vue ├── jsconfig.json ├── .editorconfig ├── pages │ ├── README.md │ └── index.vue ├── App.vue ├── middleware │ └── README.md ├── store │ └── README.md ├── README.md ├── package.json ├── .gitignore └── nuxt.config.js ├── jest.config.js ├── babel.config.js ├── public ├── favicon.ico └── index.html ├── cypress.json ├── demo ├── index.js └── demo.vue ├── vue.config.js ├── tests └── e2e │ ├── .eslintrc.js │ ├── support │ ├── index.js │ └── commands.js │ ├── plugins │ └── index.js │ └── specs │ └── test.js ├── .gitignore ├── .github └── workflows │ └── main.yml ├── .eslintrc.js ├── package.json ├── README.md └── src └── index.js /.npmignore: -------------------------------------------------------------------------------- 1 | site 2 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /site/assets/scss/base/_img.scss: -------------------------------------------------------------------------------- 1 | img { 2 | max-width: 100%; 3 | } 4 | -------------------------------------------------------------------------------- /site/layouts/default.vue: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /site/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /site/assets/scss/base/_icons.scss: -------------------------------------------------------------------------------- 1 | svg { 2 | fill: currentColor; 3 | } 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "@vue/cli-plugin-unit-jest" 3 | }; 4 | -------------------------------------------------------------------------------- /site/assets/scss/bootstrap/utilities/_position.scss: -------------------------------------------------------------------------------- 1 | .top-0 { 2 | top: 0; 3 | } 4 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/cli-plugin-babel/preset"] 3 | }; 4 | -------------------------------------------------------------------------------- /site/assets/scss/bootstrap/utilities/_sizes.scss: -------------------------------------------------------------------------------- 1 | .mh-v100 { 2 | min-height: 100vh; 3 | } 4 | -------------------------------------------------------------------------------- /site/assets/scss/components/configurator/_configurator-zone-editor.scss: -------------------------------------------------------------------------------- 1 | .c-zone-editor { 2 | } 3 | -------------------------------------------------------------------------------- /site/assets/scss/helpers/_object-fit.scss: -------------------------------------------------------------------------------- 1 | .object-fit-cover { 2 | object-fit: cover; 3 | } 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gijsroge/vue-responsive-menu/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /site/assets/scss/components/_carousel.scss: -------------------------------------------------------------------------------- 1 | .flickity-viewport{ 2 | background-color: $light; 3 | } 4 | -------------------------------------------------------------------------------- /site/assets/scss/helpers/_helpers.scss: -------------------------------------------------------------------------------- 1 | @import 'opacity'; 2 | @import 'type'; 3 | @import 'object-fit'; 4 | -------------------------------------------------------------------------------- /site/static/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gijsroge/vue-responsive-menu/HEAD/site/static/demo.gif -------------------------------------------------------------------------------- /site/static/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gijsroge/vue-responsive-menu/HEAD/site/static/icon.png -------------------------------------------------------------------------------- /site/assets/scss/bootstrap/utilities/_transforms.scss: -------------------------------------------------------------------------------- 1 | .rotate-180 { 2 | transform: rotate(180deg); 3 | } 4 | -------------------------------------------------------------------------------- /site/plugins/vue-prism.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VuePrism from 'vue-prism' 3 | Vue.use(VuePrism) 4 | -------------------------------------------------------------------------------- /site/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gijsroge/vue-responsive-menu/HEAD/site/static/favicon.png -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "pluginsFile": "tests/e2e/plugins/index.js", 3 | "baseUrl": "http://localhost:8080" 4 | } 5 | -------------------------------------------------------------------------------- /site/assets/scss/bootstrap/utilities/_backgrounds.scss: -------------------------------------------------------------------------------- 1 | .bg-cover { 2 | background-size: cover !important; 3 | } 4 | -------------------------------------------------------------------------------- /site/assets/scss/bootstrap/utilities/_transition.scss: -------------------------------------------------------------------------------- 1 | .transition-base { 2 | transition: $transition-base; 3 | } 4 | -------------------------------------------------------------------------------- /site/static/favicons/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gijsroge/vue-responsive-menu/HEAD/site/static/favicons/favicon.png -------------------------------------------------------------------------------- /site/assets/scss/bootstrap/utilities/_font-weights.scss: -------------------------------------------------------------------------------- 1 | .font-weight-black { 2 | font-weight: $font-weight-black !important; 3 | } 4 | -------------------------------------------------------------------------------- /site/static/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gijsroge/vue-responsive-menu/HEAD/site/static/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /site/static/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gijsroge/vue-responsive-menu/HEAD/site/static/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /site/assets/scss/base/_headings.scss: -------------------------------------------------------------------------------- 1 | h1 { 2 | @include media-breakpoint-down(sm) { 3 | @include font-size($h2-font-size); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /site/assets/scss/bootstrap/_buttons.scss: -------------------------------------------------------------------------------- 1 | .btn { 2 | text-decoration: none; 3 | } 4 | .btn-twitter { 5 | background-color: $twitter; 6 | } 7 | -------------------------------------------------------------------------------- /site/static/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gijsroge/vue-responsive-menu/HEAD/site/static/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /site/static/favicons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gijsroge/vue-responsive-menu/HEAD/site/static/favicons/android-chrome-192x192.png -------------------------------------------------------------------------------- /site/static/favicons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gijsroge/vue-responsive-menu/HEAD/site/static/favicons/android-chrome-512x512.png -------------------------------------------------------------------------------- /site/static/favicons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gijsroge/vue-responsive-menu/HEAD/site/static/favicons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /site/static/favicons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gijsroge/vue-responsive-menu/HEAD/site/static/favicons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /site/static/favicons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gijsroge/vue-responsive-menu/HEAD/site/static/favicons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /site/static/favicons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gijsroge/vue-responsive-menu/HEAD/site/static/favicons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /site/static/favicons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gijsroge/vue-responsive-menu/HEAD/site/static/favicons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /site/assets/scss/components/_address-grid.scss: -------------------------------------------------------------------------------- 1 | .address-grid { 2 | display: grid; 3 | grid-gap: 1rem; 4 | grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); 5 | } 6 | -------------------------------------------------------------------------------- /site/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "./App.vue"; 3 | 4 | Vue.config.productionTip = false; 5 | 6 | new Vue({ 7 | render: h => h(App) 8 | }).$mount("#app"); 9 | -------------------------------------------------------------------------------- /demo/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "./demo.vue"; 3 | 4 | Vue.config.productionTip = false; 5 | 6 | new Vue({ 7 | render: h => h(App) 8 | }).$mount("#app"); 9 | -------------------------------------------------------------------------------- /site/assets/scss/helpers/_type.scss: -------------------------------------------------------------------------------- 1 | .headings-font-family { 2 | font-family: $headings-font-family; 3 | } 4 | 5 | .base-font-family { 6 | font-family: $font-family-base; 7 | } 8 | -------------------------------------------------------------------------------- /site/assets/images/svgs/icons/knob.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | chainWebpack: config => { 3 | config 4 | .entry("app") 5 | .clear() 6 | .add("./demo/index.js") 7 | .end(); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /tests/e2e/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ["cypress"], 3 | env: { 4 | mocha: true, 5 | "cypress/globals": true 6 | }, 7 | rules: { 8 | strict: "off" 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /site/assets/scss/components/_icon-label.scss: -------------------------------------------------------------------------------- 1 | .icon-label { 2 | display: flex; 3 | align-items: baseline; 4 | 5 | > svg, 6 | > img { 7 | transform: translateY(calc(50% - 0.4em)); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /site/assets/scss/components/_case-thumb.scss: -------------------------------------------------------------------------------- 1 | .link-block { 2 | color: inherit; 3 | text-decoration: none; 4 | 5 | &:hover, &:focus{ 6 | color: inherit; 7 | text-decoration: none; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /site/assets/scss/bootstrap/_page-row.scss: -------------------------------------------------------------------------------- 1 | .page-row-lg { 2 | margin-bottom: 2rem; 3 | @include media-breakpoint-up(md) { 4 | margin-bottom: 3rem; 5 | } 6 | } 7 | 8 | .page-row-xl { 9 | margin-bottom: 5rem; 10 | } 11 | -------------------------------------------------------------------------------- /site/assets/scss/components/_link-block.scss: -------------------------------------------------------------------------------- 1 | .link-block { 2 | color: inherit !important; 3 | text-decoration: none; 4 | 5 | &:hover, &:focus{ 6 | color: inherit !important; 7 | text-decoration: none; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /site/assets/scss/bootstrap/utilities/_text.scss: -------------------------------------------------------------------------------- 1 | .text-underline { 2 | @include e-link(); 3 | } 4 | 5 | .text-inherit { 6 | color: inherit !important; 7 | } 8 | 9 | .text-transform-none { 10 | text-transform: none !important; 11 | } 12 | -------------------------------------------------------------------------------- /site/components/README.md: -------------------------------------------------------------------------------- 1 | # COMPONENTS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | The components directory contains your Vue.js Components. 6 | 7 | _Nuxt.js doesn't supercharge these components._ 8 | -------------------------------------------------------------------------------- /site/assets/scss/components/_snackbar.scss: -------------------------------------------------------------------------------- 1 | .snackbar--button { 2 | height: auto !important; 3 | line-height: 1; 4 | 5 | &:before { 6 | display: none !important; 7 | } 8 | } 9 | .snackbar { 10 | font-weight: $font-weight-normal; 11 | } 12 | -------------------------------------------------------------------------------- /site/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "~/*": ["./*"], 6 | "@/*": ["./*"], 7 | "~~/*": ["./*"], 8 | "@@/*": ["./*"] 9 | } 10 | }, 11 | "exclude": ["node_modules", ".nuxt", "dist"] 12 | } 13 | -------------------------------------------------------------------------------- /site/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /site/assets/scss/bootstrap/_dropdowns.scss: -------------------------------------------------------------------------------- 1 | .dropdown-black { 2 | background-color: $black; 3 | 4 | .dropdown-item { 5 | color: $body-color; 6 | @include e-link($body-color); 7 | 8 | &:hover, &:focus{ 9 | background-color: $dark; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /site/assets/scss/bootstrap/utilities/_links.scss: -------------------------------------------------------------------------------- 1 | .e-link { 2 | @include e-link(); 3 | } 4 | 5 | .e-link--secondary { 6 | @include e-link--secondary(); 7 | } 8 | 9 | .e-link--dark { 10 | @include e-link--dark(); 11 | } 12 | 13 | .e-link--white { 14 | @include e-link($white); 15 | } 16 | -------------------------------------------------------------------------------- /site/layouts/README.md: -------------------------------------------------------------------------------- 1 | # LAYOUTS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your Application Layouts. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/views#layouts). 8 | -------------------------------------------------------------------------------- /site/assets/scss/components/_alert.scss: -------------------------------------------------------------------------------- 1 | .alert { 2 | background-color: $white; 3 | } 4 | 5 | @each $color, $value in $theme-colors { 6 | .alert-#{$color} { 7 | border-color: $value; 8 | color: $body-color; 9 | } 10 | } 11 | 12 | .alert-success { 13 | border-color: $green-acid; 14 | } 15 | -------------------------------------------------------------------------------- /site/pages/README.md: -------------------------------------------------------------------------------- 1 | # PAGES 2 | 3 | This directory contains your Application Views and Routes. 4 | The framework reads all the `*.vue` files inside this directory and creates the router of your application. 5 | 6 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing). 7 | -------------------------------------------------------------------------------- /site/assets/scss/components/_scroll-menu.scss: -------------------------------------------------------------------------------- 1 | .scroll-menu { 2 | overflow-x: auto; 3 | white-space: nowrap; 4 | flex-wrap: nowrap; 5 | } 6 | .scroll-menu--center { 7 | > * { 8 | &:first-child { 9 | margin-left: auto; 10 | } 11 | &:last-child { 12 | margin-right: auto; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /site/assets/README.md: -------------------------------------------------------------------------------- 1 | # ASSETS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your un-compiled assets such as LESS, SASS, or JavaScript. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked). 8 | -------------------------------------------------------------------------------- /site/plugins/README.md: -------------------------------------------------------------------------------- 1 | # PLUGINS 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains Javascript plugins that you want to run before mounting the root Vue.js application. 6 | 7 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/plugins). 8 | -------------------------------------------------------------------------------- /site/assets/scss/components/_title-border.scss: -------------------------------------------------------------------------------- 1 | .title-border { 2 | display: flex; 3 | width: 100%; 4 | justify-content: center; 5 | align-items: center; 6 | 7 | > span { 8 | margin: 0 0.5em; 9 | } 10 | 11 | &:after, 12 | &:before { 13 | content: ''; 14 | height: 1px; 15 | background-color: $border-color; 16 | flex: 1; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | /tests/e2e/videos/ 6 | /tests/e2e/screenshots/ 7 | 8 | # local env files 9 | .env.local 10 | .env.*.local 11 | 12 | # Log files 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /site/assets/scss/components/_grid.scss: -------------------------------------------------------------------------------- 1 | @supports (display: grid) { 2 | .grid { 3 | display: grid; 4 | margin-left: 0; 5 | margin-right: 0; 6 | 7 | > [class^='col'], 8 | > [class*=' col'] { 9 | width: initial; 10 | flex: initial; 11 | max-width: initial; 12 | padding-left: 0; 13 | padding-right: 0; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /site/assets/scss/base/_base.scss: -------------------------------------------------------------------------------- 1 | @import 'focus-visible'; 2 | @import 'general'; 3 | @import 'icons'; 4 | @import 'img'; 5 | @import 'headings'; 6 | 7 | @media screen and (prefers-reduced-motion: reduce), (update: slow) { 8 | * { 9 | animation-duration: 0.001ms !important; 10 | animation-iteration-count: 1 !important; 11 | transition-duration: 0.001ms !important; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /site/assets/scss/components/configurator/_configurator-tabs.scss: -------------------------------------------------------------------------------- 1 | .c-tabs__control { 2 | padding: 0.25rem 1rem; 3 | text-transform: none; 4 | font-size: 0.9rem; 5 | 6 | &.is-active { 7 | padding: 0.6rem 1rem; 8 | background-color: white; 9 | color: $primary; 10 | font-size: 1rem; 11 | } 12 | } 13 | 14 | .c-tab { 15 | padding: 1rem; 16 | background-color: white; 17 | } 18 | -------------------------------------------------------------------------------- /site/assets/scss/helpers/_opacity.scss: -------------------------------------------------------------------------------- 1 | .opacity-0 { 2 | opacity: 0 !important; 3 | } 4 | 5 | .opacity-15 { 6 | opacity: 0.15 !important; 7 | } 8 | 9 | .opacity-25 { 10 | opacity: 0.25 !important; 11 | } 12 | 13 | .opacity-50 { 14 | opacity: 0.5 !important; 15 | } 16 | 17 | .opacity-75 { 18 | opacity: 0.75 !important; 19 | } 20 | 21 | .opacity-100 { 22 | opacity: 1 !important; 23 | } 24 | -------------------------------------------------------------------------------- /site/assets/scss/components/_global-messages.scss: -------------------------------------------------------------------------------- 1 | .global-messages { 2 | position: fixed; 3 | left: 50%; 4 | bottom: 1rem; 5 | will-change: transform; 6 | box-shadow: 0 0 400px 0 rgba($primary, 0.9) !important; 7 | z-index: $zindex-tooltip; 8 | max-height: 80vh; 9 | overflow-y: auto; 10 | max-width: calc(100% - 4rem); 11 | transform: translateX(-50%); 12 | width: max-content; 13 | } 14 | -------------------------------------------------------------------------------- /site/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 21 | -------------------------------------------------------------------------------- /site/assets/scss/components/_components.scss: -------------------------------------------------------------------------------- 1 | @import 'grid'; 2 | @import 'dialog'; 3 | @import 'title-border'; 4 | @import 'icon-label'; 5 | @import 'transitions'; 6 | @import 'global-messages'; 7 | @import 'popper'; 8 | @import 'dropdown'; 9 | @import 'link-block'; 10 | @import 'social-icons-list'; 11 | @import 'image-block'; 12 | @import 'snackbar'; 13 | @import 'address-grid'; 14 | @import 'alert'; 15 | @import 'prism'; 16 | -------------------------------------------------------------------------------- /site/middleware/README.md: -------------------------------------------------------------------------------- 1 | # MIDDLEWARE 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your application middleware. 6 | Middleware let you define custom functions that can be run before rendering either a page or a group of pages. 7 | 8 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware). 9 | -------------------------------------------------------------------------------- /site/assets/scss/base/_focus-visible.scss: -------------------------------------------------------------------------------- 1 | *:focus, 2 | a:focus, 3 | button:focus { 4 | box-shadow: $input-btn-focus-box-shadow; 5 | outline: none; 6 | } 7 | 8 | .js-focus-visible { 9 | & *:focus { 10 | &:not([data-focus-visible-added]) { 11 | box-shadow: none; 12 | outline: none; 13 | } 14 | } 15 | } 16 | 17 | .no-outline { 18 | &:focus { 19 | box-shadow: none; 20 | outline: 0; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /site/store/README.md: -------------------------------------------------------------------------------- 1 | # STORE 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your Vuex Store files. 6 | Vuex Store option is implemented in the Nuxt.js framework. 7 | 8 | Creating a file in this directory automatically activates the option in the framework. 9 | 10 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store). 11 | -------------------------------------------------------------------------------- /site/README.md: -------------------------------------------------------------------------------- 1 | # vue-responsive-menu 2 | 3 | > My astonishing Nuxt.js project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | $ yarn install 10 | 11 | # serve with hot reload at localhost:3000 12 | $ yarn dev 13 | 14 | # build for production and launch server 15 | $ yarn build 16 | $ yarn start 17 | 18 | # generate static project 19 | $ yarn generate 20 | ``` 21 | 22 | For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org). 23 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: End-to-end tests 2 | on: [push] 3 | jobs: 4 | cypress-run: 5 | runs-on: ubuntu-16.04 6 | steps: 7 | - name: Install Dependencies 8 | run: yarn 9 | - name: Checkout 10 | uses: actions/checkout@v2.0.0 11 | # Install NPM dependencies, cache them correctly 12 | # and run all Cypress tests 13 | - name: Cypress run 14 | uses: cypress-io/github-action@v1 15 | with: 16 | start: yarn serve 17 | -------------------------------------------------------------------------------- /site/static/README.md: -------------------------------------------------------------------------------- 1 | # STATIC 2 | 3 | **This directory is not required, you can delete it if you don't want to use it.** 4 | 5 | This directory contains your static files. 6 | Each file inside this directory is mapped to `/`. 7 | Thus you'd want to delete this README.md before deploying to production. 8 | 9 | Example: `/static/robots.txt` is mapped as `/robots.txt`. 10 | 11 | More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static). 12 | -------------------------------------------------------------------------------- /site/assets/scss/bootstrap/_row-gutters.scss: -------------------------------------------------------------------------------- 1 | .row-gutter-0 { 2 | margin-left: 0; 3 | margin-right: 0; 4 | > * { 5 | padding-left: 0; 6 | padding-right: 0; 7 | } 8 | } 9 | 10 | .row-gutter-md { 11 | margin-left: -1rem; 12 | margin-right: -1rem; 13 | > * { 14 | padding-left: 1rem; 15 | padding-right: 1rem; 16 | } 17 | } 18 | 19 | .row-gutter-lg { 20 | margin-left: -3rem; 21 | margin-right: -3rem; 22 | > * { 23 | padding-left: 3rem; 24 | padding-right: 3rem; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: ["plugin:vue/essential", "eslint:recommended", "@vue/prettier"], 7 | parserOptions: { 8 | parser: "babel-eslint" 9 | }, 10 | rules: { 11 | "no-console": process.env.NODE_ENV === "production" ? "error" : "off", 12 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off" 13 | }, 14 | overrides: [ 15 | { 16 | files: [ 17 | "**/__tests__/*.{j,t}s?(x)", 18 | "**/tests/unit/**/*.spec.{j,t}s?(x)" 19 | ], 20 | env: { 21 | jest: true 22 | } 23 | } 24 | ] 25 | }; 26 | -------------------------------------------------------------------------------- /site/assets/scss/bootstrap/_general.scss: -------------------------------------------------------------------------------- 1 | h1, 2 | h2, 3 | h3, 4 | h4, 5 | h5, 6 | h6, 7 | .h1, 8 | .h2, 9 | .h3, 10 | .h4, 11 | .h5, 12 | .h6 { 13 | &:not(:first-child) { 14 | margin-top: 1.5em; 15 | } 16 | } 17 | 18 | .container { 19 | @include container(); 20 | } 21 | 22 | .container-small { 23 | @include container-small(); 24 | @include media-breakpoint-down(xxl) { 25 | padding-left: 1.25rem; 26 | padding-right: 1.25rem; 27 | } 28 | } 29 | 30 | .container { 31 | .container, 32 | .container-small { 33 | padding-left: 0; 34 | padding-right: 0; 35 | } 36 | } 37 | 38 | html { 39 | scroll-behavior: smooth; 40 | } 41 | -------------------------------------------------------------------------------- /site/assets/scss/global.scss: -------------------------------------------------------------------------------- 1 | @import 'private/variables'; 2 | @import 'private/mixins'; 3 | @import 'bootstrap/bootstrap'; 4 | @import './base/base'; 5 | @import './components/components'; 6 | @import './helpers/helpers'; 7 | 8 | body { 9 | @media all and (display-mode: standalone) { 10 | -webkit-touch-callout: none; 11 | -webkit-tap-highlight-color: transparent; 12 | -webkit-user-select: none; 13 | } 14 | } 15 | 16 | 17 | 18 | ::-webkit-scrollbar { 19 | width: 5px; 20 | height: 5px; 21 | } 22 | 23 | ::-webkit-scrollbar-track { 24 | background-color: $dark; 25 | } 26 | 27 | ::-webkit-scrollbar-thumb { 28 | background-color: $primary; 29 | } 30 | -------------------------------------------------------------------------------- /site/assets/scss/base/_general.scss: -------------------------------------------------------------------------------- 1 | html { 2 | -webkit-font-smoothing: antialiased; 3 | -moz-osx-font-smoothing: grayscale; 4 | } 5 | 6 | html, 7 | body { 8 | height: 100%; 9 | } 10 | 11 | body { 12 | overflow-x: hidden; 13 | } 14 | 15 | p { 16 | @include font-size($font-size-md); 17 | &:last-child { 18 | margin-bottom: 0; 19 | } 20 | } 21 | 22 | strong { 23 | font-weight: $font-weight-bold; 24 | color: $dark; 25 | } 26 | 27 | .cursor-loading { 28 | cursor: wait; 29 | 30 | * { 31 | cursor: wait; 32 | } 33 | } 34 | 35 | a, 36 | u { 37 | @include e-link(); 38 | } 39 | 40 | .nav-link { 41 | text-decoration: none; 42 | } 43 | 44 | p { 45 | max-width: 60ch; 46 | } 47 | -------------------------------------------------------------------------------- /site/assets/images/svgs/icons/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vue-responsive-menu 9 | 10 | 11 | 12 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /site/assets/scss/components/_social-icons-list.scss: -------------------------------------------------------------------------------- 1 | .circle-button { 2 | display: inline-flex; 3 | justify-content: center; 4 | align-items: center; 5 | color: $gray-600; 6 | padding: 0.5rem; 7 | position: relative; 8 | transition: $transition-base; 9 | background-color: transparent; 10 | 11 | > * { 12 | position: relative; 13 | z-index: 2; 14 | } 15 | 16 | &:after { 17 | content: ''; 18 | position: absolute; 19 | border-radius: 50%; 20 | background-color: $light; 21 | transition: $transition-base; 22 | height: 100%; 23 | width: 100%; 24 | z-index: 1; 25 | } 26 | 27 | &:focus, 28 | &:hover { 29 | color: white; 30 | background-color: transparent; 31 | &:after { 32 | background-color: darken($light, 10%); 33 | transform: scale(0.9); 34 | } 35 | svg { 36 | transform: scale(1.1); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/e2e/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import "./commands"; 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | 22 | Cypress.on("uncaught:exception", () => { 23 | // returning false here prevents Cypress from 24 | // failing the test 25 | return false; 26 | }); 27 | -------------------------------------------------------------------------------- /tests/e2e/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | -------------------------------------------------------------------------------- /site/assets/scss/private/variables.scss: -------------------------------------------------------------------------------- 1 | // Colors 2 | $body-color: #b0b0b0; 3 | 4 | // Colors 5 | $blue: #24abee; 6 | $red: #ff2940; 7 | $orange: #ff5b15; 8 | $yellow: #ffcb16; 9 | $green: #55c12d; 10 | $green-acid: #33d450; 11 | $green-secondary: #9dc13c; 12 | $cyan: #17a2b8; 13 | $warning: $orange; 14 | $success: $green; 15 | $twitter: #1da1f2; 16 | $pink: #e83e8c; 17 | 18 | $primary: $red; 19 | $secondary: $pink; 20 | $dark: #202020; 21 | $white: white; 22 | $black: #0f0f10; 23 | 24 | $gray-100: #fcfcfd; 25 | $gray-200: #ededee; 26 | $gray-300: #d1d1d2; 27 | $gray-400: #959596; 28 | $gray-500: #6d6d6e; 29 | $gray-600: #575758; 30 | $gray-700: #444445; 31 | $gray-800: #29292a; 32 | $gray-900: #1b1b1c; 33 | $gray: $gray-500; 34 | $dark-gray: $gray-600; 35 | $code-color: $secondary; 36 | 37 | $light: $gray-200; 38 | 39 | $body-bg: $dark; 40 | 41 | @import '~bootstrap/scss/functions'; 42 | @import '~bootstrap/scss/variables'; 43 | @import '~assets/scss/bootstrap/variable-overrides'; 44 | -------------------------------------------------------------------------------- /tests/e2e/plugins/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable arrow-body-style */ 2 | // https://docs.cypress.io/guides/guides/plugins-guide.html 3 | 4 | // if you need a custom webpack configuration you can uncomment the following import 5 | // and then use the `file:preprocessor` event 6 | // as explained in the cypress docs 7 | // https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples 8 | 9 | // /* eslint-disable import/no-extraneous-dependencies, global-require */ 10 | // const webpack = require('@cypress/webpack-preprocessor') 11 | 12 | module.exports = (on, config) => { 13 | // on('file:preprocessor', webpack({ 14 | // webpackOptions: require('@vue/cli-service/webpack.config'), 15 | // watchOptions: {} 16 | // })) 17 | 18 | return Object.assign({}, config, { 19 | fixturesFolder: "tests/e2e/fixtures", 20 | integrationFolder: "tests/e2e/specs", 21 | screenshotsFolder: "tests/e2e/screenshots", 22 | videosFolder: "tests/e2e/videos", 23 | supportFile: "tests/e2e/support/index.js" 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /site/assets/scss/bootstrap/utilities/_font-sizes.scss: -------------------------------------------------------------------------------- 1 | @each $breakpoint in map-keys($grid-breakpoints) { 2 | @include media-breakpoint-up($breakpoint) { 3 | $infix: breakpoint-infix($breakpoint, $grid-breakpoints); 4 | 5 | .font#{$infix}-size-xxl { 6 | @include font-size($font-size-xxl); 7 | } 8 | 9 | .font#{$infix}-size-xl { 10 | @include font-size($font-size-xl); 11 | } 12 | 13 | .font#{$infix}-size-lg { 14 | @include font-size($font-size-lg); 15 | } 16 | 17 | .font#{$infix}-size-md { 18 | @include font-size($font-size-md); 19 | } 20 | 21 | .font#{$infix}-size-base { 22 | @include font-size($font-size-base); 23 | } 24 | 25 | .font#{$infix}-size-sm { 26 | @include font-size($font-size-sm); 27 | } 28 | 29 | .font#{$infix}-size-xs { 30 | @include font-size($font-size-xs); 31 | } 32 | 33 | .font#{$infix}-size-xxs { 34 | @include font-size($font-size-xxs); 35 | } 36 | } 37 | } 38 | 39 | .line-height-sm { 40 | line-height: $line-height-sm !important; 41 | } 42 | -------------------------------------------------------------------------------- /site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-responsive-menu", 3 | "version": "1.0.0", 4 | "description": "My astonishing Nuxt.js project", 5 | "author": "gijsroge", 6 | "private": true, 7 | "scripts": { 8 | "dev": "nuxt", 9 | "build": "nuxt build", 10 | "start": "nuxt start", 11 | "generate": "nuxt generate" 12 | }, 13 | "dependencies": { 14 | "@juggle/resize-observer": "^3.0.2", 15 | "@nuxtjs/axios": "^5.3.6", 16 | "@nuxtjs/dotenv": "^1.4.0", 17 | "@nuxtjs/pwa": "^3.0.0-0", 18 | "@nuxtjs/style-resources": "^1.0.0", 19 | "@nuxtjs/svg-sprite": "^0.4.7", 20 | "bootstrap": "^4.4.1", 21 | "interactjs": "^1.8.4", 22 | "node-sass": "^4.13.1", 23 | "nuxt": "^2.0.0", 24 | "postcss-inline-svg": "^4.1.0", 25 | "sass-loader": "^8.0.2", 26 | "vue-focus-lock": "^1.3.2", 27 | "vue-github-button": "^1.1.2", 28 | "vue-global-events": "^1.1.2", 29 | "vue-popperjs": "^2.3.0", 30 | "vue-prism": "^1.0.5", 31 | "vue-svg-loader": "^0.15.0" 32 | }, 33 | "devDependencies": { 34 | "prettier": "^1.16.4" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /site/assets/images/svgs/icons/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /site/assets/scss/components/_dropdown.scss: -------------------------------------------------------------------------------- 1 | /*! purgecss start ignore */ 2 | .dropdown-menu { 3 | position: static; 4 | transform-origin: 50% 0; 5 | } 6 | .dropdown-menu-wrapper { 7 | position: absolute; 8 | perspective: 200px !important; 9 | } 10 | .dropdown-enter-active, 11 | .dropdown-leave-active { 12 | transition: all 0.15s ease; 13 | .dropdown-menu { 14 | will-change: transform, opacity; 15 | transition: opacity 0.35s, transform 0.25s cubic-bezier(0.23, 1, 0.32, 1); 16 | } 17 | .popper__arrow { 18 | will-change: opacity; 19 | transition: opacity 0.35s; 20 | } 21 | } 22 | .dropdown-leave-active { 23 | .dropdown-menu { 24 | transition: opacity 0.25s, transform 0.15s cubic-bezier(0.23, 1, 0.32, 1); 25 | } 26 | } 27 | .dropdown-enter { 28 | .popper__arrow { 29 | opacity: 0; 30 | } 31 | .dropdown-menu { 32 | transform: scale(0.98) rotate3d(1, 0, 0, -10deg); 33 | opacity: 0; 34 | } 35 | } 36 | .dropdown-leave-to { 37 | .popper__arrow { 38 | opacity: 0; 39 | } 40 | .dropdown-menu { 41 | transform: scale(1) rotate3d(1, 0, 0, -5deg); 42 | opacity: 0; 43 | } 44 | } 45 | /*! purgecss end ignore */ -------------------------------------------------------------------------------- /site/components/Logo.vue: -------------------------------------------------------------------------------- 1 | 26 | 40 | -------------------------------------------------------------------------------- /site/plugins/ga.js: -------------------------------------------------------------------------------- 1 | export default ({ app }) => { 2 | /* 3 | ** Only run on client-side and only in production mode 4 | */ 5 | if (process.env.NODE_ENV !== 'production') return 6 | /* 7 | ** Include Google Analytics Script 8 | */ 9 | ;(function(i, s, o, g, r, a, m) { 10 | i['GoogleAnalyticsObject'] = r 11 | ;(i[r] = 12 | i[r] || 13 | function() { 14 | ;(i[r].q = i[r].q || []).push(arguments) 15 | }), 16 | (i[r].l = 1 * new Date()) 17 | ;(a = s.createElement(o)), (m = s.getElementsByTagName(o)[0]) 18 | a.async = 1 19 | a.src = g 20 | m.parentNode.insertBefore(a, m) 21 | })( 22 | window, 23 | document, 24 | 'script', 25 | 'https://www.google-analytics.com/analytics.js', 26 | 'ga' 27 | ) 28 | /* 29 | ** Set the current page 30 | */ 31 | ga('create', 'UA-157971972-1', 'auto') 32 | /* 33 | ** Every time the route changes (fired on initialization too) 34 | */ 35 | app.router.afterEach((to, from) => { 36 | /* 37 | ** We tell Google Analytics to add a `pageview` 38 | */ 39 | ga('set', 'page', to.fullPath) 40 | ga('send', 'pageview') 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /site/assets/scss/bootstrap/utilities/_width.scss: -------------------------------------------------------------------------------- 1 | @each $breakpoint in map-keys($grid-breakpoints) { 2 | @include media-breakpoint-up($breakpoint) { 3 | $infix: breakpoint-infix($breakpoint, $grid-breakpoints); 4 | .w#{$infix}-2 { 5 | max-width: 2rem; 6 | } 7 | .w#{$infix}-3 { 8 | max-width: 3rem; 9 | } 10 | .w#{$infix}-5 { 11 | max-width: 5rem; 12 | } 13 | .w#{$infix}-60 { 14 | max-width: 60rem; 15 | } 16 | .w#{$infix}-medium { 17 | max-width: 30rem; 18 | } 19 | .w#{$infix}-max-content { 20 | max-width: max-content; 21 | } 22 | 23 | .w#{$infix}-fullwidth { 24 | width: 100% !important; 25 | } 26 | 27 | .w#{$infix}-initial { 28 | width: initial !important; 29 | } 30 | } 31 | } 32 | 33 | .w-mobile-breakout-viewport { 34 | @include media-breakpoint-down(xs) { 35 | width: 100vw !important; 36 | position: relative !important; 37 | left: 50% !important; 38 | right: 50% !important; 39 | margin-left: -50vw !important; 40 | margin-right: -50vw !important; 41 | } 42 | } 43 | 44 | .mw-min-content { 45 | max-width: min-content !important; 46 | } 47 | -------------------------------------------------------------------------------- /site/assets/scss/components/_news-item.scss: -------------------------------------------------------------------------------- 1 | @include media-breakpoint-up(md) { 2 | .news-item-large { 3 | position: relative; 4 | background-color: $light; 5 | display: block; 6 | color: $white !important; 7 | &:hover,&:focus{ 8 | color: $white !important; 9 | } 10 | 11 | &:after { 12 | content: ''; 13 | position: absolute; 14 | width: 100%; 15 | top: 0; 16 | left: 0; 17 | height: 100%; 18 | opacity: 0.5; 19 | background-image: linear-gradient(to right, rgba(black, 1) 0%, rgba(black, 0) 100%); 20 | } 21 | } 22 | .news-item-large__content { 23 | @include font-size($font-size-lg); 24 | } 25 | .news-item-large__title { 26 | @include font-size($h1-font-size); 27 | @supports (-webkit-line-clamp: 1) { 28 | max-height: none; 29 | } 30 | display: -webkit-box; 31 | -webkit-line-clamp: 3; 32 | -webkit-box-orient: vertical; 33 | overflow: hidden; 34 | } 35 | .news-item-large__image { 36 | height: 550px; 37 | object-fit: cover; 38 | width: 100%; 39 | } 40 | .news-item-large__block { 41 | position: absolute; 42 | top: 50%; 43 | transform: translateY(-50%); 44 | left: 6.25rem; 45 | z-index: 1; 46 | width: 500px; 47 | max-width: 100%; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /site/assets/scss/components/configurator/_configurator-shape-picker.scss: -------------------------------------------------------------------------------- 1 | .c-shape-picker { 2 | flex-wrap: wrap; 3 | width: calc(100% + 0.5rem); 4 | padding-top: 0; 5 | padding-bottom: 0; 6 | } 7 | 8 | .c-shape-picker--dark { 9 | .c-shape-picker__item { 10 | background-color: #d0d0d0; 11 | } 12 | } 13 | 14 | .c-shape-picker__item { 15 | background-color: white; 16 | width: calc(16.66667% - 0.5rem); 17 | margin-right: 0.5rem; 18 | margin-bottom: 0.5rem; 19 | padding: 0.2rem; 20 | border: 3px solid transparent; 21 | cursor: pointer; 22 | height: 51px; 23 | display: flex; 24 | align-items: center; 25 | justify-content: center; 26 | 27 | @media (max-width: 1200px) { 28 | width: calc(20% - 0.5rem); 29 | } 30 | 31 | img { 32 | margin: auto; 33 | max-height: 100%; 34 | } 35 | 36 | &:hover, 37 | &.is-active { 38 | border-color: $primary; 39 | } 40 | 41 | &.clear { 42 | position: relative; 43 | overflow: hidden; 44 | border: none; 45 | background-color: white; 46 | 47 | &::before { 48 | content: ''; 49 | background: red; 50 | height: 3px; 51 | width: 160%; 52 | transform: rotate(-45deg); 53 | transform-origin: left bottom; 54 | position: absolute; 55 | top: 47px; 56 | left: -1px; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tests/e2e/specs/test.js: -------------------------------------------------------------------------------- 1 | // https://docs.cypress.io/api/introduction/api.html 2 | 3 | describe("Test core functionality", () => { 4 | it("Renders only 9 items", () => { 5 | cy.viewport(1140, 660); 6 | cy.visit("/"); 7 | cy.get('[data-cypress="mainnav1"]') 8 | .children() 9 | .should("have.length", 9); 10 | }); 11 | 12 | it("Renders only 6 items", () => { 13 | cy.viewport(1140, 660); 14 | cy.visit("/"); 15 | cy.get('[data-cypress="mainnav2"]') 16 | .children() 17 | .should("have.length", 6); 18 | }); 19 | 20 | it("Renders only 2 items", () => { 21 | cy.viewport(1140, 660); 22 | cy.visit("/"); 23 | cy.get('[data-cypress="mainnav3"]') 24 | .children() 25 | .should("have.length", 2); 26 | }); 27 | 28 | it("Should render all items because of incompatible root element.", () => { 29 | cy.viewport(1140, 660); 30 | cy.visit("/"); 31 | cy.get('[data-cypress="mainnav4"] ul') 32 | .children() 33 | .should("have.length", 10); 34 | }); 35 | 36 | it("Renders 9 items with a nested menu marked with a data attribute 'data-vue-responsive-menu'", () => { 37 | cy.viewport(1140, 660); 38 | cy.visit("/"); 39 | cy.get('[data-cypress="mainnav5"] [data-vue-responsive-menu]') 40 | .children() 41 | .should("have.length", 9); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /site/assets/scss/components/_image-block.scss: -------------------------------------------------------------------------------- 1 | .image-block__title { 2 | position: absolute; 3 | bottom: 0; 4 | left: 0; 5 | padding-bottom: 1.5rem; 6 | padding-left: 1.5rem; 7 | text-transform: uppercase; 8 | font-weight: $font-weight-bold; 9 | color: $white; 10 | width: 100%; 11 | z-index: 1; 12 | margin-bottom: 0; 13 | 14 | &:after { 15 | content: ''; 16 | position: absolute; 17 | width: 100%; 18 | bottom: 0; 19 | left: 0; 20 | height: 300%; 21 | opacity: 0.5; 22 | background-image: linear-gradient(to bottom, rgba(black, 0) 0%, rgba(black, 1) 100%); 23 | z-index: -1; 24 | } 25 | } 26 | .image-block-inner { 27 | background-color: $light; 28 | overflow: hidden; 29 | } 30 | 31 | .image-block { 32 | display: flex; 33 | transition: 0.15s; 34 | position: relative; 35 | 36 | &:after { 37 | transition: 0.15s; 38 | opacity: 0; 39 | position: absolute; 40 | content: ''; 41 | left: 0; 42 | bottom: 0; 43 | width: 100%; 44 | height: 5rem; 45 | box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.3), 0 1rem 1rem rgba(0, 0, 0, 0.22); 46 | transform: translateY(-.5rem) scale(.95); 47 | z-index: -1; 48 | } 49 | 50 | &:hover, 51 | &:focus { 52 | transform: translateY(-0.25rem); 53 | 54 | &:after { 55 | transition: 0.55s; 56 | opacity: 1; 57 | transform: scale(.95); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /site/assets/scss/components/_pagination.scss: -------------------------------------------------------------------------------- 1 | .pagination { 2 | display: flex; 3 | justify-content: center; 4 | 5 | .page-link { 6 | background: none; 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | padding: 0; 11 | width: 2rem; 12 | height: 2rem; 13 | border: none; 14 | margin: 0 0.25rem; 15 | position: relative; 16 | z-index: 1; 17 | transition: 0.15s; 18 | 19 | &:not(.active) { 20 | &:hover, 21 | &:focus { 22 | &:after { 23 | transform: rotate(20deg); 24 | border-radius: 0; 25 | background-color: $secondary; 26 | } 27 | } 28 | } 29 | 30 | &.active { 31 | color: $white; 32 | &:after { 33 | background-color: $primary; 34 | border-radius: 0; 35 | transform: rotate(20deg); 36 | } 37 | 38 | &:hover, 39 | &:focus { 40 | &:after { 41 | transform: rotate(25deg); 42 | } 43 | } 44 | } 45 | 46 | &:after { 47 | content: ''; 48 | position: absolute; 49 | top: 0; 50 | left: 0; 51 | height: 100%; 52 | width: 100%; 53 | background-color: $light; 54 | transition: 0.15s; 55 | border-radius: 50%; 56 | z-index: -1; 57 | } 58 | } 59 | 60 | .prev-next { 61 | color: $white; 62 | &:after { 63 | background-color: $yellow; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /site/assets/scss/components/_product-thumb.scss: -------------------------------------------------------------------------------- 1 | .product-thumb__title { 2 | position: absolute; 3 | bottom: 0; 4 | left: 0; 5 | padding-bottom: 1.5rem; 6 | padding-left: 1.5rem; 7 | text-transform: uppercase; 8 | font-weight: $font-weight-bold; 9 | color: $white; 10 | width: 100%; 11 | z-index: 1; 12 | 13 | @include media-breakpoint-down(sm) { 14 | padding-right: 0.5rem; 15 | @include font-size($font-size-sm); 16 | } 17 | 18 | &:after { 19 | content: ''; 20 | position: absolute; 21 | width: 100%; 22 | bottom: 0; 23 | left: 0; 24 | height: 300%; 25 | opacity: 0.5; 26 | background-image: linear-gradient(to bottom, rgba(black, 0) 0%, rgba(black, 1) 100%); 27 | z-index: -1; 28 | } 29 | } 30 | .product-thumb-inner { 31 | width: 100%; 32 | background-color: $light; 33 | overflow: hidden; 34 | } 35 | 36 | .product-thumb { 37 | width: 100%; 38 | display: flex; 39 | transition: 0.15s; 40 | 41 | &:after { 42 | transition: 0.15s; 43 | opacity: 0; 44 | position: absolute; 45 | content: ''; 46 | left: 0; 47 | bottom: 0; 48 | width: 100%; 49 | height: 5rem; 50 | box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.3), 0 1rem 1rem rgba(0, 0, 0, 0.22); 51 | transform: translateY(-0.5rem) scale(0.95); 52 | z-index: -1; 53 | } 54 | 55 | &:hover, 56 | &:focus { 57 | transform: translateY(-0.25rem); 58 | 59 | &:after { 60 | transition: 0.55s; 61 | opacity: 1; 62 | transform: scale(0.95); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /site/assets/scss/components/_popper.scss: -------------------------------------------------------------------------------- 1 | /*! purgecss start ignore */ 2 | .popper { 3 | width: auto; 4 | position: absolute; 5 | z-index: $zindex-dropdown; 6 | } 7 | .popper .popper__arrow { 8 | width: 0; 9 | height: 0; 10 | border-style: solid; 11 | position: absolute; 12 | margin: 5px; 13 | } 14 | .popper[x-placement^='top'] { 15 | margin-bottom: 5px; 16 | } 17 | .popper[x-placement^='top'] .popper__arrow { 18 | border-width: 5px 5px 0 5px; 19 | border-color: $black transparent transparent transparent; 20 | bottom: -5px; 21 | left: calc(50% - 5px); 22 | margin-top: 0; 23 | margin-bottom: 0; 24 | } 25 | .popper[x-placement^='bottom'] { 26 | margin-top: 5px; 27 | } 28 | .popper[x-placement^='bottom'] .popper__arrow { 29 | border-width: 0 5px 5px 5px; 30 | border-color: transparent transparent $black transparent; 31 | top: -5px; 32 | left: calc(50% - 5px); 33 | margin-top: 0; 34 | margin-bottom: 0; 35 | } 36 | .popper[x-placement^='right'] { 37 | margin-left: 5px; 38 | } 39 | .popper[x-placement^='right'] .popper__arrow { 40 | border-width: 5px 5px 5px 0; 41 | border-color: transparent $black transparent transparent; 42 | left: -5px; 43 | top: calc(50% - 5px); 44 | margin-left: 0; 45 | margin-right: 0; 46 | } 47 | .popper[x-placement^='left'] { 48 | margin-right: 5px; 49 | } 50 | .popper[x-placement^='left'] .popper__arrow { 51 | border-width: 5px 0 5px 5px; 52 | border-color: transparent transparent transparent $black; 53 | right: -5px; 54 | top: calc(50% - 5px); 55 | margin-left: 0; 56 | margin-right: 0; 57 | } 58 | /*! purgecss end ignore */ -------------------------------------------------------------------------------- /site/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | /logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # parcel-bundler cache (https://parceljs.org/) 63 | .cache 64 | 65 | # next.js build output 66 | .next 67 | 68 | # nuxt.js build output 69 | .nuxt 70 | 71 | # Nuxt generate 72 | dist 73 | 74 | # vuepress build output 75 | .vuepress/dist 76 | 77 | # Serverless directories 78 | .serverless 79 | 80 | # IDE / Editor 81 | .idea 82 | 83 | # Service worker 84 | sw.* 85 | 86 | # Mac OSX 87 | .DS_Store 88 | 89 | # Vim swap files 90 | *.swp 91 | -------------------------------------------------------------------------------- /site/assets/scss/bootstrap/bootstrap.scss: -------------------------------------------------------------------------------- 1 | @import '~bootstrap/scss/root'; 2 | @import '~bootstrap/scss/reboot'; 3 | @import '~bootstrap/scss/type'; 4 | @import '~bootstrap/scss/images'; 5 | @import '~bootstrap/scss/code'; 6 | @import '~bootstrap/scss/grid'; 7 | @import '~bootstrap/scss/tables'; 8 | @import '~bootstrap/scss/forms'; 9 | @import '~bootstrap/scss/buttons'; 10 | @import '~bootstrap/scss/transitions'; 11 | @import '~bootstrap/scss/dropdown'; 12 | @import '~bootstrap/scss/button-group'; 13 | @import '~bootstrap/scss/input-group'; 14 | @import '~bootstrap/scss/custom-forms'; 15 | @import '~bootstrap/scss/nav'; 16 | @import '~bootstrap/scss/navbar'; 17 | @import '~bootstrap/scss/card'; 18 | @import '~bootstrap/scss/breadcrumb'; 19 | @import '~bootstrap/scss/badge'; 20 | @import '~bootstrap/scss/jumbotron'; 21 | @import '~bootstrap/scss/alert'; 22 | @import '~bootstrap/scss/progress'; 23 | @import '~bootstrap/scss/media'; 24 | @import '~bootstrap/scss/list-group'; 25 | @import '~bootstrap/scss/close'; 26 | @import '~bootstrap/scss/toasts'; 27 | @import '~bootstrap/scss/modal'; 28 | @import '~bootstrap/scss/tooltip'; 29 | @import '~bootstrap/scss/popover'; 30 | @import '~bootstrap/scss/carousel'; 31 | @import '~bootstrap/scss/spinners'; 32 | @import '~bootstrap/scss/utilities'; 33 | @import '~bootstrap/scss/print'; 34 | 35 | // Custom bootstrap extensions 36 | @import 'buttons'; 37 | @import 'row-gutters'; 38 | @import 'general'; 39 | @import 'dropdowns'; 40 | @import 'utilities/font-sizes'; 41 | @import 'utilities/width'; 42 | @import 'utilities/transforms'; 43 | @import 'utilities/transition'; 44 | @import 'utilities/backgrounds'; 45 | @import 'utilities/links'; 46 | @import 'utilities/sizes'; 47 | @import 'utilities/text'; 48 | @import 'utilities/position'; 49 | @import 'utilities/font-weights'; 50 | -------------------------------------------------------------------------------- /site/assets/scss/bootstrap/_cards.scss: -------------------------------------------------------------------------------- 1 | .card--skewed { 2 | border: none; 3 | 4 | .card-body { 5 | position: relative; 6 | z-index: 1; 7 | background-color: $light; 8 | &:after, 9 | &:before { 10 | position: absolute; 11 | content: ''; 12 | width: 100%; 13 | height: 10px; 14 | left: 0; 15 | background-size: 100% 100%; 16 | color: $light; 17 | } 18 | &:before { 19 | top: -10px; 20 | background-image: svg-load('../images/svgs/top-skew.svg', fill = currentColor); 21 | } 22 | &:after { 23 | bottom: -10px; 24 | background-image: svg-load('../images/svgs/bottom-skew.svg', fill = currentColor); 25 | } 26 | } 27 | } 28 | 29 | @each $color, $value in ('primary': $primary, 'light': $light) { 30 | .card-bg-#{$color} { 31 | .card-body { 32 | background-color: $value; 33 | 34 | .e-link { 35 | @include e-link(color-yiq($value)); 36 | } 37 | 38 | &:before { 39 | background-image: svg-load('../images/svgs/top-skew.svg', fill = $value); 40 | } 41 | &:after { 42 | background-image: svg-load('../images/svgs/bottom-skew.svg', fill = $value); 43 | } 44 | } 45 | } 46 | } 47 | 48 | .card--skewed-inverted { 49 | .card-body { 50 | &:after, 51 | &:before { 52 | transform: scaleX(-1); 53 | } 54 | } 55 | } 56 | 57 | .card--skewed-bottom-only { 58 | .card-body { 59 | &:before { 60 | display: none; 61 | } 62 | } 63 | } 64 | 65 | .card--skewed-lg { 66 | .card-body { 67 | &:after, 68 | &:before { 69 | @include media-breakpoint-up(lg) { 70 | height: 40px; 71 | } 72 | } 73 | &:before { 74 | @include media-breakpoint-up(lg) { 75 | top: -40px; 76 | } 77 | } 78 | &:after { 79 | @include media-breakpoint-up(lg) { 80 | bottom: -40px; 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /site/assets/scss/private/mixins.scss: -------------------------------------------------------------------------------- 1 | @import '~bootstrap/scss/mixins'; 2 | 3 | @mixin e-link($color: $primary) { 4 | color: rgba($color, 0.8); 5 | text-decoration: underline; 6 | text-decoration-color: rgba($color, 0.2); 7 | text-decoration-thickness: 0.125em; 8 | text-underline-position: under; 9 | 10 | @include hover { 11 | color: $color; 12 | text-decoration-color: rgba($color, 0.4); 13 | } 14 | } 15 | 16 | @mixin e-link--secondary() { 17 | color: $secondary !important; 18 | text-decoration: underline !important; 19 | text-decoration-color: rgba($secondary, 0.2) !important; 20 | text-decoration-thickness: 0.125em !important; 21 | text-underline-position: under !important; 22 | 23 | @include hover { 24 | text-decoration-color: rgba($secondary, 0.4) !important; 25 | } 26 | } 27 | 28 | @mixin e-link--dark() { 29 | color: $dark !important; 30 | text-decoration: underline !important; 31 | text-decoration-color: rgba($dark, 0.15) !important; 32 | text-decoration-thickness: 0.125em !important; 33 | text-underline-position: under !important; 34 | 35 | @include hover { 36 | text-decoration-color: rgba($dark, 0.05) !important; 37 | color: rgba($dark, 0.6) !important; 38 | } 39 | } 40 | 41 | @mixin container() { 42 | @include make-container(); 43 | 44 | @include media-breakpoint-down(xxl) { 45 | padding-left: 1.25rem; 46 | padding-right: 1.25rem; 47 | } 48 | } 49 | 50 | :root { 51 | --container-small: #{$container-small}; 52 | 53 | @include media-breakpoint-down(xxl) { 54 | --container-small: calc(#{$container-small} - 40px); 55 | } 56 | } 57 | 58 | @mixin container-small() { 59 | max-width: $container-small - $grid-gutter-width; 60 | max-width: calc(var(--container-small) - #{$grid-gutter-width}); 61 | 62 | @include media-breakpoint-down(xxl) { 63 | max-width: $container-small - 40px; 64 | max-width: var(--container-small); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /site/assets/scss/components/configurator/_configurator-canvas.scss: -------------------------------------------------------------------------------- 1 | .c-configurator__canvas { 2 | background-color: #d0d0d0; 3 | position: relative; 4 | transition: opacity ease 150ms; 5 | 6 | &::before { 7 | content: ''; 8 | position: absolute; 9 | background-color: white; 10 | top: -9px; 11 | right: -10px; 12 | width: calc(100% + 10px); 13 | height: 15px; 14 | transform: rotate(1deg); 15 | z-index: 1; 16 | } 17 | } 18 | 19 | .c-configurator__tooltip { 20 | position: absolute; 21 | top: 0; 22 | left: 0; 23 | padding: 0.35rem; 24 | background-color: white; 25 | box-shadow: 2px 2px 4px rgba(black, 0.35); 26 | border-radius: 5px; 27 | border-top-left-radius: 0; 28 | border-top-right-radius: 0; 29 | width: 140px; 30 | font-size: 12px; 31 | 32 | label { 33 | display: inline-block; 34 | font-weight: bold; 35 | width: 50px; 36 | margin: 0; 37 | } 38 | } 39 | 40 | .c-configurator__loading { 41 | position: absolute; 42 | margin: auto; 43 | top: 0; 44 | bottom: 0; 45 | left: 0; 46 | right: 0; 47 | display: flex; 48 | align-items: center; 49 | justify-content: center; 50 | font-weight: bold; 51 | pointer-events: none; 52 | background-color: white; 53 | padding: 0.75rem; 54 | border-radius: 5px; 55 | flex-direction: column; 56 | transition: opacity ease 300ms; 57 | 58 | svg { 59 | width: 64px; 60 | height: 64px; 61 | margin-bottom: 1rem; 62 | animation: rotation ease-in-out 2s forwards infinite; 63 | } 64 | } 65 | 66 | @keyframes rotation { 67 | 0% { 68 | transform: rotate(0deg); 69 | } 70 | 100% { 71 | transform: rotate(360deg); 72 | } 73 | } 74 | 75 | .c-configurator__canvas-container { 76 | max-width: calc(100% - 400px); 77 | 78 | @media (max-width: 1200px) { 79 | max-width: calc(100% - 300px); 80 | } 81 | } 82 | 83 | .c-configurator__loading { 84 | transition: opacity ease 150ms; 85 | } 86 | -------------------------------------------------------------------------------- /site/assets/scss/components/_transitions.scss: -------------------------------------------------------------------------------- 1 | .fade-enter-active, 2 | .fade-leave-active { 3 | transition: opacity 0.15s; 4 | } 5 | .fade-enter, 6 | .fade-leave-to { 7 | opacity: 0; 8 | } 9 | 10 | .slide-in-enter-active, 11 | .slide-in-leave-active { 12 | transition: all 0.4s; 13 | } 14 | .slide-in-enter, 15 | .slide-in-leave-to { 16 | transform: translateY(1rem); 17 | opacity: 0; 18 | } 19 | 20 | .list-enter-active { 21 | @for $i from 2 to 50 { 22 | &:nth-child(#{$i}) { 23 | transition-delay: $i * 0.02s !important; 24 | } 25 | } 26 | } 27 | .list-enter-active { 28 | transition: all 0.4s; 29 | } 30 | .list-enter, 31 | .list-leave-to { 32 | opacity: 0; 33 | transform: translateY(0.5rem); 34 | } 35 | 36 | // Address grid 37 | .address-grid-enter-active, 38 | .address-grid-leave-active { 39 | transition: all 0.3s; 40 | } 41 | .address-grid-enter, 42 | .address-grid-leave-to { 43 | opacity: 0; 44 | transform: scale(0.95); 45 | } 46 | 47 | .btn-loader-enter-active, 48 | .btn-loader-leave-active { 49 | transition: all 0.4s; 50 | } 51 | .btn-loader-enter { 52 | transform: translateY(100%); 53 | } 54 | .btn-loader-leave-to { 55 | transform: translateY(-100%); 56 | } 57 | 58 | .btn-loader { 59 | padding: 0 !important; 60 | overflow: hidden; 61 | display: flex !important; 62 | justify-content: center; 63 | align-items: center; 64 | 65 | &.btn-xs { 66 | .btn-loader-inner { 67 | height: 2.25rem; 68 | } 69 | } 70 | } 71 | 72 | .btn-loader-inner { 73 | position: relative; 74 | height: 3rem; 75 | > * { 76 | display: flex; 77 | justify-content: center; 78 | align-items: center; 79 | position: absolute; 80 | left: 0; 81 | top: 0; 82 | height: 100%; 83 | width: 100%; 84 | } 85 | } 86 | 87 | // Inspiration bar 88 | .inspiration-bar-enter-active, 89 | .inspiration-bar-leave-active { 90 | transition: all 0.2s; 91 | } 92 | .inspiration-bar-enter, 93 | .inspiration-bar-leave-to { 94 | transform: translateY(100%); 95 | opacity: 0; 96 | } 97 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-responsive-menu", 3 | "version": "0.2.2", 4 | "description": "A renderless Vue component that will auto detect if menu items don't fit and moves them to a separate dropdown. Also known as the Priority+ pattern.", 5 | "author": { 6 | "name": "Gijs Rogé", 7 | "email": "rogegijs@gmail.com" 8 | }, 9 | "scripts": { 10 | "serve": "vue-cli-service serve demo", 11 | "test:unit": "vue-cli-service test:unit", 12 | "test:e2e": "vue-cli-service test:e2e", 13 | "lint": "vue-cli-service lint", 14 | "build:npm": "rollit", 15 | "release": "release-it" 16 | }, 17 | "main": "dist/vue-responsive-menu.cjs.js", 18 | "module": "dist/vue-responsive-menu.es.js", 19 | "browser": "dist/vue-responsive-menu.es.js", 20 | "unpkg": "dist/vue-responsive-menu.js", 21 | "homepage": "https://vue-responsive-menu.netlify.com/", 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/gijsroge/vue-responsive-menu.git" 25 | }, 26 | "files": [ 27 | "src", 28 | "dist/*.js", 29 | "LICENSE", 30 | "README.md" 31 | ], 32 | "dependencies": { 33 | "core-js": "^3.4.4", 34 | "vue": "^2.6.10" 35 | }, 36 | "devDependencies": { 37 | "@vue/cli-plugin-babel": "^4.2.0", 38 | "@vue/cli-plugin-e2e-cypress": "^4.2.2", 39 | "@vue/cli-plugin-eslint": "^4.2.0", 40 | "@vue/cli-plugin-unit-jest": "^4.2.0", 41 | "@vue/cli-service": "^4.2.0", 42 | "@vue/eslint-config-prettier": "^6.0.0", 43 | "@vue/test-utils": "1.0.0-beta.31", 44 | "babel-eslint": "^10.0.3", 45 | "eslint": "^6.7.2", 46 | "eslint-plugin-prettier": "^3.1.1", 47 | "eslint-plugin-vue": "^6.1.2", 48 | "lint-staged": "^9.5.0", 49 | "node-sass": "^4.13.1", 50 | "prettier": "^1.19.1", 51 | "sass-loader": "^8.0.2", 52 | "vue-template-compiler": "^2.6.11" 53 | }, 54 | "gitHooks": { 55 | "pre-commit": "lint-staged" 56 | }, 57 | "jsdelivr": "dist/vue-responsive-menu.js", 58 | "lint-staged": { 59 | "*.{js,vue},!site/**/*": [ 60 | "vue-cli-service lint", 61 | "git add" 62 | ] 63 | }, 64 | "bugs": { 65 | "url": "https://github.com/gijsroge/vue-responsive-menu/issues" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /site/components/ResizeContent.vue: -------------------------------------------------------------------------------- 1 | 7 | 61 | 94 | -------------------------------------------------------------------------------- /site/assets/scss/components/_dialog.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * When the native `` element is supported, the overlay is implied and 3 | * can be styled with `::backdrop`, which means the DOM one should be removed. 4 | * 5 | * The `data-a11y-dialog-native` attribute is set by the script when the 6 | * `` element is properly supported. 7 | * 8 | * Feel free to replace `:first-child` with the overlay selector you prefer. 9 | */ 10 | [data-a11y-dialog-native] > :first-child { 11 | display: none; 12 | } 13 | 14 | /** 15 | * When the `` element is not supported, its default display is `inline` 16 | * which can cause layout issues. This makes sure the dialog is correctly 17 | * displayed when open. 18 | */ 19 | dialog[open] { 20 | display: block; 21 | } 22 | 23 | /** 24 | * When the native `` element is not supported, the script toggles the 25 | * `aria-hidden` attribute on the container. If `aria-hidden` is set to `true`, 26 | * the container should be hidden entirely. 27 | * 28 | * Feel free to replace `.dialog-container` with the container selector you 29 | * prefer. 30 | */ 31 | .c-dialog[aria-hidden='true'] { 32 | &:not([data-a11y-dialog-native]) { 33 | display: none !important; 34 | } 35 | } 36 | 37 | @keyframes dialog-enter { 38 | to { 39 | transform: none; 40 | opacity: 1; 41 | } 42 | } 43 | 44 | @keyframes dialog-backdrop-enter { 45 | to { 46 | opacity: 1; 47 | } 48 | } 49 | 50 | .c-dialog--default { 51 | &:not([data-a11y-dialog-native]) { 52 | position: fixed; 53 | top: 0; 54 | left: 0; 55 | height: 100%; 56 | width: 100%; 57 | z-index: $zindex-modal; 58 | display: flex; 59 | justify-content: center; 60 | align-items: center; 61 | 62 | .c-dialog-overlay { 63 | position: absolute; 64 | top: 0; 65 | height: 100%; 66 | width: 100%; 67 | background-color: rgba($gray-200, 0.9); 68 | } 69 | 70 | .c-dialog-element { 71 | opacity: 0; 72 | transform: translateY(-3rem); 73 | 74 | z-index: 5; 75 | background-color: white; 76 | animation: dialog-enter 0.3s forwards; 77 | max-height: 95vh; 78 | overflow-y: auto; 79 | margin-top: 5rem; 80 | margin-bottom: 5rem; 81 | } 82 | } 83 | 84 | .c-dialog-element { 85 | border: none; 86 | padding: 0; 87 | margin-left: auto; 88 | margin-right: auto; 89 | @include media-breakpoint-up(sm) { 90 | max-width: 50rem !important; 91 | } 92 | 93 | .dialog-close { 94 | position: absolute; 95 | top: 1rem; 96 | right: 1rem; 97 | z-index: 2; 98 | } 99 | 100 | &::backdrop { 101 | background-color: rgba($gray-200, 0.9); 102 | opacity: 0; 103 | animation: dialog-backdrop-enter 0.15s forwards; 104 | } 105 | 106 | opacity: 0; 107 | transform: translateY(1rem) scale(0.97); 108 | transform-origin: top center; 109 | animation: dialog-enter 0.15s forwards; 110 | } 111 | } 112 | 113 | .c-dialog--minimal { 114 | .dialog-close { 115 | color: $white; 116 | position: absolute; 117 | right: 0; 118 | top: -3rem; 119 | } 120 | .c-dialog-element { 121 | border: none; 122 | padding: 0; 123 | max-width: calc(100% - 4rem) !important; 124 | background-color: transparent; 125 | 126 | .c-dialog-element-inner { 127 | background-color: white; 128 | } 129 | &::backdrop { 130 | background-color: rgba($dark, 0.9); 131 | opacity: 0; 132 | animation: dialog-backdrop-enter 0.15s forwards; 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /site/nuxt.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mode: 'universal', 3 | /* 4 | ** Headers of the page 5 | */ 6 | head: { 7 | title: 'Vue Responsive Menu | auto hide excessive menu items', 8 | meta: [ 9 | { charset: 'utf-8' }, 10 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 11 | { 12 | hid: 'description', 13 | name: 'description', 14 | content: process.env.npm_package_description || '' 15 | }, 16 | { name: 'theme-color', content: '#ff2940' } 17 | ], 18 | link: [ 19 | { 20 | rel: 'stylesheet', 21 | href: 22 | 'https://fonts.googleapis.com/css?family=Lato:300|Open+Sans:300,400,700&display=swap' 23 | }, 24 | { 25 | rel: 'apple-touch-icon', 26 | href: '/favicons/apple-touch-icon-152x152.png' 27 | }, 28 | { 29 | rel: 'apple-touch-icon', 30 | sizes: '180x180', 31 | href: '/favicons/apple-touch-icon.png' 32 | }, 33 | { 34 | rel: 'icon', 35 | type: 'image/png', 36 | sizes: '32x32', 37 | href: '/favicons/favicon-32x32.png' 38 | }, 39 | { 40 | rel: 'icon', 41 | type: 'image/png', 42 | sizes: '16x16', 43 | href: '/favicons/favicon-16x16.png' 44 | }, 45 | { 46 | rel: 'mask-icon', 47 | href: '/favicons/safari-pinned-tab.svg', 48 | color: '#ff2940' 49 | }, 50 | { rel: 'icon', type: 'image/png', href: '/favicons/favicon.png' } 51 | ] 52 | }, 53 | /* 54 | ** Customize the progress-bar color 55 | */ 56 | loading: { color: '#fff' }, 57 | /* 58 | ** Global CSS 59 | */ 60 | css: ['@/assets/scss/global.scss'], 61 | styleResources: { 62 | scss: [ 63 | '@/assets/scss/private/variables.scss', 64 | '@/assets/scss/private/mixins.scss' 65 | ] 66 | }, 67 | svgSprite: { 68 | input: '~/assets/images/svgs/icons' 69 | }, 70 | /* 71 | ** Plugins to load before mounting the App 72 | */ 73 | plugins: [ 74 | { src: '~/plugins/vue-prism.js', mode: 'client' }, 75 | { src: '~plugins/ga.js', mode: 'client' } 76 | ], 77 | /* 78 | ** Nuxt.js dev-modules 79 | */ 80 | buildModules: [], 81 | /* 82 | ** Nuxt.js modules 83 | */ 84 | modules: [ 85 | '@nuxtjs/style-resources', 86 | '@nuxtjs/svg-sprite', 87 | // Doc: https://axios.nuxtjs.org/usage 88 | '@nuxtjs/pwa', 89 | // Doc: https://github.com/nuxt-community/dotenv-module 90 | '@nuxtjs/dotenv' 91 | ], 92 | /* 93 | ** Axios module configuration 94 | ** See https://axios.nuxtjs.org/options 95 | */ 96 | axios: {}, 97 | /* 98 | ** Build configuration 99 | */ 100 | build: { 101 | postcss: { 102 | plugins: { 103 | 'postcss-inline-svg': {} 104 | } 105 | }, 106 | extend(config, ctx) { 107 | const svgRule = config.module.rules.find(rule => rule.test.test('.svg')) 108 | 109 | svgRule.test = /\.(png|jpe?g|gif|webp)$/ 110 | 111 | config.module.rules.push({ 112 | test: /\.svg$/, 113 | oneOf: [ 114 | { 115 | resourceQuery: /inline/, 116 | loader: 'file-loader', 117 | query: { 118 | name: 'assets/[name].[hash:8].[ext]' 119 | } 120 | }, 121 | { 122 | loader: 'vue-svg-loader', 123 | options: { 124 | // Optional svgo options 125 | svgo: { 126 | plugins: [{ removeViewBox: false }] 127 | } 128 | } 129 | } 130 | ] 131 | }) 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /site/assets/scss/components/configurator/_configurator-controls.scss: -------------------------------------------------------------------------------- 1 | .c-controls { 2 | background-color: #f4f4f4; 3 | flex-shrink: 0; 4 | width: 400px; 5 | 6 | @media (max-width: 1200px) { 7 | width: 300px; 8 | } 9 | 10 | .btn-outline-dark.btn-shadow { 11 | box-shadow: 6px 6px 0 0 #e5e5e5; 12 | } 13 | 14 | .form-control { 15 | border-radius: 3px; 16 | border: 1px solid #d5d5d5; 17 | } 18 | 19 | h3 { 20 | color: #231f20; 21 | font-size: 1rem; 22 | text-transform: none; 23 | padding-top: 0; 24 | margin-top: 0; 25 | } 26 | } 27 | 28 | .c-controls__header { 29 | background-color: #e5e5e5; 30 | padding: 1rem 1.625rem; 31 | cursor: pointer; 32 | 33 | &:hover { 34 | text-decoration: underline; 35 | } 36 | 37 | &.is-active { 38 | background-color: $secondary; 39 | } 40 | 41 | &.is-curved { 42 | padding-top: 1.6rem; 43 | position: relative; 44 | 45 | &::before { 46 | content: ''; 47 | position: absolute; 48 | background-color: white; 49 | top: -9px; 50 | left: -10px; 51 | width: calc(100% + 10px); 52 | height: 15px; 53 | transform: rotate(-2deg); 54 | } 55 | } 56 | } 57 | 58 | .c-controls__header-sequence { 59 | background-color: #231f20; 60 | width: 2rem; 61 | height: 2rem; 62 | border-radius: 50%; 63 | color: white; 64 | font-weight: bold; 65 | flex-shrink: 0; 66 | } 67 | 68 | .c-controls__header-title { 69 | margin: auto; 70 | margin-left: 0.875rem; 71 | 72 | &:not(:first-child) { 73 | margin-top: auto; 74 | } 75 | } 76 | 77 | .c-controls__content { 78 | padding: 1rem 1.625rem; 79 | padding-bottom: 2rem; 80 | max-height: 60vh; 81 | overflow-y: auto; 82 | 83 | .c-controls__content { 84 | padding: 0; 85 | } 86 | } 87 | 88 | .c-controls__content-title { 89 | color: #231f20; 90 | padding-top: 1.25rem; 91 | font-size: 1.125rem; 92 | } 93 | 94 | .c-controls__buttons { 95 | padding-bottom: 0.5rem; 96 | 97 | .btn:not(:last-child) { 98 | margin-right: 1rem; 99 | } 100 | } 101 | 102 | .c-controls__text-settings-row { 103 | margin-bottom: 0.75rem; 104 | 105 | > *:not(:last-child) { 106 | margin-right: 0.5rem; 107 | } 108 | } 109 | 110 | .c-controls__text-style { 111 | background-color: #f4f4f4; 112 | border: 1px solid #d5d5d5; 113 | border-radius: 3px; 114 | font-family: serif; 115 | color: black; 116 | font-weight: bold; 117 | padding: 0.4rem 1rem; 118 | flex-shrink: 0; 119 | 120 | svg { 121 | fill: #787d84; 122 | width: 1.75rem; 123 | height: 1.75rem; 124 | } 125 | 126 | &.is-active { 127 | background-color: $secondary; 128 | border-color: transparent; 129 | 130 | svg { 131 | fill: black; 132 | } 133 | } 134 | } 135 | 136 | .c-color-picker { 137 | cursor: pointer; 138 | 139 | .vc-compact { 140 | position: absolute; 141 | top: 100%; 142 | right: 0; 143 | z-index: 2; 144 | width: 245px; 145 | } 146 | } 147 | 148 | .c-color-picker__preview { 149 | width: 50px; 150 | height: calc(100% - 2px); 151 | min-height: 25px; 152 | border-radius: 3px; 153 | background-color: black; 154 | border: 3px solid white; 155 | display: block; 156 | overflow: hidden; 157 | box-shadow: 0 0 0 1px black; 158 | margin-top: 1px; 159 | margin-right: 1px; 160 | 161 | &.is-active { 162 | border-bottom-left-radius: 0; 163 | border-bottom-right-radius: 0; 164 | } 165 | } 166 | 167 | .vc-compact-colors { 168 | li:first-child { 169 | position: relative; 170 | overflow: hidden; 171 | 172 | &::before { 173 | content: ''; 174 | transform-origin: center center; 175 | transform: rotate(-45deg); 176 | background: red; 177 | width: 50px; 178 | height: 3px; 179 | position: absolute; 180 | top: 0; 181 | left: -12px; 182 | } 183 | } 184 | } 185 | 186 | .c-controls__option { 187 | padding-bottom: 0.5rem; 188 | padding-top: 0.5rem; 189 | 190 | &:not(:last-child) { 191 | border-bottom: 1px solid rgba(black, 0.1); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /site/assets/scss/components/_prism.scss: -------------------------------------------------------------------------------- 1 | pre[class*='language-'], 2 | code[class*='language-'] { 3 | color: #d4d4d4; 4 | text-shadow: none; 5 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; 6 | direction: ltr; 7 | text-align: left; 8 | white-space: pre; 9 | word-spacing: normal; 10 | word-break: normal; 11 | line-height: 1.5; 12 | -moz-tab-size: 4; 13 | -o-tab-size: 4; 14 | tab-size: 4; 15 | -webkit-hyphens: none; 16 | -moz-hyphens: none; 17 | -ms-hyphens: none; 18 | hyphens: none; 19 | } 20 | 21 | pre[class*='language-']::selection, 22 | code[class*='language-']::selection { 23 | text-shadow: none; 24 | background: #b3d4fc; 25 | } 26 | 27 | @media print { 28 | pre[class*='language-'], 29 | code[class*='language-'] { 30 | text-shadow: none; 31 | } 32 | } 33 | 34 | pre[class*='language-'] { 35 | overflow: auto; 36 | 37 | } 38 | 39 | :not(pre) > code[class*='language-'] { 40 | padding: 0.3em 0.5em; 41 | color: $secondary; 42 | } 43 | /********************************************************* 44 | * Tokens 45 | */ 46 | .namespace { 47 | opacity: 0.7; 48 | } 49 | 50 | .token.comment, 51 | .token.prolog, 52 | .token.doctype, 53 | .token.cdata { 54 | color: #444445; 55 | } 56 | 57 | .token.punctuation { 58 | color: #d4d4d4; 59 | } 60 | 61 | .token.property, 62 | .token.tag, 63 | .token.boolean, 64 | .token.number, 65 | .token.constant, 66 | .token.symbol, 67 | .token.deleted { 68 | color: #b5cea8; 69 | } 70 | 71 | .token.selector, 72 | .token.attr-name, 73 | .token.string, 74 | .token.char, 75 | .token.builtin, 76 | .token.inserted { 77 | color: #ce9178; 78 | } 79 | 80 | .token.operator, 81 | .token.entity, 82 | .token.url, 83 | .language-css .token.string, 84 | .style .token.string { 85 | color: #d4d4d4; 86 | background: #1e1e1e; 87 | } 88 | 89 | .token.atrule, 90 | .token.attr-value, 91 | .token.keyword { 92 | color: #c586c0; 93 | } 94 | 95 | .token.function { 96 | color: #dcdcaa; 97 | } 98 | 99 | .token.regex, 100 | .token.important, 101 | .token.variable { 102 | color: #d16969; 103 | } 104 | 105 | .token.important, 106 | .token.bold { 107 | font-weight: bold; 108 | } 109 | 110 | .token.italic { 111 | font-style: italic; 112 | } 113 | 114 | .token.constant { 115 | color: #9cdcfe; 116 | } 117 | 118 | .token.class-name { 119 | color: #4ec9b0; 120 | } 121 | 122 | .token.parameter { 123 | color: #9cdcfe; 124 | } 125 | 126 | .token.interpolation { 127 | color: #9cdcfe; 128 | } 129 | 130 | .token.punctuation.interpolation-punctuation { 131 | color: #569cd6; 132 | } 133 | 134 | .token.boolean { 135 | color: #569cd6; 136 | } 137 | 138 | .token.property { 139 | color: #9cdcfe; 140 | } 141 | 142 | .token.selector { 143 | color: #d7ba7d; 144 | } 145 | 146 | .token.tag { 147 | color: #569cd6; 148 | } 149 | 150 | .token.attr-name { 151 | color: #9cdcfe; 152 | } 153 | 154 | .token.attr-value { 155 | color: #ce9178; 156 | } 157 | 158 | .token.entity { 159 | color: #4ec9b0; 160 | cursor: unset; 161 | } 162 | 163 | .token.namespace { 164 | color: #4ec9b0; 165 | } 166 | /********************************************************* 167 | * Language Specific 168 | */ 169 | pre[class*='language-javascript'], 170 | code[class*='language-javascript'] { 171 | color: #4ec9b0; 172 | } 173 | 174 | pre[class*='language-css'], 175 | code[class*='language-css'] { 176 | color: #ce9178; 177 | } 178 | 179 | pre[class*='language-html'], 180 | code[class*='language-html'] { 181 | color: #d4d4d4; 182 | } 183 | 184 | .language-html .token.punctuation { 185 | color: #808080; 186 | } 187 | /********************************************************* 188 | * Line highlighting 189 | */ 190 | pre[data-line] { 191 | position: relative; 192 | } 193 | 194 | pre[class*='language-'] > code[class*='language-'] { 195 | position: relative; 196 | z-index: 1; 197 | } 198 | 199 | .line-highlight { 200 | position: absolute; 201 | left: 0; 202 | right: 0; 203 | padding: inherit 0; 204 | margin-top: 1em; 205 | background: #f7ebc6; 206 | box-shadow: inset 5px 0 0 #f7d87c; 207 | z-index: 0; 208 | pointer-events: none; 209 | line-height: inherit; 210 | white-space: pre; 211 | } 212 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Vue responsive menu logo](https://vue-responsive-menu.netlify.com/favicons/apple-touch-icon-152x152.png)](https://vue-responsive-menu.netlify.com/) 2 | 3 | # A responsive menu build for Vue.js 4 | 5 | A renderless Vue component that will auto detect if menu items don't fit and moves them to a separate dropdown. Also known as the Priority+ pattern. 6 | 7 | ![End-to-end tests](https://github.com/gijsroge/vue-responsive-menu/workflows/End-to-end%20tests/badge.svg) 8 | 9 | [![Vue responsive menu demo](https://vue-responsive-menu.netlify.com/demo.gif)](https://vue-responsive-menu.netlify.com/) 10 | 11 | ## 👉 **[Demo](https://vue-responsive-menu.netlify.com/)** 👈 12 | 13 | --- 14 | 15 | ### Install 16 | 17 | `yarn add vue-responsive-menu` 18 | 19 | ### Register as a Vue component 20 | 21 | ```javascript 22 | import VueResponsiveMenu from "vue-responsive-menu"; 23 | 24 | export default { 25 | components: { 26 | VueResponsiveMenu 27 | } 28 | }; 29 | ``` 30 | 31 | ### Pass your menu in the `:nav` prop 32 | 33 | Responsive menu will expose 2 new arrays in the default prop, **1 normal menu** & **1 with the excess items** 34 | 35 | ```html 36 | 66 | 67 | 90 | ``` 91 | 92 | ### Props 93 | 94 | | Prop | Type | Default | Description | 95 | | -------------- | --------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------ | 96 | | :nav | `array` | `[]` | 97 | | :maxCharacters | `number` or `boolean` | `false` | 98 | | label | `string` | `'label'` | Key to read the menu item label. Only needed if you enable maxCharacters. | 99 | | offset | `number` | `0` | Adds x amount of pixels to the total width of menu items. This causes menu items to be moved more quickly to the more dropdown | 100 | 101 | ### Events 102 | 103 | | Name | Payload | Description | 104 | | ----------------- | -------- | -------------------------------- | 105 | | @menu-resized | `number` | current width of menu | 106 | | @item-to-dropdown | `object` | Item from nav prop | 107 | | @item-to-menu | `object` | Item from nav prop | 108 | | @moreMenuItems | `array` | Current array of more menu items | 109 | | @menuItems | `array` | Current array of menu items | 110 | 111 | ### Example with options 112 | 113 | ```html 114 | 115 | 121 | 122 | 123 | ``` 124 | 125 | ### Todo 126 | 127 | - [x] Make a public example site 128 | - [x] Create GIF in documentation 129 | - [x] Add documentation 130 | - [x] Write tests 131 | - [x] Setup CI 132 | - [ ] Add contribution guidelines 133 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const getWidthIncludingMargin = el => { 2 | return ( 3 | el.offsetWidth + 4 | parseFloat(getComputedStyle(el).marginLeft) + 5 | parseFloat(getComputedStyle(el).marginRight) 6 | ); 7 | }; 8 | 9 | export default { 10 | props: { 11 | maxCharacters: { 12 | type: [Number, Boolean], 13 | default: false 14 | }, 15 | label: { 16 | type: String, 17 | default: "label" 18 | }, 19 | nav: { 20 | type: Array, 21 | required: true, 22 | default() { 23 | return []; 24 | } 25 | }, 26 | offset: { 27 | type: Number, 28 | default: 0 29 | } 30 | }, 31 | 32 | data() { 33 | return { 34 | currentMenuWidth: null, 35 | previousMenuWidth: null, 36 | menuItems: [], 37 | moreMenuItems: [], 38 | firstRun: true, 39 | moved: false 40 | }; 41 | }, 42 | 43 | watch: { 44 | nav(nav) { 45 | this.menuItems = nav; 46 | this.preRender(); 47 | this.moveItem(); 48 | } 49 | }, 50 | 51 | created() { 52 | this.preRender(); 53 | this.$emit("moreMenuItems", this.moreMenuItems); 54 | this.$emit("menuItems", this.menuItems); 55 | }, 56 | 57 | mounted() { 58 | // Register observer 59 | this.observer = new ResizeObserver(entries => { 60 | entries.forEach(entry => { 61 | this.$emit("menu-resized", entry.contentRect.width); 62 | this.currentMenuWidth = entry.contentRect.width; 63 | // Only execute if the width of our menu changed 64 | if ( 65 | this.previousMenuWidth !== entry.contentRect.width || 66 | this.firstRun 67 | ) { 68 | if (this.firstRun) { 69 | this.previousMenuWidth = entry.contentRect.width; 70 | } 71 | this.moveItem(entry.contentRect.width); 72 | this.firstRun = false; 73 | } 74 | this.previousMenuWidth = entry.contentRect.width; 75 | }); 76 | }); 77 | 78 | // Attach resize handler to monitor menu width 79 | if (!this.element) { 80 | console.warn( 81 | "Vue responsive menu: root element does not contain more then 1 child. If you have a nested menu please mark your menu with the 'data-vue-responsive-menu' attribute." 82 | ); 83 | return; 84 | } 85 | this.observer.observe(this.element); 86 | }, 87 | 88 | methods: { 89 | preRender() { 90 | /* Presort menu items if maxCharacters is set. Useful for when you pre-render or SSR */ 91 | if (!this.maxCharacters) { 92 | this.menuItems = this.nav; 93 | } else { 94 | let total = 0; 95 | this.menuItems = this.nav.filter(navItem => { 96 | total += navItem[this.label].length; 97 | if (total < this.maxCharacters) { 98 | return navItem; 99 | } 100 | }); 101 | } 102 | 103 | if (!this.maxCharacters) { 104 | this.moreMenuItems = []; 105 | } else { 106 | let total = 0; 107 | this.moreMenuItems = this.nav.filter(navItem => { 108 | total += navItem[this.label].length; 109 | if (total >= this.maxCharacters) { 110 | return navItem; 111 | } 112 | }); 113 | } 114 | }, 115 | moveItem() { 116 | const exceededCharacterCount = 117 | this.maxCharacters && this.menuCharacters > this.maxCharacters; 118 | 119 | if (this.isOverflown || exceededCharacterCount) { 120 | const lastElement = this.menuItems[this.menuItems.length - 1] || null; 121 | this.$emit("item-to-dropdown", lastElement); 122 | this.moreMenuItems.unshift(lastElement); 123 | this.menuItems.pop(); 124 | this.moved = false; 125 | this.$emit("moreMenuItems", this.moreMenuItems); 126 | this.$emit("menuItems", this.menuItems); 127 | this.$nextTick(() => { 128 | this.moveItem(); 129 | }); 130 | } else if ( 131 | (this.currentMenuWidth > this.previousMenuWidth || 132 | this.firstRun || 133 | this.moved) && 134 | this.moreMenuItems[0] && 135 | !exceededCharacterCount 136 | ) { 137 | const firstElement = this.moreMenuItems[0]; 138 | this.menuItems.push(firstElement); 139 | this.$emit("item-to-menu", firstElement); 140 | this.moreMenuItems.shift(); 141 | this.moved = true; 142 | this.$emit("moreMenuItems", this.moreMenuItems); 143 | this.$emit("menuItems", this.menuItems); 144 | this.$nextTick(() => { 145 | this.moveItem(); 146 | }); 147 | } 148 | }, 149 | totalWidthOfChildren() { 150 | if (this.menuItems.length === 0) { 151 | return 0; 152 | } 153 | return Array.from(this.element.children).reduce((total, child) => { 154 | return total + getWidthIncludingMargin(child); 155 | }, 0); 156 | } 157 | }, 158 | 159 | render() { 160 | return this.$scopedSlots.default({ 161 | menuItems: this.menuItems, 162 | moreMenuItems: this.moreMenuItems 163 | }); 164 | }, 165 | 166 | computed: { 167 | menuCharacters() { 168 | return this.menuItems.reduce((total, menuItem) => { 169 | return (total += menuItem[this.label].length); 170 | }, 0); 171 | }, 172 | element() { 173 | if ( 174 | this.$el.children.length === 1 || 175 | !(this.$el instanceof HTMLElement) 176 | ) { 177 | return this.$el.querySelector("[data-vue-responsive-menu]"); 178 | } 179 | 180 | return this.$el; 181 | }, 182 | isOverflown() { 183 | if (!this.currentMenuWidth) return false; 184 | return this.currentMenuWidth < this.totalWidthOfChildren() + this.offset; 185 | } 186 | } 187 | }; 188 | -------------------------------------------------------------------------------- /demo/demo.vue: -------------------------------------------------------------------------------- 1 | 143 | 144 | 154 | 155 | 170 | 171 | 199 | -------------------------------------------------------------------------------- /site/assets/scss/bootstrap/_variable-overrides.scss: -------------------------------------------------------------------------------- 1 | $colors: map-merge( 2 | ( 3 | 'dark': $dark, 4 | 'gray': $gray-500 5 | ), 6 | $colors 7 | ); 8 | 9 | $theme-colors: map-merge( 10 | ( 11 | 'primary': $primary, 12 | 'secondary': $secondary, 13 | 'twitter': $twitter, 14 | 'white': $white, 15 | 'yellow': $yellow, 16 | 'green': $green, 17 | 'green-acid': $green-acid, 18 | 'dark': $dark, 19 | 'black': $black, 20 | 'body': $body-color, 21 | 'gray': $gray, 22 | 'dark-gray': $dark-gray 23 | ), 24 | $theme-colors 25 | ); 26 | 27 | // Export so we can import in our JavaScript. 28 | :export { 29 | primary: $primary; 30 | secondary: $secondary; 31 | white: $white; 32 | success: $success; 33 | error: $danger; 34 | } 35 | 36 | $grid-breakpoints: ( 37 | xxs: 0, 38 | xs: 340px, 39 | sm: 576px, 40 | md: 768px, 41 | lg: 992px, 42 | xl: 1200px, 43 | xxl: 1400px 44 | ); 45 | 46 | $container-small: 962px; 47 | 48 | // Export so we can import in our JavaScript. 49 | :export { 50 | xxs: map-get($grid-breakpoints, xxs); 51 | xs: map-get($grid-breakpoints, xs); 52 | sm: map-get($grid-breakpoints, sm); 53 | md: map-get($grid-breakpoints, md); 54 | lg: map-get($grid-breakpoints, lg); 55 | } 56 | 57 | $container-max-widths: ( 58 | xs: 540px, 59 | sm: 720px, 60 | md: 960px, 61 | ); 62 | 63 | @include _assert-ascending($container-max-widths, '$container-max-widths'); 64 | 65 | // Grid columns 66 | // 67 | // Set the number of columns and specify the width of the gutters. 68 | 69 | $grid-columns: 12; 70 | $grid-gutter-width: 20px; 71 | 72 | // Spacing 73 | // 74 | // Control the default styling of most Bootstrap elements by modifying these 75 | // variables. Mostly focused on spacing. 76 | // You can add more entries to the $spacers map, should you need more variation. 77 | 78 | $spacer: 1rem; 79 | $spacers: ( 80 | 0: 0, 81 | 1: ( 82 | $spacer * 0.25 83 | ), 84 | 2: ( 85 | $spacer * 0.5 86 | ), 87 | 3: $spacer * 1, 88 | 4: ( 89 | $spacer * 2 90 | ), 91 | 5: ( 92 | $spacer * 2.5 93 | ), 94 | 6: ( 95 | $spacer * 4 96 | ), 97 | 7: ( 98 | $spacer * 6 99 | ), 100 | 8: ( 101 | $spacer * 9 102 | ) 103 | ); 104 | 105 | $enable-shadows: false; 106 | 107 | $component-active-color: $white; 108 | $component-active-bg: theme-color('primary'); 109 | 110 | // Shadows 111 | $box-shadow-sm: 0 0.125rem 0.25rem rgba($black, 0.075); 112 | $box-shadow: 0 14px 28px rgba(0, 0, 0, 0.1), 0 10px 10px rgba(0, 0, 0, 0.07); 113 | $box-shadow-lg: 0 50px 100px -20px rgba(50, 50, 93, 0.25), 114 | 0 30px 60px -30px rgba(0, 0, 0, 0.3), 0 -18px 60px -10px rgba(0, 0, 0, 0.025); 115 | 116 | // Border radius 117 | $border-radius: 0.25rem; 118 | $border-radius-lg: 0.3rem; 119 | $border-radius-sm: 0.2rem; 120 | 121 | // Border 122 | $border-width: 1px; 123 | $border-color: $gray-800; 124 | 125 | //transitions 126 | $transition-base: all 0.15s; 127 | 128 | // Font 129 | $font-family-sans-serif: 'Open Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', 130 | Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 131 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 132 | $font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, 133 | 'Liberation Mono', 'Courier New', monospace; 134 | $font-family-base: $font-family-sans-serif; 135 | $font-size-base: 1rem; 136 | $font-size-xxl: $font-size-base * 6.75; 137 | $font-size-xl: $font-size-base * 2.5; 138 | $font-size-lg: $font-size-base * 1.7125; 139 | $font-size-md: $font-size-base * 1.125; 140 | $font-size-sm: $font-size-base * 0.875; 141 | $font-size-xs: $font-size-base * 0.75; 142 | $font-size-xxs: $font-size-base * 0.5625; 143 | $enable-responsive-font-sizes: true; 144 | $font-weight-lighter: lighter; 145 | $font-weight-light: 300; 146 | $font-weight-normal: 400; 147 | $font-weight-medium: 500; 148 | $font-weight-bold: 700; 149 | $font-weight-black: 900; 150 | $font-weight-bolder: bolder; 151 | $font-weight-base: $font-weight-normal; 152 | $line-height-base: 1.875; 153 | $line-height-lg: 1.875; 154 | $line-height-sm: 1; 155 | 156 | $h1-font-size: $font-size-base * 6.75; 157 | $h2-font-size: $font-size-base * 2.5; 158 | $h3-font-size: $font-size-base * 1.8375; 159 | $h4-font-size: $font-size-base * 1.6; 160 | $h5-font-size: $font-size-base; 161 | $h6-font-size: $font-size-base; 162 | 163 | $headings-margin-bottom: 1rem; 164 | $headings-font-family: 'Lato', -apple-system, BlinkMacSystemFont, 'Segoe UI', 165 | Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 166 | 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; 167 | $headings-font-weight: $font-weight-light; 168 | $headings-line-height: 1; 169 | $headings-color: $red; 170 | 171 | // Buttons + Forms 172 | // 173 | // Shared variables that are reassigned to `$input-` and `$btn-` specific variables. 174 | 175 | $input-btn-padding-y: .375rem; 176 | $input-btn-padding-x: .75rem; 177 | $input-btn-font-family: null; 178 | $input-btn-font-size: $font-size-base; 179 | $input-btn-line-height: $line-height-base; 180 | 181 | $input-btn-focus-width: .2rem; 182 | $input-btn-focus-color: rgba($component-active-bg, .25); 183 | $input-btn-focus-box-shadow: 0 0 0 $input-btn-focus-width $input-btn-focus-color; 184 | 185 | $input-btn-padding-y-sm: .05rem; 186 | $input-btn-padding-x-sm: .5rem; 187 | $input-btn-font-size-sm: $font-size-sm; 188 | $input-btn-line-height-sm: $line-height-sm; 189 | 190 | $input-btn-padding-y-lg: .5rem; 191 | $input-btn-padding-x-lg: 1rem; 192 | $input-btn-font-size-lg: $font-size-lg; 193 | $input-btn-line-height-lg: $line-height-lg; 194 | 195 | $input-btn-border-width: $border-width; 196 | 197 | // Buttons 198 | // 199 | // For each of Bootstrap's buttons, define text, background, and border color. 200 | 201 | $btn-padding-y: $input-btn-padding-y; 202 | $btn-padding-x: $input-btn-padding-x; 203 | $btn-font-family: $input-btn-font-family; 204 | $btn-font-size: $input-btn-font-size; 205 | $btn-line-height: $input-btn-line-height; 206 | $btn-white-space: null; // Set to `nowrap` to prevent text wrapping 207 | 208 | $btn-padding-y-sm: $input-btn-padding-y-sm; 209 | $btn-padding-x-sm: $input-btn-padding-x-sm; 210 | $btn-font-size-sm: $input-btn-font-size-sm; 211 | $btn-line-height-sm: $input-btn-line-height-sm; 212 | 213 | $btn-padding-y-lg: $input-btn-padding-y-lg; 214 | $btn-padding-x-lg: $input-btn-padding-x-lg; 215 | $btn-font-size-lg: $input-btn-font-size-lg; 216 | $btn-line-height-lg: $input-btn-line-height-lg; 217 | 218 | $btn-border-width: $input-btn-border-width; 219 | 220 | $btn-font-weight: $font-weight-normal; 221 | $btn-box-shadow: inset 0 1px 0 rgba($white, .15), 0 1px 1px rgba($black, .075); 222 | $btn-focus-width: $input-btn-focus-width; 223 | $btn-focus-box-shadow: $input-btn-focus-box-shadow; 224 | $btn-disabled-opacity: .65; 225 | $btn-active-box-shadow: inset 0 3px 5px rgba($black, .125); 226 | 227 | $btn-link-disabled-color: $gray-600; 228 | 229 | $btn-block-spacing-y: .5rem; 230 | 231 | // Allows for customizing button radius independently from global border radius 232 | $btn-border-radius: $border-radius; 233 | $btn-border-radius-lg: $border-radius-lg; 234 | $btn-border-radius-sm: $border-radius-sm; 235 | 236 | $btn-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out; 237 | 238 | 239 | 240 | 241 | $dropdown-min-width: 10rem; 242 | $dropdown-padding-y: .5rem; 243 | $dropdown-spacer: .125rem; 244 | $dropdown-font-size: $font-size-base; 245 | $dropdown-color: $body-color; 246 | $dropdown-bg: $white; 247 | $dropdown-border-color: rgba($black, .15); 248 | $dropdown-border-radius: 0; 249 | $dropdown-border-width: $border-width; 250 | $dropdown-inner-border-radius: subtract($dropdown-border-radius, $dropdown-border-width); 251 | $dropdown-divider-bg: $gray-200; 252 | $dropdown-divider-margin-y: $nav-divider-margin-y; 253 | $dropdown-box-shadow: 0 .5rem 1rem rgba($black, .175); 254 | 255 | $dropdown-link-color: $gray-900; 256 | $dropdown-link-hover-color: darken($gray-900, 5%); 257 | $dropdown-link-hover-bg: $gray-100; 258 | 259 | $dropdown-link-active-color: $component-active-color; 260 | $dropdown-link-active-bg: $component-active-bg; 261 | 262 | $dropdown-link-disabled-color: $gray-600; 263 | 264 | $dropdown-item-padding-y: .25rem; 265 | $dropdown-item-padding-x: 1.5rem; 266 | 267 | $dropdown-header-color: $gray-600; 268 | -------------------------------------------------------------------------------- /site/pages/index.vue: -------------------------------------------------------------------------------- 1 | 271 | 272 | 457 | 458 | 495 | 496 | 548 | --------------------------------------------------------------------------------