From f6fcda8ca5b4b75fea02d9f78f3279612e9ccde7 Mon Sep 17 00:00:00 2001 From: Enoch Riese Date: Thu, 29 Jun 2023 17:29:13 +0000 Subject: [PATCH] (chore) document new mobile menu stuff --- .../workbench/menus/mobile-menubar.mjs | 27 ++++++--- .../workbench/menus/shared/menu-wrapper.mjs | 14 ++++- sites/shared/components/workbench/new.mjs | 1 + .../workbench/views/pattern-with-menu.mjs | 1 + .../workbench/views/view-header.mjs | 4 +- .../shared/context/mobile-menubar-context.mjs | 59 ++++++++++++++++++- 6 files changed, 92 insertions(+), 14 deletions(-) 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)