Skip to content

Commit

Permalink
Dan docs left nav manage state w js update (#1417)
Browse files Browse the repository at this point in the history
  • Loading branch information
chillenberger committed Apr 26, 2024
1 parent 2b83a11 commit 65b898d
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 37 deletions.
10 changes: 10 additions & 0 deletions pgml-dashboard/src/components/cms/index_link/mod.rs
Expand Up @@ -12,6 +12,7 @@ pub struct IndexLink {
pub open: bool,
pub active: bool,
pub level: i32,
pub id_suffix: String,
}

impl IndexLink {
Expand All @@ -25,6 +26,7 @@ impl IndexLink {
open: false,
active: false,
level,
id_suffix: "".to_owned(),
}
}

Expand Down Expand Up @@ -70,4 +72,12 @@ impl IndexLink {
}
self
}

// Adds a suffix to this and all children ids.
// this prevents id collision with multiple naves on one screen
// like d-none for mobile nav
pub fn id_suffix(mut self, id_suffix: &str) -> IndexLink {
self.id_suffix = id_suffix.to_owned();
self
}
}
9 changes: 5 additions & 4 deletions pgml-dashboard/src/components/cms/index_link/template.html
Expand Up @@ -46,7 +46,7 @@
<span class="text-wrap"><%- title %></span>
</a>
<div class="pt-2">
<span class="material-symbols-outlined rotate-on-aria-expanded text-white" href="#doc-<%= id %>" role="button" aria-expanded="<%- aria %>" aria-controls="doc-<%= id %>" data-action="click->navigation-left-nav-docs#expand">expand_more</span>
<span class="material-symbols-outlined rotate-on-aria-expanded text-white" href="#doc-<%= id %><%- id_suffix %>" role="button" aria-expanded="<%- aria %>" aria-controls="doc-<%= id %><%- id_suffix %>" data-action="click->navigation-left-nav-docs#toggle">expand_more</span>
</div>
</div>
</div>
Expand All @@ -56,15 +56,16 @@
<span class="text-wrap"><%- title %></span>
</a>
<div class="pt-2">
<span class="material-symbols-outlined rotate-on-aria-expanded" href="#doc-<%= id %>" role="button" aria-expanded="<%- aria %>" aria-controls="doc-<%= id %>" data-action="click->navigation-left-nav-docs#expand">expand_more</span>
<span class="material-symbols-outlined rotate-on-aria-expanded" href="#doc-<%= id %><%- id_suffix %>" role="button" aria-expanded="<%- aria %>" aria-controls="doc-<%= id %><%- id_suffix %>" data-action="click->navigation-left-nav-docs#toggle">expand_more</span>
</div>
</span>
<% } %>

<div class="collapse <%- show %>" id="doc-<%= id %>">
<div class="collapse <%- show %>" id="doc-<%= id %><%- id_suffix %>">
<div class='nav flex-column level-<%- level %>-list' role="tablist" aria-orentation="vertical">
<% for child in children.into_iter() { %>
<%- child.render_once().unwrap() %>
<% let child = child.id_suffix(&id_suffix); %>
<%- child.render_once().unwrap() %>
<% } %>
</div>
</div>
Expand Down
@@ -1,69 +1,154 @@
import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
static targets = ["level1Container", "level1Link", "highLevels"];
static targets = ["level1Container", "level1Link", "highLevels", "leftNav"];

// After page update we reset scroll position of nave back to where it was
// After page update we reset scroll position of nav back to where it
// was and ensure left nave and window location match.
connect() {
let nav = document.getElementsByClassName("doc-leftnav");
if (nav.length > 0) {
let position = nav[0].getAttribute("data-scroll");
nav[0].scrollTop = position;
}

this.callback = () => {
this.setNavToLocation();
};

document.addEventListener("turbo:load", this.callback);
}

