Sidebar horizontal or vertical collapse (needs some fine tuning)
authorNick Downing <nick@ndcode.org>
Thu, 10 Feb 2022 04:02:30 +0000 (15:02 +1100)
committerNick Downing <nick@ndcode.org>
Sat, 12 Feb 2022 01:53:36 +0000 (12:53 +1100)
_lib/breadcrumbs.jst
_lib/navbar.jst
css/bootstrap/_custom.scss

index 59517a6..60b0c38 100644 (file)
@@ -8,6 +8,7 @@ return async (env, _out) => {
 
   // present component_titles as breadcrumbs, except last one as text
   h2.page-header.grid-gutter-background.'py-2'.mb-0 {
+    button.btn.btn-outline-secondary.mr-3#navbar-toggle {'Toggle'}
     for (let i = 0; i < component_names.length; ++i) {
       a.h4(
         href=
index db36ffd..92d3373 100644 (file)
@@ -129,123 +129,125 @@ return async (env, head, body, scripts) => {
 
       div.container-fluid {
         div.row {
-          div.'col-md-4'.sidebar {
-            div.'mt-1'.mb-4 {
-              div(style="width: 128px; height: 128px;") {
-                _out.push(avatar_maker)
-              }
-              b.h1 {
-                `${site_title}`
+          div.'col-md-4'.sidebar-outer#navbar-sidebar-outer {
+            div.sidebar-inner#navbar-sidebar-inner {
+              div.mb-4 {
+                div(style="width: 128px; height: 128px;") {
+                  _out.push(avatar_maker)
+                }
+                b.h1 {
+                  `${site_title}`
+                }
               }
-            }
 
-            div.mb-2 {
-              span#navbar-signed-in-status {
+              div.mb-2 {
+                span#navbar-signed-in-status {
+                  if (signed_in_as !== undefined)
+                    'Signed in.'
+                  else
+                    'Signed out.'
+                }
+                ' '
+                if (signed_in_as !== undefined)
+                  a#navbar-sign-in(href="#" hidden) {'Sign in'}
+                else
+                  a#navbar-sign-in(href="#") {'Sign in'}
+                ' '
+                if (signed_in_as !== undefined)
+                  a#navbar-sign-up(href="/my_account/sign_up/index.html" hidden) {'Sign up'}
+                else
+                  a#navbar-sign-up(href="/my_account/sign_up/index.html") {'Sign up'}
+                ' '
                 if (signed_in_as !== undefined)
-                  'Signed in.'
+                  a#navbar-sign-out(href="#") {'Sign out'}
                 else
-                  'Signed out.'
+                  a#navbar-sign-out(href="#" hidden) {'Sign out'}
               }
-              ' '
-              if (signed_in_as !== undefined)
-                a#navbar-sign-in(href="#" hidden) {'Sign in'}
-              else
-                a#navbar-sign-in(href="#") {'Sign in'}
-              ' '
-              if (signed_in_as !== undefined)
-                a#navbar-sign-up(href="/my_account/sign_up/index.html" hidden) {'Sign up'}
-              else
-                a#navbar-sign-up(href="/my_account/sign_up/index.html") {'Sign up'}
-              ' '
-              if (signed_in_as !== undefined)
-                a#navbar-sign-out(href="#") {'Sign out'}
-              else
-                a#navbar-sign-out(href="#" hidden) {'Sign out'}
-            }
 
-            form.mb-4(action="/search/index.html") {
-              div.input-group {
-                input.form-control(name="query" type="text" placeholder="Search" aria-describedby="search-button") {}
-                div.input-group-append {
-                  button.btn.btn-outline-secondary#navbar-search-button(type="submit") {
-                    div.icon24-outer {
-                      div.icon24-inner {_out.push(fa_search)}
+              form.mb-4(action="/search/index.html") {
+                div.input-group {
+                  input.form-control(name="query" type="text" placeholder="Search" aria-describedby="search-button") {}
+                  div.input-group-append {
+                    button.btn.btn-outline-secondary#navbar-search-button(type="submit") {
+                      div.icon24-outer {
+                        div.icon24-inner {_out.push(fa_search)}
+                      }
                     }
                   }
                 }
               }
-            }
 
-            //nav.navbar.navbar-expand-lg.navbar-dark.bg-primary.scrollbar-fix {
-              //a.navbar-brand(href="#") {'Navbar'}
-              //' '
-              //button.navbar-toggler(type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation") {
-              //  span.navbar-toggler-icon {}
-              //}
-              //div.collapse.navbar-collapse#navbarSupportedContent {
-                ul.nav.flex-column { //ul.navbar-nav.mr-auto {
-                  // the active entry in the navbar bar is based on which top-level
-                  // page we are under, even if we are not directly on that page
-                  // but one of its children, this may be unexpected as the active
-                  // entry does not highlight on hover, but you can still click it;
-                  // we determine here the path to the corresponding top-level page
-                  let component_prefix = component_names.slice(0, 1)
-    
-                  for (let i = 0; i < menu_titles.length; ++i) {
-                    // construct path to the top-level page about to be described
-                    let menu_prefix =
-                      i == 0 ? [] : [menu_names[i - 1]]
-                    let menu_prefix_path =
-                      menu_prefix.map(name => '/' + name).join('') + '/index.html'
-    
-                    if (arrays_equal(menu_prefix, component_prefix))
-                      li.nav-item.active {
-                        a.nav-link.nav-link2.grid-gutter-background(href=menu_prefix_path) {
-                          `${menu_titles[i]}`
-                          span.sr-only {' (current)'}
+              //nav.navbar.navbar-expand-lg.navbar-dark.bg-primary.scrollbar-fix {
+                //a.navbar-brand(href="#") {'Navbar'}
+                //' '
+                //button.navbar-toggler(type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation") {
+                //  span.navbar-toggler-icon {}
+                //}
+                //div.collapse.navbar-collapse#navbarSupportedContent {
+                  ul.nav.flex-column { //ul.navbar-nav.mr-auto {
+                    // the active entry in the navbar bar is based on which top-level
+                    // page we are under, even if we are not directly on that page
+                    // but one of its children, this may be unexpected as the active
+                    // entry does not highlight on hover, but you can still click it;
+                    // we determine here the path to the corresponding top-level page
+                    let component_prefix = component_names.slice(0, 1)
+      
+                    for (let i = 0; i < menu_titles.length; ++i) {
+                      // construct path to the top-level page about to be described
+                      let menu_prefix =
+                        i === 0 ? [] : [menu_names[i - 1]]
+                      let menu_prefix_path =
+                        menu_prefix.map(name => '/' + name).join('') + '/index.html'
+      
+                      if (arrays_equal(menu_prefix, component_prefix))
+                        li.nav-item.active {
+                          a.nav-link.nav-link2.grid-gutter-background(href=menu_prefix_path) {
+                            `${menu_titles[i]}`
+                            span.sr-only {' (current)'}
+                          }
                         }
-                      }
-                    else
-                      li.nav-item {
-                        a.nav-link.nav-link2.grid-gutter-background(href=menu_prefix_path) {
-                          `${menu_titles[i]}`
+                      else
+                        li.nav-item {
+                          a.nav-link.nav-link2.grid-gutter-background(href=menu_prefix_path) {
+                            `${menu_titles[i]}`
+                          }
                         }
-                      }
-                  }
-                  //li.nav-item.dropdown {
-                  //  a.nav-link.dropdown-toggle#navbarDropdown(href="#" role="button" data-toggle="dropdown" aria-expanded="false") {
-                  //    'Dropdown'
-                  //  }
-                  //  div.dropdown-menu(aria-labelledby="navbarDropdown") {
-                  //    a.dropdown-item(href="#") {
-                  //      'Action'
-                  //    }
-                  //    ' '
-                  //    a.dropdown-item(href="#") {
-                  //      'Another action'
-                  //    }
-                  //    div.dropdown-divider {}
-                  //    a.dropdown-item(href="#") {
-                  //      'Something else here'
-                  //    }
-                  //  }
-                  //}
-                  //li.nav-item {
-                  //  a.nav-link.disabled {
-                  //    'Disabled'
-                  //  }
+                    }
+                    //li.nav-item.dropdown {
+                    //  a.nav-link.dropdown-toggle#navbarDropdown(href="#" role="button" data-toggle="dropdown" aria-expanded="false") {
+                    //    'Dropdown'
+                    //  }
+                    //  div.dropdown-menu(aria-labelledby="navbarDropdown") {
+                    //    a.dropdown-item(href="#") {
+                    //      'Action'
+                    //    }
+                    //    ' '
+                    //    a.dropdown-item(href="#") {
+                    //      'Another action'
+                    //    }
+                    //    div.dropdown-divider {}
+                    //    a.dropdown-item(href="#") {
+                    //      'Something else here'
+                    //    }
+                    //  }
+                    //}
+                    //li.nav-item {
+                    //  a.nav-link.disabled {
+                    //    'Disabled'
+                    //  }
+                    //}
                   //}
-                //}
-                //ul.navbar-nav.ml-auto {
-                  li.nav-item.mt-4 {
-                    a.nav-link.nav-link2.grid-gutter-background#navbar-give-feedback(href="#") {'Give feedback'}
+                  //ul.navbar-nav.ml-auto {
+                    li.nav-item.mt-4 {
+                      a.nav-link.nav-link2.grid-gutter-background#navbar-give-feedback(href="#") {'Give feedback'}
+                    }
                   }
-                }
+                //}
               //}
-            //}
+            }
           }
 
-          div.'col-md-8'.'col-xl-7'/*.'col-xxl-6'*/ {
+          div.'col-md-8'.'col-xl-7' {
             await body(_out)
 
             footer.page-footer.grid-gutter-background.py-5 {
@@ -462,6 +464,8 @@ return async (env, head, body, scripts) => {
             let id_navbar_message_modal = document.getElementById('navbar-message-modal')
             let id_navbar_message_modal_message = document.getElementById('navbar-message-modal-message')
             let id_navbar_search_button = document.getElementById('navbar-search-button')
+            let id_navbar_sidebar_inner = document.getElementById('navbar-sidebar-inner')
+            let id_navbar_sidebar_outer = document.getElementById('navbar-sidebar-outer')
             let id_navbar_sign_in = document.getElementById('navbar-sign-in')
             let id_navbar_sign_in_cross = document.getElementById('navbar-sign-in-cross')
             let id_navbar_sign_in_email = document.getElementById('navbar-sign-in-email')
@@ -478,6 +482,7 @@ return async (env, head, body, scripts) => {
             let id_navbar_signed_in_status = document.getElementById('navbar-signed-in-status')
             //let id_navbarDropdown = document.getElementById('navbarDropdown')
             //let id_navbarSupportedContent = document.getElementById('navbarSupportedContent')
+            let id_navbar_toggle = document.getElementById('navbar-toggle') // defined in breadcrumbs.jst
 
             // sign in form
             id_navbar_sign_in.addEventListener(
@@ -732,6 +737,61 @@ return async (env, head, body, scripts) => {
                 $('#navbar-message-modal').modal('show')
               }
             )
+
+            // sidebar
+            let sidebar_computed_style = window.getComputedStyle(
+              id_navbar_sidebar_outer
+            )
+            let sidebar_collapse_update = () => {
+              if (sidebar_computed_style.position === 'sticky') { // md and up
+                id_navbar_sidebar_outer.style.flexBasis =
+                  id_navbar_sidebar_outer.classList.contains(
+                    'sidebar-outer-collapsed'
+                  ) ? '0px' : `${id_navbar_sidebar_inner.clientWidth}px`
+                id_navbar_sidebar_outer.style.removeProperty('height')
+              }
+              else {
+                id_navbar_sidebar_outer.style.height =
+                  id_navbar_sidebar_outer.classList.contains(
+                    'sidebar-outer-collapsed'
+                  ) ? '0px' : `${id_navbar_sidebar_inner.clientHeight}px`
+                id_navbar_sidebar_outer.style.removeProperty('flex-basis')
+              }
+            }
+            window.addEventListener('resize', sidebar_collapse_update)
+            sidebar_collapse_update()
+
+            id_navbar_sidebar_outer.addEventListener(
+              'transitionend',
+              () => {
+                // transitions are only allowed after clicking collapse button,
+                // otherwise they can be triggered by media queries on resize
+                id_navbar_sidebar_outer.style.removeProperty('transitionProperty')
+              }
+            )
+
+            id_navbar_toggle.addEventListener(
+              'click',
+              () => {
+                if (
+                  id_navbar_sidebar_outer.classList.contains(
+                    'sidebar-outer-collapsed'
+                  )
+                )
+                  id_navbar_sidebar_outer.classList.remove(
+                    'sidebar-outer-collapsed'
+                  )
+                else
+                   id_navbar_sidebar_outer.classList.add(
+                    'sidebar-outer-collapsed'
+                  )
+                id_navbar_sidebar_outer.style.transitionProperty =
+                  sidebar_computed_style.position === 'sticky' ?
+                    'flex-basis' : // md and up
+                    'height'
+                sidebar_collapse_update()
+              }
+            )
           }
         )
       }
index 6fad316..d6e7cac 100644 (file)
@@ -35,17 +35,27 @@ $footer-link-hover-color: darken($footer-link-color, 10%);
   padding-right: .5 * $grid-gutter-width;
 }
 
-.sidebar {
+.sidebar-outer {
+  padding-left: 0;
+  padding-right: 0;
+  overflow: hidden;
   @include media-breakpoint-up(md) {
     position: sticky;
     top: 0;
     min-height: 100vh;
     max-height: 100vh;
-    max-width: 350px;
     overflow-y: auto;
   }
+  transition: none .35s ease; // transition property is set via inline style
   background-color: $gray-200;
 }
+.sidebar-inner {
+  padding-left: $grid-gutter-width * .5;
+  padding-right: $grid-gutter-width * .5;
+  @include media-breakpoint-up(md) {
+    width: 350px;
+  }
+}
 
 // needed for svg icons inside buttons, card headers, etc
 // creates an inline element with correct width but no height