From 808285d2cb0300d918d04a1bac414405ef048190 Mon Sep 17 00:00:00 2001 From: Jeremy Gordillo Date: Thu, 11 Jan 2024 18:16:09 +0100 Subject: [PATCH] [DEV-1243] Guide mobile menu (#535) * Implementation of mobile guide menu * refactor top position value * refactor * design improvements * fix duplicated translation key * Create thirty-eels-notice.md * udpate changeset --------- Co-authored-by: marcobottaro <39835990+marcobottaro@users.noreply.github.com> --- .changeset/thirty-eels-notice.md | 5 + .../guides/[...productGuidePage]/page.tsx | 2 +- .../components/atoms/GuideMenu/GuideMenu.tsx | 341 +++++++----------- .../atoms/GuideMenu/GuideVersionSelector.tsx | 47 +++ .../src/components/atoms/GuideMenu/Menu.tsx | 180 +++++++++ apps/nextjs-website/src/messages/it.json | 3 +- 6 files changed, 363 insertions(+), 215 deletions(-) create mode 100644 .changeset/thirty-eels-notice.md create mode 100644 apps/nextjs-website/src/components/atoms/GuideMenu/GuideVersionSelector.tsx create mode 100644 apps/nextjs-website/src/components/atoms/GuideMenu/Menu.tsx diff --git a/.changeset/thirty-eels-notice.md b/.changeset/thirty-eels-notice.md new file mode 100644 index 0000000000..5bd9725855 --- /dev/null +++ b/.changeset/thirty-eels-notice.md @@ -0,0 +1,5 @@ +--- +"nextjs-website": patch +--- + +[DEV-1243] Guide mobile menu diff --git a/apps/nextjs-website/src/app/[productSlug]/guides/[...productGuidePage]/page.tsx b/apps/nextjs-website/src/app/[productSlug]/guides/[...productGuidePage]/page.tsx index 4235e6254e..8f0921dae1 100644 --- a/apps/nextjs-website/src/app/[productSlug]/guides/[...productGuidePage]/page.tsx +++ b/apps/nextjs-website/src/app/[productSlug]/guides/[...productGuidePage]/page.tsx @@ -96,7 +96,7 @@ const Page = async ({ params }: { params: Params }) => { ({ - [`&`]: { - '--x': 32, - }, - [`& .${treeItemClasses.content}`]: { - boxSizing: 'border-box', - flexDirection: 'row-reverse', - width: '100%', - paddingTop: 0, - paddingBottom: 0, - paddingLeft: 0, - paddingRight: 32, - alignItems: 'space-between', - }, - [`& .${treeItemClasses.content}:has(.${treeItemClasses.iconContainer}:empty)`]: - { - paddingRight: 0, - }, - [`& .${treeItemClasses.iconContainer}`]: { - justifyContent: 'flex-end', - marginRight: 0, - paddingRight: 0, - paddingLeft: 0, - }, - [`& .${treeItemClasses.iconContainer}:empty`]: { - display: 'none', - }, - [`& .${treeItemClasses.content} > .${treeItemClasses.label}`]: { - display: 'flex', - flexDirection: 'column', - alignItems: 'stretch', - position: 'relative', - }, - [`& .${treeItemClasses.content} > .${treeItemClasses.label} > a`]: { - paddingTop: 16, - paddingBottom: 16, - paddingRight: 32, - }, - [`& ul`]: { - paddingLeft: 0, - '--y': 'calc(var(--x) + 0)', - }, - [`& li`]: { - '--x': 'calc(var(--y) + 24)', - }, - ['& a']: { - paddingLeft: 'calc(1px * var(--x))', - }, - [`& .${treeItemClasses.group}`]: { - marginLeft: 0, - marginRight: 0, - }, - [`& .${treeItemClasses.group} .${treeItemClasses.label}`]: { - paddingLeft: 0, - paddingRight: 0, - }, - [`& .${treeItemClasses.label}`]: { - padding: 0, - paddingLeft: 0, - }, - [`& .${treeItemClasses.root}`]: { - margin: 0, - paddingLeft: 0, - }, - [`& .${treeItemClasses.selected}`]: { - borderRight: `2px solid ${theme.palette.primary.dark}`, - }, - [`& .${treeItemClasses.selected} > .${treeItemClasses.label} > *`]: { - color: theme.palette.primary.dark, - }, -})); +type GuideMenuProps = GuideMenuItemsProps; -const components: RenderingComponents = { - Item: ({ href, title, children }) => { - const label = ( - - {title} - - ); +export const PRODUCT_HEADER_HEIGHT = 75; - return ( - : undefined} - > - {children} - - ); - }, - Title: ({ children }) => ( - - {children} - - ), -}; - -const GuideMenu = ({ - menu, - assetsPrefix, - linkPrefix, - guideName, - versionName, - versions, -}: GuideMenuProps) => { +const GuideMenu = (menuProps: GuideMenuProps) => { + const [open, setOpen] = useState(false); const { palette } = useTheme(); - const t = useTranslations('shared'); + const t = useTranslations('productGuidePage'); + const isDesktop = useMediaQuery((theme: Theme) => theme.breakpoints.up('lg')); const scrollUp = useScrollUp(); const currentPath = usePathname(); + const segments = currentPath.split('/'); const expanded = segments.map((_, i) => segments.slice(0, i + 1).join('/')); + const top = scrollUp ? SITE_HEADER_HEIGHT + PRODUCT_HEADER_HEIGHT : PRODUCT_HEADER_HEIGHT; + + const height = `calc(100vh - ${top}px)`; + + const handleClick = useCallback(() => { + setOpen((prev) => !prev); + }, []); + + useEffect(() => { + if (isDesktop) { + setOpen(false); + } + }, [isDesktop]); + + const items = ( + + ); + return ( - - + - - {guideName} - - ({ - href: version.path, - label: version.name, - }))} - icons={{ opened: , closed: }} - buttonStyle={{ - color: palette.action.active, - display: 'flex', - justifyContent: 'space-between', - padding: '16px 32px', - }} - menuStyle={{ - style: { - width: '347px', - maxWidth: '347px', - left: 0, - right: 0, - }, - }} - menuAnchorOrigin={{ - vertical: 'bottom', - horizontal: 'center', + padding: { lg: '80px 0' }, + flexGrow: { lg: 0 }, + flexShrink: { lg: 0 }, }} - /> - } - defaultExpanded={expanded} - selected={currentPath} - defaultExpandIcon={} > - {renderMenu( - parseMenu(menu, { assetsPrefix, linkPrefix }), - React, - components - )} - - - + + + {t('tableOfContents')} + + + + + + {isDesktop && items} + + + {!isDesktop && ( + + + + {t('tableOfContents')} + + + + + + {items} + + )} + ); }; diff --git a/apps/nextjs-website/src/components/atoms/GuideMenu/GuideVersionSelector.tsx b/apps/nextjs-website/src/components/atoms/GuideMenu/GuideVersionSelector.tsx new file mode 100644 index 0000000000..029b3b1fdc --- /dev/null +++ b/apps/nextjs-website/src/components/atoms/GuideMenu/GuideVersionSelector.tsx @@ -0,0 +1,47 @@ +import Dropdown from '@/components/atoms/Dropdown/Dropdown'; +import { ExpandLess, ExpandMore } from '@mui/icons-material'; +import { translations } from '@/_contents/translations'; +import { useTheme } from '@mui/material'; + +export type GuideVersionSelectorProps = { + versionName: string; + versions: { name: string; path: string }[]; +}; + +const GuideVersionSelector = ({ + versionName, + versions, +}: GuideVersionSelectorProps) => { + const { palette } = useTheme(); + const { shared } = translations; + return ( + ({ + href: version.path, + label: version.name, + }))} + icons={{ opened: , closed: }} + buttonStyle={{ + color: palette.action.active, + display: 'flex', + justifyContent: 'space-between', + padding: '16px 32px', + }} + menuStyle={{ + style: { + width: '347px', + maxWidth: '347px', + left: 0, + right: 0, + }, + }} + menuAnchorOrigin={{ + vertical: 'bottom', + horizontal: 'center', + }} + /> + ); +}; + +export default GuideVersionSelector; diff --git a/apps/nextjs-website/src/components/atoms/GuideMenu/Menu.tsx b/apps/nextjs-website/src/components/atoms/GuideMenu/Menu.tsx new file mode 100644 index 0000000000..1d9a6c922a --- /dev/null +++ b/apps/nextjs-website/src/components/atoms/GuideMenu/Menu.tsx @@ -0,0 +1,180 @@ +import React, { useMemo } from 'react'; +import NextLink from 'next/link'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import ExpandLessIcon from '@mui/icons-material/ExpandLess'; +import OpenInNewIcon from '@mui/icons-material/OpenInNew'; +import { TreeItem, TreeView, treeItemClasses } from '@mui/lab'; +import { styled } from '@mui/material/styles'; +import { RenderingComponents, renderMenu } from 'gitbook-docs/renderMenu'; +import { parseMenu } from 'gitbook-docs/parseMenu'; + +import GuideVersionSelector, { + type GuideVersionSelectorProps, +} from './GuideVersionSelector'; +import { Typography } from '@mui/material'; + +const StyledTreeItem = styled(TreeItem)(({ theme }) => ({ + [`&`]: { + '--x': 32, + }, + [`& .${treeItemClasses.content}`]: { + boxSizing: 'border-box', + flexDirection: 'row-reverse', + width: '100%', + paddingTop: 0, + paddingBottom: 0, + paddingLeft: 0, + paddingRight: 32, + alignItems: 'space-between', + }, + [`& .${treeItemClasses.content}:has(.${treeItemClasses.iconContainer}:empty)`]: + { + paddingRight: 0, + }, + [`& .${treeItemClasses.iconContainer}`]: { + justifyContent: 'flex-end', + marginRight: 0, + paddingRight: 0, + paddingLeft: 0, + }, + [`& .${treeItemClasses.iconContainer}:empty`]: { + display: 'none', + }, + [`& .${treeItemClasses.content} > .${treeItemClasses.label}`]: { + display: 'flex', + flexDirection: 'column', + alignItems: 'stretch', + position: 'relative', + }, + [`& .${treeItemClasses.content} > .${treeItemClasses.label} > a`]: { + paddingTop: 16, + paddingBottom: 16, + paddingRight: 32, + }, + [`& ul`]: { + paddingLeft: 0, + '--y': 'calc(var(--x) + 0)', + }, + [`& li`]: { + '--x': 'calc(var(--y) + 24)', + }, + ['& a']: { + paddingLeft: 'calc(1px * var(--x))', + }, + [`& .${treeItemClasses.group}`]: { + marginLeft: 0, + marginRight: 0, + }, + [`& .${treeItemClasses.group} .${treeItemClasses.label}`]: { + paddingLeft: 0, + paddingRight: 0, + }, + [`& .${treeItemClasses.label}`]: { + padding: 0, + paddingLeft: 0, + }, + [`& .${treeItemClasses.root}`]: { + margin: 0, + paddingLeft: 0, + }, + [`& .${treeItemClasses.selected}`]: { + borderRight: `2px solid ${theme.palette.primary.dark}`, + }, + [`& .${treeItemClasses.selected} > .${treeItemClasses.label} > *`]: { + color: theme.palette.primary.dark, + }, +})); + +const components: RenderingComponents = { + Item: ({ href, title, children }) => { + const label = ( + + {title} + + ); + + return ( + : undefined} + > + {children} + + ); + }, + Title: ({ children }) => ( + + {children} + + ), +}; + +export type GuideMenuItemsProps = GuideVersionSelectorProps & { + guideName: string; + assetsPrefix: string; + currentPath?: string; + expanded?: string[]; + menu: string; + linkPrefix: string; +}; + +const GuideMenuItems = ({ + assetsPrefix, + currentPath, + expanded = [], + guideName, + linkPrefix, + menu, + versionName, + versions, +}: GuideMenuItemsProps) => { + const children = useMemo(() => { + const parsed = parseMenu(menu, { assetsPrefix, linkPrefix }); + return renderMenu(parsed, React, components); + }, [menu, assetsPrefix, linkPrefix]); + return ( + <> + + {guideName} + + + } + defaultExpanded={expanded} + selected={currentPath} + defaultExpandIcon={} + > + {children} + + + ); +}; + +export default GuideMenuItems; diff --git a/apps/nextjs-website/src/messages/it.json b/apps/nextjs-website/src/messages/it.json index abe42e55d1..4458fbb3c5 100644 --- a/apps/nextjs-website/src/messages/it.json +++ b/apps/nextjs-website/src/messages/it.json @@ -240,7 +240,8 @@ "description": "La pagina che stai cercando non esiste" }, "productGuidePage": { - "onThisPage": "In questa pagina" + "onThisPage": "In questa pagina", + "tableOfContents": "Tabella dei contenuti" }, "homepage": { "news": {