diff --git a/demo/index.html b/demo/index.html index 41d2d5e..6faa31e 100644 --- a/demo/index.html +++ b/demo/index.html @@ -63,6 +63,7 @@ @@include('./partials/skeleton.html') @@include('./partials/tooltips.html') @@include('./partials/dropdowns.html') + @@include('./partials/overlays.html') @@include('./partials/navigation-shell.html') @@include('./partials/backgrounds.html') @@include('./partials/breadcrumbs.html') diff --git a/demo/partials/overlays.html b/demo/partials/overlays.html new file mode 100644 index 0000000..11bf378 --- /dev/null +++ b/demo/partials/overlays.html @@ -0,0 +1,22 @@ +
+

Overlays

+

Managed by VMKUIKit.Overlays using data-toggle attributes.

+ +

Dropdown toggle

+ + +

Tooltip toggle

+ + + Overlay-managed tooltip + +
diff --git a/src/js/components/overlays.js b/src/js/components/overlays.js new file mode 100644 index 0000000..d46a20f --- /dev/null +++ b/src/js/components/overlays.js @@ -0,0 +1,107 @@ +/** + * Overlays browser helper. + * + * Mirrors gnexus-ui-kit Overlays public surface: + * init(root), closeAll() + * + * Centralizes management of dropdown, popover and tooltip toggle state + * using data-* trigger attributes. Complements the dedicated dropdowns.js, + * popover.js and tooltips.js helpers. + */ + +const initializedRoots = new WeakSet(); +let keyboardDismissInitialized = false; + +function closeNode(node) { + node.classList.remove("is-open"); + node.querySelectorAll("[aria-expanded='true']").forEach(trigger => { + trigger.setAttribute("aria-expanded", "false"); + }); +} + +function closeAll(except = null) { + document.querySelectorAll(".dropdown.is-open, .popover.is-open, .tooltip.is-open").forEach(node => { + if(node !== except) { + closeNode(node); + } + }); +} + +function initDropdowns(root = document) { + root.addEventListener("click", event => { + const trigger = event.target.closest("[data-dropdown-toggle]"); + + if(!trigger) { + return; + } + + const dropdown = trigger.closest(".dropdown"); + + if(!dropdown) { + return; + } + + event.preventDefault(); + const isOpen = dropdown.classList.contains("is-open"); + closeAll(dropdown); + dropdown.classList.toggle("is-open", !isOpen); + trigger.setAttribute("aria-expanded", String(!isOpen)); + }); +} + +function initTooltips(root = document) { + root.addEventListener("click", event => { + const trigger = event.target.closest("[data-tooltip-toggle]"); + + if(!trigger) { + return; + } + + const tooltip = trigger.closest(".tooltip"); + + if(!tooltip) { + return; + } + + event.preventDefault(); + const isOpen = tooltip.classList.contains("is-open"); + closeAll(tooltip); + tooltip.classList.toggle("is-open", !isOpen); + }); +} + +function initDismiss(root = document) { + root.addEventListener("click", event => { + if(event.target.closest(".dropdown, .popover, .tooltip")) { + return; + } + + closeAll(); + }); + + if(!keyboardDismissInitialized) { + document.addEventListener("keydown", event => { + if(event.key === "Escape") { + closeAll(); + } + }); + + keyboardDismissInitialized = true; + } +} + +function init(root = document) { + if(initializedRoots.has(root)) { + return; + } + + initDropdowns(root); + initTooltips(root); + initDismiss(root); + initializedRoots.add(root); +} + +export default { + init, + closeAll +}; diff --git a/src/js/index.js b/src/js/index.js index d7b0558..d50c279 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -9,6 +9,7 @@ import confirmPopup from "./components/confirm-popup.js"; import InputPatterns from "./components/input-patterns.js"; import editableString from "./components/editable-string.js"; +import Overlays from "./components/overlays.js"; import Tabs from "./components/tabs.js"; import Progress from "./components/progress.js"; import Tables from "./components/tables.js"; @@ -30,6 +31,7 @@ confirmPopup, InputPatterns, editableString, + Overlays, Tabs, Progress, Tables, @@ -60,8 +62,9 @@ Loader.init?.(); NavigationShell.init?.(); InputPatterns.init?.(); + Overlays.init?.(); }); } -export { Icons as Helper, Toasts, Modals, confirmPopup, InputPatterns, editableString, Tabs, Progress, Tables, Lists, Skeleton, Tooltips, Dropdowns, NavigationShell, Background, Breadcrumbs, Accordion, Popover, Loader }; +export { Icons as Helper, Toasts, Modals, confirmPopup, InputPatterns, editableString, Overlays, Tabs, Progress, Tables, Lists, Skeleton, Tooltips, Dropdowns, NavigationShell, Background, Breadcrumbs, Accordion, Popover, Loader }; export default api; diff --git a/src/scss/components/_dropdown.scss b/src/scss/components/_dropdown.scss index 97be422..bea83e5 100644 --- a/src/scss/components/_dropdown.scss +++ b/src/scss/components/_dropdown.scss @@ -31,7 +31,8 @@ visibility $motion-fast $motion-ease, transform $motion-fast $motion-ease; - &.is-open { + &.is-open, + .dropdown.is-open & { opacity: 1; visibility: visible; transform: translateY(0); diff --git a/src/scss/components/_tooltip.scss b/src/scss/components/_tooltip.scss index 3b300d6..81acf54 100644 --- a/src/scss/components/_tooltip.scss +++ b/src/scss/components/_tooltip.scss @@ -105,6 +105,7 @@ } &.is-visible .tooltip-content, + &.is-open .tooltip-content, &:hover .tooltip-content, .tooltip-trigger:focus + .tooltip-content, .tooltip-trigger:focus-within + .tooltip-content { @@ -114,6 +115,7 @@ } &[data-placement="left"].is-visible .tooltip-content, + &[data-placement="left"].is-open .tooltip-content, &[data-placement="left"]:hover .tooltip-content { transform: translateY(-50%) translateX(0); }