├── .babelrc ├── .editorconfig ├── .gitignore ├── .prettierrc ├── .yarnrc.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── dist ├── vue-navigation-bar.css ├── vue-navigation-bar.esm.js ├── vue-navigation-bar.min.js └── vue-navigation-bar.umd.js ├── docs ├── assets │ ├── index.2f3ff1f9.css │ ├── index.d2236a20.js │ └── lockup-color.5dabaa34.png └── index.html ├── example ├── App.vue ├── index.html └── index.js ├── package.json ├── rollup.config.js ├── src ├── assets │ ├── css │ │ └── main.scss │ └── images │ │ ├── badge.png │ │ ├── chevron-down.svg │ │ ├── collapse-menu-dark.svg │ │ ├── collapse-menu-light.svg │ │ ├── favicon.png │ │ ├── lockup-color.png │ │ ├── social.png │ │ └── times.svg ├── common │ └── uuidV4.js ├── components │ ├── BrandImage.vue │ ├── CollapseButton.vue │ ├── DesktopMenuItemButton.vue │ ├── DesktopMenuItemLink.vue │ ├── DesktopMenuItemSpacer.vue │ ├── DynamicLink.vue │ ├── MenuOptions.vue │ └── Popup.vue ├── index.js └── vue-navigation-bar.vue ├── test ├── VueNavigationBar.spec.js └── styleMock.js ├── vite.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "browsers": [ 8 | "last 2 versions", 9 | "safari >= 7" 10 | ] 11 | } 12 | } 13 | ] 14 | ], 15 | "plugins": [ 16 | [ 17 | "@babel/plugin-transform-runtime" 18 | ] 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | 4 | # https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored 5 | .pnp.* 6 | .yarn/* 7 | !.yarn/patches 8 | !.yarn/plugins 9 | !.yarn/releases 10 | !.yarn/sdks 11 | !.yarn/versions 12 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "semi": true, 4 | "singleQuote": true, 5 | "tabWidth": 2, 6 | "trailingComma": "all", 7 | "useTabs": false 8 | } 9 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | enableTelemetry: false 2 | nodeLinker: node-modules 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG.md 2 | 3 | ## 5.0.2 (2022-03-20) 4 | 5 | - Upgrade to Vue 3 support at `v5.0.0` and beyond. Vue 2 support is available at `v4.1.0`. 6 | 7 | ## 4.1.0 (2020-10-15) 8 | 9 | - Adds `tooltipPlacement` prop to control direction of menu tooltip. This prevents the tooltip libraries from adjusting the position once set. This is preferable for a dropdown-type system we have here. Thank you @jeffreykthomas for your research and help on this. 10 | 11 | ## 1.3.1 (2019-08-06) 12 | 13 | - Adjusting project formatting. 14 | - Renamed events `mobile-popup-shown` and `mobile-popup-hidden` to `vnb-mobile-popup-shown` and `vnb-mobile-popup-hidden`. 15 | - Added ability to make links `emit` events instead of triggering their path. Good for cases when you want to run a function using a `menuOption` instead of following a link. 16 | - Hiding the popup and collapse button if there are no `menuOptions` to show. 17 | - Added `tooltipAnimationType` to the options so that you can change the dropdown animation type. 18 | 19 | ## 1.3.0 (2019-04-23) 20 | 21 | - Added the ability to provide your own collapse open and close image - `collapseButtonImageOpen` and `collapseButtonImageClose` . Added dynamic `id` functionality to the menu options to allow for multiple instances of the component. 22 | 23 | ## 1.2.0 (2019-03-29) 24 | 25 | - Added optional `slot` option named `custom-slot` . 26 | 27 | ## 1.0.0 (2019-03-18) 28 | 29 | - Initial work 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 John Datserakis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

vue-navigation-bar

2 | 3 | # vue-navigation-bar 4 | 5 | A simple, pretty navbar for your Vue projects. 6 | 7 |

8 | NPM Version 9 | NPM Downloads 10 | License 11 | Tweet 12 |

13 | 14 | ## Vue 3 Support 15 | 16 | Vue 3 is supported from `v5.0.0` and beyond (current `master`). To use `vue-navigation-bar` with Vue 2, use `v4.1.0`. 17 | 18 | ## Links 19 | 20 | - [Demo](https://johndatserakis.github.io/vue-navigation-bar/) 21 | - [View on GitHub](https://github.com/johndatserakis/vue-navigation-bar) 22 | - [View on npm](https://www.npmjs.com/package/vue-navigation-bar) 23 | 24 | ## Install 25 | 26 | ```bash 27 | yarn add vue-navigation-bar 28 | ``` 29 | 30 | Or you can include it through the browser at the bottom of your page along with the css: 31 | 32 | ```html 33 | 34 | 35 | 40 | ``` 41 | 42 | ## Usage 43 | 44 | ```js 45 | import { createApp } from 'vue'; 46 | import VueNavigationBar from 'vue-navigation-bar'; 47 | import 'vue-navigation-bar/dist/vue-navigation-bar.css'; 48 | 49 | const app = createApp(App); 50 | 51 | app.component('vue-navigation-bar', VueNavigationBar); 52 | ``` 53 | 54 | ## About 55 | 56 | Often when starting a new project I like to get together the main foundation pieces first. A main part of that process is working on the main navbar. 57 | 58 | This component is meant to help with that process - it gives you a standard looking navigation bar for for your app that can be easily defined using `JSON` or a just an array of objects. 59 | 60 | `vue-navigation-bar` is meant to be used for the 80% of cases that exist when you need a standard navbar for your app/website. The layout has the `brand-image` anchored on the left side, and two slots for `menu-options` that push and pull based on designation. 61 | 62 | I know there are lots of other styles that navbar's can be, for instance the `brand-image` could be in the middle - but this component won't be allowing you to do that at the moment - so if that's your thing then I would probably look to roll out something on your own, or fork this to apply to your needs if possible. That being said, the actual `css` here is very easy to override - I'm using `BEM` with `SASS` ( `.scss` ) and have the style skeleton posted below - so you should be able to style it quite nicely without issue. I've put a lot of time in placing sensible defaults that should work well against any style. 63 | 64 | The trade-off is that the initialization and usage of this component is very easy and won't force you to do anything except declare the structure initially and declare a few `css` styles as necessary. 65 | 66 | `vue-navigation-bar` is compatible with both `vue-router` projects and non- `vue-router` projects - just make sure to pass in `true` for the `isUsingVueRouter` option if you're using `vue-router`. 67 | 68 | The component will work well with frontend component frameworks. I'm using Bootstrap 4 in the demo page and have it sitting in a container - that helps keep it from stretching too far across the page - although that may be the style you're going for, so have at it. 69 | 70 | ## Usage Example 71 | 72 | ```javascript 73 | 76 | 77 | 184 | 185 | 196 | ``` 197 | 198 | ## Notes 199 | 200 | Above is a basic usage example. You'll see that a lot of the work is actually just declaring your `options` object. 201 | 202 | In the style section you'll see that I provide a button class to color a button in the navbar. This is done like this to give you the most control over the button color and other pseudo properties. In this case, I want one of my buttons to be red, so I provide a class in my `options` object and then style the class appropriately like this: 203 | 204 | Note - the first example uses basic `css` , the second example is the same thing just using `sass` nesting - same result. 205 | 206 | ```css 207 | .vnb .button-red { 208 | background: #ff3b30; 209 | } 210 | 211 | .vnb .button-red:hover { 212 | background: #fc0d00; 213 | } 214 | ``` 215 | 216 | ```scss 217 | .vnb { 218 | .button-red { 219 | background: #ff3b30; 220 | 221 | &:hover { 222 | background: darken(#ff3b30, 10%); 223 | } 224 | } 225 | } 226 | ``` 227 | 228 | You can make a bunch of button-color classes and set them up just like above. 229 | 230 | Take a look at the `./example` folder in this project - it has the complete working example that you see in the demo.(FYI - the `vue-router` setup there is really rudimentary so all the different pages aren't real - doesn't affect the demo.) 231 | 232 | You may need to adjust your `brand-image` a bit - that's normal as brand images come in different shapes and sizes - go ahead and set a `max-width` or `max-height` in pixels using this selector: `.vnb__brand-image-wrapper__link__image` . Even a little margin may help. For instance, the base I have it at is `max-height: 34px;` - should work for most cases. 233 | 234 | Sometimes you may want your links to perform an action instead of following a link. To do this, add the `isLinkAction` property to your `menuOption` and it'll `emit` the `vnb-item-clicked` event with the `text` property of the `menuOption` . This will suppress the `path` option so the link no longer goes anywhere, but instead just sends the event. Checkout the example page to see this in action. 235 | 236 | ### Props 237 | 238 | | prop | type | required | default | possible values | description | 239 | | ---------------------------------------- | ---------------- | -------- | ----------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | 240 | | elementId | String | no | A generated uuid | | This value will be set as the `id` of the instance | 241 | | isUsingVueRouter | Boolean | no | false | | If you want to use vue-router, set this to true and all links will automatically be `` | 242 | | mobileBreakpoint | Number | no | 992 | | Width at which the navbar turns into the mobile version | 243 | | brandImagePath | String or Object | no | '/' | | Link path of menu-option. If you have `isUsingVueRouter === true`, then this needs to be an `Object` with a `name` property or just a `String` of your path. Otherwise, just provide a `String`. link | 244 | | brandImage | Image | no | | | `import` your image here to use your brand image | 245 | | brandImageAltText | String | no | 'brand-image' | | The `alt` tag text for your brand image | 246 | | collapseButtonImageOpen | Image | no | A hamburger icon | | `import` your image here | 247 | | collapseButtonImageClose | Image | no | A times icon | | `import` your image here | 248 | | collapseButtonOpenColor | String | no | `#373737` | | CSS hex - `#FFF`. Only applicable if you don't supply a `collapseButtonImageOpen`. | 249 | | collapseButtonCloseColor | String | no | `#373737` | | CSS hex - `#FFF`. Only applicable if you don't supply a `collapseButtonImageClose`. | 250 | | showBrandImageInMobilePopup | Boolean | no | false | | If you want to show your brand logo in the mobile popup | 251 | | ariaLabelMainNav | String | no | 'Main Navigation' | | The `aria-label` value for the main navbar element | 252 | | tooltipAnimationType | String | no | 'shift-away' | 'shift-away', 'shift-toward', 'scale', 'perspective' | See [tippy.js docs](https://atomiks.github.io/tippyjs/all-options/) | 253 | | tooltipPlacement | String | no | 'bottom' | 'top', 'bottom', 'left', 'right' ... and more. | See [tippy.js docs](https://atomiks.github.io/tippyjs/v6/all-props/#placement) for the complete list. Also, make sure to cross reference with popper.js's options. The tooltip dropdown will always drop in the direction you set here. | 254 | | menuOptionsLeft | Object | no | {} | | Menu options that will be _pulled_ to the left towards the `brand-image` | 255 | | menuOptionsLeft.type | String | yes | | 'link', 'button', 'spacer', 'dropdown' | What type of link will this menu-option be? `link` will be a link, `button` will be a button, `spacer` will be a spacer with a width of `30px` , `dropdown` will create a dropdown on desktop and a `ul/li` list on mobile. `dropdown` only works on menuOptions, not subMenuOptions. | 256 | | menuOptionsLeft.text | String | yes | | | Text of menu-option | 257 | | menuOptionsLeft.path | String or Object | no | | | Link path of menu-option. If you have `isUsingVueRouter === true`, then this needs to be an `Object` with at least a `name` property or just a `String` of your path. Otherwise, just provide a `String`. Not applicable to `dropdown` menuOption types. Do not use when setting `isLinkAction`. | 258 | | menuOptionsLeft.arrowColor | String | no | | | CSS hex - `#FFF`. This styles the little chevron icon. | 259 | | menuOptionsLeft.class | String | no | | | Only for `menuOptionsLeft.type === 'button'` - provide a class name so you can style your buttons | 260 | | menuOptionsLeft.isLinkAction | Boolean | no | false | | When `true`, any `path` option of the `menuOption` will not fire - instead, you'll be able to register for the `@vnb-item-clicked` event which will spit you out the `text` value of your `menuOption` . That way, you can do an action you may want to trigger. | 261 | | menuOptionsLeft.iconLeft | HTML String | no | | | Only for `menuOptionsLeft.type === 'link or menuOptionsLeft.type === 'dropdown'`. HTML string of the icon you want to use. See more info on the `Icon` section of the README. | 262 | | menuOptionsLeft.iconRight | HTML String | no | | | Only for `menuOptionsLeft.type === 'link or menuOptionsLeft.type === 'dropdown'`. HTML string of the icon you want to use. See more info on the `Icon` section of the README. | 263 | | menuOptionsLeft.subMenuOptions | Object | no | | | Sub-menu-options that will be shown | 264 | | menuOptionsLeft.subMenuOptions.type | String | yes | | 'link', 'hr' | What type of link will this sub-menu-option be? `link` will be a link, `hr` will be a `hr` spacer | 265 | | menuOptionsLeft.subMenuOptions.text | String | yes | | | Text of sub-menu-option | 266 | | menuOptionsLeft.subMenuOptions.subText | String | no | | | Sub text of sub-menu-option | 267 | | menuOptionsLeft.subMenuOptions.path | String | no | | | Link path of sub-menu-option | 268 | | menuOptionsLeft.subMenuOptions.iconLeft | HTML String | no | | | HTML string of the icon you want to use. See more info on the `Icon` section of the README. | 269 | | menuOptionsLeft.subMenuOptions.iconRight | HTML String | no | | | HTML string of the icon you want to use. See more info on the `Icon` section of the README. | 270 | | menuOptionsRight | Object | no | {} | | Menu options that will be pushed to the right of the navbar. See above - all `menuOptionsLeft` apply | 271 | 272 | ## Events 273 | 274 | | event | value | description | 275 | | ----------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 276 | | vnb-mobile-popup-shown | null | Emitted when the mobile popup is shown | 277 | | vnb-mobile-popup-hidden | null | Emitted when the mobile popup is hidden | 278 | | vnb-item-clicked | String, `menuOption.text` | Emitted when a menu option is clicked. Listen to this event to then trigger a function based on the returned value, which is the `text` value of the `menuOption` that was clicked. | 279 | 280 | ## Methods 281 | 282 | | method | parameters | description | 283 | | ---------------- | ---------- | ---------------------- | 284 | | closeMobilePopup | | Close the mobile popup | 285 | | showMobilePopup | | Show the mobile popup | 286 | 287 | Note - to call these methods set a `ref` on your `` , something like this: `` . Then, manually call the methods like this in your javascript: `this.$refs.myNavbar.closeMobilePopup()` . 288 | 289 | ## Slots 290 | 291 | | name | description | 292 | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | 293 | | custom-section | Use this to provide custom content in the navbar. Checkout the example code - in the commented-out section is an example search-bar setup. | 294 | 295 | ## SCSS Structure 296 | 297 | ```scss 298 | .vnb { 299 | &__brand-image-wrapper { 300 | &__link { 301 | &__image { 302 | } 303 | } 304 | } 305 | 306 | &__menu-options { 307 | &--left { 308 | } 309 | 310 | &--right { 311 | } 312 | 313 | &__option { 314 | &__link { 315 | &:hover { 316 | } 317 | 318 | &__icon { 319 | svg { 320 | } 321 | 322 | &--left { 323 | } 324 | 325 | &--right { 326 | } 327 | } 328 | } 329 | 330 | &__arrow { 331 | &--hover { 332 | } 333 | } 334 | 335 | &__button { 336 | &__icon { 337 | svg { 338 | } 339 | 340 | &--left { 341 | } 342 | 343 | &--right { 344 | } 345 | } 346 | } 347 | 348 | &__spacer { 349 | } 350 | } 351 | } 352 | 353 | &__sub-menu-options { 354 | &__option { 355 | &__link { 356 | &:hover { 357 | } 358 | 359 | &__icon { 360 | svg { 361 | } 362 | 363 | &--left { 364 | } 365 | 366 | &--right { 367 | } 368 | } 369 | 370 | &__text-wrapper { 371 | &__text { 372 | } 373 | 374 | &__sub-text { 375 | } 376 | } 377 | } 378 | 379 | &__hr { 380 | } 381 | } 382 | } 383 | 384 | &__collapse-button { 385 | &:hover { 386 | } 387 | 388 | &__image { 389 | } 390 | } 391 | 392 | &__popup { 393 | &__top { 394 | &__image { 395 | } 396 | 397 | &__close-button { 398 | &:hover { 399 | } 400 | 401 | &__image { 402 | } 403 | } 404 | } 405 | 406 | &__bottom { 407 | &__custom-section { 408 | } 409 | 410 | &__menu-options { 411 | &__option { 412 | &:not(:last-child) { 413 | } 414 | 415 | &__link { 416 | &:hover { 417 | } 418 | 419 | &--no-highlight { 420 | &:hover { 421 | } 422 | } 423 | 424 | &__icon { 425 | svg { 426 | } 427 | 428 | &--left { 429 | } 430 | 431 | &--right { 432 | } 433 | } 434 | } 435 | } 436 | } 437 | 438 | &__sub-menu-options { 439 | &__option { 440 | &__link { 441 | &:hover { 442 | } 443 | 444 | &__sub-text { 445 | } 446 | } 447 | } 448 | } 449 | } 450 | } 451 | } 452 | 453 | .vnb-button { 454 | &:hover { 455 | } 456 | } 457 | ``` 458 | 459 | ## Icons 460 | 461 | So right now to use icons in some of the options, you need to pass in full `HTML` strings which get rendered as `HTML` in the desktop version of the navbar. 462 | 463 | In the demo I use this really great set of `svg` icons called [bytesize-icons](https://github.com/danklammer/bytesize-icons). You can just copy the `` code and pass it in your initialization object. You can do the same thing with the FontAwesome style of `` . In the future I'll be looking at a way to pass in full components as icons. Right now, passing in `svg` icons works well and does the job. Check out the `.example` folder to see how I have it in the demo. 464 | 465 | ## Accessibility 466 | 467 | Throughout the development of this component I've been making sure to allow for proper a11y options to be set when possible. This means things like `aria-haspopup` and `aria-expanded` are set on the popup-menus, `aria-label` 's are set on the elements, and any user can come through and use the navbar nicely using the `tab` button. Of course there can probably be improvements on this front, so I'll keep an eye on it myself and look for any pull-requests that improve it. 468 | 469 | ## Browser Support 470 | 471 | To have this work with a browser like IE11, stick this at the bottom of your `index.html` 472 | 473 | ```html 474 | 475 | ``` 476 | 477 | Or, you can install `babel-polyfill` and import that in the main script of your app. You can read more about `babel-polyfill` [here](https://babeljs.io/docs/en/babel-polyfill). In the example folder I use the external script method. 478 | 479 | ## Development 480 | 481 | ```bash 482 | # Install dependencies 483 | yarn 484 | 485 | # Serve with hot reload 486 | yarn dev 487 | 488 | # Run the tests 489 | yarn test 490 | 491 | # Build demo page 492 | yarn build:example 493 | 494 | # Build library 495 | yarn build:library 496 | 497 | # Build everything and run tests 498 | yarn build 499 | ``` 500 | 501 | ## TODO 502 | 503 | - [x] ~~Add an optional search input bar.~~ What I've done is add an optional `slot` named `custom-section` . Use this `slot` to add whatever custom content you want to show in the nav bar. In the demo, I have an example search bar shown. Look at the `./example` folder to check it out. 504 | - [ ] See if there's a way to let users pass a component for an icon and not be limited to HTML strings of the icon they want. 505 | - [ ] Add more thorough tests. 506 | - [ ] Add `prettier`, and `eslint` to the project. Pretty much mirror the settings from something like [this](https://github.com/johndatserakis/koa-vue-notes-web). 507 | - [ ] Fix the awful nesting job I did with the `SCSS` `BEM` nesting. I took the nesting way too far - really I should only need to go one level deep - maybe two. Anyways - I'll get to this soon - sorry about that until then. It's not anything too bad, but I've grown since then and realized the error in my ways ha. 508 | 509 | ## Other 510 | 511 | Go ahead and fork the project! Submit an issue if needed. Have fun! 512 | 513 | ## Thank You 514 | 515 | Thank you to [Stripe](https://stripe.com/) for making that sick navbar - absolutely filthy. A lot of my styling is inspired by them - although I'm definitely butchering it. Also to [Bootstrap](https://getbootstrap.com/) - I've used Bootstrap and their navbar for many years and drew inspiration from lessons I've learned using it. 516 | 517 | ## License 518 | 519 | [MIT](http://opensource.org/licenses/MIT) 520 | -------------------------------------------------------------------------------- /dist/vue-navigation-bar.css: -------------------------------------------------------------------------------- 1 | .vnb { 2 | background: #fff; 3 | padding-top: 15px; 4 | padding-bottom: 15px; 5 | display: flex; 6 | flex-direction: row; 7 | align-items: center; 8 | justify-content: space-between; 9 | } 10 | .vnb * { 11 | box-sizing: border-box; 12 | } 13 | .vnb a { 14 | text-decoration: none; 15 | } 16 | 17 | .tippy-tooltip { 18 | padding: 0; 19 | } 20 | 21 | .vnb-image { 22 | max-width: 100%; 23 | height: auto; 24 | } 25 | 26 | .vnb__menu-options { 27 | display: flex; 28 | flex-direction: row; 29 | align-items: center; 30 | } 31 | .vnb__menu-options--left { 32 | margin-right: auto; 33 | justify-content: flex-start; 34 | padding-left: 30px; 35 | } 36 | .vnb__menu-options--right { 37 | margin-left: auto; 38 | justify-content: flex-end; 39 | padding-right: 10px; 40 | } 41 | .vnb__menu-options__option:not(:last-child) { 42 | margin-right: 20px; 43 | } 44 | 45 | .vnb__collapse-button { 46 | cursor: pointer; 47 | border: 0; 48 | background: transparent; 49 | margin-right: 10px; 50 | } 51 | .vnb__collapse-button:hover { 52 | opacity: 0.75; 53 | } 54 | .vnb__collapse-button__image { 55 | max-height: 30px; 56 | max-width: 30px; 57 | } 58 | 59 | .vnb__popup { 60 | background: #fff; 61 | position: absolute; 62 | left: 10px; 63 | top: 10px; 64 | right: 10px; 65 | display: flex; 66 | flex-direction: column; 67 | perspective: 2000px; 68 | box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.1); 69 | margin-bottom: 20px; 70 | z-index: 100000; 71 | } 72 | .vnb__popup__top { 73 | padding: 15px 24px 0; 74 | border-top: 1px solid #e0e0e0; 75 | border-left: 1px solid #e0e0e0; 76 | border-right: 1px solid #e0e0e0; 77 | border-top-right-radius: 6px; 78 | border-top-left-radius: 6px; 79 | } 80 | .vnb__popup__top__image { 81 | max-height: 27px; 82 | margin-bottom: 5px; 83 | } 84 | .vnb__popup__top__close-button { 85 | position: absolute; 86 | top: 10px; 87 | right: 10px; 88 | cursor: pointer; 89 | border: 0; 90 | background: transparent; 91 | } 92 | .vnb__popup__top__close-button:hover { 93 | opacity: 0.75; 94 | } 95 | .vnb__popup__top__close-button__image { 96 | max-height: 15px; 97 | } 98 | .vnb__popup__top__close-button svg { 99 | width: auto; 100 | } 101 | .vnb__popup__bottom { 102 | background: #fff; 103 | padding: 10px 0 10px; 104 | border-left: 1px solid #e0e0e0; 105 | border-right: 1px solid #e0e0e0; 106 | border-bottom: 1px solid #e0e0e0; 107 | border-bottom-right-radius: 6px; 108 | border-bottom-left-radius: 6px; 109 | } 110 | .vnb__popup__bottom__custom-section { 111 | padding: 12px 24px; 112 | } 113 | .vnb__popup__bottom__menu-options { 114 | list-style-type: none; 115 | padding-left: 0; 116 | display: flex; 117 | flex-direction: column; 118 | } 119 | .vnb__popup__bottom__menu-options__option:not(:last-child) { 120 | margin-bottom: 10px; 121 | } 122 | .vnb__popup__bottom__menu-options__option__link { 123 | padding: 12px 24px; 124 | color: #595959; 125 | font-weight: 500; 126 | display: flex; 127 | flex-direction: row; 128 | justify-content: flex-start; 129 | align-items: center; 130 | transition: color 0.2s ease-in, background 0.2s ease-in, border 0.2s ease-in; 131 | border-left: 2px solid #fff; 132 | width: 100%; 133 | } 134 | .vnb__popup__bottom__menu-options__option__link:hover { 135 | color: #333; 136 | text-decoration: none; 137 | background: #f3f3f3; 138 | border-left: 2px solid #007aff; 139 | } 140 | .vnb__popup__bottom__menu-options__option__link--no-highlight { 141 | padding: 12px 24px 6px; 142 | font-weight: 400; 143 | font-size: 0.8rem; 144 | color: #757575; 145 | } 146 | .vnb__popup__bottom__menu-options__option__link--no-highlight:hover { 147 | color: #757575; 148 | background: #fff; 149 | border-left: 2px solid #fff; 150 | } 151 | .vnb__popup__bottom__menu-options__option__link__icon svg { 152 | max-height: 16px; 153 | max-width: 16px; 154 | } 155 | .vnb__popup__bottom__menu-options__option__link__icon--left { 156 | margin-right: 5px; 157 | } 158 | .vnb__popup__bottom__menu-options__option__link__icon--right { 159 | margin-left: 5px; 160 | } 161 | .vnb__popup__bottom__sub-menu-options { 162 | display: flex; 163 | flex-direction: row; 164 | flex-wrap: wrap; 165 | width: 100%; 166 | font-size: 0.9rem; 167 | } 168 | .vnb__popup__bottom__sub-menu-options__option { 169 | width: 100%; 170 | } 171 | .vnb__popup__bottom__sub-menu-options__option__link { 172 | padding: 6px 24px; 173 | width: 100%; 174 | display: block; 175 | color: #595959; 176 | font-weight: 500; 177 | transition: color 0.2s ease-in, background 0.2s ease-in, border 0.2s ease-in; 178 | border-left: 2px solid #fff; 179 | } 180 | .vnb__popup__bottom__sub-menu-options__option__link:hover { 181 | color: #333; 182 | text-decoration: none; 183 | background: #eee; 184 | border-left: 2px solid #333; 185 | } 186 | .vnb__popup__bottom__sub-menu-options__option__link__sub-text { 187 | margin-top: 5px; 188 | display: block; 189 | font-size: 0.75rem; 190 | color: #8c8c8c; 191 | } 192 | 193 | .vnb-image { 194 | max-width: 100%; 195 | height: auto; 196 | } 197 | 198 | .vnb__brand-image-wrapper { 199 | padding-left: 10px; 200 | } 201 | .vnb__brand-image-wrapper__link__image { 202 | max-height: 30px; 203 | } 204 | 205 | .vnb-image { 206 | max-width: 100%; 207 | height: auto; 208 | } 209 | 210 | .vnb__menu-options__option__link { 211 | cursor: pointer; 212 | font-weight: 500; 213 | color: #595959; 214 | transition: color 0.2s ease-in; 215 | display: flex; 216 | flex-direction: row; 217 | align-items: center; 218 | justify-content: center; 219 | font-size: 0.9rem; 220 | } 221 | .vnb__menu-options__option__link:hover { 222 | color: #333; 223 | text-decoration: none; 224 | } 225 | .vnb__menu-options__option__link__icon svg { 226 | max-height: 20px; 227 | } 228 | .vnb__menu-options__option__link__icon--left { 229 | margin-right: 5px; 230 | } 231 | .vnb__menu-options__option__link__icon--right { 232 | margin-left: 5px; 233 | } 234 | .vnb__menu-options__option__arrow { 235 | max-height: 5px; 236 | max-width: 25px; 237 | transition: transform 0.2s ease-in-out; 238 | } 239 | .vnb__menu-options__option__arrow--hover { 240 | transform: rotate(180deg); 241 | } 242 | .vnb__sub-menu-options { 243 | background: #fff; 244 | max-width: 500px; 245 | display: flex; 246 | flex-direction: column; 247 | justify-content: center; 248 | align-items: center; 249 | border-radius: 4px; 250 | padding: 10px 0; 251 | } 252 | .vnb__sub-menu-options__option { 253 | min-width: 250px; 254 | max-width: 300px; 255 | } 256 | .vnb__sub-menu-options__option__link { 257 | padding: 12px 12px; 258 | width: 100%; 259 | display: flex; 260 | flex-direction: row; 261 | justify-content: center; 262 | align-items: center; 263 | color: #595959; 264 | transition: color 0.2s ease-in, background 0.2s ease-in, border 0.2s ease-in; 265 | border-left: 2px solid #fff; 266 | } 267 | .vnb__sub-menu-options__option__link:hover { 268 | color: #333; 269 | text-decoration: none; 270 | background: #f3f3f3; 271 | border-left: 2px solid #007aff; 272 | cursor: pointer; 273 | } 274 | .vnb__sub-menu-options__option__link__icon svg { 275 | max-height: 40px; 276 | } 277 | .vnb__sub-menu-options__option__link__icon--left { 278 | margin-right: 15px; 279 | } 280 | .vnb__sub-menu-options__option__link__icon--right { 281 | margin-left: 15px; 282 | } 283 | .vnb__sub-menu-options__option__link__text-wrapper { 284 | width: 100%; 285 | } 286 | .vnb__sub-menu-options__option__link__text-wrapper__text { 287 | display: block; 288 | text-align: left; 289 | } 290 | .vnb__sub-menu-options__option__link__text-wrapper__sub-text { 291 | margin-top: 5px; 292 | display: block; 293 | font-size: 0.8rem; 294 | text-align: left; 295 | color: #8c8c8c; 296 | } 297 | .vnb__sub-menu-options__option__hr { 298 | margin-top: 10px; 299 | margin-bottom: 10px; 300 | border-color: rgba(0, 0, 0, 0.1); 301 | } 302 | 303 | .vnb__menu-options__option__button { 304 | display: flex; 305 | flex-direction: row; 306 | justify-content: center; 307 | align-items: center; 308 | font-weight: 400; 309 | color: #fff; 310 | text-align: center; 311 | vertical-align: middle; 312 | user-select: none; 313 | font-size: 0.9rem; 314 | line-height: 1; 315 | border-radius: 0.25rem; 316 | text-transform: uppercase; 317 | letter-spacing: 1px; 318 | padding: 0.5rem 1rem; 319 | transition: background 0.2s ease-in; 320 | } 321 | .vnb__menu-options__option__button__icon svg { 322 | max-height: 16px; 323 | max-width: 16px; 324 | } 325 | .vnb__menu-options__option__button__icon--left { 326 | margin-right: 5px; 327 | } 328 | .vnb__menu-options__option__button__icon--right { 329 | margin-left: 5px; 330 | } 331 | 332 | .vnb-button { 333 | background: #007aff; 334 | } 335 | .vnb-button:hover { 336 | color: #fff; 337 | background: #0062cc; 338 | text-decoration: none; 339 | } 340 | 341 | .vnb__menu-options__option__spacer { 342 | width: 30px; 343 | } 344 | 345 | .tippy-box[data-animation=scale][data-placement^=top] { 346 | transform-origin: bottom; 347 | } 348 | 349 | .tippy-box[data-animation=scale][data-placement^=bottom] { 350 | transform-origin: top; 351 | } 352 | 353 | .tippy-box[data-animation=scale][data-placement^=left] { 354 | transform-origin: right; 355 | } 356 | 357 | .tippy-box[data-animation=scale][data-placement^=right] { 358 | transform-origin: left; 359 | } 360 | 361 | .tippy-box[data-animation=scale][data-state=hidden] { 362 | transform: scale(0.5); 363 | opacity: 0; 364 | } 365 | 366 | .tippy-box[data-animation=shift-toward][data-state=hidden] { 367 | opacity: 0; 368 | } 369 | 370 | .tippy-box[data-animation=shift-toward][data-state=hidden][data-placement^=top] { 371 | transform: translateY(-10px); 372 | } 373 | 374 | .tippy-box[data-animation=shift-toward][data-state=hidden][data-placement^=bottom] { 375 | transform: translateY(10px); 376 | } 377 | 378 | .tippy-box[data-animation=shift-toward][data-state=hidden][data-placement^=left] { 379 | transform: translateX(-10px); 380 | } 381 | 382 | .tippy-box[data-animation=shift-toward][data-state=hidden][data-placement^=right] { 383 | transform: translateX(10px); 384 | } 385 | 386 | .tippy-box[data-animation=perspective][data-placement^=top] { 387 | transform-origin: bottom; 388 | } 389 | 390 | .tippy-box[data-animation=perspective][data-placement^=top][data-state=visible] { 391 | transform: perspective(700px); 392 | } 393 | 394 | .tippy-box[data-animation=perspective][data-placement^=top][data-state=hidden] { 395 | transform: perspective(700px) translateY(8px) rotateX(60deg); 396 | } 397 | 398 | .tippy-box[data-animation=perspective][data-placement^=bottom] { 399 | transform-origin: top; 400 | } 401 | 402 | .tippy-box[data-animation=perspective][data-placement^=bottom][data-state=visible] { 403 | transform: perspective(700px); 404 | } 405 | 406 | .tippy-box[data-animation=perspective][data-placement^=bottom][data-state=hidden] { 407 | transform: perspective(700px) translateY(-8px) rotateX(-60deg); 408 | } 409 | 410 | .tippy-box[data-animation=perspective][data-placement^=left] { 411 | transform-origin: right; 412 | } 413 | 414 | .tippy-box[data-animation=perspective][data-placement^=left][data-state=visible] { 415 | transform: perspective(700px); 416 | } 417 | 418 | .tippy-box[data-animation=perspective][data-placement^=left][data-state=hidden] { 419 | transform: perspective(700px) translateX(8px) rotateY(-60deg); 420 | } 421 | 422 | .tippy-box[data-animation=perspective][data-placement^=right] { 423 | transform-origin: left; 424 | } 425 | 426 | .tippy-box[data-animation=perspective][data-placement^=right][data-state=visible] { 427 | transform: perspective(700px); 428 | } 429 | 430 | .tippy-box[data-animation=perspective][data-placement^=right][data-state=hidden] { 431 | transform: perspective(700px) translateX(-8px) rotateY(60deg); 432 | } 433 | 434 | .tippy-box[data-animation=perspective][data-state=hidden] { 435 | opacity: 0; 436 | } 437 | 438 | .tippy-box[data-theme~=light] { 439 | color: #26323d; 440 | box-shadow: 0 0 20px 4px rgba(154, 161, 177, 0.15), 0 4px 80px -8px rgba(36, 40, 47, 0.25), 0 4px 4px -2px rgba(91, 94, 105, 0.15); 441 | background-color: #fff; 442 | } 443 | 444 | .tippy-box[data-theme~=light][data-placement^=top] > .tippy-arrow:before { 445 | border-top-color: #fff; 446 | } 447 | 448 | .tippy-box[data-theme~=light][data-placement^=bottom] > .tippy-arrow:before { 449 | border-bottom-color: #fff; 450 | } 451 | 452 | .tippy-box[data-theme~=light][data-placement^=left] > .tippy-arrow:before { 453 | border-left-color: #fff; 454 | } 455 | 456 | .tippy-box[data-theme~=light][data-placement^=right] > .tippy-arrow:before { 457 | border-right-color: #fff; 458 | } 459 | 460 | .tippy-box[data-theme~=light] > .tippy-backdrop { 461 | background-color: #fff; 462 | } 463 | 464 | .tippy-box[data-theme~=light] > .tippy-svg-arrow { 465 | fill: #fff; 466 | } 467 | 468 | .tippy-box[data-animation=shift-away][data-state=hidden] { 469 | opacity: 0; 470 | } 471 | 472 | .tippy-box[data-animation=shift-away][data-state=hidden][data-placement^=top] { 473 | transform: translateY(10px); 474 | } 475 | 476 | .tippy-box[data-animation=shift-away][data-state=hidden][data-placement^=bottom] { 477 | transform: translateY(-10px); 478 | } 479 | 480 | .tippy-box[data-animation=shift-away][data-state=hidden][data-placement^=left] { 481 | transform: translateX(10px); 482 | } 483 | 484 | .tippy-box[data-animation=shift-away][data-state=hidden][data-placement^=right] { 485 | transform: translateX(-10px); 486 | } -------------------------------------------------------------------------------- /dist/vue-navigation-bar.esm.js: -------------------------------------------------------------------------------- 1 | import { VueScreenSizeMixin } from 'vue-screen-size'; 2 | import { resolveComponent, openBlock, createElementBlock, Fragment, createBlock, mergeProps, withCtx, renderSlot, createCommentVNode, createVNode, normalizeStyle, createElementVNode, normalizeClass, createTextVNode, toDisplayString, renderList, withKeys } from 'vue'; 3 | import tippy, { hideAll } from 'tippy.js'; 4 | 5 | // https://stackoverflow.com/a/2117523/8014660 6 | function uuidV4() { 7 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 8 | var r = (Math.random() * 16) | 0, 9 | v = c == 'x' ? r : (r & 0x3) | 0x8; 10 | return v.toString(16); 11 | }); 12 | } 13 | 14 | var script$8 = { 15 | name: 'dynamic-link', 16 | props: { 17 | isUsingVueRouter: { 18 | type: Boolean, 19 | required: true, 20 | }, 21 | path: { 22 | type: [String, Object], 23 | required: false, 24 | }, 25 | isLinkAction: { 26 | type: Boolean, 27 | required: true, 28 | }, 29 | }, 30 | computed: { 31 | localPath: function localPath() { 32 | if (!this.path) { return; } 33 | 34 | return typeof this.path === 'string' ? this.path : Object.assign({}, this.path); 35 | }, 36 | }, 37 | }; 38 | 39 | var _hoisted_1$7 = ["href"]; 40 | 41 | function render$8(_ctx, _cache, $props, $setup, $data, $options) { 42 | var _component_router_link = resolveComponent("router-link"); 43 | 44 | return (openBlock(), createElementBlock(Fragment, null, [ 45 | ($props.isUsingVueRouter && $props.path) 46 | ? (openBlock(), createBlock(_component_router_link, mergeProps({ key: 0 }, _ctx.$attrs, { to: $options.localPath }), { 47 | default: withCtx(function () { return [ 48 | renderSlot(_ctx.$slots, "content") 49 | ]; }), 50 | _: 3 /* FORWARDED */ 51 | }, 16 /* FULL_PROPS */, ["to"])) 52 | : createCommentVNode("v-if", true), 53 | (!$props.isUsingVueRouter && !$props.isLinkAction && $props.path) 54 | ? (openBlock(), createElementBlock("a", mergeProps({ key: 1 }, _ctx.$attrs, { href: $props.path }), [ 55 | renderSlot(_ctx.$slots, "content") 56 | ], 16 /* FULL_PROPS */, _hoisted_1$7)) 57 | : createCommentVNode("v-if", true), 58 | ($props.isLinkAction) 59 | ? (openBlock(), createElementBlock("a", mergeProps({ key: 2 }, _ctx.$attrs, { href: "javascript:void(0);" }), [ 60 | renderSlot(_ctx.$slots, "content") 61 | ], 16 /* FULL_PROPS */)) 62 | : createCommentVNode("v-if", true) 63 | ], 64 /* STABLE_FRAGMENT */)) 64 | } 65 | 66 | script$8.render = render$8; 67 | script$8.__file = "src/components/DynamicLink.vue"; 68 | 69 | var script$7 = { 70 | name: 'brand-image', 71 | props: { 72 | options: { 73 | type: Object, 74 | required: true, 75 | }, 76 | }, 77 | data: function data () { 78 | return {}; 79 | }, 80 | components: { 81 | DynamicLink: script$8, 82 | }, 83 | emits: [ 84 | 'vnb-item-clicked' ] 85 | }; 86 | 87 | var _hoisted_1$6 = { class: "vnb__brand-image-wrapper" }; 88 | var _hoisted_2$4 = ["src", "alt"]; 89 | 90 | function render$7(_ctx, _cache, $props, $setup, $data, $options) { 91 | var _component_dynamic_link = resolveComponent("dynamic-link"); 92 | 93 | return (openBlock(), createElementBlock("div", _hoisted_1$6, [ 94 | createVNode(_component_dynamic_link, { 95 | path: $props.options.brandImagePath, 96 | isUsingVueRouter: $props.options.isUsingVueRouter, 97 | class: "vnb__brand-image-wrapper__link", 98 | "aria-label": "Homepage", 99 | isLinkAction: false, 100 | onClick: _cache[0] || (_cache[0] = function ($event) { return (_ctx.$emit('vnb-item-clicked', 'brand-image')); }) 101 | }, { 102 | content: withCtx(function () { return [ 103 | ($props.options.brandImage) 104 | ? (openBlock(), createElementBlock("img", { 105 | key: 0, 106 | src: $props.options.brandImage, 107 | alt: $props.options.brandImageAltText, 108 | class: "vnb-image vnb__brand-image-wrapper__link__image" 109 | }, null, 8 /* PROPS */, _hoisted_2$4)) 110 | : createCommentVNode("v-if", true) 111 | ]; }), 112 | _: 1 /* STABLE */ 113 | }, 8 /* PROPS */, ["path", "isUsingVueRouter"]) 114 | ])) 115 | } 116 | 117 | script$7.render = render$7; 118 | script$7.__file = "src/components/BrandImage.vue"; 119 | 120 | var script$6 = { 121 | name: 'collapse-button', 122 | mixins: [VueScreenSizeMixin], 123 | props: { 124 | options: { 125 | type: Object, 126 | required: true, 127 | }, 128 | menuIsVisible: { 129 | type: Boolean, 130 | required: true, 131 | }, 132 | }, 133 | data: function data () { 134 | return {}; 135 | }, 136 | methods: { 137 | collapseButtonClicked: function collapseButtonClicked () { 138 | this.$emit('collapse-button-clicked'); 139 | }, 140 | }, 141 | emits: [ 142 | 'collapse-button-clicked' ] 143 | }; 144 | 145 | var _hoisted_1$5 = ["aria-expanded"]; 146 | var _hoisted_2$3 = ["src"]; 147 | var _hoisted_3$2 = /*#__PURE__*/createElementVNode("title", null, "Menu", -1 /* HOISTED */); 148 | var _hoisted_4$2 = /*#__PURE__*/createElementVNode("g", { transform: "matrix(.1 0 0 -.1 0 100)" }, [ 149 | /*#__PURE__*/createElementVNode("path", { d: "m0 850v-40h500 500v40 40h-500-500z" }), 150 | /*#__PURE__*/createElementVNode("path", { d: "m0 495v-45h500 500v45 45h-500-500z" }), 151 | /*#__PURE__*/createElementVNode("path", { d: "m0 140v-40h500 500v40 40h-500-500z" }) 152 | ], -1 /* HOISTED */); 153 | var _hoisted_5$2 = [ 154 | _hoisted_3$2, 155 | _hoisted_4$2 156 | ]; 157 | 158 | function render$6(_ctx, _cache, $props, $setup, $data, $options) { 159 | return (_ctx.$vssWidth < $props.options.mobileBreakpoint) 160 | ? (openBlock(), createElementBlock("button", { 161 | key: 0, 162 | class: "vnb__collapse-button", 163 | onClick: _cache[0] || (_cache[0] = function () { 164 | var args = [], len = arguments.length; 165 | while ( len-- ) args[ len ] = arguments[ len ]; 166 | 167 | return ($options.collapseButtonClicked && $options.collapseButtonClicked.apply($options, args)); 168 | }), 169 | type: "button", 170 | "aria-expanded": $props.menuIsVisible ? 'true' : 'false' 171 | }, [ 172 | ($props.options.collapseButtonImageOpen) 173 | ? (openBlock(), createElementBlock("img", { 174 | key: 0, 175 | src: $props.options.collapseButtonImageOpen, 176 | alt: 'Menu', 177 | class: "vnb__collapse-button__image" 178 | }, null, 8 /* PROPS */, _hoisted_2$3)) 179 | : (openBlock(), createElementBlock("svg", { 180 | key: 1, 181 | height: "100pt", 182 | preserveAspectRatio: "xMidYMid meet", 183 | viewBox: "0 0 100 100", 184 | width: "100pt", 185 | xmlns: "http://www.w3.org/2000/svg", 186 | class: "vnb__collapse-button__image", 187 | style: normalizeStyle({fill: $props.options.collapseButtonOpenColor}) 188 | }, _hoisted_5$2, 4 /* STYLE */)) 189 | ], 8 /* PROPS */, _hoisted_1$5)) 190 | : createCommentVNode("v-if", true) 191 | } 192 | 193 | script$6.render = render$6; 194 | script$6.__file = "src/components/CollapseButton.vue"; 195 | 196 | var script$5 = { 197 | name: 'desktop-menu-item-button', 198 | props: { 199 | option: { 200 | type: Object, 201 | required: true, 202 | }, 203 | options: { 204 | type: Object, 205 | required: true, 206 | }, 207 | }, 208 | data: function data () { 209 | return {}; 210 | }, 211 | components: { 212 | DynamicLink: script$8, 213 | }, 214 | emits: [ 215 | 'vnb-item-clicked' ] 216 | }; 217 | 218 | var _hoisted_1$4 = ["innerHTML"]; 219 | var _hoisted_2$2 = ["innerHTML"]; 220 | 221 | function render$5(_ctx, _cache, $props, $setup, $data, $options) { 222 | var _component_dynamic_link = resolveComponent("dynamic-link"); 223 | 224 | return (openBlock(), createBlock(_component_dynamic_link, { 225 | path: $props.option.path, 226 | isUsingVueRouter: $props.options.isUsingVueRouter, 227 | class: normalizeClass(['vnb__menu-options__option__button', 'vnb-button', $props.option.class]), 228 | "aria-label": $props.option.text, 229 | isLinkAction: $props.option.isLinkAction ? true : false, 230 | onClick: _cache[0] || (_cache[0] = function ($event) { return (_ctx.$emit('vnb-item-clicked', $props.option.text)); }) 231 | }, { 232 | content: withCtx(function () { return [ 233 | ($props.option.iconLeft) 234 | ? (openBlock(), createElementBlock("span", { 235 | key: 0, 236 | class: "vnb__menu-options__option__button__icon vnb__menu-options__option__button__icon--left", 237 | innerHTML: $props.option.iconLeft 238 | }, null, 8 /* PROPS */, _hoisted_1$4)) 239 | : createCommentVNode("v-if", true), 240 | createTextVNode(" " + toDisplayString($props.option.text) + " ", 1 /* TEXT */), 241 | ($props.option.iconRight) 242 | ? (openBlock(), createElementBlock("span", { 243 | key: 1, 244 | class: "vnb__menu-options__option__button__icon vnb__menu-options__option__button__icon--right", 245 | innerHTML: $props.option.iconRight 246 | }, null, 8 /* PROPS */, _hoisted_2$2)) 247 | : createCommentVNode("v-if", true) 248 | ]; }), 249 | _: 1 /* STABLE */ 250 | }, 8 /* PROPS */, ["path", "isUsingVueRouter", "class", "aria-label", "isLinkAction"])) 251 | } 252 | 253 | script$5.render = render$5; 254 | script$5.__file = "src/components/DesktopMenuItemButton.vue"; 255 | 256 | var script$4 = { 257 | name: 'desktop-menu-item-link', 258 | props: { 259 | option: { 260 | type: Object, 261 | required: true, 262 | }, 263 | options: { 264 | type: Object, 265 | required: true, 266 | }, 267 | }, 268 | data: function data () { 269 | return { 270 | currentExpandedStatus: 'closed', 271 | }; 272 | }, 273 | computed: { 274 | isExpanded: function isExpanded () { 275 | if (this.currentExpandedStatus === 'open') { 276 | return true; 277 | } 278 | 279 | return false; 280 | }, 281 | }, 282 | methods: { 283 | // Each time a sub-menu-option is clicked, close all the tooltips 284 | subMenuItemSelected: function subMenuItemSelected (text) { 285 | this.closeAllTooltips(); 286 | }, 287 | 288 | // When we keydown tab on the last sub-menu-option, we want to close 289 | // all the tooltips 290 | subMenuItemTabbed: function subMenuItemTabbed (text) { 291 | // Let's check to see if this item is the last 292 | // item in the subMenuOptions array 293 | if (this.option.subMenuOptions[this.option.subMenuOptions.length - 1].text === text) { 294 | this.closeAllTooltips(); 295 | } 296 | }, 297 | 298 | menuShown: function menuShown () { 299 | this.currentExpandedStatus = 'open'; 300 | }, 301 | menuHidden: function menuHidden () { 302 | this.currentExpandedStatus = 'closed'; 303 | }, 304 | 305 | closeAllTooltips: function closeAllTooltips () { 306 | // https://atomiks.github.io/tippyjs/v6/methods/#hideall 307 | hideAll(); 308 | }, 309 | 310 | initTippy: function initTippy () { 311 | var this$1$1 = this; 312 | 313 | var el = document.getElementById('dropdown-menu-parent-' + this.option.id); 314 | 315 | var template = document.getElementById('sub-menu-options-' + this.option.id); 316 | template.style.display = 'block'; 317 | 318 | tippy(el, { 319 | theme: 'light', 320 | content: template, 321 | interactive: true, 322 | animation: this.options.tooltipAnimationType, 323 | role: 'Menu', 324 | // trigger: 'click', // for testing 325 | trigger: 'click mouseenter focus', 326 | appendTo: 'parent', 327 | arrow: true, 328 | inertia: false, 329 | placement: this.options.tooltipPlacement, 330 | popperOptions: { 331 | modifiers: [ 332 | { 333 | name: 'flip', 334 | options: { 335 | fallbackPlacements: [this.options.tooltipPlacement], 336 | }, 337 | } ], 338 | }, 339 | onShow: function (instance) { 340 | hideAll({exclude: instance}); 341 | 342 | // fire the menuShown function 343 | this$1$1.menuShown(); 344 | }, 345 | onHide: function () { 346 | // fire the menuHidden function 347 | this$1$1.menuHidden(); 348 | }, 349 | }); 350 | }, 351 | }, 352 | mounted: function mounted () { 353 | // Let's setup our tippy here in mounted 354 | if (this.option.subMenuOptions && this.option.subMenuOptions.length) { 355 | this.initTippy(); 356 | } 357 | }, 358 | components: { 359 | DynamicLink: script$8, 360 | }, 361 | emits: [ 362 | 'vnb-item-clicked' ] 363 | }; 364 | 365 | var _hoisted_1$3 = ["innerHTML"]; 366 | var _hoisted_2$1 = ["innerHTML"]; 367 | var _hoisted_3$1 = ["id", "aria-expanded", "aria-label"]; 368 | var _hoisted_4$1 = ["innerHTML"]; 369 | var _hoisted_5$1 = ["innerHTML"]; 370 | var _hoisted_6$1 = /*#__PURE__*/createElementVNode("title", null, "Toggle Arrow", -1 /* HOISTED */); 371 | var _hoisted_7$1 = /*#__PURE__*/createElementVNode("path", { 372 | d: "m12 268c-7-7-12-17-12-23 0-13 232-245 245-245 6 0 64 54 129 119 119 119 132 142 90 158-11 4-44-23-113-91-53-53-101-96-106-96-6 0-53 43-105 95s-99 95-105 95-16-5-23-12z", 373 | transform: "matrix(.1 0 0 -.1 0 28)" 374 | }, null, -1 /* HOISTED */); 375 | var _hoisted_8$1 = [ 376 | _hoisted_6$1, 377 | _hoisted_7$1 378 | ]; 379 | var _hoisted_9$1 = ["id"]; 380 | var _hoisted_10$1 = { 381 | class: "vnb__sub-menu-options__option", 382 | tabindex: "-1" 383 | }; 384 | var _hoisted_11$1 = ["innerHTML"]; 385 | var _hoisted_12$1 = { class: "vnb__sub-menu-options__option__link__text-wrapper" }; 386 | var _hoisted_13$1 = { class: "vnb__sub-menu-options__option__link__text-wrapper__text" }; 387 | var _hoisted_14$1 = { 388 | key: 0, 389 | class: "vnb__sub-menu-options__option__link__text-wrapper__sub-text" 390 | }; 391 | var _hoisted_15$1 = ["innerHTML"]; 392 | var _hoisted_16$1 = { 393 | key: 1, 394 | class: "vnb__sub-menu-options__option__hr", 395 | tabindex: "-1" 396 | }; 397 | 398 | function render$4(_ctx, _cache, $props, $setup, $data, $options) { 399 | var _component_dynamic_link = resolveComponent("dynamic-link"); 400 | 401 | return (!$props.option.subMenuOptions || !$props.option.subMenuOptions.length) 402 | ? (openBlock(), createBlock(_component_dynamic_link, { 403 | key: 0, 404 | path: $props.option.path, 405 | isUsingVueRouter: $props.options.isUsingVueRouter, 406 | class: normalizeClass(['vnb__menu-options__option__link', $props.option.class]), 407 | "aria-label": $props.option.text, 408 | tabindex: "0", 409 | isLinkAction: $props.option.isLinkAction ? true : false, 410 | onClick: _cache[0] || (_cache[0] = function ($event) { return (_ctx.$emit('vnb-item-clicked', $props.option.text)); }) 411 | }, { 412 | content: withCtx(function () { return [ 413 | ($props.option.iconLeft) 414 | ? (openBlock(), createElementBlock("span", { 415 | key: 0, 416 | class: "vnb__menu-options__option__link__icon vnb__menu-options__option__button__icon--left", 417 | innerHTML: $props.option.iconLeft 418 | }, null, 8 /* PROPS */, _hoisted_1$3)) 419 | : createCommentVNode("v-if", true), 420 | createTextVNode(" " + toDisplayString($props.option.text) + " ", 1 /* TEXT */), 421 | ($props.option.iconRight) 422 | ? (openBlock(), createElementBlock("span", { 423 | key: 1, 424 | class: "vnb__menu-options__option__link__icon vnb__menu-options__option__button__icon--right", 425 | innerHTML: $props.option.iconRight 426 | }, null, 8 /* PROPS */, _hoisted_2$1)) 427 | : createCommentVNode("v-if", true) 428 | ]; }), 429 | _: 1 /* STABLE */ 430 | }, 8 /* PROPS */, ["path", "isUsingVueRouter", "class", "aria-label", "isLinkAction"])) 431 | : (openBlock(), createElementBlock("span", { 432 | key: 1, 433 | class: normalizeClass(['vnb__menu-options__option__link', $props.option.class]), 434 | id: 'dropdown-menu-parent-' + $props.option.id, 435 | "aria-haspopup": "true", 436 | "aria-expanded": $options.isExpanded ? 'true' : 'false', 437 | "aria-label": $props.option.text, 438 | tabindex: "0" 439 | }, [ 440 | ($props.option.iconLeft) 441 | ? (openBlock(), createElementBlock("span", { 442 | key: 0, 443 | class: "vnb__menu-options__option__link__icon vnb__menu-options__option__button__icon--left", 444 | innerHTML: $props.option.iconLeft 445 | }, null, 8 /* PROPS */, _hoisted_4$1)) 446 | : createCommentVNode("v-if", true), 447 | createTextVNode(" " + toDisplayString($props.option.text) + " ", 1 /* TEXT */), 448 | ($props.option.iconRight) 449 | ? (openBlock(), createElementBlock("span", { 450 | key: 1, 451 | class: "vnb__menu-options__option__link__icon vnb__menu-options__option__button__icon--right", 452 | innerHTML: $props.option.iconRight 453 | }, null, 8 /* PROPS */, _hoisted_5$1)) 454 | : createCommentVNode("v-if", true), 455 | (openBlock(), createElementBlock("svg", { 456 | height: "28pt", 457 | preserveAspectRatio: "xMidYMid meet", 458 | viewBox: "0 0 49 28", 459 | width: "49pt", 460 | xmlns: "http://www.w3.org/2000/svg", 461 | style: normalizeStyle({fill: $props.option.arrowColor}), 462 | class: normalizeClass([ 463 | 'vnb__menu-options__option__arrow', 464 | {'vnb__menu-options__option__arrow--hover': $options.isExpanded} ]) 465 | }, _hoisted_8$1, 6 /* CLASS, STYLE */)), 466 | ($props.option.type === 'link') 467 | ? (openBlock(), createElementBlock("div", { 468 | key: 2, 469 | class: "vnb__sub-menu-options", 470 | id: 'sub-menu-options-' + $props.option.id 471 | }, [ 472 | createElementVNode("div", _hoisted_10$1, [ 473 | (openBlock(true), createElementBlock(Fragment, null, renderList($props.option.subMenuOptions, function (subOption, index) { 474 | return (openBlock(), createElementBlock("div", null, [ 475 | (subOption.type === 'link') 476 | ? (openBlock(), createBlock(_component_dynamic_link, { 477 | path: subOption.path, 478 | isUsingVueRouter: $props.options.isUsingVueRouter, 479 | key: index, 480 | class: "vnb__sub-menu-options__option__link", 481 | onClick: function ($event) { 482 | $options.subMenuItemSelected(subOption.text); 483 | _ctx.$emit('vnb-item-clicked', subOption.text); 484 | }, 485 | "aria-label": subOption.text, 486 | tabindex: "0", 487 | onKeydown: withKeys(function ($event) { return ($options.subMenuItemTabbed(subOption.text)); }, ["tab"]), 488 | isLinkAction: subOption.isLinkAction ? true : false 489 | }, { 490 | content: withCtx(function () { return [ 491 | (subOption.iconLeft) 492 | ? (openBlock(), createElementBlock("span", { 493 | key: 0, 494 | class: "vnb__sub-menu-options__option__link__icon vnb__sub-menu-options__option__link__icon--left", 495 | innerHTML: subOption.iconLeft 496 | }, null, 8 /* PROPS */, _hoisted_11$1)) 497 | : createCommentVNode("v-if", true), 498 | createElementVNode("span", _hoisted_12$1, [ 499 | createElementVNode("span", _hoisted_13$1, toDisplayString(subOption.text), 1 /* TEXT */), 500 | (subOption.subText) 501 | ? (openBlock(), createElementBlock("span", _hoisted_14$1, toDisplayString(subOption.subText), 1 /* TEXT */)) 502 | : createCommentVNode("v-if", true) 503 | ]), 504 | (subOption.iconRight) 505 | ? (openBlock(), createElementBlock("span", { 506 | key: 1, 507 | class: "vnb__sub-menu-options__option__link__icon vnb__sub-menu-options__option__link__icon--right", 508 | innerHTML: subOption.iconRight 509 | }, null, 8 /* PROPS */, _hoisted_15$1)) 510 | : createCommentVNode("v-if", true) 511 | ]; }), 512 | _: 2 /* DYNAMIC */ 513 | }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["path", "isUsingVueRouter", "onClick", "aria-label", "onKeydown", "isLinkAction"])) 514 | : (openBlock(), createElementBlock("hr", _hoisted_16$1)) 515 | ])) 516 | }), 256 /* UNKEYED_FRAGMENT */)) 517 | ]) 518 | ], 8 /* PROPS */, _hoisted_9$1)) 519 | : createCommentVNode("v-if", true) 520 | ], 10 /* CLASS, PROPS */, _hoisted_3$1)) 521 | } 522 | 523 | script$4.render = render$4; 524 | script$4.__file = "src/components/DesktopMenuItemLink.vue"; 525 | 526 | var script$3 = { 527 | name: 'desktop-menu-item-spacer', 528 | props: { 529 | option: { 530 | type: Object, 531 | required: true 532 | } 533 | }, 534 | data: function data () { 535 | return { 536 | } 537 | } 538 | }; 539 | 540 | var _hoisted_1$2 = { class: "vnb__menu-options__option__spacer" }; 541 | 542 | function render$3(_ctx, _cache, $props, $setup, $data, $options) { 543 | return (openBlock(), createElementBlock("div", _hoisted_1$2)) 544 | } 545 | 546 | script$3.render = render$3; 547 | script$3.__file = "src/components/DesktopMenuItemSpacer.vue"; 548 | 549 | var script$2 = { 550 | name: 'menu-options', 551 | mixins: [VueScreenSizeMixin], 552 | props: { 553 | options: { 554 | type: Object, 555 | required: true, 556 | }, 557 | type: { 558 | type: String, 559 | required: true, 560 | }, 561 | }, 562 | data: function data () { 563 | return {}; 564 | }, 565 | methods: { 566 | vnbItemClicked: function vnbItemClicked (text) { 567 | this.$emit('vnb-item-clicked', text); 568 | }, 569 | }, 570 | components: { 571 | DesktopMenuItemLink: script$4, 572 | DesktopMenuItemButton: script$5, 573 | DesktopMenuItemSpacer: script$3, 574 | }, 575 | emits: [ 576 | 'vnb-item-clicked' ] 577 | }; 578 | 579 | function render$2(_ctx, _cache, $props, $setup, $data, $options) { 580 | var _component_desktop_menu_item_link = resolveComponent("desktop-menu-item-link"); 581 | var _component_desktop_menu_item_button = resolveComponent("desktop-menu-item-button"); 582 | var _component_desktop_menu_item_spacer = resolveComponent("desktop-menu-item-spacer"); 583 | 584 | return (_ctx.$vssWidth > $props.options.mobileBreakpoint) 585 | ? (openBlock(), createElementBlock("div", { 586 | key: 0, 587 | class: normalizeClass([ 588 | 'vnb__menu-options', 589 | {'vnb__menu-options--left': $props.type === 'left'}, 590 | {'vnb__menu-options--right': $props.type === 'right'} ]) 591 | }, [ 592 | (openBlock(true), createElementBlock(Fragment, null, renderList($props.type === 'left' 593 | ? $props.options.menuOptionsLeft 594 | : $props.options.menuOptionsRight, function (option, index) { 595 | return (openBlock(), createElementBlock("div", { 596 | key: index, 597 | class: "vnb__menu-options__option" 598 | }, [ 599 | (option.type === 'link') 600 | ? (openBlock(), createBlock(_component_desktop_menu_item_link, { 601 | key: 0, 602 | class: normalizeClass(option.class), 603 | option: option, 604 | options: $props.options, 605 | onVnbItemClicked: $options.vnbItemClicked 606 | }, null, 8 /* PROPS */, ["class", "option", "options", "onVnbItemClicked"])) 607 | : (option.type === 'button') 608 | ? (openBlock(), createBlock(_component_desktop_menu_item_button, { 609 | key: 1, 610 | option: option, 611 | options: $props.options, 612 | onVnbItemClicked: $options.vnbItemClicked 613 | }, null, 8 /* PROPS */, ["option", "options", "onVnbItemClicked"])) 614 | : (openBlock(), createBlock(_component_desktop_menu_item_spacer, { 615 | key: 2, 616 | option: option 617 | }, null, 8 /* PROPS */, ["option"])) 618 | ])) 619 | }), 128 /* KEYED_FRAGMENT */)) 620 | ], 2 /* CLASS */)) 621 | : createCommentVNode("v-if", true) 622 | } 623 | 624 | script$2.render = render$2; 625 | script$2.__file = "src/components/MenuOptions.vue"; 626 | 627 | var script$1 = { 628 | name: 'popup', 629 | props: { 630 | options: { 631 | type: Object, 632 | required: true, 633 | }, 634 | menuIsVisible: { 635 | type: Boolean, 636 | required: true, 637 | }, 638 | }, 639 | data: function data() { 640 | return {}; 641 | }, 642 | computed: { 643 | combinedMenuItems: function combinedMenuItems() { 644 | var combinedArray = this.options.menuOptionsLeft.concat(this.options.menuOptionsRight); 645 | return combinedArray; 646 | }, 647 | }, 648 | methods: { 649 | closeButtonClicked: function closeButtonClicked() { 650 | this.$emit('close-button-clicked'); 651 | }, 652 | itemSelected: function itemSelected(option) { 653 | this.$emit('vnb-item-clicked', option.text); 654 | this.closeButtonClicked(); 655 | }, 656 | }, 657 | components: { 658 | DynamicLink: script$8, 659 | }, 660 | emits: ['close-button-clicked', 'vnb-item-clicked'], 661 | }; 662 | 663 | var _hoisted_1$1 = { 664 | key: 0, 665 | class: "vnb__popup" 666 | }; 667 | var _hoisted_2 = { class: "vnb__popup__top" }; 668 | var _hoisted_3 = ["src", "alt"]; 669 | var _hoisted_4 = ["aria-expanded"]; 670 | var _hoisted_5 = ["src"]; 671 | var _hoisted_6 = /*#__PURE__*/createElementVNode("title", null, "Close button", -1 /* HOISTED */); 672 | var _hoisted_7 = /*#__PURE__*/createElementVNode("path", { 673 | d: "m42 967c-12-13-22-27-22-33 0-5 93-102 207-216l208-208-208-208c-114-114-207-214-207-223 0-8 11-26 25-39l26-24 214 214 215 215 215-215 214-214 26 24c14 13 25 28 25 34s-92 103-205 216-205 209-205 215 92 102 205 215 205 210 205 216c0 12-42 54-55 54-5 0-104-94-220-210l-210-210-210 210c-115 116-212 210-216 210-3 0-15-10-27-23z", 674 | transform: "matrix(.1 0 0 -.1 0 100)" 675 | }, null, -1 /* HOISTED */); 676 | var _hoisted_8 = [ 677 | _hoisted_6, 678 | _hoisted_7 679 | ]; 680 | var _hoisted_9 = { class: "vnb__popup__bottom" }; 681 | var _hoisted_10 = { 682 | key: 0, 683 | class: "vnb__popup__bottom__custom-section" 684 | }; 685 | var _hoisted_11 = { class: "vnb__popup__bottom__menu-options" }; 686 | var _hoisted_12 = ["innerHTML"]; 687 | var _hoisted_13 = ["innerHTML"]; 688 | var _hoisted_14 = { 689 | key: 1, 690 | class: "vnb__popup__bottom__menu-options__option__link vnb__popup__bottom__menu-options__option__link--no-highlight" 691 | }; 692 | var _hoisted_15 = { class: "vnb__popup__bottom__sub-menu-options" }; 693 | var _hoisted_16 = { class: "vnb__popup__bottom__sub-menu-options__option__link__sub-text" }; 694 | 695 | function render$1(_ctx, _cache, $props, $setup, $data, $options) { 696 | var _component_dynamic_link = resolveComponent("dynamic-link"); 697 | 698 | return ($props.menuIsVisible) 699 | ? (openBlock(), createElementBlock("div", _hoisted_1$1, [ 700 | createElementVNode("div", _hoisted_2, [ 701 | ($props.options.showBrandImageInMobilePopup && $props.options.brandImage) 702 | ? (openBlock(), createElementBlock("img", { 703 | key: 0, 704 | src: $props.options.brandImage, 705 | alt: $props.options.brandImageAltText, 706 | class: "vnb-image vnb__popup__top__image" 707 | }, null, 8 /* PROPS */, _hoisted_3)) 708 | : createCommentVNode("v-if", true), 709 | createElementVNode("button", { 710 | class: "vnb__popup__top__close-button", 711 | onClick: _cache[0] || (_cache[0] = function () { 712 | var args = [], len = arguments.length; 713 | while ( len-- ) args[ len ] = arguments[ len ]; 714 | 715 | return ($options.closeButtonClicked && $options.closeButtonClicked.apply($options, args)); 716 | }), 717 | "aria-label": "Close Button", 718 | title: "Close", 719 | "aria-expanded": $props.menuIsVisible ? 'true' : 'false' 720 | }, [ 721 | ($props.options.collapseButtonImageClose) 722 | ? (openBlock(), createElementBlock("img", { 723 | key: 0, 724 | src: $props.options.collapseButtonImageClose, 725 | alt: 'Close button', 726 | class: "vnb__popup__top__close-button__image" 727 | }, null, 8 /* PROPS */, _hoisted_5)) 728 | : (openBlock(), createElementBlock("svg", { 729 | key: 1, 730 | height: "100pt", 731 | preserveAspectRatio: "xMidYMid meet", 732 | viewBox: "0 0 100 100", 733 | width: "100pt", 734 | xmlns: "http://www.w3.org/2000/svg", 735 | class: "vnb__popup__top__close-button__image", 736 | style: normalizeStyle({ fill: $props.options.collapseButtonCloseColor }) 737 | }, _hoisted_8, 4 /* STYLE */)) 738 | ], 8 /* PROPS */, _hoisted_4) 739 | ]), 740 | createElementVNode("div", _hoisted_9, [ 741 | (!!this.$slots['custom-section']) 742 | ? (openBlock(), createElementBlock("div", _hoisted_10, [ 743 | renderSlot(_ctx.$slots, "custom-section") 744 | ])) 745 | : createCommentVNode("v-if", true), 746 | createElementVNode("ul", _hoisted_11, [ 747 | (openBlock(true), createElementBlock(Fragment, null, renderList($options.combinedMenuItems, function (option, index) { 748 | return (openBlock(), createElementBlock("li", { 749 | key: index, 750 | class: "vnb__popup__bottom__menu-options__option" 751 | }, [ 752 | (!option.subMenuOptions) 753 | ? (openBlock(), createBlock(_component_dynamic_link, { 754 | key: 0, 755 | path: option.path, 756 | isUsingVueRouter: $props.options.isUsingVueRouter, 757 | class: normalizeClass(['vnb__popup__bottom__menu-options__option__link', option.class]), 758 | onClick: function ($event) { return ($options.itemSelected(option)); }, 759 | "aria-label": option.text, 760 | isLinkAction: option.isLinkAction ? true : false 761 | }, { 762 | content: withCtx(function () { return [ 763 | (option.iconLeft) 764 | ? (openBlock(), createElementBlock("span", { 765 | key: 0, 766 | class: "vnb__popup__bottom__menu-options__option__link__icon vnb__popup__bottom__menu-options__option__link__icon--left", 767 | innerHTML: option.iconLeft 768 | }, null, 8 /* PROPS */, _hoisted_12)) 769 | : createCommentVNode("v-if", true), 770 | createTextVNode(" " + toDisplayString(option.text) + " ", 1 /* TEXT */), 771 | (option.iconRight) 772 | ? (openBlock(), createElementBlock("span", { 773 | key: 1, 774 | class: "vnb__popup__bottom__menu-options__option__link__icon vnb__popup__bottom__menu-options__option__link__icon--right", 775 | innerHTML: option.iconRight 776 | }, null, 8 /* PROPS */, _hoisted_13)) 777 | : createCommentVNode("v-if", true) 778 | ]; }), 779 | _: 2 /* DYNAMIC */ 780 | }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["path", "isUsingVueRouter", "class", "onClick", "aria-label", "isLinkAction"])) 781 | : (openBlock(), createElementBlock("span", _hoisted_14, toDisplayString(option.text), 1 /* TEXT */)), 782 | createElementVNode("div", _hoisted_15, [ 783 | (openBlock(true), createElementBlock(Fragment, null, renderList(option.subMenuOptions, function (subOption, index) { 784 | return (openBlock(), createElementBlock("div", { 785 | key: index, 786 | class: "vnb__popup__bottom__sub-menu-options__option" 787 | }, [ 788 | (subOption.type === 'link') 789 | ? (openBlock(), createBlock(_component_dynamic_link, { 790 | key: 0, 791 | path: subOption.path, 792 | isUsingVueRouter: $props.options.isUsingVueRouter, 793 | class: "vnb__popup__bottom__sub-menu-options__option__link", 794 | onClick: function ($event) { return ($options.itemSelected(subOption)); }, 795 | "aria-label": subOption.text, 796 | isLinkAction: subOption.isLinkAction ? true : false 797 | }, { 798 | content: withCtx(function () { return [ 799 | createTextVNode(toDisplayString(subOption.text) + " ", 1 /* TEXT */), 800 | createElementVNode("span", _hoisted_16, toDisplayString(subOption.subText), 1 /* TEXT */) 801 | ]; }), 802 | _: 2 /* DYNAMIC */ 803 | }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["path", "isUsingVueRouter", "onClick", "aria-label", "isLinkAction"])) 804 | : createCommentVNode("v-if", true) 805 | ])) 806 | }), 128 /* KEYED_FRAGMENT */)) 807 | ]) 808 | ])) 809 | }), 128 /* KEYED_FRAGMENT */)) 810 | ]) 811 | ]) 812 | ])) 813 | : createCommentVNode("v-if", true) 814 | } 815 | 816 | script$1.render = render$1; 817 | script$1.__file = "src/components/Popup.vue"; 818 | 819 | var script = { 820 | name: 'vue-navigation-bar', 821 | mixins: [VueScreenSizeMixin], 822 | props: { 823 | options: { 824 | type: Object, 825 | required: true, 826 | }, 827 | }, 828 | data: function data () { 829 | return { 830 | menuIsVisible: false, 831 | }; 832 | }, 833 | computed: { 834 | finalOptions: function finalOptions () { 835 | // What we're doing here is giving each top-level menu-option a unique id 836 | if (this.options.menuOptionsLeft) { 837 | for (var x = 0; x < this.options.menuOptionsLeft.length; x++) { 838 | this.options.menuOptionsLeft[x].id = uuidV4(); 839 | } 840 | } 841 | if (this.options.menuOptionsRight) { 842 | for (var x$1 = 0; x$1 < this.options.menuOptionsRight.length; x$1++) { 843 | this.options.menuOptionsRight[x$1].id = uuidV4(); 844 | } 845 | } 846 | 847 | return { 848 | elementId: this.options.elementId ? this.options.elementId : uuidV4(), 849 | isUsingVueRouter: this.options.isUsingVueRouter ? true : false, 850 | mobileBreakpoint: this.options.mobileBreakpoint ? this.options.mobileBreakpoint : 992, 851 | brandImagePath: this.options.brandImagePath ? this.options.brandImagePath : '/', 852 | brandImage: this.options.brandImage ? this.options.brandImage : null, 853 | brandImageAltText: this.options.brandImageAltText 854 | ? this.options.brandImageAltText 855 | : 'brand-image', 856 | collapseButtonImageOpen: this.options.collapseButtonImageOpen 857 | ? this.options.collapseButtonImageOpen 858 | : null, 859 | collapseButtonImageClose: this.options.collapseButtonImageClose 860 | ? this.options.collapseButtonImageClose 861 | : null, 862 | collapseButtonOpenColor: this.options.collapseButtonOpenColor 863 | ? this.options.collapseButtonOpenColor 864 | : '#373737', 865 | collapseButtonCloseColor: this.options.collapseButtonCloseColor 866 | ? this.options.collapseButtonCloseColor 867 | : '#373737', 868 | showBrandImageInMobilePopup: this.options.showBrandImageInMobilePopup ? true : false, 869 | ariaLabelMainNav: this.options.ariaLabelMainNav 870 | ? this.options.ariaLabelMainNav 871 | : 'Main Navigation', 872 | tooltipAnimationType: this.options.tooltipAnimationType 873 | ? this.options.tooltipAnimationType 874 | : 'shift-away', 875 | tooltipPlacement: this.options.tooltipPlacement || 'bottom', 876 | menuOptionsLeft: this.options.menuOptionsLeft ? this.options.menuOptionsLeft : [], 877 | menuOptionsRight: this.options.menuOptionsRight ? this.options.menuOptionsRight : [], 878 | }; 879 | }, 880 | }, 881 | methods: { 882 | closeMobilePopup: function closeMobilePopup () { 883 | this.menuIsVisible = false; 884 | this.$emit('vnb-mobile-popup-hidden'); 885 | }, 886 | showMobilePopup: function showMobilePopup () { 887 | this.menuIsVisible = true; 888 | this.$emit('vnb-mobile-popup-shown'); 889 | }, 890 | vnbItemClicked: function vnbItemClicked (text) { 891 | this.$emit('vnb-item-clicked', text); 892 | }, 893 | }, 894 | components: { 895 | BrandImage: script$7, 896 | MenuOptions: script$2, 897 | CollapseButton: script$6, 898 | Popup: script$1, 899 | }, 900 | emits: [ 901 | 'vnb-mobile-popup-hidden', 902 | 'vnb-mobile-popup-shown', 903 | 'vnb-item-clicked' ] 904 | }; 905 | 906 | var _hoisted_1 = ["id", "aria-label"]; 907 | 908 | function render(_ctx, _cache, $props, $setup, $data, $options) { 909 | var _component_brand_image = resolveComponent("brand-image"); 910 | var _component_menu_options = resolveComponent("menu-options"); 911 | var _component_collapse_button = resolveComponent("collapse-button"); 912 | var _component_popup = resolveComponent("popup"); 913 | 914 | return (openBlock(), createElementBlock("nav", { 915 | class: "vnb", 916 | id: $options.finalOptions.elementId, 917 | "aria-label": $options.finalOptions.ariaLabelMainNav 918 | }, [ 919 | createVNode(_component_brand_image, { 920 | options: $options.finalOptions, 921 | onVnbItemClicked: $options.vnbItemClicked 922 | }, null, 8 /* PROPS */, ["options", "onVnbItemClicked"]), 923 | createVNode(_component_menu_options, { 924 | options: $options.finalOptions, 925 | type: 'left', 926 | onVnbItemClicked: $options.vnbItemClicked 927 | }, null, 8 /* PROPS */, ["options", "onVnbItemClicked"]), 928 | (_ctx.$vssWidth > $props.options.mobileBreakpoint) 929 | ? renderSlot(_ctx.$slots, "custom-section", { key: 0 }) 930 | : createCommentVNode("v-if", true), 931 | createVNode(_component_menu_options, { 932 | options: $options.finalOptions, 933 | type: 'right', 934 | onVnbItemClicked: $options.vnbItemClicked 935 | }, null, 8 /* PROPS */, ["options", "onVnbItemClicked"]), 936 | ($options.finalOptions.menuOptionsLeft.length || $options.finalOptions.menuOptionsRight.length) 937 | ? (openBlock(), createBlock(_component_collapse_button, { 938 | key: 1, 939 | options: $options.finalOptions, 940 | menuIsVisible: $data.menuIsVisible, 941 | onCollapseButtonClicked: $options.showMobilePopup 942 | }, null, 8 /* PROPS */, ["options", "menuIsVisible", "onCollapseButtonClicked"])) 943 | : createCommentVNode("v-if", true), 944 | ($options.finalOptions.menuOptionsLeft.length || $options.finalOptions.menuOptionsRight.length) 945 | ? (openBlock(), createBlock(_component_popup, { 946 | key: 2, 947 | options: $options.finalOptions, 948 | menuIsVisible: $data.menuIsVisible, 949 | onCloseButtonClicked: $options.closeMobilePopup, 950 | onVnbItemClicked: $options.vnbItemClicked 951 | }, { 952 | "custom-section": withCtx(function () { return [ 953 | renderSlot(_ctx.$slots, "custom-section") 954 | ]; }), 955 | _: 3 /* FORWARDED */ 956 | }, 8 /* PROPS */, ["options", "menuIsVisible", "onCloseButtonClicked", "onVnbItemClicked"])) 957 | : createCommentVNode("v-if", true) 958 | ], 8 /* PROPS */, _hoisted_1)) 959 | } 960 | 961 | script.render = render; 962 | script.__file = "src/vue-navigation-bar.vue"; 963 | 964 | // Import vue component 965 | 966 | // install function executed by Vue.use() 967 | function install(app) { 968 | if (install.installed) { return; } 969 | 970 | install.installed = true; 971 | app.component('VueNavigationBar', script); 972 | } 973 | 974 | var plugin = { install: install }; 975 | 976 | // To auto-install when vue is found 977 | var GlobalVue = null; 978 | if (typeof window !== 'undefined') { 979 | GlobalVue = window.Vue; 980 | } else if (typeof global !== 'undefined') { 981 | GlobalVue = global.Vue; 982 | } 983 | if (GlobalVue) { 984 | GlobalVue.use(plugin); 985 | } 986 | 987 | // It's possible to expose named exports when writing components that can 988 | // also be used as directives, etc. - eg. import { RollupDemoDirective } from 'rollup-demo'; 989 | // export const RollupDemoDirective = component; 990 | 991 | export { script as default, install }; 992 | -------------------------------------------------------------------------------- /dist/vue-navigation-bar.umd.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vue-screen-size'), require('vue'), require('tippy.js')) : 3 | typeof define === 'function' && define.amd ? define(['exports', 'vue-screen-size', 'vue', 'tippy.js'], factory) : 4 | (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.VueNavigationBar = {}, global.VueScreenSize, global.Vue, global.tippy)); 5 | })(this, (function (exports, vueScreenSize, vue, tippy) { 'use strict'; 6 | 7 | function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } 8 | 9 | var tippy__default = /*#__PURE__*/_interopDefaultLegacy(tippy); 10 | 11 | // https://stackoverflow.com/a/2117523/8014660 12 | function uuidV4() { 13 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 14 | var r = (Math.random() * 16) | 0, 15 | v = c == 'x' ? r : (r & 0x3) | 0x8; 16 | return v.toString(16); 17 | }); 18 | } 19 | 20 | var script$8 = { 21 | name: 'dynamic-link', 22 | props: { 23 | isUsingVueRouter: { 24 | type: Boolean, 25 | required: true, 26 | }, 27 | path: { 28 | type: [String, Object], 29 | required: false, 30 | }, 31 | isLinkAction: { 32 | type: Boolean, 33 | required: true, 34 | }, 35 | }, 36 | computed: { 37 | localPath: function localPath() { 38 | if (!this.path) { return; } 39 | 40 | return typeof this.path === 'string' ? this.path : Object.assign({}, this.path); 41 | }, 42 | }, 43 | }; 44 | 45 | var _hoisted_1$7 = ["href"]; 46 | 47 | function render$8(_ctx, _cache, $props, $setup, $data, $options) { 48 | var _component_router_link = vue.resolveComponent("router-link"); 49 | 50 | return (vue.openBlock(), vue.createElementBlock(vue.Fragment, null, [ 51 | ($props.isUsingVueRouter && $props.path) 52 | ? (vue.openBlock(), vue.createBlock(_component_router_link, vue.mergeProps({ key: 0 }, _ctx.$attrs, { to: $options.localPath }), { 53 | default: vue.withCtx(function () { return [ 54 | vue.renderSlot(_ctx.$slots, "content") 55 | ]; }), 56 | _: 3 /* FORWARDED */ 57 | }, 16 /* FULL_PROPS */, ["to"])) 58 | : vue.createCommentVNode("v-if", true), 59 | (!$props.isUsingVueRouter && !$props.isLinkAction && $props.path) 60 | ? (vue.openBlock(), vue.createElementBlock("a", vue.mergeProps({ key: 1 }, _ctx.$attrs, { href: $props.path }), [ 61 | vue.renderSlot(_ctx.$slots, "content") 62 | ], 16 /* FULL_PROPS */, _hoisted_1$7)) 63 | : vue.createCommentVNode("v-if", true), 64 | ($props.isLinkAction) 65 | ? (vue.openBlock(), vue.createElementBlock("a", vue.mergeProps({ key: 2 }, _ctx.$attrs, { href: "javascript:void(0);" }), [ 66 | vue.renderSlot(_ctx.$slots, "content") 67 | ], 16 /* FULL_PROPS */)) 68 | : vue.createCommentVNode("v-if", true) 69 | ], 64 /* STABLE_FRAGMENT */)) 70 | } 71 | 72 | script$8.render = render$8; 73 | script$8.__file = "src/components/DynamicLink.vue"; 74 | 75 | var script$7 = { 76 | name: 'brand-image', 77 | props: { 78 | options: { 79 | type: Object, 80 | required: true, 81 | }, 82 | }, 83 | data: function data () { 84 | return {}; 85 | }, 86 | components: { 87 | DynamicLink: script$8, 88 | }, 89 | emits: [ 90 | 'vnb-item-clicked' ] 91 | }; 92 | 93 | var _hoisted_1$6 = { class: "vnb__brand-image-wrapper" }; 94 | var _hoisted_2$4 = ["src", "alt"]; 95 | 96 | function render$7(_ctx, _cache, $props, $setup, $data, $options) { 97 | var _component_dynamic_link = vue.resolveComponent("dynamic-link"); 98 | 99 | return (vue.openBlock(), vue.createElementBlock("div", _hoisted_1$6, [ 100 | vue.createVNode(_component_dynamic_link, { 101 | path: $props.options.brandImagePath, 102 | isUsingVueRouter: $props.options.isUsingVueRouter, 103 | class: "vnb__brand-image-wrapper__link", 104 | "aria-label": "Homepage", 105 | isLinkAction: false, 106 | onClick: _cache[0] || (_cache[0] = function ($event) { return (_ctx.$emit('vnb-item-clicked', 'brand-image')); }) 107 | }, { 108 | content: vue.withCtx(function () { return [ 109 | ($props.options.brandImage) 110 | ? (vue.openBlock(), vue.createElementBlock("img", { 111 | key: 0, 112 | src: $props.options.brandImage, 113 | alt: $props.options.brandImageAltText, 114 | class: "vnb-image vnb__brand-image-wrapper__link__image" 115 | }, null, 8 /* PROPS */, _hoisted_2$4)) 116 | : vue.createCommentVNode("v-if", true) 117 | ]; }), 118 | _: 1 /* STABLE */ 119 | }, 8 /* PROPS */, ["path", "isUsingVueRouter"]) 120 | ])) 121 | } 122 | 123 | script$7.render = render$7; 124 | script$7.__file = "src/components/BrandImage.vue"; 125 | 126 | var script$6 = { 127 | name: 'collapse-button', 128 | mixins: [vueScreenSize.VueScreenSizeMixin], 129 | props: { 130 | options: { 131 | type: Object, 132 | required: true, 133 | }, 134 | menuIsVisible: { 135 | type: Boolean, 136 | required: true, 137 | }, 138 | }, 139 | data: function data () { 140 | return {}; 141 | }, 142 | methods: { 143 | collapseButtonClicked: function collapseButtonClicked () { 144 | this.$emit('collapse-button-clicked'); 145 | }, 146 | }, 147 | emits: [ 148 | 'collapse-button-clicked' ] 149 | }; 150 | 151 | var _hoisted_1$5 = ["aria-expanded"]; 152 | var _hoisted_2$3 = ["src"]; 153 | var _hoisted_3$2 = /*#__PURE__*/vue.createElementVNode("title", null, "Menu", -1 /* HOISTED */); 154 | var _hoisted_4$2 = /*#__PURE__*/vue.createElementVNode("g", { transform: "matrix(.1 0 0 -.1 0 100)" }, [ 155 | /*#__PURE__*/vue.createElementVNode("path", { d: "m0 850v-40h500 500v40 40h-500-500z" }), 156 | /*#__PURE__*/vue.createElementVNode("path", { d: "m0 495v-45h500 500v45 45h-500-500z" }), 157 | /*#__PURE__*/vue.createElementVNode("path", { d: "m0 140v-40h500 500v40 40h-500-500z" }) 158 | ], -1 /* HOISTED */); 159 | var _hoisted_5$2 = [ 160 | _hoisted_3$2, 161 | _hoisted_4$2 162 | ]; 163 | 164 | function render$6(_ctx, _cache, $props, $setup, $data, $options) { 165 | return (_ctx.$vssWidth < $props.options.mobileBreakpoint) 166 | ? (vue.openBlock(), vue.createElementBlock("button", { 167 | key: 0, 168 | class: "vnb__collapse-button", 169 | onClick: _cache[0] || (_cache[0] = function () { 170 | var args = [], len = arguments.length; 171 | while ( len-- ) args[ len ] = arguments[ len ]; 172 | 173 | return ($options.collapseButtonClicked && $options.collapseButtonClicked.apply($options, args)); 174 | }), 175 | type: "button", 176 | "aria-expanded": $props.menuIsVisible ? 'true' : 'false' 177 | }, [ 178 | ($props.options.collapseButtonImageOpen) 179 | ? (vue.openBlock(), vue.createElementBlock("img", { 180 | key: 0, 181 | src: $props.options.collapseButtonImageOpen, 182 | alt: 'Menu', 183 | class: "vnb__collapse-button__image" 184 | }, null, 8 /* PROPS */, _hoisted_2$3)) 185 | : (vue.openBlock(), vue.createElementBlock("svg", { 186 | key: 1, 187 | height: "100pt", 188 | preserveAspectRatio: "xMidYMid meet", 189 | viewBox: "0 0 100 100", 190 | width: "100pt", 191 | xmlns: "http://www.w3.org/2000/svg", 192 | class: "vnb__collapse-button__image", 193 | style: vue.normalizeStyle({fill: $props.options.collapseButtonOpenColor}) 194 | }, _hoisted_5$2, 4 /* STYLE */)) 195 | ], 8 /* PROPS */, _hoisted_1$5)) 196 | : vue.createCommentVNode("v-if", true) 197 | } 198 | 199 | script$6.render = render$6; 200 | script$6.__file = "src/components/CollapseButton.vue"; 201 | 202 | var script$5 = { 203 | name: 'desktop-menu-item-button', 204 | props: { 205 | option: { 206 | type: Object, 207 | required: true, 208 | }, 209 | options: { 210 | type: Object, 211 | required: true, 212 | }, 213 | }, 214 | data: function data () { 215 | return {}; 216 | }, 217 | components: { 218 | DynamicLink: script$8, 219 | }, 220 | emits: [ 221 | 'vnb-item-clicked' ] 222 | }; 223 | 224 | var _hoisted_1$4 = ["innerHTML"]; 225 | var _hoisted_2$2 = ["innerHTML"]; 226 | 227 | function render$5(_ctx, _cache, $props, $setup, $data, $options) { 228 | var _component_dynamic_link = vue.resolveComponent("dynamic-link"); 229 | 230 | return (vue.openBlock(), vue.createBlock(_component_dynamic_link, { 231 | path: $props.option.path, 232 | isUsingVueRouter: $props.options.isUsingVueRouter, 233 | class: vue.normalizeClass(['vnb__menu-options__option__button', 'vnb-button', $props.option.class]), 234 | "aria-label": $props.option.text, 235 | isLinkAction: $props.option.isLinkAction ? true : false, 236 | onClick: _cache[0] || (_cache[0] = function ($event) { return (_ctx.$emit('vnb-item-clicked', $props.option.text)); }) 237 | }, { 238 | content: vue.withCtx(function () { return [ 239 | ($props.option.iconLeft) 240 | ? (vue.openBlock(), vue.createElementBlock("span", { 241 | key: 0, 242 | class: "vnb__menu-options__option__button__icon vnb__menu-options__option__button__icon--left", 243 | innerHTML: $props.option.iconLeft 244 | }, null, 8 /* PROPS */, _hoisted_1$4)) 245 | : vue.createCommentVNode("v-if", true), 246 | vue.createTextVNode(" " + vue.toDisplayString($props.option.text) + " ", 1 /* TEXT */), 247 | ($props.option.iconRight) 248 | ? (vue.openBlock(), vue.createElementBlock("span", { 249 | key: 1, 250 | class: "vnb__menu-options__option__button__icon vnb__menu-options__option__button__icon--right", 251 | innerHTML: $props.option.iconRight 252 | }, null, 8 /* PROPS */, _hoisted_2$2)) 253 | : vue.createCommentVNode("v-if", true) 254 | ]; }), 255 | _: 1 /* STABLE */ 256 | }, 8 /* PROPS */, ["path", "isUsingVueRouter", "class", "aria-label", "isLinkAction"])) 257 | } 258 | 259 | script$5.render = render$5; 260 | script$5.__file = "src/components/DesktopMenuItemButton.vue"; 261 | 262 | var script$4 = { 263 | name: 'desktop-menu-item-link', 264 | props: { 265 | option: { 266 | type: Object, 267 | required: true, 268 | }, 269 | options: { 270 | type: Object, 271 | required: true, 272 | }, 273 | }, 274 | data: function data () { 275 | return { 276 | currentExpandedStatus: 'closed', 277 | }; 278 | }, 279 | computed: { 280 | isExpanded: function isExpanded () { 281 | if (this.currentExpandedStatus === 'open') { 282 | return true; 283 | } 284 | 285 | return false; 286 | }, 287 | }, 288 | methods: { 289 | // Each time a sub-menu-option is clicked, close all the tooltips 290 | subMenuItemSelected: function subMenuItemSelected (text) { 291 | this.closeAllTooltips(); 292 | }, 293 | 294 | // When we keydown tab on the last sub-menu-option, we want to close 295 | // all the tooltips 296 | subMenuItemTabbed: function subMenuItemTabbed (text) { 297 | // Let's check to see if this item is the last 298 | // item in the subMenuOptions array 299 | if (this.option.subMenuOptions[this.option.subMenuOptions.length - 1].text === text) { 300 | this.closeAllTooltips(); 301 | } 302 | }, 303 | 304 | menuShown: function menuShown () { 305 | this.currentExpandedStatus = 'open'; 306 | }, 307 | menuHidden: function menuHidden () { 308 | this.currentExpandedStatus = 'closed'; 309 | }, 310 | 311 | closeAllTooltips: function closeAllTooltips () { 312 | // https://atomiks.github.io/tippyjs/v6/methods/#hideall 313 | tippy.hideAll(); 314 | }, 315 | 316 | initTippy: function initTippy () { 317 | var this$1$1 = this; 318 | 319 | var el = document.getElementById('dropdown-menu-parent-' + this.option.id); 320 | 321 | var template = document.getElementById('sub-menu-options-' + this.option.id); 322 | template.style.display = 'block'; 323 | 324 | tippy__default["default"](el, { 325 | theme: 'light', 326 | content: template, 327 | interactive: true, 328 | animation: this.options.tooltipAnimationType, 329 | role: 'Menu', 330 | // trigger: 'click', // for testing 331 | trigger: 'click mouseenter focus', 332 | appendTo: 'parent', 333 | arrow: true, 334 | inertia: false, 335 | placement: this.options.tooltipPlacement, 336 | popperOptions: { 337 | modifiers: [ 338 | { 339 | name: 'flip', 340 | options: { 341 | fallbackPlacements: [this.options.tooltipPlacement], 342 | }, 343 | } ], 344 | }, 345 | onShow: function (instance) { 346 | tippy.hideAll({exclude: instance}); 347 | 348 | // fire the menuShown function 349 | this$1$1.menuShown(); 350 | }, 351 | onHide: function () { 352 | // fire the menuHidden function 353 | this$1$1.menuHidden(); 354 | }, 355 | }); 356 | }, 357 | }, 358 | mounted: function mounted () { 359 | // Let's setup our tippy here in mounted 360 | if (this.option.subMenuOptions && this.option.subMenuOptions.length) { 361 | this.initTippy(); 362 | } 363 | }, 364 | components: { 365 | DynamicLink: script$8, 366 | }, 367 | emits: [ 368 | 'vnb-item-clicked' ] 369 | }; 370 | 371 | var _hoisted_1$3 = ["innerHTML"]; 372 | var _hoisted_2$1 = ["innerHTML"]; 373 | var _hoisted_3$1 = ["id", "aria-expanded", "aria-label"]; 374 | var _hoisted_4$1 = ["innerHTML"]; 375 | var _hoisted_5$1 = ["innerHTML"]; 376 | var _hoisted_6$1 = /*#__PURE__*/vue.createElementVNode("title", null, "Toggle Arrow", -1 /* HOISTED */); 377 | var _hoisted_7$1 = /*#__PURE__*/vue.createElementVNode("path", { 378 | d: "m12 268c-7-7-12-17-12-23 0-13 232-245 245-245 6 0 64 54 129 119 119 119 132 142 90 158-11 4-44-23-113-91-53-53-101-96-106-96-6 0-53 43-105 95s-99 95-105 95-16-5-23-12z", 379 | transform: "matrix(.1 0 0 -.1 0 28)" 380 | }, null, -1 /* HOISTED */); 381 | var _hoisted_8$1 = [ 382 | _hoisted_6$1, 383 | _hoisted_7$1 384 | ]; 385 | var _hoisted_9$1 = ["id"]; 386 | var _hoisted_10$1 = { 387 | class: "vnb__sub-menu-options__option", 388 | tabindex: "-1" 389 | }; 390 | var _hoisted_11$1 = ["innerHTML"]; 391 | var _hoisted_12$1 = { class: "vnb__sub-menu-options__option__link__text-wrapper" }; 392 | var _hoisted_13$1 = { class: "vnb__sub-menu-options__option__link__text-wrapper__text" }; 393 | var _hoisted_14$1 = { 394 | key: 0, 395 | class: "vnb__sub-menu-options__option__link__text-wrapper__sub-text" 396 | }; 397 | var _hoisted_15$1 = ["innerHTML"]; 398 | var _hoisted_16$1 = { 399 | key: 1, 400 | class: "vnb__sub-menu-options__option__hr", 401 | tabindex: "-1" 402 | }; 403 | 404 | function render$4(_ctx, _cache, $props, $setup, $data, $options) { 405 | var _component_dynamic_link = vue.resolveComponent("dynamic-link"); 406 | 407 | return (!$props.option.subMenuOptions || !$props.option.subMenuOptions.length) 408 | ? (vue.openBlock(), vue.createBlock(_component_dynamic_link, { 409 | key: 0, 410 | path: $props.option.path, 411 | isUsingVueRouter: $props.options.isUsingVueRouter, 412 | class: vue.normalizeClass(['vnb__menu-options__option__link', $props.option.class]), 413 | "aria-label": $props.option.text, 414 | tabindex: "0", 415 | isLinkAction: $props.option.isLinkAction ? true : false, 416 | onClick: _cache[0] || (_cache[0] = function ($event) { return (_ctx.$emit('vnb-item-clicked', $props.option.text)); }) 417 | }, { 418 | content: vue.withCtx(function () { return [ 419 | ($props.option.iconLeft) 420 | ? (vue.openBlock(), vue.createElementBlock("span", { 421 | key: 0, 422 | class: "vnb__menu-options__option__link__icon vnb__menu-options__option__button__icon--left", 423 | innerHTML: $props.option.iconLeft 424 | }, null, 8 /* PROPS */, _hoisted_1$3)) 425 | : vue.createCommentVNode("v-if", true), 426 | vue.createTextVNode(" " + vue.toDisplayString($props.option.text) + " ", 1 /* TEXT */), 427 | ($props.option.iconRight) 428 | ? (vue.openBlock(), vue.createElementBlock("span", { 429 | key: 1, 430 | class: "vnb__menu-options__option__link__icon vnb__menu-options__option__button__icon--right", 431 | innerHTML: $props.option.iconRight 432 | }, null, 8 /* PROPS */, _hoisted_2$1)) 433 | : vue.createCommentVNode("v-if", true) 434 | ]; }), 435 | _: 1 /* STABLE */ 436 | }, 8 /* PROPS */, ["path", "isUsingVueRouter", "class", "aria-label", "isLinkAction"])) 437 | : (vue.openBlock(), vue.createElementBlock("span", { 438 | key: 1, 439 | class: vue.normalizeClass(['vnb__menu-options__option__link', $props.option.class]), 440 | id: 'dropdown-menu-parent-' + $props.option.id, 441 | "aria-haspopup": "true", 442 | "aria-expanded": $options.isExpanded ? 'true' : 'false', 443 | "aria-label": $props.option.text, 444 | tabindex: "0" 445 | }, [ 446 | ($props.option.iconLeft) 447 | ? (vue.openBlock(), vue.createElementBlock("span", { 448 | key: 0, 449 | class: "vnb__menu-options__option__link__icon vnb__menu-options__option__button__icon--left", 450 | innerHTML: $props.option.iconLeft 451 | }, null, 8 /* PROPS */, _hoisted_4$1)) 452 | : vue.createCommentVNode("v-if", true), 453 | vue.createTextVNode(" " + vue.toDisplayString($props.option.text) + " ", 1 /* TEXT */), 454 | ($props.option.iconRight) 455 | ? (vue.openBlock(), vue.createElementBlock("span", { 456 | key: 1, 457 | class: "vnb__menu-options__option__link__icon vnb__menu-options__option__button__icon--right", 458 | innerHTML: $props.option.iconRight 459 | }, null, 8 /* PROPS */, _hoisted_5$1)) 460 | : vue.createCommentVNode("v-if", true), 461 | (vue.openBlock(), vue.createElementBlock("svg", { 462 | height: "28pt", 463 | preserveAspectRatio: "xMidYMid meet", 464 | viewBox: "0 0 49 28", 465 | width: "49pt", 466 | xmlns: "http://www.w3.org/2000/svg", 467 | style: vue.normalizeStyle({fill: $props.option.arrowColor}), 468 | class: vue.normalizeClass([ 469 | 'vnb__menu-options__option__arrow', 470 | {'vnb__menu-options__option__arrow--hover': $options.isExpanded} ]) 471 | }, _hoisted_8$1, 6 /* CLASS, STYLE */)), 472 | ($props.option.type === 'link') 473 | ? (vue.openBlock(), vue.createElementBlock("div", { 474 | key: 2, 475 | class: "vnb__sub-menu-options", 476 | id: 'sub-menu-options-' + $props.option.id 477 | }, [ 478 | vue.createElementVNode("div", _hoisted_10$1, [ 479 | (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList($props.option.subMenuOptions, function (subOption, index) { 480 | return (vue.openBlock(), vue.createElementBlock("div", null, [ 481 | (subOption.type === 'link') 482 | ? (vue.openBlock(), vue.createBlock(_component_dynamic_link, { 483 | path: subOption.path, 484 | isUsingVueRouter: $props.options.isUsingVueRouter, 485 | key: index, 486 | class: "vnb__sub-menu-options__option__link", 487 | onClick: function ($event) { 488 | $options.subMenuItemSelected(subOption.text); 489 | _ctx.$emit('vnb-item-clicked', subOption.text); 490 | }, 491 | "aria-label": subOption.text, 492 | tabindex: "0", 493 | onKeydown: vue.withKeys(function ($event) { return ($options.subMenuItemTabbed(subOption.text)); }, ["tab"]), 494 | isLinkAction: subOption.isLinkAction ? true : false 495 | }, { 496 | content: vue.withCtx(function () { return [ 497 | (subOption.iconLeft) 498 | ? (vue.openBlock(), vue.createElementBlock("span", { 499 | key: 0, 500 | class: "vnb__sub-menu-options__option__link__icon vnb__sub-menu-options__option__link__icon--left", 501 | innerHTML: subOption.iconLeft 502 | }, null, 8 /* PROPS */, _hoisted_11$1)) 503 | : vue.createCommentVNode("v-if", true), 504 | vue.createElementVNode("span", _hoisted_12$1, [ 505 | vue.createElementVNode("span", _hoisted_13$1, vue.toDisplayString(subOption.text), 1 /* TEXT */), 506 | (subOption.subText) 507 | ? (vue.openBlock(), vue.createElementBlock("span", _hoisted_14$1, vue.toDisplayString(subOption.subText), 1 /* TEXT */)) 508 | : vue.createCommentVNode("v-if", true) 509 | ]), 510 | (subOption.iconRight) 511 | ? (vue.openBlock(), vue.createElementBlock("span", { 512 | key: 1, 513 | class: "vnb__sub-menu-options__option__link__icon vnb__sub-menu-options__option__link__icon--right", 514 | innerHTML: subOption.iconRight 515 | }, null, 8 /* PROPS */, _hoisted_15$1)) 516 | : vue.createCommentVNode("v-if", true) 517 | ]; }), 518 | _: 2 /* DYNAMIC */ 519 | }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["path", "isUsingVueRouter", "onClick", "aria-label", "onKeydown", "isLinkAction"])) 520 | : (vue.openBlock(), vue.createElementBlock("hr", _hoisted_16$1)) 521 | ])) 522 | }), 256 /* UNKEYED_FRAGMENT */)) 523 | ]) 524 | ], 8 /* PROPS */, _hoisted_9$1)) 525 | : vue.createCommentVNode("v-if", true) 526 | ], 10 /* CLASS, PROPS */, _hoisted_3$1)) 527 | } 528 | 529 | script$4.render = render$4; 530 | script$4.__file = "src/components/DesktopMenuItemLink.vue"; 531 | 532 | var script$3 = { 533 | name: 'desktop-menu-item-spacer', 534 | props: { 535 | option: { 536 | type: Object, 537 | required: true 538 | } 539 | }, 540 | data: function data () { 541 | return { 542 | } 543 | } 544 | }; 545 | 546 | var _hoisted_1$2 = { class: "vnb__menu-options__option__spacer" }; 547 | 548 | function render$3(_ctx, _cache, $props, $setup, $data, $options) { 549 | return (vue.openBlock(), vue.createElementBlock("div", _hoisted_1$2)) 550 | } 551 | 552 | script$3.render = render$3; 553 | script$3.__file = "src/components/DesktopMenuItemSpacer.vue"; 554 | 555 | var script$2 = { 556 | name: 'menu-options', 557 | mixins: [vueScreenSize.VueScreenSizeMixin], 558 | props: { 559 | options: { 560 | type: Object, 561 | required: true, 562 | }, 563 | type: { 564 | type: String, 565 | required: true, 566 | }, 567 | }, 568 | data: function data () { 569 | return {}; 570 | }, 571 | methods: { 572 | vnbItemClicked: function vnbItemClicked (text) { 573 | this.$emit('vnb-item-clicked', text); 574 | }, 575 | }, 576 | components: { 577 | DesktopMenuItemLink: script$4, 578 | DesktopMenuItemButton: script$5, 579 | DesktopMenuItemSpacer: script$3, 580 | }, 581 | emits: [ 582 | 'vnb-item-clicked' ] 583 | }; 584 | 585 | function render$2(_ctx, _cache, $props, $setup, $data, $options) { 586 | var _component_desktop_menu_item_link = vue.resolveComponent("desktop-menu-item-link"); 587 | var _component_desktop_menu_item_button = vue.resolveComponent("desktop-menu-item-button"); 588 | var _component_desktop_menu_item_spacer = vue.resolveComponent("desktop-menu-item-spacer"); 589 | 590 | return (_ctx.$vssWidth > $props.options.mobileBreakpoint) 591 | ? (vue.openBlock(), vue.createElementBlock("div", { 592 | key: 0, 593 | class: vue.normalizeClass([ 594 | 'vnb__menu-options', 595 | {'vnb__menu-options--left': $props.type === 'left'}, 596 | {'vnb__menu-options--right': $props.type === 'right'} ]) 597 | }, [ 598 | (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList($props.type === 'left' 599 | ? $props.options.menuOptionsLeft 600 | : $props.options.menuOptionsRight, function (option, index) { 601 | return (vue.openBlock(), vue.createElementBlock("div", { 602 | key: index, 603 | class: "vnb__menu-options__option" 604 | }, [ 605 | (option.type === 'link') 606 | ? (vue.openBlock(), vue.createBlock(_component_desktop_menu_item_link, { 607 | key: 0, 608 | class: vue.normalizeClass(option.class), 609 | option: option, 610 | options: $props.options, 611 | onVnbItemClicked: $options.vnbItemClicked 612 | }, null, 8 /* PROPS */, ["class", "option", "options", "onVnbItemClicked"])) 613 | : (option.type === 'button') 614 | ? (vue.openBlock(), vue.createBlock(_component_desktop_menu_item_button, { 615 | key: 1, 616 | option: option, 617 | options: $props.options, 618 | onVnbItemClicked: $options.vnbItemClicked 619 | }, null, 8 /* PROPS */, ["option", "options", "onVnbItemClicked"])) 620 | : (vue.openBlock(), vue.createBlock(_component_desktop_menu_item_spacer, { 621 | key: 2, 622 | option: option 623 | }, null, 8 /* PROPS */, ["option"])) 624 | ])) 625 | }), 128 /* KEYED_FRAGMENT */)) 626 | ], 2 /* CLASS */)) 627 | : vue.createCommentVNode("v-if", true) 628 | } 629 | 630 | script$2.render = render$2; 631 | script$2.__file = "src/components/MenuOptions.vue"; 632 | 633 | var script$1 = { 634 | name: 'popup', 635 | props: { 636 | options: { 637 | type: Object, 638 | required: true, 639 | }, 640 | menuIsVisible: { 641 | type: Boolean, 642 | required: true, 643 | }, 644 | }, 645 | data: function data() { 646 | return {}; 647 | }, 648 | computed: { 649 | combinedMenuItems: function combinedMenuItems() { 650 | var combinedArray = this.options.menuOptionsLeft.concat(this.options.menuOptionsRight); 651 | return combinedArray; 652 | }, 653 | }, 654 | methods: { 655 | closeButtonClicked: function closeButtonClicked() { 656 | this.$emit('close-button-clicked'); 657 | }, 658 | itemSelected: function itemSelected(option) { 659 | this.$emit('vnb-item-clicked', option.text); 660 | this.closeButtonClicked(); 661 | }, 662 | }, 663 | components: { 664 | DynamicLink: script$8, 665 | }, 666 | emits: ['close-button-clicked', 'vnb-item-clicked'], 667 | }; 668 | 669 | var _hoisted_1$1 = { 670 | key: 0, 671 | class: "vnb__popup" 672 | }; 673 | var _hoisted_2 = { class: "vnb__popup__top" }; 674 | var _hoisted_3 = ["src", "alt"]; 675 | var _hoisted_4 = ["aria-expanded"]; 676 | var _hoisted_5 = ["src"]; 677 | var _hoisted_6 = /*#__PURE__*/vue.createElementVNode("title", null, "Close button", -1 /* HOISTED */); 678 | var _hoisted_7 = /*#__PURE__*/vue.createElementVNode("path", { 679 | d: "m42 967c-12-13-22-27-22-33 0-5 93-102 207-216l208-208-208-208c-114-114-207-214-207-223 0-8 11-26 25-39l26-24 214 214 215 215 215-215 214-214 26 24c14 13 25 28 25 34s-92 103-205 216-205 209-205 215 92 102 205 215 205 210 205 216c0 12-42 54-55 54-5 0-104-94-220-210l-210-210-210 210c-115 116-212 210-216 210-3 0-15-10-27-23z", 680 | transform: "matrix(.1 0 0 -.1 0 100)" 681 | }, null, -1 /* HOISTED */); 682 | var _hoisted_8 = [ 683 | _hoisted_6, 684 | _hoisted_7 685 | ]; 686 | var _hoisted_9 = { class: "vnb__popup__bottom" }; 687 | var _hoisted_10 = { 688 | key: 0, 689 | class: "vnb__popup__bottom__custom-section" 690 | }; 691 | var _hoisted_11 = { class: "vnb__popup__bottom__menu-options" }; 692 | var _hoisted_12 = ["innerHTML"]; 693 | var _hoisted_13 = ["innerHTML"]; 694 | var _hoisted_14 = { 695 | key: 1, 696 | class: "vnb__popup__bottom__menu-options__option__link vnb__popup__bottom__menu-options__option__link--no-highlight" 697 | }; 698 | var _hoisted_15 = { class: "vnb__popup__bottom__sub-menu-options" }; 699 | var _hoisted_16 = { class: "vnb__popup__bottom__sub-menu-options__option__link__sub-text" }; 700 | 701 | function render$1(_ctx, _cache, $props, $setup, $data, $options) { 702 | var _component_dynamic_link = vue.resolveComponent("dynamic-link"); 703 | 704 | return ($props.menuIsVisible) 705 | ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_1$1, [ 706 | vue.createElementVNode("div", _hoisted_2, [ 707 | ($props.options.showBrandImageInMobilePopup && $props.options.brandImage) 708 | ? (vue.openBlock(), vue.createElementBlock("img", { 709 | key: 0, 710 | src: $props.options.brandImage, 711 | alt: $props.options.brandImageAltText, 712 | class: "vnb-image vnb__popup__top__image" 713 | }, null, 8 /* PROPS */, _hoisted_3)) 714 | : vue.createCommentVNode("v-if", true), 715 | vue.createElementVNode("button", { 716 | class: "vnb__popup__top__close-button", 717 | onClick: _cache[0] || (_cache[0] = function () { 718 | var args = [], len = arguments.length; 719 | while ( len-- ) args[ len ] = arguments[ len ]; 720 | 721 | return ($options.closeButtonClicked && $options.closeButtonClicked.apply($options, args)); 722 | }), 723 | "aria-label": "Close Button", 724 | title: "Close", 725 | "aria-expanded": $props.menuIsVisible ? 'true' : 'false' 726 | }, [ 727 | ($props.options.collapseButtonImageClose) 728 | ? (vue.openBlock(), vue.createElementBlock("img", { 729 | key: 0, 730 | src: $props.options.collapseButtonImageClose, 731 | alt: 'Close button', 732 | class: "vnb__popup__top__close-button__image" 733 | }, null, 8 /* PROPS */, _hoisted_5)) 734 | : (vue.openBlock(), vue.createElementBlock("svg", { 735 | key: 1, 736 | height: "100pt", 737 | preserveAspectRatio: "xMidYMid meet", 738 | viewBox: "0 0 100 100", 739 | width: "100pt", 740 | xmlns: "http://www.w3.org/2000/svg", 741 | class: "vnb__popup__top__close-button__image", 742 | style: vue.normalizeStyle({ fill: $props.options.collapseButtonCloseColor }) 743 | }, _hoisted_8, 4 /* STYLE */)) 744 | ], 8 /* PROPS */, _hoisted_4) 745 | ]), 746 | vue.createElementVNode("div", _hoisted_9, [ 747 | (!!this.$slots['custom-section']) 748 | ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_10, [ 749 | vue.renderSlot(_ctx.$slots, "custom-section") 750 | ])) 751 | : vue.createCommentVNode("v-if", true), 752 | vue.createElementVNode("ul", _hoisted_11, [ 753 | (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList($options.combinedMenuItems, function (option, index) { 754 | return (vue.openBlock(), vue.createElementBlock("li", { 755 | key: index, 756 | class: "vnb__popup__bottom__menu-options__option" 757 | }, [ 758 | (!option.subMenuOptions) 759 | ? (vue.openBlock(), vue.createBlock(_component_dynamic_link, { 760 | key: 0, 761 | path: option.path, 762 | isUsingVueRouter: $props.options.isUsingVueRouter, 763 | class: vue.normalizeClass(['vnb__popup__bottom__menu-options__option__link', option.class]), 764 | onClick: function ($event) { return ($options.itemSelected(option)); }, 765 | "aria-label": option.text, 766 | isLinkAction: option.isLinkAction ? true : false 767 | }, { 768 | content: vue.withCtx(function () { return [ 769 | (option.iconLeft) 770 | ? (vue.openBlock(), vue.createElementBlock("span", { 771 | key: 0, 772 | class: "vnb__popup__bottom__menu-options__option__link__icon vnb__popup__bottom__menu-options__option__link__icon--left", 773 | innerHTML: option.iconLeft 774 | }, null, 8 /* PROPS */, _hoisted_12)) 775 | : vue.createCommentVNode("v-if", true), 776 | vue.createTextVNode(" " + vue.toDisplayString(option.text) + " ", 1 /* TEXT */), 777 | (option.iconRight) 778 | ? (vue.openBlock(), vue.createElementBlock("span", { 779 | key: 1, 780 | class: "vnb__popup__bottom__menu-options__option__link__icon vnb__popup__bottom__menu-options__option__link__icon--right", 781 | innerHTML: option.iconRight 782 | }, null, 8 /* PROPS */, _hoisted_13)) 783 | : vue.createCommentVNode("v-if", true) 784 | ]; }), 785 | _: 2 /* DYNAMIC */ 786 | }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["path", "isUsingVueRouter", "class", "onClick", "aria-label", "isLinkAction"])) 787 | : (vue.openBlock(), vue.createElementBlock("span", _hoisted_14, vue.toDisplayString(option.text), 1 /* TEXT */)), 788 | vue.createElementVNode("div", _hoisted_15, [ 789 | (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(option.subMenuOptions, function (subOption, index) { 790 | return (vue.openBlock(), vue.createElementBlock("div", { 791 | key: index, 792 | class: "vnb__popup__bottom__sub-menu-options__option" 793 | }, [ 794 | (subOption.type === 'link') 795 | ? (vue.openBlock(), vue.createBlock(_component_dynamic_link, { 796 | key: 0, 797 | path: subOption.path, 798 | isUsingVueRouter: $props.options.isUsingVueRouter, 799 | class: "vnb__popup__bottom__sub-menu-options__option__link", 800 | onClick: function ($event) { return ($options.itemSelected(subOption)); }, 801 | "aria-label": subOption.text, 802 | isLinkAction: subOption.isLinkAction ? true : false 803 | }, { 804 | content: vue.withCtx(function () { return [ 805 | vue.createTextVNode(vue.toDisplayString(subOption.text) + " ", 1 /* TEXT */), 806 | vue.createElementVNode("span", _hoisted_16, vue.toDisplayString(subOption.subText), 1 /* TEXT */) 807 | ]; }), 808 | _: 2 /* DYNAMIC */ 809 | }, 1032 /* PROPS, DYNAMIC_SLOTS */, ["path", "isUsingVueRouter", "onClick", "aria-label", "isLinkAction"])) 810 | : vue.createCommentVNode("v-if", true) 811 | ])) 812 | }), 128 /* KEYED_FRAGMENT */)) 813 | ]) 814 | ])) 815 | }), 128 /* KEYED_FRAGMENT */)) 816 | ]) 817 | ]) 818 | ])) 819 | : vue.createCommentVNode("v-if", true) 820 | } 821 | 822 | script$1.render = render$1; 823 | script$1.__file = "src/components/Popup.vue"; 824 | 825 | var script = { 826 | name: 'vue-navigation-bar', 827 | mixins: [vueScreenSize.VueScreenSizeMixin], 828 | props: { 829 | options: { 830 | type: Object, 831 | required: true, 832 | }, 833 | }, 834 | data: function data () { 835 | return { 836 | menuIsVisible: false, 837 | }; 838 | }, 839 | computed: { 840 | finalOptions: function finalOptions () { 841 | // What we're doing here is giving each top-level menu-option a unique id 842 | if (this.options.menuOptionsLeft) { 843 | for (var x = 0; x < this.options.menuOptionsLeft.length; x++) { 844 | this.options.menuOptionsLeft[x].id = uuidV4(); 845 | } 846 | } 847 | if (this.options.menuOptionsRight) { 848 | for (var x$1 = 0; x$1 < this.options.menuOptionsRight.length; x$1++) { 849 | this.options.menuOptionsRight[x$1].id = uuidV4(); 850 | } 851 | } 852 | 853 | return { 854 | elementId: this.options.elementId ? this.options.elementId : uuidV4(), 855 | isUsingVueRouter: this.options.isUsingVueRouter ? true : false, 856 | mobileBreakpoint: this.options.mobileBreakpoint ? this.options.mobileBreakpoint : 992, 857 | brandImagePath: this.options.brandImagePath ? this.options.brandImagePath : '/', 858 | brandImage: this.options.brandImage ? this.options.brandImage : null, 859 | brandImageAltText: this.options.brandImageAltText 860 | ? this.options.brandImageAltText 861 | : 'brand-image', 862 | collapseButtonImageOpen: this.options.collapseButtonImageOpen 863 | ? this.options.collapseButtonImageOpen 864 | : null, 865 | collapseButtonImageClose: this.options.collapseButtonImageClose 866 | ? this.options.collapseButtonImageClose 867 | : null, 868 | collapseButtonOpenColor: this.options.collapseButtonOpenColor 869 | ? this.options.collapseButtonOpenColor 870 | : '#373737', 871 | collapseButtonCloseColor: this.options.collapseButtonCloseColor 872 | ? this.options.collapseButtonCloseColor 873 | : '#373737', 874 | showBrandImageInMobilePopup: this.options.showBrandImageInMobilePopup ? true : false, 875 | ariaLabelMainNav: this.options.ariaLabelMainNav 876 | ? this.options.ariaLabelMainNav 877 | : 'Main Navigation', 878 | tooltipAnimationType: this.options.tooltipAnimationType 879 | ? this.options.tooltipAnimationType 880 | : 'shift-away', 881 | tooltipPlacement: this.options.tooltipPlacement || 'bottom', 882 | menuOptionsLeft: this.options.menuOptionsLeft ? this.options.menuOptionsLeft : [], 883 | menuOptionsRight: this.options.menuOptionsRight ? this.options.menuOptionsRight : [], 884 | }; 885 | }, 886 | }, 887 | methods: { 888 | closeMobilePopup: function closeMobilePopup () { 889 | this.menuIsVisible = false; 890 | this.$emit('vnb-mobile-popup-hidden'); 891 | }, 892 | showMobilePopup: function showMobilePopup () { 893 | this.menuIsVisible = true; 894 | this.$emit('vnb-mobile-popup-shown'); 895 | }, 896 | vnbItemClicked: function vnbItemClicked (text) { 897 | this.$emit('vnb-item-clicked', text); 898 | }, 899 | }, 900 | components: { 901 | BrandImage: script$7, 902 | MenuOptions: script$2, 903 | CollapseButton: script$6, 904 | Popup: script$1, 905 | }, 906 | emits: [ 907 | 'vnb-mobile-popup-hidden', 908 | 'vnb-mobile-popup-shown', 909 | 'vnb-item-clicked' ] 910 | }; 911 | 912 | var _hoisted_1 = ["id", "aria-label"]; 913 | 914 | function render(_ctx, _cache, $props, $setup, $data, $options) { 915 | var _component_brand_image = vue.resolveComponent("brand-image"); 916 | var _component_menu_options = vue.resolveComponent("menu-options"); 917 | var _component_collapse_button = vue.resolveComponent("collapse-button"); 918 | var _component_popup = vue.resolveComponent("popup"); 919 | 920 | return (vue.openBlock(), vue.createElementBlock("nav", { 921 | class: "vnb", 922 | id: $options.finalOptions.elementId, 923 | "aria-label": $options.finalOptions.ariaLabelMainNav 924 | }, [ 925 | vue.createVNode(_component_brand_image, { 926 | options: $options.finalOptions, 927 | onVnbItemClicked: $options.vnbItemClicked 928 | }, null, 8 /* PROPS */, ["options", "onVnbItemClicked"]), 929 | vue.createVNode(_component_menu_options, { 930 | options: $options.finalOptions, 931 | type: 'left', 932 | onVnbItemClicked: $options.vnbItemClicked 933 | }, null, 8 /* PROPS */, ["options", "onVnbItemClicked"]), 934 | (_ctx.$vssWidth > $props.options.mobileBreakpoint) 935 | ? vue.renderSlot(_ctx.$slots, "custom-section", { key: 0 }) 936 | : vue.createCommentVNode("v-if", true), 937 | vue.createVNode(_component_menu_options, { 938 | options: $options.finalOptions, 939 | type: 'right', 940 | onVnbItemClicked: $options.vnbItemClicked 941 | }, null, 8 /* PROPS */, ["options", "onVnbItemClicked"]), 942 | ($options.finalOptions.menuOptionsLeft.length || $options.finalOptions.menuOptionsRight.length) 943 | ? (vue.openBlock(), vue.createBlock(_component_collapse_button, { 944 | key: 1, 945 | options: $options.finalOptions, 946 | menuIsVisible: $data.menuIsVisible, 947 | onCollapseButtonClicked: $options.showMobilePopup 948 | }, null, 8 /* PROPS */, ["options", "menuIsVisible", "onCollapseButtonClicked"])) 949 | : vue.createCommentVNode("v-if", true), 950 | ($options.finalOptions.menuOptionsLeft.length || $options.finalOptions.menuOptionsRight.length) 951 | ? (vue.openBlock(), vue.createBlock(_component_popup, { 952 | key: 2, 953 | options: $options.finalOptions, 954 | menuIsVisible: $data.menuIsVisible, 955 | onCloseButtonClicked: $options.closeMobilePopup, 956 | onVnbItemClicked: $options.vnbItemClicked 957 | }, { 958 | "custom-section": vue.withCtx(function () { return [ 959 | vue.renderSlot(_ctx.$slots, "custom-section") 960 | ]; }), 961 | _: 3 /* FORWARDED */ 962 | }, 8 /* PROPS */, ["options", "menuIsVisible", "onCloseButtonClicked", "onVnbItemClicked"])) 963 | : vue.createCommentVNode("v-if", true) 964 | ], 8 /* PROPS */, _hoisted_1)) 965 | } 966 | 967 | script.render = render; 968 | script.__file = "src/vue-navigation-bar.vue"; 969 | 970 | // Import vue component 971 | 972 | // install function executed by Vue.use() 973 | function install(app) { 974 | if (install.installed) { return; } 975 | 976 | install.installed = true; 977 | app.component('VueNavigationBar', script); 978 | } 979 | 980 | var plugin = { install: install }; 981 | 982 | // To auto-install when vue is found 983 | var GlobalVue = null; 984 | if (typeof window !== 'undefined') { 985 | GlobalVue = window.Vue; 986 | } else if (typeof global !== 'undefined') { 987 | GlobalVue = global.Vue; 988 | } 989 | if (GlobalVue) { 990 | GlobalVue.use(plugin); 991 | } 992 | 993 | // It's possible to expose named exports when writing components that can 994 | // also be used as directives, etc. - eg. import { RollupDemoDirective } from 'rollup-demo'; 995 | // export const RollupDemoDirective = component; 996 | 997 | exports["default"] = script; 998 | exports.install = install; 999 | 1000 | Object.defineProperty(exports, '__esModule', { value: true }); 1001 | 1002 | })); 1003 | -------------------------------------------------------------------------------- /docs/assets/index.2f3ff1f9.css: -------------------------------------------------------------------------------- 1 | @import"https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css";@import"https://fonts.googleapis.com/css?family=Montserrat:400,500,700";html{width:100%;font-size:18px;color:#333}body{margin:0;height:100%;height:100vh;width:100%;font-family:Montserrat,sans-serif}#app{height:100%;height:100vh;width:100%;line-height:1.5}.code-text{background:#fff;border:1px solid #ddd;padding:10px 20px;border-radius:4px;margin-bottom:20px;text-align:center}@media (min-width: 992px){.code-text{margin-bottom:0}}.btn{text-transform:uppercase;font-weight:700}.github-corner:hover .octo-arm{animation:octocat-wave .56s ease-in-out}@keyframes octocat-wave{0%,to{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width: 500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave .56s ease-in-out}}.main-content-section{padding:45px 0;background:#eee;min-height:100%}.main-navbar-section{background:#fff}.vnb{font-family:Montserrat,sans-serif}@media (min-width: 992px){.vnb .button-red{background:#ff3b30}.vnb .button-red:hover{background:#fc0d00}}.vnb__menu-options__option__button__icon svg{margin-top:-3px}.vnb__popup__bottom__menu-options__option__link__icon svg{margin-top:-4px}.vnb .custom-section-content{width:100%}@media (min-width: 568px){.vnb .custom-section-content{width:50%}}@media (min-width: 992px){.vnb .custom-section-content{width:15%}}@media (min-width: 1200px){.vnb .custom-section-content{width:20%}}.vnb__brand-image-wrapper{padding-left:10px}.vnb__brand-image-wrapper__link__image{max-height:30px}.vnb__collapse-button{cursor:pointer;border:0;background:transparent;margin-right:10px}.vnb__collapse-button:hover{opacity:.75}.vnb__collapse-button__image{max-height:30px;max-width:30px}.vnb__menu-options__option__button{display:flex;flex-direction:row;justify-content:center;align-items:center;font-weight:400;color:#fff;text-align:center;vertical-align:middle;user-select:none;font-size:.9rem;line-height:1;border-radius:.25rem;text-transform:uppercase;letter-spacing:1px;padding:.5rem 1rem;transition:background .2s ease-in}.vnb__menu-options__option__button__icon svg{max-height:16px;max-width:16px}.vnb__menu-options__option__button__icon--left{margin-right:5px}.vnb__menu-options__option__button__icon--right{margin-left:5px}.vnb-button{background:#007aff}.vnb-button:hover{color:#fff;background:#0062cc;text-decoration:none}.tippy-box[data-animation=perspective][data-placement^=top]{transform-origin:bottom}.tippy-box[data-animation=perspective][data-placement^=top][data-state=visible]{transform:perspective(700px)}.tippy-box[data-animation=perspective][data-placement^=top][data-state=hidden]{transform:perspective(700px) translateY(8px) rotateX(60deg)}.tippy-box[data-animation=perspective][data-placement^=bottom]{transform-origin:top}.tippy-box[data-animation=perspective][data-placement^=bottom][data-state=visible]{transform:perspective(700px)}.tippy-box[data-animation=perspective][data-placement^=bottom][data-state=hidden]{transform:perspective(700px) translateY(-8px) rotateX(-60deg)}.tippy-box[data-animation=perspective][data-placement^=left]{transform-origin:right}.tippy-box[data-animation=perspective][data-placement^=left][data-state=visible]{transform:perspective(700px)}.tippy-box[data-animation=perspective][data-placement^=left][data-state=hidden]{transform:perspective(700px) translate(8px) rotateY(-60deg)}.tippy-box[data-animation=perspective][data-placement^=right]{transform-origin:left}.tippy-box[data-animation=perspective][data-placement^=right][data-state=visible]{transform:perspective(700px)}.tippy-box[data-animation=perspective][data-placement^=right][data-state=hidden]{transform:perspective(700px) translate(-8px) rotateY(60deg)}.tippy-box[data-animation=perspective][data-state=hidden]{opacity:0}.tippy-box[data-animation=scale][data-placement^=top]{transform-origin:bottom}.tippy-box[data-animation=scale][data-placement^=bottom]{transform-origin:top}.tippy-box[data-animation=scale][data-placement^=left]{transform-origin:right}.tippy-box[data-animation=scale][data-placement^=right]{transform-origin:left}.tippy-box[data-animation=scale][data-state=hidden]{transform:scale(.5);opacity:0}.tippy-box[data-animation=shift-away][data-state=hidden]{opacity:0}.tippy-box[data-animation=shift-away][data-state=hidden][data-placement^=top]{transform:translateY(10px)}.tippy-box[data-animation=shift-away][data-state=hidden][data-placement^=bottom]{transform:translateY(-10px)}.tippy-box[data-animation=shift-away][data-state=hidden][data-placement^=left]{transform:translate(10px)}.tippy-box[data-animation=shift-away][data-state=hidden][data-placement^=right]{transform:translate(-10px)}.tippy-box[data-animation=shift-toward][data-state=hidden]{opacity:0}.tippy-box[data-animation=shift-toward][data-state=hidden][data-placement^=top]{transform:translateY(-10px)}.tippy-box[data-animation=shift-toward][data-state=hidden][data-placement^=bottom]{transform:translateY(10px)}.tippy-box[data-animation=shift-toward][data-state=hidden][data-placement^=left]{transform:translate(-10px)}.tippy-box[data-animation=shift-toward][data-state=hidden][data-placement^=right]{transform:translate(10px)}.tippy-box[data-theme~=light]{color:#26323d;box-shadow:0 0 20px 4px #9aa1b126,0 4px 80px -8px #24282f40,0 4px 4px -2px #5b5e6926;background-color:#fff}.tippy-box[data-theme~=light][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=light][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff}.tippy-box[data-theme~=light][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=light][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff}.tippy-box[data-theme~=light]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=light]>.tippy-svg-arrow{fill:#fff}.vnb__menu-options__option__link{cursor:pointer;font-weight:500;color:#595959;transition:color .2s ease-in;display:flex;flex-direction:row;align-items:center;justify-content:center;font-size:.9rem}.vnb__menu-options__option__link:hover{color:#333;text-decoration:none}.vnb__menu-options__option__link__icon svg{max-height:20px}.vnb__menu-options__option__link__icon--left{margin-right:5px}.vnb__menu-options__option__link__icon--right{margin-left:5px}.vnb__menu-options__option__arrow{max-height:5px;max-width:25px;transition:transform .2s ease-in-out}.vnb__menu-options__option__arrow--hover{transform:rotate(180deg)}.vnb__sub-menu-options{background:#fff;max-width:500px;display:flex;flex-direction:column;justify-content:center;align-items:center;border-radius:4px;padding:10px 0}.vnb__sub-menu-options__option{min-width:250px;max-width:300px}.vnb__sub-menu-options__option__link{padding:12px;width:100%;display:flex;flex-direction:row;justify-content:center;align-items:center;color:#595959;transition:color .2s ease-in,background .2s ease-in,border .2s ease-in;border-left:2px solid #fff}.vnb__sub-menu-options__option__link:hover{color:#333;text-decoration:none;background:#f3f3f3;border-left:2px solid #007aff;cursor:pointer}.vnb__sub-menu-options__option__link__icon svg{max-height:40px}.vnb__sub-menu-options__option__link__icon--left{margin-right:15px}.vnb__sub-menu-options__option__link__icon--right{margin-left:15px}.vnb__sub-menu-options__option__link__text-wrapper{width:100%}.vnb__sub-menu-options__option__link__text-wrapper__text{display:block;text-align:left}.vnb__sub-menu-options__option__link__text-wrapper__sub-text{margin-top:5px;display:block;font-size:.8rem;text-align:left;color:#8c8c8c}.vnb__sub-menu-options__option__hr{margin-top:10px;margin-bottom:10px;border-color:#0000001a}.vnb__menu-options__option__spacer{width:30px}.vnb__menu-options{display:flex;flex-direction:row;align-items:center}.vnb__menu-options--left{margin-right:auto;justify-content:flex-start;padding-left:30px}.vnb__menu-options--right{margin-left:auto;justify-content:flex-end;padding-right:10px}.vnb__menu-options__option:not(:last-child){margin-right:20px}.vnb__popup{background:#fff;position:absolute;left:10px;top:10px;right:10px;display:flex;flex-direction:column;perspective:2000px;box-shadow:0 10px 30px #0000001a;margin-bottom:20px;z-index:100000}.vnb__popup__top{padding:15px 24px 0;border-top:1px solid #e0e0e0;border-left:1px solid #e0e0e0;border-right:1px solid #e0e0e0;border-top-right-radius:6px;border-top-left-radius:6px}.vnb__popup__top__image{max-height:27px;margin-bottom:5px}.vnb__popup__top__close-button{position:absolute;top:10px;right:10px;cursor:pointer;border:0;background:transparent}.vnb__popup__top__close-button:hover{opacity:.75}.vnb__popup__top__close-button__image{max-height:15px}.vnb__popup__top__close-button svg{width:auto}.vnb__popup__bottom{background:#fff;padding:10px 0;border-left:1px solid #e0e0e0;border-right:1px solid #e0e0e0;border-bottom:1px solid #e0e0e0;border-bottom-right-radius:6px;border-bottom-left-radius:6px}.vnb__popup__bottom__custom-section{padding:12px 24px}.vnb__popup__bottom__menu-options{list-style-type:none;padding-left:0;display:flex;flex-direction:column}.vnb__popup__bottom__menu-options__option:not(:last-child){margin-bottom:10px}.vnb__popup__bottom__menu-options__option__link{padding:12px 24px;color:#595959;font-weight:500;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;transition:color .2s ease-in,background .2s ease-in,border .2s ease-in;border-left:2px solid #fff;width:100%}.vnb__popup__bottom__menu-options__option__link:hover{color:#333;text-decoration:none;background:#f3f3f3;border-left:2px solid #007aff}.vnb__popup__bottom__menu-options__option__link--no-highlight{padding:12px 24px 6px;font-weight:400;font-size:.8rem;color:#757575}.vnb__popup__bottom__menu-options__option__link--no-highlight:hover{color:#757575;background:#fff;border-left:2px solid #fff}.vnb__popup__bottom__menu-options__option__link__icon svg{max-height:16px;max-width:16px}.vnb__popup__bottom__menu-options__option__link__icon--left{margin-right:5px}.vnb__popup__bottom__menu-options__option__link__icon--right{margin-left:5px}.vnb__popup__bottom__sub-menu-options{display:flex;flex-direction:row;flex-wrap:wrap;width:100%;font-size:.9rem}.vnb__popup__bottom__sub-menu-options__option{width:100%}.vnb__popup__bottom__sub-menu-options__option__link{padding:6px 24px;width:100%;display:block;color:#595959;font-weight:500;transition:color .2s ease-in,background .2s ease-in,border .2s ease-in;border-left:2px solid #fff}.vnb__popup__bottom__sub-menu-options__option__link:hover{color:#333;text-decoration:none;background:#eee;border-left:2px solid #333}.vnb__popup__bottom__sub-menu-options__option__link__sub-text{margin-top:5px;display:block;font-size:.75rem;color:#8c8c8c}.vnb{background:#fff;padding-top:15px;padding-bottom:15px;display:flex;flex-direction:row;align-items:center;justify-content:space-between}.vnb *{box-sizing:border-box}.vnb a{text-decoration:none}.tippy-tooltip{padding:0}.vnb-image{max-width:100%;height:auto} 2 | -------------------------------------------------------------------------------- /docs/assets/lockup-color.5dabaa34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndatserakis/vue-navigation-bar/02b44b9120b6ec6ce04448dd74818f416e6ef2fc/docs/assets/lockup-color.5dabaa34.png -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | vue-navigation-bar | John Datserakis 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /example/App.vue: -------------------------------------------------------------------------------- 1 | 100 | 101 | 210 | 211 | 354 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | vue-navigation-bar | John Datserakis 14 | 15 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | import VueNavigationBar from '../src/index.js'; 4 | import { createRouter, createWebHistory } from 'vue-router'; 5 | 6 | const app = createApp(App); 7 | 8 | const StubbedRoute = { template: '
' }; 9 | 10 | const router = createRouter({ 11 | history: createWebHistory(), 12 | routes: [ 13 | { path: '/', name: 'home', component: StubbedRoute }, 14 | { path: '/about', name: 'about', component: StubbedRoute }, 15 | { path: '/locations', name: 'locations', component: StubbedRoute }, 16 | { path: '/blog', name: 'blog', component: StubbedRoute }, 17 | { path: '/pricing', name: 'pricing', component: StubbedRoute }, 18 | { path: '/pricing/pro', name: 'pricing-pro', component: StubbedRoute }, 19 | { path: '/pricing/starter', name: 'pricing-starter', component: StubbedRoute }, 20 | { path: '/contact', name: 'contact', component: StubbedRoute }, 21 | { path: '/customer-service', name: 'customer-service', component: StubbedRoute }, 22 | { path: '/accounting', name: 'accounting', component: StubbedRoute }, 23 | { path: '/reception', name: 'reception', component: StubbedRoute }, 24 | { path: '/signup', name: 'signup', component: StubbedRoute }, 25 | { path: '/login', name: 'login', component: StubbedRoute }, 26 | ], 27 | }); 28 | 29 | app.use(router); 30 | 31 | app.component('vue-navigation-bar', VueNavigationBar); 32 | 33 | app.mount('#app'); 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "John Datserakis ", 3 | "bugs": { 4 | "url": "https://github.com/johndatserakis/vue-navigation-bar/issues" 5 | }, 6 | "dependencies": { 7 | "tippy.js": "^6.2.4", 8 | "vue-screen-size": "^2.0.0" 9 | }, 10 | "description": "A simple, pretty navbar for your Vue projects.", 11 | "devDependencies": { 12 | "@babel/cli": "^7.10.4", 13 | "@babel/core": "^7.10.4", 14 | "@babel/plugin-transform-runtime": "^7.10.4", 15 | "@babel/preset-env": "^7.10.4", 16 | "@babel/runtime": "^7.10.4", 17 | "@rollup/plugin-buble": "^0.21.3", 18 | "@rollup/plugin-commonjs": "^13.0.0", 19 | "@rollup/plugin-node-resolve": "^8.1.0", 20 | "@rollup/plugin-url": "^5.0.1", 21 | "@vitejs/plugin-vue": "^2.3.3", 22 | "@vue/compiler-sfc": "^3.2.31", 23 | "@vue/test-utils": "^2.0.0-rc.18", 24 | "babel-core": "7.0.0-bridge.0", 25 | "babel-jest": "^26.1.0", 26 | "jest": "^26.1.0", 27 | "jest-serializer-vue": "^2.0.2", 28 | "rollup": "^2.19.0", 29 | "rollup-plugin-scss": "^3.0.0", 30 | "rollup-plugin-terser": "^6.1.0", 31 | "rollup-plugin-vue": "^6.0.0", 32 | "sass": "^1.49.9", 33 | "vite": "^2.9.9", 34 | "vue": "^3.2.31", 35 | "vue-jest": "^5.0.0-alpha.10", 36 | "vue-router": "^4.0.12" 37 | }, 38 | "jest": { 39 | "moduleFileExtensions": [ 40 | "js", 41 | "vue" 42 | ], 43 | "moduleNameMapper": { 44 | "\\.(css|less)$": "/test/styleMock.js", 45 | "^@/(.*)$": "/src/$1" 46 | }, 47 | "snapshotSerializers": [ 48 | "/node_modules/jest-serializer-vue" 49 | ], 50 | "transform": { 51 | ".*\\.(vue)$": "/node_modules/vue-jest", 52 | "^.+\\.js$": "/node_modules/babel-jest" 53 | }, 54 | "transformIgnorePatterns": [ 55 | "/node_modules/(?!tippy.js/themes/light.css)" 56 | ] 57 | }, 58 | "keywords": [ 59 | "vue", 60 | "nav", 61 | "navbar", 62 | "navigation", 63 | "bar", 64 | "menu" 65 | ], 66 | "license": "MIT", 67 | "main": "dist/vue-navigation-bar.umd.js", 68 | "module": "dist/vue-navigation-bar.esm.js", 69 | "name": "vue-navigation-bar", 70 | "peerDependencies": { 71 | "vue": "^3.2.31" 72 | }, 73 | "repository": { 74 | "type": "git", 75 | "url": "git+https://github.com/johndatserakis/vue-navigation-bar.git" 76 | }, 77 | "scripts": { 78 | "build": "npm run test && npm run build:example && npm run build:library", 79 | "build:es": "rollup --config rollup.config.js --format es --file dist/vue-navigation-bar.esm.js", 80 | "build:example": "rm -rf ./docs && vite build", 81 | "build:library": "rm -rf ./dist && npm run build:unpkg & npm run build:es & npm run build:umd & npm run build:unpkg", 82 | "build:umd": "rollup --config rollup.config.js --format umd --file dist/vue-navigation-bar.umd.js", 83 | "build:unpkg": "rollup --config rollup.config.js --format iife --file dist/vue-navigation-bar.min.js", 84 | "dev": "vite", 85 | "test": "jest" 86 | }, 87 | "sideEffects": false, 88 | "unpkg": "dist/vue-navigation-bar.min.js", 89 | "version": "6.1.0" 90 | } 91 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import buble from '@rollup/plugin-buble'; 2 | import commonjs from '@rollup/plugin-commonjs'; 3 | import resolve from '@rollup/plugin-node-resolve'; 4 | import url from '@rollup/plugin-url'; 5 | import minimist from 'minimist'; 6 | import scss from 'rollup-plugin-scss'; 7 | import { terser } from 'rollup-plugin-terser'; 8 | import vue from 'rollup-plugin-vue'; 9 | 10 | const argv = minimist(process.argv.slice(2)); 11 | 12 | const config = { 13 | input: 'src/index.js', 14 | output: { 15 | name: 'VueNavigationBar', 16 | exports: 'named', 17 | globals: { 18 | vue: 'Vue', 19 | 'vue-screen-size': 'VueScreenSize', 20 | 'tippy.js': 'tippy', 21 | }, 22 | }, 23 | plugins: [ 24 | vue({ 25 | css: false, 26 | compileTemplate: true, 27 | }), 28 | scss({ output: 'dist/vue-navigation-bar.css' }), 29 | resolve({ 30 | jsnext: true, 31 | main: true, 32 | }), 33 | commonjs(), 34 | buble(), 35 | url(), 36 | ], 37 | external: ['vue', 'vue-screen-size', 'tippy.js'], 38 | }; 39 | 40 | // Only minify browser (iife) version 41 | if (argv.format === 'iife') { 42 | config.plugins.push(terser()); 43 | 44 | // Here we remove our `external` dependency that we have in this project 45 | // Be careful with the index here - it has to match any dependency that 46 | // you want to be built into to the iife output 47 | config.external.splice(1); 48 | config.external.splice(1); 49 | } 50 | 51 | export default config; 52 | -------------------------------------------------------------------------------- /src/assets/css/main.scss: -------------------------------------------------------------------------------- 1 | // Colors 2 | $white: #fff; 3 | $black: #333; 4 | $grey: #eee; 5 | $blue: #007aff; 6 | $orange: #ff9500; 7 | $red: #ff3b30; 8 | $focus-outline-blue: #6098ee; 9 | 10 | // Settings 11 | $focus-border: 1px solid $focus-outline-blue; 12 | $backgroundColor: $grey; 13 | $box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.1); 14 | $border-radius: 4px; 15 | -------------------------------------------------------------------------------- /src/assets/images/badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndatserakis/vue-navigation-bar/02b44b9120b6ec6ce04448dd74818f416e6ef2fc/src/assets/images/badge.png -------------------------------------------------------------------------------- /src/assets/images/chevron-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/collapse-menu-dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/collapse-menu-light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndatserakis/vue-navigation-bar/02b44b9120b6ec6ce04448dd74818f416e6ef2fc/src/assets/images/favicon.png -------------------------------------------------------------------------------- /src/assets/images/lockup-color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndatserakis/vue-navigation-bar/02b44b9120b6ec6ce04448dd74818f416e6ef2fc/src/assets/images/lockup-color.png -------------------------------------------------------------------------------- /src/assets/images/social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johndatserakis/vue-navigation-bar/02b44b9120b6ec6ce04448dd74818f416e6ef2fc/src/assets/images/social.png -------------------------------------------------------------------------------- /src/assets/images/times.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/common/uuidV4.js: -------------------------------------------------------------------------------- 1 | // https://stackoverflow.com/a/2117523/8014660 2 | function uuidV4() { 3 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 4 | var r = (Math.random() * 16) | 0, 5 | v = c == 'x' ? r : (r & 0x3) | 0x8; 6 | return v.toString(16); 7 | }); 8 | } 9 | 10 | export default uuidV4; 11 | -------------------------------------------------------------------------------- /src/components/BrandImage.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 45 | 46 | 66 | -------------------------------------------------------------------------------- /src/components/CollapseButton.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 64 | 65 | 86 | -------------------------------------------------------------------------------- /src/components/DesktopMenuItemButton.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 52 | 53 | 107 | -------------------------------------------------------------------------------- /src/components/DesktopMenuItemLink.vue: -------------------------------------------------------------------------------- 1 | 122 | 123 | 241 | 242 | 366 | -------------------------------------------------------------------------------- /src/components/DesktopMenuItemSpacer.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 20 | 21 | 32 | -------------------------------------------------------------------------------- /src/components/DynamicLink.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 39 | -------------------------------------------------------------------------------- /src/components/MenuOptions.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 74 | 75 | 104 | -------------------------------------------------------------------------------- /src/components/Popup.vue: -------------------------------------------------------------------------------- 1 | 113 | 114 | 153 | 154 | 324 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // Import vue component 2 | import component from './vue-navigation-bar.vue'; 3 | 4 | // install function executed by Vue.use() 5 | export function install(app) { 6 | if (install.installed) return; 7 | 8 | install.installed = true; 9 | app.component('VueNavigationBar', component); 10 | } 11 | 12 | const plugin = { install }; 13 | 14 | // To auto-install when vue is found 15 | let GlobalVue = null; 16 | if (typeof window !== 'undefined') { 17 | GlobalVue = window.Vue; 18 | } else if (typeof global !== 'undefined') { 19 | GlobalVue = global.Vue; 20 | } 21 | if (GlobalVue) { 22 | GlobalVue.use(plugin); 23 | } 24 | 25 | // To allow use as module (npm/webpack/etc.) export component 26 | export default component; 27 | 28 | // It's possible to expose named exports when writing components that can 29 | // also be used as directives, etc. - eg. import { RollupDemoDirective } from 'rollup-demo'; 30 | // export const RollupDemoDirective = component; 31 | -------------------------------------------------------------------------------- /src/vue-navigation-bar.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 128 | 129 | 159 | -------------------------------------------------------------------------------- /test/VueNavigationBar.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils'; 2 | import VueNavigationBar from '@/vue-navigation-bar.vue'; 3 | 4 | describe('VueNavigationBar.vue', () => { 5 | it('Sets props correctly', async () => { 6 | let initialProps = { 7 | elementId: 'main-navbar', 8 | isUsingVueRouter: true, 9 | mobileBreakpoint: 992, 10 | brandImagePath: '/', 11 | brandImage: null, 12 | brandImageAltText: 'vnb', 13 | collapseButtonImageOpen: null, 14 | collapseButtonImageClose: null, 15 | showBrandImageInMobilePopup: false, 16 | ariaLabelMainNav: 'Main Navigation', 17 | tooltipAnimationType: 'shift-away', 18 | menuOptionsLeft: [ 19 | { 20 | type: 'link', 21 | text: 'Why Dunder Mifflin', 22 | path: '/why', 23 | subMenuOptions: [ 24 | { 25 | isLinkAction: true, 26 | type: 'link', 27 | text: 'About', 28 | }, 29 | { 30 | type: 'hr', 31 | }, 32 | { 33 | type: 'link', 34 | text: 'Locations', 35 | path: { name: 'locations', query: { item: 2 } }, 36 | }, 37 | { 38 | type: 'link', 39 | text: 'Blog', 40 | path: '/blog', 41 | }, 42 | ], 43 | }, 44 | { 45 | type: 'link', 46 | text: 'Contact', 47 | path: '/contact', 48 | subMenuOptions: [ 49 | { 50 | type: 'link', 51 | text: 'Customer Service', 52 | path: '/customer-service', 53 | }, 54 | { 55 | type: 'link', 56 | text: 'Accounting', 57 | path: '/accounting', 58 | }, 59 | { 60 | type: 'link', 61 | text: 'Reception', 62 | path: '/reception', 63 | }, 64 | ], 65 | }, 66 | { 67 | type: 'link', 68 | text: 'Pricing', 69 | path: '/pricing', 70 | }, 71 | ], 72 | menuOptionsRight: [ 73 | { 74 | type: 'button', 75 | text: 'Signup', 76 | path: '/signup', 77 | class: 'button-red', 78 | }, 79 | { 80 | type: 'button', 81 | text: 'Login', 82 | path: '/login', 83 | }, 84 | ], 85 | }; 86 | 87 | const wrapper = shallowMount(VueNavigationBar, { 88 | props: { 89 | options: { 90 | elementId: initialProps.elementId, 91 | isUsingVueRouter: initialProps.isUsingVueRouter, 92 | mobileBreakpoint: initialProps.mobileBreakpoint, 93 | brandImagePath: initialProps.brandImagePath, 94 | brandImage: initialProps.brandImage, 95 | brandImageAltText: initialProps.brandImageAltText, 96 | collapseButtonImageOpen: initialProps.collapseButtonImageOpen, 97 | collapseButtonImageClose: initialProps.collapseButtonImageClose, 98 | showBrandImageInMobilePopup: initialProps.showBrandImageInMobilePopup, 99 | ariaLabelMainNav: initialProps.ariaLabelMainNav, 100 | tooltipAnimationType: initialProps.tooltipAnimationType, 101 | menuOptionsLeft: initialProps.menuOptionsLeft, 102 | menuOptionsRight: initialProps.menuOptionsRight, 103 | }, 104 | }, 105 | }); 106 | 107 | expect(wrapper.vm.finalOptions.elementId).toBe(initialProps.elementId); 108 | expect(wrapper.vm.finalOptions.isUsingVueRouter).toBe(initialProps.isUsingVueRouter); 109 | expect(wrapper.vm.finalOptions.mobileBreakpoint).toBe(initialProps.mobileBreakpoint); 110 | expect(wrapper.vm.finalOptions.brandImagePath).toBe(initialProps.brandImagePath); 111 | expect(wrapper.vm.finalOptions.brandImage).toBe(initialProps.brandImage); 112 | expect(wrapper.vm.finalOptions.brandImageAltText).toBe(initialProps.brandImageAltText); 113 | expect(wrapper.vm.finalOptions.collapseButtonImageOpen).toBe( 114 | initialProps.collapseButtonImageOpen, 115 | ); 116 | expect(wrapper.vm.finalOptions.collapseButtonImageClose).toBe( 117 | initialProps.collapseButtonImageClose, 118 | ); 119 | expect(wrapper.vm.finalOptions.showBrandImageInMobilePopup).toBe( 120 | initialProps.showBrandImageInMobilePopup, 121 | ); 122 | expect(wrapper.vm.finalOptions.ariaLabelMainNav).toBe(initialProps.ariaLabelMainNav); 123 | expect(wrapper.vm.finalOptions.tooltipAnimationType).toBe(initialProps.tooltipAnimationType); 124 | expect(wrapper.vm.finalOptions.menuOptionsLeft).toStrictEqual(initialProps.menuOptionsLeft); 125 | expect(wrapper.vm.finalOptions.menuOptionsRight).toStrictEqual(initialProps.menuOptionsRight); 126 | }); 127 | 128 | it('Confirms the `vue-navigation-bar` was built', async () => { 129 | let initialProps = { 130 | elementId: 'main-navbar', 131 | }; 132 | 133 | const wrapper = shallowMount(VueNavigationBar, { 134 | props: { 135 | options: { 136 | elementId: initialProps.elementId, 137 | }, 138 | }, 139 | }); 140 | 141 | expect(wrapper.findComponent({ name: 'vue-navigation-bar' })).toBeTruthy(); 142 | }); 143 | }); 144 | -------------------------------------------------------------------------------- /test/styleMock.js: -------------------------------------------------------------------------------- 1 | // Added because Jest was failing on the import statment 2 | // https://stackoverflow.com/questions/37072641/make-jest-ignore-the-less-import-when-testing 3 | 4 | module.exports = { 5 | process() { 6 | return ''; 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import vue from '@vitejs/plugin-vue'; 3 | 4 | export default defineConfig({ 5 | base: '/vue-navigation-bar/', // For GitHub docs support 6 | build: { 7 | outDir: '../docs', 8 | }, 9 | plugins: [vue()], 10 | root: 'example', 11 | }); 12 | --------------------------------------------------------------------------------