diff --git a/sites/shared/components/workbench/menus/mobile-menubar.mjs b/sites/shared/components/workbench/menus/mobile-menubar.mjs
index 633b4241cdd..8b4fab1fa56 100644
--- a/sites/shared/components/workbench/menus/mobile-menubar.mjs
+++ b/sites/shared/components/workbench/menus/mobile-menubar.mjs
@@ -5,18 +5,25 @@ import { CloseIcon } from 'shared/components/icons.mjs'
import { MobileMenubarContext } from 'shared/context/mobile-menubar-context.mjs'
import { shownHeaderSelector } from 'shared/components/wrappers/header.mjs'
+/**
+ * A component to display menu buttons and actions in mobile.
+ * Draws its contents from items added to the {@link MobileMenubarContext}
+ * @returns
+ */
export const MobileMenubar = () => {
const { setModal, clearModal, modalContent } = useContext(ModalContext)
const { menus, actions } = useContext(MobileMenubarContext)
const [selectedModal, setSelectedModal] = useState(false)
+ // get the content of the selected modal because this is what will be changing if there are updates
const selectedMenu = menus[selectedModal]
+ // when the content changes, or the selection changes
useEffect(() => {
// there's no selected modal, we're in the clear
if (!selectedModal) return
- // otherwise, set the modal and keep an internal record of having opened it
+ // generate a new modal with the content
const Modal = () => {
const closeModal = () => {
setSelectedModal(false)
@@ -40,9 +47,11 @@ export const MobileMenubar = () => {
)
}
+ // set it
setModal(Modal)
}, [selectedMenu, selectedModal, clearModal, setModal])
+ // clear the selection if the modal was cleared externally
useEffect(() => {
if (modalContent === null) {
setSelectedModal(false)
@@ -52,14 +61,14 @@ export const MobileMenubar = () => {
return (
{Object.keys(menus)
.sort((a, b) => menus[a].order - menus[b].order)
diff --git a/sites/shared/components/workbench/menus/shared/menu-wrapper.mjs b/sites/shared/components/workbench/menus/shared/menu-wrapper.mjs
index 7860ef995f3..b7f748165cb 100644
--- a/sites/shared/components/workbench/menus/shared/menu-wrapper.mjs
+++ b/sites/shared/components/workbench/menus/shared/menu-wrapper.mjs
@@ -3,6 +3,17 @@ import { WrenchIcon } from 'shared/components/icons.mjs'
import { useMobileMenu } from 'shared/context/mobile-menubar-context.mjs'
const defaultClasses = `w-1/3 shrink grow-0 lg:p-4 max-w-2xl h-full overflow-scroll`
+
+/**
+ * a wrapper that displays its contents normally on larger screens
+ * and adds its contents as a menu on the {@link MobileMenubar} for smaller screens
+ * @param {ReactChildren} children
+ * @param {String} wrapperClass - classes to add to the wrapper for larger screens
+ * @param {ReactComponent} Icon - Icon for the menu button on smaller screens
+ * @param {Boolean} [keepOpenOnClick=true] - should the modal for this menu stay open when clicked?
+ * @param {String} [type='settings'] - the type of menu this is. will be used to key the menu in the MobileMenubar
+ * @param {Number} [order=-1] - the order of this menu in the MobileMenubar
+ */
export const MenuWrapper = ({
children,
wrapperClass = defaultClasses,
@@ -16,10 +27,9 @@ export const MenuWrapper = ({
Icon,
menuContent: children,
keepOpenOnClick,
- type,
order,
}),
- [Icon, children, keepOpenOnClick, type]
+ [Icon, children, keepOpenOnClick, order]
)
useMobileMenu(type, menuProps)
diff --git a/sites/shared/components/workbench/new.mjs b/sites/shared/components/workbench/new.mjs
index 69ae3a277e9..a0af7157eb5 100644
--- a/sites/shared/components/workbench/new.mjs
+++ b/sites/shared/components/workbench/new.mjs
@@ -76,6 +76,7 @@ export const Workbench = ({ design, Design, DynamicDocs }) => {
const setView = useCallback(
(newView) => {
+ // hacky little way to scroll to the top but keep the menu hidden if it was hidden
const endScroll = Math.min(window.scrollY, 21)
window.scrollTo({ top: 0, behavior: 'instant' })
_setView(newView)
diff --git a/sites/shared/components/workbench/views/pattern-with-menu.mjs b/sites/shared/components/workbench/views/pattern-with-menu.mjs
index b361e474b7c..ef3ac13a20b 100644
--- a/sites/shared/components/workbench/views/pattern-with-menu.mjs
+++ b/sites/shared/components/workbench/views/pattern-with-menu.mjs
@@ -4,6 +4,7 @@ import { MenuWrapper } from 'shared/components/workbench/menus/shared/menu-wrapp
export const ns = headerNs
+/** a layout for views that include a drafted pattern, a sidebar menu, and the draft view header */
export const PatternWithMenu = ({
settings,
ui,
diff --git a/sites/shared/components/workbench/views/view-header.mjs b/sites/shared/components/workbench/views/view-header.mjs
index ed17c6a8805..cbaa2824a94 100644
--- a/sites/shared/components/workbench/views/view-header.mjs
+++ b/sites/shared/components/workbench/views/view-header.mjs
@@ -1,4 +1,4 @@
-import { useContext, useEffect, useMemo } from 'react'
+import { useContext, useMemo } from 'react'
import { PanZoomContext } from 'shared/components/workbench/pattern/pan-zoom-context.mjs'
import { useMobileAction } from 'shared/context/mobile-menubar-context.mjs'
import { useTranslation } from 'next-i18next'
@@ -80,10 +80,12 @@ export const ViewHeader = ({ update, settings, ui, control, setSettings }) => {
const { t } = useTranslation(ns)
const { zoomFunctions, zoomed } = useContext(PanZoomContext)
+ // make the zoom buttons so we can pass them to the mobile menubar
const headerZoomButtons = useMemo(
() => ,
[zoomed, t, zoomFunctions]
)
+ // add the zoom buttons as an action on the mobile menubar
useMobileAction('zoom', { order: 0, actionContent: headerZoomButtons })
return (
diff --git a/sites/shared/context/mobile-menubar-context.mjs b/sites/shared/context/mobile-menubar-context.mjs
index f5336a23d4c..3031e62f956 100644
--- a/sites/shared/context/mobile-menubar-context.mjs
+++ b/sites/shared/context/mobile-menubar-context.mjs
@@ -1,11 +1,34 @@
import React, { useState, useContext, useEffect, useCallback } from 'react'
+/**
+ * A context for holding elements from various places that should be presented
+ * cohesively in mobile, e.g. sidebar menus and toolbars
+ *
+ * There are two ways of presenting menus on the menubar:
+ * 1) Menus: These are larger interfaces that should be placed in a modal on mobile
+ * They will be presented as buttons on the menubar which will open a modal when clicked
+ * example: workbench nav menu
+ * 2) Actions: These are smaller and will be presented directly on the menubar
+ * example: zoom buttons
+ */
export const MobileMenubarContext = React.createContext(null)
+/**
+ * A provider for the {@link MobileMenubarContext}
+ * */
export const MobileMenubarContextProvider = ({ children }) => {
const [menus, setMenus] = useState({})
const [actions, setActions] = useState({})
+ /**
+ * Add a menu to the menubar
+ * @type Function
+ * @param {String} key - the key for this menu in the menus object
+ * @param {Object} menuProps - the properties of this menu
+ * @param {React.Component} menuProps.Icon - the icon for the menu button
+ * @param {ReactElement} menuProps.menuContent - the content of the menu to be displayed in a modal
+ * @param {Number} menuProps.order - the sort order of this menu
+ * */
const addMenu = useCallback(
(key, menuProps) => {
setMenus((oldMenus) => ({ ...oldMenus, [key]: menuProps }))
@@ -13,6 +36,11 @@ export const MobileMenubarContextProvider = ({ children }) => {
[setMenus]
)
+ /**
+ * Remove a menu from the menubar
+ * @type Function
+ * @param {String} key - the key that was used to add the menu to the menubar
+ */
const removeMenu = useCallback(
(key) => {
setMenus((oldMenus) => {
@@ -24,16 +52,28 @@ export const MobileMenubarContextProvider = ({ children }) => {
[setMenus]
)
+ /**
+ * Add an action to the menubar
+ * @param {String} key - the key for this action in the actions object
+ * @param {Object} actionProps - the properties of this action
+ * @param {ReactElement} actionProps.actionContent - the content of the action to be displayed in a modal
+ * @param {Number} actionProps.order - the sort order of this action
+ */
const addAction = useCallback(
- (key, content) => {
+ (key, actionProps) => {
setActions((oldActions) => ({
...oldActions,
- [key]: content,
+ [key]: actionProps,
}))
},
[setActions]
)
+ /**
+ * Remove an action from the menubar
+ * @type Function
+ * @param {String} key - the key that was used to add the action to the menubar
+ */
const removeAction = useCallback(
(key) => {
setActions((oldActions) => {
@@ -57,6 +97,14 @@ export const MobileMenubarContextProvider = ({ children }) => {
return {children}
}
+/**
+ * A hook to add content as a menu in the mobile menubar and handle remove on unmount
+ * @param {String} key - the key for this menu in the menus object
+ * @param {Object} menuProps - the properties of this menu
+ * @param {React.Component} menuProps.Icon - the icon for the menu button
+ * @param {ReactElement} menuProps.menuContent - the content of the menu to be displayed in a modal
+ * @param {Number} menuProps.order - the sort order of this menu
+ * */
export const useMobileMenu = (key, menuProps) => {
const { addMenu, removeMenu } = useContext(MobileMenubarContext)
@@ -67,6 +115,13 @@ export const useMobileMenu = (key, menuProps) => {
}, [menuProps, key, addMenu, removeMenu])
}
+/**
+ * A hook to add content as an action to the mobile menubar and handle removal on unmount
+ * @param @param {String} key - the key for this action in the actions object
+ * @param {Object} actionProps - the properties of this action
+ * @param {ReactElement} actionProps.actionContent - the content of the action to be displayed in a modal
+ * @param {Number} actionProps.order - the sort order of this action
+ */
export const useMobileAction = (key, content) => {
const { addAction, removeAction } = useContext(MobileMenubarContext)