-
Notifications
You must be signed in to change notification settings - Fork 133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make navigation menus accessible on smaller screens #1445
Make navigation menus accessible on smaller screens #1445
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! @jonahtanjz
Regarding the usage (from author pov), I think we could try to encapsulate the implementation in the already existing Navbar.vue
, and create some reusable components. Something along the lines of this that dosen't make this a fixed, mandatory placement:
// author writing a navbar
<navbar>
...
<div slot="lower-navbar"> // a new slot for the "secondary navbar" you have here
<page-nav-button> // new component the user may use to link this up
<site-nav-button> // similarly
</div>
</navbar>
that way, we don't restrict the author to a single layout (secondary navbar), they may even omit the secondary navbar entirely, or place the buttons in the "main (top most) navbar" if they wish.
we could also provide customisation options for page-nav-button
/ etc. in the future.
With the comments below:
Most of the functions are binded to window and the function calls are done using <script>. Is this method of implementation recommended? Is there a better way to call these functions when building the siteNav or pageNav?
So I guess, the answer to this lies in the (author) usage design. Changing it should result in a more sealed off implementation as well.
There are probably better ways of doing this besides the two suggestions here, but this is what I can propose right now 🤔🤔🤔
Would love to hear your suggestions @jonahtanjz @damithc @acjh
packages/core-web/src/navMenuBar.js
Outdated
|
||
function toggleSiteNav() { | ||
if (!navMenuStates.siteNavOpened) { | ||
document.getElementById('site-nav').classList.add('nav-menu-open'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
an element with id site-nav
may not exist at all, its only in the layout files to serve as organizers / identifiers / some default styling which the author may discard
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As the site-nav, page-nav is "component-fied", although unlikely, we may have multiple in the page, which makes assigning identifiers to them ineffective (if we try to move site-nav
/ onto page-nav
<site-nav>
/ ) (forgot {{ pageNav }}
pageNavs
were already limited to one 🙈, that would have to be changed if we go with this)
I'm wondering if we should create a generic overlay
component instead (in fact bootstrap-vue has a b-sidebar
component we could easily use)
// author writing a navbar
<navbar>
...
<div slot="lower-navbar"> // a new slot for the "secondary navbar" you have here
<overlay icon="...">
<site-nav>
...
</site-nav>
</overlay>
<overlay icon="...">
{{ pageNav }}
</overlay>
</div>
</navbar>
This way, the author is able to show different content for the mobile navbar overlays. Content repetition can be easily resolved with <include>
s
The downsides are
- still more things to write for the author
- I imagine the overlay wouldn't exactly be like docusaurus', since its a generic component. (would cover the entire screen, instead of from the navbar downward)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this approach also likely results in zero changes needed to the backend code - slightly better decoupling
packages/core-web/src/navMenuBar.js
Outdated
} | ||
|
||
function closePageNav() { | ||
document.getElementById('page-nav').classList.remove('nav-menu-open'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
likewise
@@ -0,0 +1,65 @@ | |||
@media (min-width: 993px) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice work styling this. we can very likely reuse the work here still
packages/core/src/Page/index.js
Outdated
@@ -347,7 +347,7 @@ class Page { | |||
const headingStack = []; | |||
Object.keys(this.navigableHeadings).forEach((key) => { | |||
const currentHeadingLevel = this.navigableHeadings[key].level; | |||
const currentHeadingHTML = `<a class="nav-link py-1" href="#${key}">` | |||
const currentHeadingHTML = `<a onclick="closePageNav();" class="nav-link py-1" href="#${key}">` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we may catch the bubbled event instead (also checking e.target
and other parameters to make it as restrictive as possible) to hide the overlay. This way it stays decoupled
Thanks @ang-zeyu for the suggestions and review. Yup I agree that making the I can probably start working on making the |
Any thoughts? @damithc Option 1: + prettier (overlay would extend from navbar downward only)
Option 2: + generic, easy to implement
@jonahtanjz In either case, feel free to make the |
I need some info to understand this one. |
Sorry missed this out earlier. Does this mean
yup. the main issue here is that shifting to expressive layouts means there's no longer a mandatory "site-nav section". (can't know which section of the layout file that is supposed to be) (versus before, all files in We certainly could identify My opinion here is that the placement, styling of the button, etc. should not be fixed either (e.g. the author should be able to omit it entirely, or place it somewhere else, change the icon, ...). Suggestions hence:
We could also stick the current PR, which means nothing to do for the author at all - but it would still be a little restrictive (e.g. no "User Guide" title, images, etc. ). (Option 3) Or minimally, we can require the author to specify some sort of identifier in any element in the layout file https://markbind-master.netlify.app/userguide/tweakingthepagestructure#layouts (e.g. Not sure if there are better compromises |
Related question: does this require the author to specify two site-navs, one for normal use and one for mobile? If yes, that feels like extra work an author shouldn't have to do. |
Was thinking more along the lines of having a slot with fallback content in the |
Ahh, that sounds nice 👍
Would require some extra things inside the existing With @jonahtanjz 's proposal above, it is also optional (can be omitted, but also customised if needed) |
Sounds good. 👍 |
The main issue still exists though (identifying what to pull into the overlay). Suggestions:
|
@ang-zeyu I would say option 2 is better as it is more clear and straightforward. This will allow users to specify which |
👍 let's go with that then Could also still add in the default behaviour of assuming <= 1 page/sitenav (then pulling those in at a lower priority), so the author dosen't have to do anything. (if the page dosen't contain
Since its a new feature, don't worry about backward compatibility. Our template sites ( So, in this order:
Sounds right? @damithc @jonahtanjz |
Yes, no objections from me. 👍 |
04a4544
to
fb628c9
Compare
@ang-zeyu The PR is ready for review. I've created separate vue components for Also added tests and documentations :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the rework! : )
@ang-zeyu The PR is ready for review.
I've created separate vue components for
<site-nav-button />
and<page-nav-button />
. For now, they can be used under a slot in thenavbar
so that when it is opened, the menus extend nicely below the navbar. Both components will pull in identifier firstid=site/page-nav
then fallback to the first or only site/page navigation respectively. Likewise, I've also included fallback content in thelower-navbar
slot so that if the<site/page-nav-button />
components are not specified, they will still show up by default.
Couple of user issues:
- not scrollable (tried on mobile device + chrome resized)
- the page nav button shows up when there's no page nav
Currently, both components are
navbar
specific as some of the logics are handled in thenavbar
(hiding and showing buttons on different screen sizes, as well as adjust the fixed header padding when the secondary navbar appears/disappears). Perhaps in the future if we want to increase customizability, we can extract it out of thenavbar
and have a custom wrapper component just for the buttons 🤔?Also added tests and documentations :)
This sounds good, also not just for customisability - but separation of concerns
Left a few suggestions below on how to tackle the separation here (customisability can come later)
@@ -159,3 +161,40 @@ export function VueFixer (vue) { | |||
vue.mixins.unshift(mixin) | |||
return vue | |||
} | |||
|
|||
export function resizeHeader(isNavMenuShowing) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm not sure if we should be putting this within navbar.vue
. (note a header isn't limited to just a navbar)
how about adding the window listener in index.js
where the fixed header padding is originally setup?
It should keep these concerns closer.
Also, could override the selectors' properties (by cascading) instead querying every element
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, could override the selectors' properties (by cascading) instead querying every element
Sorry I don't really get what you meant by this 😅. Do you mean inserting new CSS rules to target these classes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the clarification :)
hmm not sure if we should be putting this within navbar.vue. (note a header isn't limited to just a navbar)
how about adding the window listener in index.js where the fixed header padding is originally setup?
It should keep these concerns closer.
One more thing, the reason why I placed this in navbar
is because a header padding resize is only necessary if the lower-navbar
slot is present. In this case, is it ok to let the listener stay in navbar
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One more thing, the reason why I placed this in navbar is because a header padding resize is only necessary if the lower-navbar slot is present. In this case, is it ok to let the listener stay in navbar?
not necessarily, e.g.
<header><box dismissible>...</box></header>
-> close box -> fixed-header-padding
is not changed (everything appears pushed downward)
It's part of a larger generic issue (not listed anywhere) that can be dealt with separately, or in this PR since its a prerequisite (preferably a separate commit)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh thanks, wasn't aware of this issue. Added a listener in index.js
to resize the header if it changes. Should be fixed now :)
this.toggleSiteNavButton(); | ||
}, | ||
toggleSiteNavButton() { | ||
const siteNavButton = document.getElementById('toggle-site-nav-button'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would :class
work? Similar to what you did for nav-menu-open
above. (also for the page nav variant)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm I'm not sure if :class
would work for this because this is querying page-nav-button
from site-nav-button
when it is opened. They should be considered sibling elements so not sure how to pass the data to each other unless we pass it to the parent and then back to the child. Will just using getElementById
be better because the buttons id
are fixed and will not be changed by the user 🤔?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ahh, my bad. I didn't notice that this was for the other component at all. 🙈
They should be considered sibling elements so not sure how to pass the data to each other unless we pass it to the parent and then back to the child.
I agree this is a little messy (and again reintroduces coupling with the navbar).
my concern here is with trying to limit dom effects that can be limited to vue to be limited to vue, to minimise side effects outside of vue. (as opposed to interfacing with <ul>
/ <li>
in dropdowns - no choice there)
but introducing global state management for this seems overkill as well.. hmm.......
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about just a event subscribe / consume implementation?
// pagenavbutton / sitenavbutton
subscribe('closeoverlays', () => this.show = false)
publish('closeoverlays')
// simple event implementation
window.subscribers = [ ... ]
......... or just this https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
this way the dom manipulation stays within the subscriber component (page / site button); won't need to tie the implementation to the classes and ids as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion 😄
this.isNavMenuShowing = false; | ||
}, | ||
toggleNavMenu() { | ||
if ((this.hasPageNav && window.innerWidth < 1300) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about (to avoid coupling this slot showing with the buttons):
- let the button components define their own
window.innerWidth
limits, and destroy themselves upon exceeding this limit (or show themselves) - let
navbar
do a generic check for whether there is any html within the div you added above (then this slot can also be used for non-button content)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shifted this to their individual component. navbar
should be free of page/site-nav-button
components other than the fallback content in the slot
.
<span class="navbar-toggler-icon"></span> | ||
<slot name="collapse"></slot> | ||
</button> | ||
<div ref="navbarContainer"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's this ref
for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops it's not in used anymore 😬. Has been removed.
}; | ||
|
||
const $el = $(this.$refs.pageNavMenu); | ||
const pageNav = document.getElementById('page-nav'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cloning the html directly would disable reactivity / other things if the site-nav
/ page-nav
has reactive elements that have already been processed in some way (e.g. vue components)
you can refer to retriever.vue
(used in <panel src="...">
) for mounting a clone of the original html source (as received by the browser).
However, it makes a webrequest (although it would likely hit the cache), so if there's a 'native' method to retrieve the original page source, all the more better. If not, directly reusing <retriever>
should be fine too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright I've decided to use <retriever>
to get the contents of the identifier id=page/site-nav
. However, if these identifiers are not present, will have to look for their respective classes still so it will fallback to using appendChild
. Not sure if that's fine?
@@ -0,0 +1,39 @@ | |||
.hide-nav-button { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about: create a internal base <overlay>
component, with these styles
use it in site/page-nav-button
so we can scope these styles still, also to avoid repeating similar logic / template in both
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created an overlay
component for both <page/site-nav-button>
. Abstracted all the similar template and logic into this component.
@ang-zeyu Updated the PR accordingly. Would appreciate if you can look through it :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, just a couple more nits:
</script> | ||
|
||
<style scoped> | ||
#toggle-site-nav-button { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's rename these into classes since the ids are no longer necessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Updated :)
}, | ||
computed: { | ||
showPageNav() { | ||
return this.show && this.hasPageNav; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should it have || this.hasIdentifier
as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm actually this was implemented before but this will cause the page/site nav button to show up even if they are not present on the page. This happens when there is an identifier, for example, id=page-nav
but the page does not have any headers, which will display an empty page nav menu when opened.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ahh I see. Thanks for clarifying!
}, | ||
computed: { | ||
showSiteNav() { | ||
return this.show && this.hasSiteNav; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
<template> | ||
<overlay | ||
v-if="showPageNav" | ||
:type="'pageNav'" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could do
:type="'pageNav'" | |
type="pageNav" |
also for site nav, and :fragment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Updated :)
packages/core-web/src/index.js
Outdated
insertCss(`.nav-menu-open { max-height: calc(100% - ${headerHeight}px); }`); | ||
} | ||
|
||
function addResizeHeaderListener() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouhld we put this under detectAndApplyFixedHeaderStyles
instead? (it has a guard for no fixed header as well)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Shifted this into detectAndApplyFixedHeaderStyles
.
@@ -0,0 +1,185 @@ | |||
<template> | |||
<div> | |||
<span |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: pull the icon upward using scoped slots, so the component specific styles can be moved up there
not sure if it would end up more verbose though 😐
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shifted this up. Doesn't look too verbose :)
} | ||
|
||
.site-nav-menu, | ||
.page-nav-menu { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could we combine these?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Updated :)
docs/userGuide/syntax/navBars.mbdf
Outdated
To make these accessible on smaller screens, you can use the `<site-nav-button />` and `<page-nav-button />` components in the `lower-navbar` slot. By default, if the `lower-navbar` slot is not specified, both the site and | ||
page navigation buttons will automatically be added if they exist. | ||
|
||
```html |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's disable the line numbers here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright updated :)
docs/userGuide/syntax/navBars.mbdf
Outdated
|
||
Component | Description | ||
--- | --- | ||
`page-nav-button` | Will pull everything wrapped with an identifier, `id=page-nav`, in the layout file into the menu. Alternatively, if the identifier is not present, it will pull the page navigation menu in automatically if it exist. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's shorten this
`page-nav-button` | Will pull everything wrapped with an identifier, `id=page-nav`, in the layout file into the menu. Alternatively, if the identifier is not present, it will pull the page navigation menu in automatically if it exist. | |
`page-nav-button` | Pulls any element with an identifier, `id=page-nav` into the menu. If no such element exists, it pulls any [page navigation menu](insert link here) used in the layout. |
or anything else :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Updated :)
@@ -120,7 +120,7 @@ code.hljs.inline { | |||
header[fixed] { | |||
position: fixed; | |||
width: 100%; | |||
z-index: 1000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Fixed :)
@ang-zeyu Thanks for the review! The PR is updated accordingly except for the part regarding the |
}, | ||
beforeDestroy() { | ||
$('.dropdown', this.$el).off('click').offBlur(); | ||
if (this.slots.collapse) $('[data-toggle="collapse"]', this.$el).off('click'); | ||
$(window).off(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's make it specific, so other listeners don't get removed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright thanks. Updated :)
}, | ||
computed: { | ||
showPageNav() { | ||
return this.show && this.hasPageNav; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ahh I see. Thanks for clarifying!
@@ -52,11 +60,17 @@ export default { | |||
default: 'sibling-or-child', | |||
}, | |||
}, | |||
provide() { | |||
return { | |||
toggleLowerNavbar: this.toggleLowerNavbar, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about just a prop? since they're in the same compilation scope
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm just to clarify, if this is passed to <page/site-nav-button>
component through a prop, since lower-navbar
is a slot, if a user defines new content in the layout file for this slot without specifying the prop, for example,
<div slot="lower-navbar">
<site-nav-button />
</div>
then it won't be passed down to the component in this case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
}, | ||
provide() { | ||
return { | ||
getNavMenuContent: this.getPageNavContent, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sorry, commented on the wrong line 🙊
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the quick clarification 👍. Updated :)
@ang-zeyu Thanks for the review 👍. PR is updated accordingly :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lgtm! 👍
Let's organise the commits if that's ok with you (just separate addResizeHeaderListener
will do, with suitable commit titles / messages)
Implement support for site and page navigation menus on smaller screens
Note the hard limit for the merge / squash commit title is 72 - 8 characters (brackets and pr number github appends)
While not necessary, try to keep < 50 characters too (succinct titles go a long way in making the history easy to read)
E.g. Implement mobile site and page navigation menus
would do for the merge commit title
81c666e
to
b5d7f2b
Compare
@jonahtanjz Thanks, sorry for the over simplification, you'll need to shift and squash the other ones into one commit too (I'm going to do a merge commit here) |
When header size is changed, the respective css rules that depend on the height of the header remain the same. Let's modify the css rules to change according to the size of the header.
b5d7f2b
to
732c39d
Compare
Oh right sorry about that 😅. Updated the commits :) |
What is the purpose of this pull request?
Overview of changes:
Fixes #980, fixes #591.
Currently, siteNav and pageNav are hidden when screen size is small (< 993px). This PR adds an additional navigation bar below topNav to toggle the siteNav and pageNav menu when screen is small (similar to Docusaurus design).
This is done by adding additional HTML code directly in
index.js
ofPage
, similar to how scroll to top button is implemented. Additional Javascript is also added to help with the toggling of the buttons. To check if a page contains siteNav or pageNav, a<script>
tag is added tositeNavProcessor.js
andindex.js
ofPage
to call the custom js function to let it know if they are present respectively on the page.Anything you'd like to highlight / discuss:
Most of the functions are binded to
window
and the function calls are done using<script>
. Is this method of implementation recommended? Is there a better way to call these functions when building the siteNav or pageNav?Testing instructions:
Proposed commit message: (wrap lines at 72 characters)
Implement mobile site and page navigation menus
Checklist: ☑️