diff --git a/sites/shared/components/mdx/tabs.mjs b/sites/shared/components/mdx/tabs.mjs index 7b30835f92e..a386d012c62 100644 --- a/sites/shared/components/mdx/tabs.mjs +++ b/sites/shared/components/mdx/tabs.mjs @@ -19,7 +19,7 @@ export const Tabs = ({ tabs = '', active = 0, children }) => { {tablist.map((title, tabId) => ( + + ) + }, [selectedMenu, clearModal]) + + useEffect(() => { + if (!selectedModal) return + + setModal(Modal) + }, [selectedModal, Modal, setModal]) + + useEffect(() => { + if (modalContent === null) { + setSelectedModal(false) + } + }, [modalContent, setSelectedModal]) + + return ( +
+ {Object.keys(menus) + .sort((a, b) => menus[a].order - menus[b].order) + .map((m) => { + const Icon = menus[m].Icon + return ( + + ) + })} +
+ ) +} diff --git a/sites/shared/components/workbench/menus/shared/menu-wrapper.mjs b/sites/shared/components/workbench/menus/shared/menu-wrapper.mjs index 41ecdbce350..d77f145d7fc 100644 --- a/sites/shared/components/workbench/menus/shared/menu-wrapper.mjs +++ b/sites/shared/components/workbench/menus/shared/menu-wrapper.mjs @@ -1,58 +1,31 @@ -import { useContext, useState, useEffect, useCallback } from 'react' -import { ModalContext } from 'shared/context/modal-context.mjs' -import { ModalWrapper } from 'shared/components/wrappers/modal.mjs' -import { CloseIcon, WrenchIcon } from 'shared/components/icons.mjs' +import { useMemo } from 'react' +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' -const defaultButtonClasses = 'bottom-24 mb-16' +const defaultClasses = `w-1/3 shrink grow-0 lg:p-4 max-w-2xl h-full overflow-scroll` export const MenuWrapper = ({ children, wrapperClass = defaultClasses, - buttonClass = defaultButtonClasses, Icon = WrenchIcon, keepOpenOnClick = true, + type = 'settings', + order = -1, }) => { - const { setModal, clearModal, modalContent } = useContext(ModalContext) - const [modalOpen, setModalOpen] = useState(false) + const menuProps = useMemo( + () => ({ + Icon, + MenuContent: children, + keepOpenOnClick, + type, + order, + }), + [Icon, children, keepOpenOnClick, type] + ) - const Modal = useCallback(() => { - const closeModal = () => { - setModalOpen(false) - clearModal() - } - - return ( - -
{children}
- -
- ) - }, [children, clearModal, keepOpenOnClick]) - - useEffect(() => { - if (!modalOpen) return - - setModal(Modal) - }, [modalOpen, Modal, setModal]) - - useEffect(() => { - if (modalContent === null) setModalOpen(false) - }, [modalContent, setModalOpen]) - - const onClick = () => { - setModalOpen(true) - } + useMobileMenu(type, menuProps) return ( <> -
{children}
) diff --git a/sites/shared/components/workbench/new.mjs b/sites/shared/components/workbench/new.mjs index bbb4943645f..69ae3a277e9 100644 --- a/sites/shared/components/workbench/new.mjs +++ b/sites/shared/components/workbench/new.mjs @@ -1,5 +1,5 @@ // Hooks -import { useEffect, useState } from 'react' +import { useEffect, useState, useCallback } from 'react' import { useTranslation } from 'next-i18next' import { useView } from 'shared/hooks/use-view.mjs' import { usePatternSettings } from 'shared/hooks/use-pattern-settings.mjs' @@ -13,6 +13,7 @@ import { objUpdate, hasRequiredMeasurements } from 'shared/utils.mjs' import { WorkbenchHeader } from './header.mjs' import { ErrorView } from 'shared/components/error/view.mjs' import { ModalSpinner } from 'shared/components/modal/spinner.mjs' +import { MobileMenubar } from './menus/mobile-menubar.mjs' // Views import { DraftView, ns as draftNs } from 'shared/components/workbench/views/draft/index.mjs' import { SaveView, ns as saveNs } from 'shared/components/workbench/views/save/index.mjs' @@ -66,13 +67,23 @@ export const Workbench = ({ design, Design, DynamicDocs }) => { const controlState = useControlState() // State - const [view, setView] = useView() + const [view, _setView] = useView() const [settings, setSettings] = usePatternSettings() const [ui, setUi] = useState(defaultUi) const [error, setError] = useState(false) const [mounted, setMounted] = useState(false) const [missingMeasurements, setMissingMeasurements] = useState(false) + const setView = useCallback( + (newView) => { + const endScroll = Math.min(window.scrollY, 21) + window.scrollTo({ top: 0, behavior: 'instant' }) + _setView(newView) + window.scroll({ top: endScroll }) + }, + [_setView] + ) + // set mounted on mount useEffect(() => setMounted(true), [setMounted]) @@ -126,6 +137,7 @@ export const Workbench = ({ design, Design, DynamicDocs }) => { <> {error} + ) @@ -196,6 +208,7 @@ export const Workbench = ({ design, Design, DynamicDocs }) => {
{viewContent}
+
) } diff --git a/sites/shared/components/workbench/views/pattern-with-menu.mjs b/sites/shared/components/workbench/views/pattern-with-menu.mjs index 8a30cce825a..b361e474b7c 100644 --- a/sites/shared/components/workbench/views/pattern-with-menu.mjs +++ b/sites/shared/components/workbench/views/pattern-with-menu.mjs @@ -30,7 +30,7 @@ export const PatternWithMenu = ({ {title} {pattern} - {menu && {menu}} + {menu && {menu}} diff --git a/sites/shared/components/workbench/views/test/options.mjs b/sites/shared/components/workbench/views/test/options.mjs index c9c83801cb9..c7e3ae382d7 100644 --- a/sites/shared/components/workbench/views/test/options.mjs +++ b/sites/shared/components/workbench/views/test/options.mjs @@ -42,6 +42,7 @@ export const SampleItem = ({ name, passProps, t, updateFunc }) => { type="radio" checked={checked} className="radio radio-primary mr-2 radio-sm" + readOnly /> {t([name + '.t', name])} diff --git a/sites/shared/components/wrappers/context.mjs b/sites/shared/components/wrappers/context.mjs index 5e8a23fb514..3cc0e896247 100644 --- a/sites/shared/components/wrappers/context.mjs +++ b/sites/shared/components/wrappers/context.mjs @@ -1,11 +1,14 @@ import { ModalContextProvider } from 'shared/context/modal-context.mjs' import { LoadingContextProvider } from 'shared/context/loading-context.mjs' import { NavigationContextProvider } from 'shared/context/navigation-context.mjs' +import { MobileMenubarContextProvider } from 'shared/context/mobile-menubar-context.mjs' export const ContextWrapper = ({ children }) => ( - {children} + + {children} + ) diff --git a/sites/shared/components/wrappers/header.mjs b/sites/shared/components/wrappers/header.mjs index cbd712c4094..db85c780b3d 100644 --- a/sites/shared/components/wrappers/header.mjs +++ b/sites/shared/components/wrappers/header.mjs @@ -25,4 +25,8 @@ export const HeaderWrapper = ({ show, children }) => { ) } -export const shownHeaderSelector = 'group-[.header-shown]/layout:' +const shownHeaderClasses = { + 'bottom-16': 'group-[.header-shown]/layout:bottom-16', +} + +export const shownHeaderSelector = (cls) => shownHeaderClasses[cls] diff --git a/sites/shared/context/mobile-menubar-context.mjs b/sites/shared/context/mobile-menubar-context.mjs new file mode 100644 index 00000000000..142f67eac20 --- /dev/null +++ b/sites/shared/context/mobile-menubar-context.mjs @@ -0,0 +1,78 @@ +import React, { useState, useContext, useEffect, useCallback } from 'react' + +export const MobileMenubarContext = React.createContext(null) + +export const MobileMenubarContextProvider = ({ children }) => { + const [menus, setMenus] = useState({}) + const [actions, setActions] = useState({}) + + const addMenu = useCallback( + (key, menuProps) => { + setMenus((oldMenus) => ({ ...oldMenus, [key]: menuProps })) + }, + [setMenus] + ) + + const removeMenu = useCallback( + (key) => { + setMenus((oldMenus) => { + const newMenus = { ...oldMenus } + delete newMenus[key] + return newMenus + }) + }, + [setMenus] + ) + + const addAction = useCallback( + (key, content) => { + setActions((oldActions) => ({ + ...oldActions, + [key]: content, + })) + }, + [setActions] + ) + + const removeAction = useCallback( + (key) => { + setActions((oldActions) => { + const newActions = { ...oldActions } + delete newActions[key] + return newActions + }) + }, + [setActions] + ) + + const value = { + menus, + addMenu, + removeMenu, + actions, + addAction, + removeAction, + } + + return {children} +} + +export const useMobileMenu = (key, menuProps) => { + const { addMenu, removeMenu } = useContext(MobileMenubarContext) + + useEffect(() => { + addMenu(key, menuProps) + + return () => removeMenu(key) + }, [menuProps, key, addMenu, removeMenu]) +} + +export const useMobileAction = (key, content) => { + const { addAction, removeAction } = useContext(MobileMenubarContext) + + useEffect(() => { + addAction(key, content) + + return () => removeAction(key) + }, [content, key, addAction, removeAction]) +}