Unverified Commit 2fdff722 authored by Ken Hawkins's avatar Ken Hawkins Committed by GitHub
Browse files

Component: vf-mega-menu (#1720)

* Component: vf-mega-menu

The initial implementation to address #1718

Starts as a direct port of https://codesandbox.io/s/vf-megamenu-qexed?file=/index.html

WIP.
parent 03a2a68d
Pipeline #215058 passed with stages
in 7 minutes and 47 seconds
## 1.4.4
* Add vf-mega-menu Sass and JS.
* https://github.com/visual-framework/vf-core/issues/1718
## 1.4.3
* Removes the unused vf-header component.
......
......@@ -143,6 +143,7 @@ button {
@import 'vf-show-more/vf-show-more.scss';
@import 'vf-mega-menu/vf-mega-menu.scss';
@import 'vf-tree/vf-tree.scss';
/* All Visual Framework Boilerplates */
......
......@@ -66,4 +66,7 @@ import { emblContentMetaProperties_Read } from "embl-content-meta-properties/emb
import { emblNotifications } from "embl-notifications/embl-notifications";
// emblNotifications();
import { vfMegaMenu } from 'vf-mega-menu/vf-mega-menu';
vfMegaMenu();
// No default invokation
bin
.github
.travis.yml
node_modules
### 1.0.0-alpha.1
* Creates a mega menu.
* https://github.com/visual-framework/vf-core/issues/1718
# Mega menu component
[![npm version](https://badge.fury.io/js/%40visual-framework%2Fvf-mega-menu.svg)](https://badge.fury.io/js/%40visual-framework%2Fvf-mega-menu)
## About
Paired with a good understanding of a site's information architecture and user journey, the mega menu can empower quick shortcut-style access to popular areas.
## Usage
The mega menu should be seen as a empowering but optional feature. While a mega menu may allow a user to quickly move to a sub-section of a website, or laterally move from one silo to another, that empowering ability should be viewed as an optional user journey.
Some users may fail to notice the mega menu by scrolling past it, be on a mobile device where the menu behaves differently, or the JavaScript-based feature may fail to load making the mega menu inaccessible.
A user journey should always be possible without the mega menu's content.
It is recommended to put your mega menu links at the `vf-global-header` level.
### Caveats
1. The mega menu currently is not designed to work on mobile
2. In principle any content can be inserted into a mega menu
3. Using more than one mega menu on a page is likely to confuse and overwhelm users
4. A mega menu is not a substitute for a good information architecture
### Accessibility
This component targets WCAG 2.1 AA accessibility.
Hiding critical or essintal information in a mega menu is harmful to users.
## Install
This repository is distributed with [npm][https://www.npmjs.com/]. After [installing npm][https://www.npmjs.com/get-npm] and [yarn](https://classic.yarnpkg.com/en/docs/install), you can install `vf-mega-menu` with this command.
```
$ yarn add --dev @visual-framework/vf-mega-menu
```
### JS
You should import this component in `./components/vf-component-rollup/scripts.js` or your other JS process:
```js
import { vfComponentName } from 'vf-mega-menu/vf-mega-menu';
// Or import directly
// import { vfComponentName } from '../components/raw/vf-mega-menu/vf-mega-menu.js';
vfComponentName(); // if needed, invoke it
```
### Sass/CSS
The style files included are written in [Sass](https://sass-lang.com/). If you're using a VF-core project, you can import it like this:
```
@import "@visual-framework/vf-mega-menu/vf-mega-menu.scss";
```
Make sure you import Sass requirements along with the modules. You can use a [project boilerplate](https://stable.visual-framework.dev/building/) or the [`vf-sass-starter`](https://stable.visual-framework.dev/components/vf-sass-starter/)
## Help
- [Read the Visual Framework troubleshooting](https://stable.visual-framework.dev/troubleshooting/)
- [Open a ticket](https://github.com/visual-framework/vf-core/issues)
- [Chat on Slack](https://join.slack.com/t/visual-framework/shared_invite/enQtNDAxNzY0NDg4NTY0LWFhMjEwNGY3ZTk3NWYxNWVjOWQ1ZWE4YjViZmY1YjBkMDQxMTNlNjQ0N2ZiMTQ1ZTZiMGM4NjU5Y2E0MjM3ZGQ)
// setup files required
// sass-lint:disable clean-import-paths
@import 'vf-global-variables';
@import 'vf-variables';
@import 'vf-functions';
@import 'vf-mixins';
// component specific styles
@import 'vf-mega-menu.variables.scss';
@import 'vf-mega-menu.scss';
// component variant styles
// @import 'vf-mega-menu--variant.scss';
{
"version": "1.0.0-alpha.0",
"name": "@visual-framework/vf-mega-menu",
"description": "vf-mega-menu component",
"homepage": "",
"author": "VF",
"license": "Apache 2.0",
"style": "vf-mega-menu.css",
"sass": "index.scss",
"main": "build/index.js",
"test": "echo \"Error: no test specified\" && exit 1",
"publishConfig": {
"access": "public"
},
"repo": "https://github.com/visual-framework/vf-core/tree/develop/components/vf-mega-menu",
"bugs": {
"url": "https://github.com/visual-framework/vf-core/issues"
},
"keywords": [
"fractal",
"component"
]
}
# The title shown on the component page
title: Mega menu
# Label shown on index pages
label: Mega menu
status: alpha
# The template used from /components/_previews
#
# Per-variant options
# variants:
# - name: default
# label: Default
# hidden: true
# context:
# children_are_possible:
# variant_title: A Easy Card Title 1
# variant_href: "JavaScript:Void(0);"
# modifier: vf-card--very-easy
# variant_image: ../../assets/vf-card/assets/vf-card-example.png
# Global component context
context:
component-type: container
# custom-values: passed as {{custom-values}}
# - note: you in your custom-values you should use dashes `-`
# and not underscores `_` as underscores prevent inherited template use
# title: Title text
# text: String of text
# image: ../../assets/vf-component-name/assets/vf-component-name.png
# - note on paths: be sure to prefix with `../../`
# - Why? https://github.com/visual-framework/vf-core/issues/364
// vf-mega-menu
// Don't need JS? Then feel free to delete this file.
/*
* A note on the Visual Framework and JavaScript:
* The VF is primarily a CSS framework so we've included only a minimal amount
* of JS in components and it's fully optional (just remove the JavaScript selectors
* i.e. `data-vf-js-tabs`). So if you'd rather use Angular or Bootstrap for your
* tabs, the Visual Framework won't get in the way.
*
* When querying the DOM for elements that should be acted on:
* 🚫 Don't: const tabs = document.querySelectorAll('.vf-tabs');
* ✅ Do: const tabs = document.querySelectorAll('[data-vf-js-tabs]');
*
* This allows users who would prefer not to have this JS engage on an element
* to drop `data-vf-js-component` and still maintain CSS styling.
*/
// Uncomment this boilerplate
// // if you need to import any other components' JS to use here
// import { vfOthercomponent } from vfImportPrefix + '../vf-other-component/vf-other-component';
//
function initMegaMenu(megaMenuComponent) {
//add activated class to this mega menu. This will help us differentiate when menu is processed with JS and when its not.
megaMenuComponent.classList.add("vf-mega-menu__activated");
let previousMenuLinkComponent, previousExpandedSectionComponent;
function getWidth() {
return Math.max(
document.body.scrollWidth,
document.documentElement.scrollWidth,
document.body.offsetWidth,
document.documentElement.offsetWidth,
document.documentElement.clientWidth
);
}
megaMenuComponent.querySelectorAll("[data-vf-js-mega-menu-section-id]").forEach(item => {
item.addEventListener('click', event => {
// For now we do not show the mega menu on mobile.
// We still need to decide on approach for mobile support.
if (getWidth() > 768) {
event.preventDefault();
event.vfMegaMenuLink = true;
const { target: linkComponent } = event;
const newReferences = handleMenuClick(
linkComponent,
previousMenuLinkComponent,
previousExpandedSectionComponent
);
previousMenuLinkComponent = newReferences?.previousMenuLinkComponent;
previousExpandedSectionComponent =
newReferences?.previousExpandedSectionComponent;
} else {
console.warn(`vf-mega-menu: No mega menu shown. Mega menu is currently alpha and not supported on small screen sizes. Your screens size is ${getWidth()}`);
}
// console.log({ linkComponent });
// console.log({ previousMenuLinkComponent });
// const associatedSection = linkComponent.getAttribute(
// "data-vf-js-mega-menu-section-id"
// );
});
});
}
function handleMenuClick(
menuItemComponent,
previousMenuLinkComponent,
previousExpandedSectionComponent
) {
// console.log("expand / collapse");
// debugger;
const sectionAttribute = menuItemComponent.getAttribute(
"data-vf-js-mega-menu-section-id"
);
const section = document.querySelector(
`[data-vf-js-mega-menu-section="${sectionAttribute}"]`
);
// console.log("section", section, sectionAttribute);
if (!section) {
return;
}
// Capture clicks on things other than mega menu elements
// https://www.blustemy.io/detecting-a-click-outside-an-element-in-javascript/
document.addEventListener("click", (evt) => {
let targetElement = evt.target; // clicked element
do {
if (targetElement == section || evt.vfMegaMenuLink == true) {
// This is a click inside. Do nothing, just return.
// console.log("Clicked inside!")
return;
}
// Go up the DOM
targetElement = targetElement.parentNode;
} while (targetElement);
// This is a click outside.
// console.log("Clicked outside!")
if (section.getAttribute("aria-hidden") === "false") {
// console.log("hiding!")
section.setAttribute("aria-hidden", "true");
}
});
//0. if section is visible, just hide it
if (section.getAttribute("aria-hidden") === "false") {
section.setAttribute("aria-hidden", "true");
menuItemComponent.classList.remove("is-expanded");
return;
}
//1. section is hidden. Hide all sections
if (previousExpandedSectionComponent) {
previousExpandedSectionComponent.setAttribute("aria-hidden", "true");
}
//2. remove highlight from previous link
if (previousMenuLinkComponent) {
previousMenuLinkComponent.classList.remove("is-expanded");
}
//3. show new section and add class to new link
section.setAttribute("aria-hidden", "false");
menuItemComponent.classList.add("is-expanded");
//4. return new link and section components to be stored as previous
return {
previousMenuLinkComponent: menuItemComponent,
previousExpandedSectionComponent: section
};
}
// function createMenuSectionMap(megaMenuComponent) {
// const allMenuComponents = megaMenuComponent.querySelectorAll(
// "[data-vf-js-mega-menu-section-id]"
// );
// const menuSectionsMap = new Map();
// allMenuComponents.forEach((component) => {
// const sectionAttribute = component.getAttribute(
// "data-vf-js-mega-menu-section-id"
// );
// const section = megaMenuComponent.querySelector(
// `[data-vf-js-mega-menu-section="${sectionAttribute}"]`
// );
// menuSectionsMap.set(component, section);
// });
// return menuSectionsMap;
// }
/**
* The global function for this component
* @example vfMegaMenu(firstPassedVar)
* @param {string} [firstPassedVar] - An option to be passed
*/
function vfMegaMenu(firstPassedVar) {
firstPassedVar = firstPassedVar || 'defaultVal';
const allMegaMenuComponents = document.querySelectorAll("[data-vf-js-mega-menu]") || [];
//for each mega-menu
allMegaMenuComponents.forEach(initMegaMenu);
}
// // If you need to invoke the component by default
// vfMegaMenu();
// By default your component should be usable with js imports
export { vfMegaMenu };
// You should also import it at ./components/vf-component-rollup/scripts.js
// import { vfMegaMenu } from 'vf-mega-menu/vf-mega-menu';
// Or import directly
// import { vfMegaMenu } from '../components/raw/vf-mega-menu/vf-mega-menu.js';
// And, if needed, invoke it
// vfMegaMenu();
{# Make sure any variables are listed inside the following if statement #}
{% if context %}
{# {% set vf-mega-menu = context.vf-mega-menu %} #}
{% endif %}
{# It is recommended to put your mega menu links at the vf-global-header level #}
<header class="vf-global-header vf-mega-menu" data-vf-js-mega-menu role="menubar">
<a href="/" class="vf-logo | vf-logo--has-text">
<img class="vf-logo__image" src="../../assets/vf-logo/assets/logo.svg" alt="Visual Framework for Life Science websites" loading="eager">
<span class="vf-logo__text">Visual Framework for Life Science websites</span>
</a>
<nav class="vf-navigation vf-navigation--global | vf-cluster">
<ul class="vf-navigation__list | vf-list | vf-cluster__inner">
<li class="vf-navigation__item">
<a class="vf-navigation__link vf-mega-menu__link vf-mega-menu__link--has-section"
data-vf-js-mega-menu-section-id="topics-content-section" href="/topics">
Topics
</a>
</li>
<li class="vf-navigation__item">
<a class="vf-navigation__link vf-mega-menu__link vf-mega-menu__link--has-section"
data-vf-js-mega-menu-section-id="organization-content-section" href="/organization">
Organization structure
</a>
</li>
<li class="vf-navigation__item">
<a href="http://www.embl.org" class="vf-navigation__link vf-mega-menu__link">Normal link</a>
</li>
<li class="vf-navigation__item">
<a href="http://www.embl.org/about" class="vf-navigation__link">About us</a>
</li>
<li class="vf-navigation__item">
<a class="vf-navigation__link vf-mega-menu__link" data-vf-js-mega-menu-section-id="search-content-section" href="/search">
Search
</a>
</li>
</ul>
</nav>
</header>
<div class="vf-mega-menu__content">
<div class="vf-mega-menu__content__section" data-vf-js-mega-menu-section="topics-content-section" role="menu" aria-hidden="true">
<section class="vf-summary-container | embl-grid">
<div class="vf-section-header">
<h2 class="vf-section-header__heading">EMBL topics</h2>
<p class="vf-section-header__subheading">A unique approach to scientific services in Europe</p>
</div>
<div class="vf-section-content | vf-grid vf-grid__col-3">
<div>
<nav class="vf-navigation vf-navigation--main">
<ul class="vf-navigation__list | vf-list | vf-cluster__inner">
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link" aria-current="page">Home</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Download</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Release Notes</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">FAQ</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Help</a>
</li>
</ul>
</nav>
</div>
<div>
<nav class="vf-navigation vf-navigation--main">
<ul class="vf-navigation__list | vf-list | vf-cluster__inner">
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link" aria-current="page">Home</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Download</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Release Notes</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">FAQ</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Help</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Licence</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">About</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Feedback</a>
</li>
</ul>
</nav>
</div>
<div>
<nav class="vf-navigation vf-navigation--main">
<ul class="vf-navigation__list | vf-list | vf-cluster__inner">
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link" aria-current="page">Home</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Download</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Release Notes</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">FAQ</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Help</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Licence</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">About</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Feedback</a>
</li>
</ul>
</nav>
</div>
</div>
</section>
</div>
<div class="vf-mega-menu__content__section" data-vf-js-mega-menu-section="organization-content-section" role="menu" aria-hidden="true">
<section class="vf-summary-container | embl-grid">
<div class="vf-section-header">
<h2 class="vf-section-header__heading">Our organization</h2>
<p>
<strong>We have strong organization here, like, really really strong
one</strong>
</p>
</div>
<div class="vf-section-content | vf-grid vf-grid__col-3">
<div>
<nav class="vf-navigation vf-navigation--main">
<ul class="vf-navigation__list | vf-list | vf-cluster__inner">
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link" aria-current="page">Home</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Download</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Release Notes</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">FAQ</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Help</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Licence</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">About</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Feedback</a>
</li>
</ul>
</nav>
</div>
<div>
<nav class="vf-navigation vf-navigation--main">
<ul class="vf-navigation__list | vf-list | vf-cluster__inner">
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link" aria-current="page">Home</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Download</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Release Notes</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">FAQ</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Help</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Licence</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">About</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Feedback</a>
</li>
</ul>
</nav>
</div>
<div>
<nav class="vf-navigation vf-navigation--main">
<ul class="vf-navigation__list | vf-list | vf-cluster__inner">
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link" aria-current="page">Home</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Download</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Release Notes</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">FAQ</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Help</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Licence</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">About</a>
</li>
<li class="vf-navigation__item">
<a href="JavaScript:Void(0);" class="vf-navigation__link">Feedback</a>
</li>
</ul>
</nav>
</div>
</div>
</section>
</div>
<div class="vf-mega-menu__content__section" data-vf-js-mega-menu-section="search-content-section" role="menu" aria-hidden="true">
<form action="#" class="vf-form vf-form--search vf-form--search--responsive | vf-sidebar vf-sidebar--end">
<div class="vf-sidebar__inner">
<div class="vf-form__item">
<label class="vf-form__label vf-u-sr-only | vf-search__label" for="searchitem">Search</label>
<input type="search" placeholder="Enter your search terms" id="searchitem" class="vf-form__input" />
</div>
<button type="submit" class="vf-search__button | vf-button vf-button--primary">
<span class="vf-button__text">Search</span>
<svg class="vf-icon vf-icon--search-btn | vf-button__icon" aria-hidden="true"
xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:svgjs="http://svgjs.com/svgjs" viewBox="0 0 140 140" width="140" height="140">
<g transform="matrix(5.833333333333333,0,0,5.833333333333333,0,0)">
<path
d="M23.414,20.591l-4.645-4.645a10.256,10.256,0,1,0-2.828,2.829l4.645,4.644a2.025,2.025,0,0,0,2.828,0A2,2,0,0,0,23.414,20.591ZM10.25,3.005A7.25,7.25,0,1,1,3,10.255,7.258,7.258,0,0,1,10.25,3.005Z"
fill="#FFFFFF" stroke="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="0">