// trubo-frame permanent breakes bootstrap data attribute collapse for aria
// so we manually controll collapse
expand(e) {
let aria = e.currentTarget.getAttribute("aria-expanded");
let id = e.currentTarget.getAttribute("aria-controls");
// The active tags should always be set to the current page location
setNavToLocation() {
const tag = "a[href='" + window.location.pathname + "']";

let link = this.element.querySelectorAll(tag);
if (link.length > 0) {
if (
link[0].getAttribute("data-navigation-left-nav-docs-target") ==
"highLevels"
) {
this.setHighLevelLeftNav(link[0]);
} else {
this.setLevel1LeftNav(link[0]);
}
}
}

let bsCollapse = bootstrap.Collapse.getOrCreateInstance(
document.getElementById(id),
expandSubmenuIfExists(containerEl) {
const controllerEl = containerEl.querySelector(
"[data-action='click->navigation-left-nav-docs#toggle']",
);
controllerEl ? this.expand(controllerEl) : null;
}

// Finds all parent submenus this element is in and expands them. Takes
// the element containing the current level
expandAllParents(element) {
let level = element.getAttribute("data-level");

this.expandSubmenuIfExists(element);
if (level > 1) {
let next = "div[data-level='" + (parseInt(level) - 1) + "']";
this.expandAllParents(element.closest(next));
}
}

// turbo-frame-permanent breaks bootstrap data attribute collapse for aria
// so we manually control collapse
toggle(event) {
let aria = event.currentTarget.getAttribute("aria-expanded");

if (aria === "true") {
bsCollapse.hide();
e.currentTarget.setAttribute("aria-expanded", "false");
this.collapse(event.currentTarget);
} else {
this.expand(event.currentTarget);
}
}

// Expands the submenu, takes submenu control element.
expand(element) {
let id = element.getAttribute("aria-controls");
let aria = element.getAttribute("aria-expanded");

if (aria === "false") {
let bsCollapse = bootstrap.Collapse.getOrCreateInstance(
document.getElementById(id),
);
bsCollapse.show();
e.currentTarget.setAttribute("aria-expanded", "true");
element.setAttribute("aria-expanded", "true");
}
}

// Activly manage nav state for level 1 links
onNavigateManageLevel1(e) {
// Collapses the submenu, takes submenu control element.
collapse(element) {
let id = element.getAttribute("aria-controls");
let aria = element.getAttribute("aria-expanded");

if (aria === "true") {
let bsCollapse = bootstrap.Collapse.getOrCreateInstance(
document.getElementById(id),
);
bsCollapse.hide();
element.setAttribute("aria-expanded", "false");
}
}

// Actively manage nav state for high level links.
setHighLevelLeftNav(element) {
this.removeAllActive();

let container = e.currentTarget.closest("div");
container.classList.add("active");
const parentContainer = element.closest('div[data-level="1"]');
const parentMenu = parentContainer.querySelector(".menu-item");
const parentLink = parentMenu.querySelector(
".doc-left-nav-level1-link-container",
);

e.currentTarget.classList.add("active");
parentLink.classList.add("active");
element.classList.add("purple");

const container = element.parentElement;
this.expandSubmenuIfExists(container);

const levelEl = container.closest("div[data-level]");
this.expandAllParents(levelEl);

this.preventScrollOnNav();
}

// Activly manage nav state for high level links
onNavigateManageHighLevels(e) {
// Actively manage nav state for level 1 links
setLevel1LeftNav(element) {
this.removeAllActive();

let container = e.currentTarget.closest('div[data-level="1"]');
let menu = container.querySelector(".menu-item");
let link = menu.querySelector(".doc-left-nav-level1-link-container");
const container = element.closest("div");
container.classList.add("active");

element.classList.add("active");

link.classList.add("active");
this.expandSubmenuIfExists(container);

e.currentTarget.classList.add("purple");
this.preventScrollOnNav();
}

// Actions to take when nav link is clicked
// currently just gets the scroll position before state change
onNavigateManageLevel1() {
this.preventScrollOnNav();
}

// Actions to take when nav link is clicked
// currently just gets the scroll position before state change
onNavigateManageHighLevels() {
this.preventScrollOnNav();
}

// trubo-frame permanent scrolles nav to top on navigation so we capture the scrroll position prior
// turbo-frame permanent scrolls nav to top on navigation so we capture the scroll position prior
// to updating the page so after we can set the scroll position back to where it was
preventScrollOnNav() {
let nav = document.getElementsByClassName("doc-leftnav");
if (nav.length > 0) {
let position = nav[0].scrollTop;
nav[0].setAttribute("data-scroll", position);
if (this.hasLeftNavTarget) {
let position = this.leftNavTarget.scrollTop;
this.leftNavTarget.setAttribute("data-scroll", position);
}
}

Expand All @@ -81,4 +166,8 @@ export default class extends Controller {
this.level1LinkTargets[i].classList.remove("active");
}
}

disconnect() {
document.removeEventListener("turbo:load", this.callback);
}
}
Expand Up @@ -24,8 +24,8 @@
%>

<% if !mobile { %>
<div class="doc-leftnav-container" id="cart-counter" data-controller="navigation-left-nav-docs" data-turbo-permanent>
<nav class="doc-leftnav" data-scroll="0">
<div class="doc-leftnav-container" id="doc-leftnav-container" data-controller="navigation-left-nav-docs" data-turbo-permanent>
<nav class="doc-leftnav" data-scroll="0" data-navigation-left-nav-docs-target="leftNav">
<div class="d-flex flex-column justify-content-between">
<div class="d-xl-flex flex-column py-4">
<div class="pt-2 ps-2 d-flex flex-column gap-4_5">
Expand All @@ -52,7 +52,7 @@
<nav class="navbar px-0">
<div class="card nav guides rounded-0 w-100">
<div class="card-body py-2 py-xl-4">
<a class="my-1 d-flex justify-content-between align-items-center text-white" role="button" data-bs-toggle="collapse" href="#guides" aria-expanded="false" aria-congrols="guides">
<a class="my-1 d-flex justify-content-between align-items-center text-white" role="button" data-bs-toggle="collapse" href="#guides" aria-expanded="false" aria-controls="guides">
<span>Docs</span><span class="material-symbols-outlined rotate-on-aria-expanded">expand_more</span>
</a>
<div class="collapse border-top pt-2" id="guides">
Expand All @@ -61,9 +61,10 @@
<%+ doc_link %>
<% } else { %>
<div class="d-flex flex-column pt-2">
<%- title(doc_link.title) %>
<%- title(doc_link.title.to_uppercase()) %>

<% for item in doc_link.children {%>
<% let item = item.id_suffix("mobile"); %>
<%+ item %>
<% } %>
</div>
Expand Down

0 comments on commit 65b898d

Please sign in to comment.