From cfebd1d3b08316ae9a7c059d04bc0ea6fcd7ec10 Mon Sep 17 00:00:00 2001 From: Sohee Lim Date: Thu, 7 Nov 2024 14:36:19 -0500 Subject: [PATCH] feat: add nav list to mobile nav --- assets/navigation-bundle.js | 39 ++-- assets/tailwind-output.css | 55 +++-- src/base/Button/index.tsx | 108 ++++------ src/lib/types.ts | 60 +++--- src/modules/navigation/MobileMenuModal.tsx | 80 +++---- src/modules/navigation/Navigation.tsx | 38 ++-- src/modules/side-nav/MobileMenuNavModule.tsx | 207 +++++++++++++++++++ src/modules/side-nav/index.ts | 2 +- src/modules/side-nav/renderSideNav.tsx | 15 ++ templates/article_page.hbs | 19 +- templates/category_page.hbs | 19 +- templates/home_page.hbs | 19 +- templates/section_page.hbs | 19 +- 13 files changed, 470 insertions(+), 210 deletions(-) create mode 100644 src/modules/side-nav/MobileMenuNavModule.tsx diff --git a/assets/navigation-bundle.js b/assets/navigation-bundle.js index 28d3bf4b5..0d5d03fad 100644 --- a/assets/navigation-bundle.js +++ b/assets/navigation-bundle.js @@ -79,19 +79,16 @@ const MobileMenuModal = ({ isOpen, close }) => { setTimeout(() => setModalTransition(true), 100); } }, [isOpen]); - return (jsxRuntimeExports.jsx(yt, { open: isOpen, onClose: handleClose, className: "MobileMenuModal relative z-modal md:hidden", children: jsxRuntimeExports.jsx("div", { className: cn("fixed bottom-0 left-0 right-0 flex w-screen translate-y-0 items-center transition-all", { - "opacity-1 translate-y-0": modalTransition, - "translate-y-4 opacity-0": !modalTransition, - }), children: jsxRuntimeExports.jsxs(je, { className: cn("w-full rounded-t-large border-t px-margin-mobile", { - "border-dark-surface-3 bg-dark-surface-1": theme === "dark", - "border-light-surface-3 bg-light-surface-1": theme === "light", - }), children: [jsxRuntimeExports.jsxs("div", { className: "pt-padding-x-large", children: [jsxRuntimeExports.jsx("button", { onClick: handleClose, className: "group absolute right-0 top-0 px-margin-mobile py-padding-x-large", children: jsxRuntimeExports.jsx(Close, { className: "h-3.5 w-3.5" }) }), jsxRuntimeExports.jsx("nav", {}), jsxRuntimeExports.jsx("div", { className: cn("my-3 border-t", { - "border-dark-surface-3": theme === "dark", - "border-light-surface-3": theme === "light", - }) }), jsxRuntimeExports.jsxs("div", { className: "flex flex-row items-center justify-between", children: [jsxRuntimeExports.jsx("h3", { className: cn("body-1", { - "text-light-neutral-1": theme === "light", - "text-dark-neutral-1": theme === "dark", - }), children: "Theme" }), jsxRuntimeExports.jsx(ThemeSwitch, {})] })] }), jsxRuntimeExports.jsx("div", { className: "py-padding-large", children: jsxRuntimeExports.jsx(PrimaryButton, { onClick: handleClose, className: "ml-padding-small-dense", label: "Submit Request", href: "https://support.uniswap.org/hc/en-us/requests/new", size: "large", theme: theme, color: "accent-2", fullWidth: true }) })] }) }) })); + return (jsxRuntimeExports.jsx(yt, { open: isOpen, onClose: handleClose, className: "MobileMenuModal relative z-modal md:hidden", children: jsxRuntimeExports.jsx("div", { className: cn('fixed bottom-0 left-0 right-0 flex w-screen translate-y-0 items-center transition-all', { + 'opacity-1 translate-y-0': modalTransition, + 'translate-y-4 opacity-0': !modalTransition, + }), children: jsxRuntimeExports.jsxs(je, { className: cn('w-full rounded-t-large border-t px-margin-mobile', { + 'border-dark-surface-3 bg-dark-surface-1': theme === 'dark', + 'border-light-surface-3 bg-light-surface-1': theme === 'light', + }), children: [jsxRuntimeExports.jsxs("div", { className: "pt-margin-mobile", children: [jsxRuntimeExports.jsxs("div", { className: "relative", children: [jsxRuntimeExports.jsx("div", { className: "flex flex-row-reverse mb-margin-mobile", children: jsxRuntimeExports.jsx("button", { onClick: handleClose, className: "group", children: jsxRuntimeExports.jsx(Close, { className: "h-3.5 w-3.5" }) }) }), jsxRuntimeExports.jsx("nav", { id: "new-mobile-nav" })] }), jsxRuntimeExports.jsxs("div", { className: "flex flex-row items-center justify-between", children: [jsxRuntimeExports.jsx("h3", { className: cn('body-1', { + 'text-light-neutral-1': theme === 'light', + 'text-dark-neutral-1': theme === 'dark', + }), children: "Theme" }), jsxRuntimeExports.jsx(ThemeSwitch, {})] })] }), jsxRuntimeExports.jsx("div", { className: "py-margin-mobile", children: jsxRuntimeExports.jsx(PrimaryButton, { onClick: handleClose, className: "ml-padding-small-dense", label: "Submit Request", href: "https://support.uniswap.org/hc/en-us/requests/new", size: "large", theme: theme, color: "accent-2", fullWidth: true }) })] }) }) })); }; const Navigation = () => { @@ -108,18 +105,18 @@ const Navigation = () => { } }; handleScroll(); - window.addEventListener("scroll", handleScroll, { passive: true }); + window.addEventListener('scroll', handleScroll, { passive: true }); return () => { - window.removeEventListener("scroll", handleScroll); + window.removeEventListener('scroll', handleScroll); }; }, [setScrollIsOnTop]); - return (jsxRuntimeExports.jsxs(UIProvider, { children: [jsxRuntimeExports.jsx("nav", { className: cn("Navigation fixed top-0 left-0 right-0 z-nav flex w-screen justify-center bg-light-surface-1 dark:border-dark-surface-3 dark:bg-dark-surface-1", { - "border-b": !scrollIsOnTop, - }), children: jsxRuntimeExports.jsxs("div", { className: "flex w-full flex-row items-center justify-between border-light-surface-3 px-4 py-[1.15625rem] md:px-[0.9375rem] md:py-3 md:h-[4.5rem]", children: [jsxRuntimeExports.jsx("div", { className: "flex flex-row items-center", children: jsxRuntimeExports.jsxs(LinkBase, { href: "/", className: "flex flex-row items-center", children: [jsxRuntimeExports.jsx(MiniUnicon, { className: "mb-[0.1875rem] h-8 w-8" }), jsxRuntimeExports.jsx("p", { className: "body-3 md:button-label-2 ml-2 text-light-accent-1 dark:text-dark-accent-1", children: "Uniswap Support" })] }) }), jsxRuntimeExports.jsx("div", { className: "md:hidden", children: jsxRuntimeExports.jsx(ButtonBase, { onClick: () => { + return (jsxRuntimeExports.jsxs(UIProvider, { children: [jsxRuntimeExports.jsx("nav", { className: cn('Navigation fixed top-0 left-0 right-0 z-nav flex w-screen justify-center bg-light-surface-1 dark:border-dark-surface-3 dark:bg-dark-surface-1', { + 'border-b': !scrollIsOnTop, + }), children: jsxRuntimeExports.jsxs("div", { className: "flex w-full flex-row items-center justify-between border-light-surface-3 px-4 py-[1.15625rem] md:px-[0.9375rem] md:py-3 md:h-[4.5rem]", children: [jsxRuntimeExports.jsx("div", { className: "flex flex-row items-center", children: jsxRuntimeExports.jsxs(LinkBase, { href: "/", className: "flex flex-row items-center", children: [jsxRuntimeExports.jsx(MiniUnicon, { className: "mb-[0.1875rem] h-8 w-8" }), jsxRuntimeExports.jsx("p", { className: "body-3 md:button-label-2 ml-2 text-light-accent-1 dark:text-dark-accent-1", children: "Uniswap Support" })] }) }), jsxRuntimeExports.jsx("div", { className: "md:hidden", children: jsxRuntimeExports.jsx(ButtonBase, { id: "mobile-menu-button", onClick: () => { setMenuIsOpen((prev) => !prev); - }, children: jsxRuntimeExports.jsx(Menu, { className: "h-padding-large w-padding-large" }) }) }), jsxRuntimeExports.jsxs("div", { className: "hidden md:flex", children: [jsxRuntimeExports.jsx(ThemeSwitch, {}), jsxRuntimeExports.jsx(PrimaryButton, { className: "ml-padding-small-dense !my-auto !py-0 !h-8", label: "Submit Request", href: "https://support.uniswap.org/hc/en-us/requests/new", color: "accent-2" })] })] }) }), jsxRuntimeExports.jsx("div", { className: cn("fixed inset-0 z-scrim bg-scrim transition duration-500", { - "pointer-events-none opacity-0": !menuIsOpen, - "opacity-1": menuIsOpen, + }, children: jsxRuntimeExports.jsx(Menu, { className: "h-padding-large w-padding-large" }) }) }), jsxRuntimeExports.jsxs("div", { className: "hidden md:flex", children: [jsxRuntimeExports.jsx(ThemeSwitch, {}), jsxRuntimeExports.jsx(PrimaryButton, { className: "ml-padding-small-dense !my-auto !py-0 !h-8", label: "Submit Request", href: "https://support.uniswap.org/hc/en-us/requests/new", color: "accent-2" })] })] }) }), jsxRuntimeExports.jsx("div", { className: cn('fixed inset-0 z-scrim bg-scrim transition duration-500', { + 'pointer-events-none opacity-0': !menuIsOpen, + 'opacity-1': menuIsOpen, }) }), jsxRuntimeExports.jsx(MobileMenuModal, { isOpen: menuIsOpen, close: () => { setMenuIsOpen(false); } })] })); diff --git a/assets/tailwind-output.css b/assets/tailwind-output.css index 4a2505853..c8d842bf5 100644 --- a/assets/tailwind-output.css +++ b/assets/tailwind-output.css @@ -644,6 +644,10 @@ video { inset: 0px; } +.-top-1 { + top: -0.25rem; +} + .bottom-0 { bottom: 0px; } @@ -724,6 +728,11 @@ video { margin-bottom: 0.375rem; } +.my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + .my-3 { margin-top: 0.75rem; margin-bottom: 0.75rem; @@ -739,6 +748,11 @@ video { margin-bottom: 2rem; } +.my-margin-mobile { + margin-top: 1.5rem; + margin-bottom: 1.5rem; +} + .my-padding-large { margin-top: 1.5rem; margin-bottom: 1.5rem; @@ -768,6 +782,10 @@ video { margin-bottom: 0.1875rem; } +.mb-margin-mobile { + margin-bottom: 1.5rem; +} + .ml-2 { margin-left: 0.5rem; } @@ -816,6 +834,10 @@ video { margin-top: 1.25rem; } +.mt-6 { + margin-top: 1.5rem; +} + .mt-\[1\.875rem\] { margin-top: 1.875rem; } @@ -967,6 +989,10 @@ video { height: 100vh; } +.max-h-\[60vh\] { + max-height: 60vh; +} + .\!min-h-\[7\.5rem\] { min-height: 7.5rem !important; } @@ -1121,6 +1147,10 @@ video { flex-direction: row; } +.flex-row-reverse { + flex-direction: row-reverse; +} + .flex-col { flex-direction: column; } @@ -1181,12 +1211,6 @@ video { margin-bottom: calc(3rem * var(--tw-space-y-reverse)); } -.space-y-2 > :not([hidden]) ~ :not([hidden]) { - --tw-space-y-reverse: 0; - margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); - margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); -} - .space-y-4 > :not([hidden]) ~ :not([hidden]) { --tw-space-y-reverse: 0; margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); @@ -1645,6 +1669,11 @@ video { padding-bottom: 0.75rem; } +.py-margin-mobile { + padding-top: 1.5rem; + padding-bottom: 1.5rem; +} + .py-margin-mobile-dense { padding-top: 0.75rem; padding-bottom: 0.75rem; @@ -1655,11 +1684,6 @@ video { padding-bottom: 2.5rem; } -.py-padding-large { - padding-top: 1.5rem; - padding-bottom: 1.5rem; -} - .py-padding-small { padding-top: 0.75rem; padding-bottom: 0.75rem; @@ -3210,6 +3234,10 @@ html:has(.ArticlePage) { margin-top: 1rem; } +.first\:mt-5:first-child { + margin-top: 1.25rem; +} + .first\:mt-6:first-child { margin-top: 1.5rem; } @@ -3299,11 +3327,6 @@ html:has(.ArticlePage) { color: rgb(252 116 254 / var(--tw-text-opacity)); } -.group:hover .group-hover\:text-dark-neutral-1 { - --tw-text-opacity: 1; - color: rgb(255 255 255 / var(--tw-text-opacity)); -} - .group:hover .group-hover\:text-light-accent-1 { --tw-text-opacity: 1; color: rgb(245 13 180 / var(--tw-text-opacity)); diff --git a/src/base/Button/index.tsx b/src/base/Button/index.tsx index 239248714..ce7830899 100644 --- a/src/base/Button/index.tsx +++ b/src/base/Button/index.tsx @@ -1,8 +1,8 @@ -import { FC, PropsWithChildren } from "react"; +import { FC, PropsWithChildren } from 'react'; -import cn from "classnames"; -import { Theme } from "../../utils/storage"; -import React from "react"; +import cn from 'classnames'; +import { Theme } from '../../utils/storage'; +import React from 'react'; function isValidEmail(href: string): boolean { // Regular expression to validate email address @@ -14,7 +14,7 @@ function formatHrefAsMailto(href: string): string { // Check if the href is a valid email address if (isValidEmail(href)) { // If it doesn't already start with "mailto:", add it - if (!href.startsWith("mailto:")) { + if (!href.startsWith('mailto:')) { return `mailto:${href}`; } } @@ -24,72 +24,57 @@ function formatHrefAsMailto(href: string): string { interface PrimaryButtonProps extends ButtonBaseProps { label: string; - color?: "accent-1" | "accent-2"; - size?: "medium" | "large"; + color?: 'accent-1' | 'accent-2'; + size?: 'medium' | 'large'; fullWidth?: boolean; theme?: Theme; } interface PrimaryLinkProps extends LinkBaseProps { label: string; - color?: "accent-1" | "accent-2" | "surface-3"; - size?: "medium" | "large"; + color?: 'accent-1' | 'accent-2' | 'surface-3'; + size?: 'medium' | 'large'; fullWidth?: boolean; theme?: Theme; } -export const PrimaryButton: FC = ( - props -) => { - const { - color = "accent-1", - size = "medium", - fullWidth = false, - theme, - } = props; - const containerStyle = cn( - "transition-colors flex flex-row items-center justify-center", - { - "bg-light-accent-1 dark:bg-dark-accent-1 hover:bg-light-accent-1-hovered dark:hover:bg-dark-accent-1-hovered": - !theme && color === "accent-1", - "bg-light-accent-2 dark:bg-dark-accent-2 hover:bg-light-accent-2-hovered dark:hover:bg-dark-accent-2-hovered": - !theme && color === "accent-2", - "bg-light-surface-3 dark:bg-dark-surface-3 hover:bg-light-surface-3-hovered dark:hover:bg-dark-surface-3-hovered": - !theme && color === "surface-3", - "bg-dark-accent-1 hover:bg-dark-accent-1-hovered": - theme === "dark" && color === "accent-1", - "bg-light-accent-1 hover:bg-light-accent-1-hovered": - theme === "light" && color === "accent-1", - "bg-dark-accent-2 hover:bg-dark-accent-2-hovered": - theme === "dark" && color === "accent-2", - "bg-light-accent-2 hover:bg-light-accent-2-hovered": - theme === "light" && color === "accent-2", - "rounded-small px-padding-small py-padding-small-dense": - size === "medium", - "rounded-medium px-padding-large p-padding-medium": size === "large", - } - ); +export const PrimaryButton: FC = (props) => { + const { color = 'accent-1', size = 'medium', fullWidth = false, theme } = props; + const containerStyle = cn('transition-colors flex flex-row items-center justify-center', { + 'bg-light-accent-1 dark:bg-dark-accent-1 hover:bg-light-accent-1-hovered dark:hover:bg-dark-accent-1-hovered': + !theme && color === 'accent-1', + 'bg-light-accent-2 dark:bg-dark-accent-2 hover:bg-light-accent-2-hovered dark:hover:bg-dark-accent-2-hovered': + !theme && color === 'accent-2', + 'bg-light-surface-3 dark:bg-dark-surface-3 hover:bg-light-surface-3-hovered dark:hover:bg-dark-surface-3-hovered': + !theme && color === 'surface-3', + 'bg-dark-accent-1 hover:bg-dark-accent-1-hovered': theme === 'dark' && color === 'accent-1', + 'bg-light-accent-1 hover:bg-light-accent-1-hovered': theme === 'light' && color === 'accent-1', + 'bg-dark-accent-2 hover:bg-dark-accent-2-hovered': theme === 'dark' && color === 'accent-2', + 'bg-light-accent-2 hover:bg-light-accent-2-hovered': theme === 'light' && color === 'accent-2', + 'rounded-small px-padding-small py-padding-small-dense': size === 'medium', + 'rounded-medium px-padding-large p-padding-medium': size === 'large', + }); const textStyle = cn({ - "text-white": color === "accent-1", - "text-light-accent-1 dark:text-dark-accent-1": color === "accent-2", - "text-light-neutral-1 dark:text-dark-neutral-1": color === "surface-3", - "button-label-4": size === "medium", - "button-label-2": size === "large", + 'text-white': color === 'accent-1', + 'text-light-accent-1 dark:text-dark-accent-1': color === 'accent-2', + 'text-light-neutral-1 dark:text-dark-neutral-1': color === 'surface-3', + 'button-label-4': size === 'medium', + 'button-label-2': size === 'large', }); - if ("href" in props) { + if ('href' in props) { const { label, href, ariaLabel, className, onClick } = props; return (
= (props) => { - const containerStyle = "TextButton group"; - const textStyle = "decoration-inherit"; + const containerStyle = 'TextButton group'; + const textStyle = 'decoration-inherit'; const textClassName = props.textClassName; - if ("href" in props) { + if ('href' in props) { const { label, href, ariaLabel, className, onClick } = props; return ( @@ -165,6 +150,7 @@ type ButtonBaseProps = { onClick?: () => void; ariaLabel?: string; role?: string; + id?: string; }; export const ButtonBase: FC> = ({ @@ -173,14 +159,10 @@ export const ButtonBase: FC> = ({ children, ariaLabel, role, + id, }) => { return ( - ); @@ -193,8 +175,8 @@ type LinkBaseProps = { onClick?: () => void; }; -const OPEN_IN_NEW_TAB_PROPS = { target: "_blank", rel: "noreferrer noopener" }; -const OPEN_IN_CURRENT_TAB_PROPS = { target: "_self" }; +const OPEN_IN_NEW_TAB_PROPS = { target: '_blank', rel: 'noreferrer noopener' }; +const OPEN_IN_CURRENT_TAB_PROPS = { target: '_self' }; export const LinkBase: FC> = ({ className, @@ -203,10 +185,8 @@ export const LinkBase: FC> = ({ ariaLabel, onClick, }) => { - const isInternalLink = href.startsWith("/") || href.startsWith("#"); - const targetProps = isInternalLink - ? OPEN_IN_CURRENT_TAB_PROPS - : OPEN_IN_NEW_TAB_PROPS; + const isInternalLink = href.startsWith('/') || href.startsWith('#'); + const targetProps = isInternalLink ? OPEN_IN_CURRENT_TAB_PROPS : OPEN_IN_NEW_TAB_PROPS; return ( = ({ isOpen, close }) => { >
-
- - -
-
-

Theme

-
+
{ const [scrollIsOnTop, setScrollIsOnTop] = useState(false); @@ -24,10 +24,10 @@ const Navigation: FC = () => { } }; handleScroll(); - window.addEventListener("scroll", handleScroll, { passive: true }); + window.addEventListener('scroll', handleScroll, { passive: true }); return () => { - window.removeEventListener("scroll", handleScroll); + window.removeEventListener('scroll', handleScroll); }; }, [setScrollIsOnTop]); @@ -35,9 +35,9 @@ const Navigation: FC = () => {
{ setMenuIsOpen((prev) => !prev); }} @@ -71,13 +72,10 @@ const Navigation: FC = () => {
= ({ sideNavData, navState }) => { + const [selectedCategory, setSelectedCategory] = useState(null); + const [activeSection, setActiveSection] = useState<{ [key: number]: boolean }>({ + [navState.section || '']: true, + }); + + const handleSectiontoggle = (sectionId: number) => { + setActiveSection((prev) => ({ + ...prev, + [sectionId]: !prev[sectionId], + })); + }; + + if (!sideNavData) { + return null; + } + + console.log(selectedCategory); + return ( + <> +
+ {sideNavData.categories.map((category) => { + const categoryIsActive = navState.category === category.id; + + return ( +
+ +
+ ); + })} +
+
+
+ +
+
+ {selectedCategory && ( + +

+ {selectedCategory.name} +

+
+ )} +
    + {selectedCategory && + selectedCategory.sections.map((section) => { + const sectionIsActive = navState.section === section.id; + + return ( +
  • + +
      +
      + {section.articles.map((article) => { + const isActiveArticle = navState.article === article.id; + + return ( +
    • + + {article.name} + +
    • + ); + })} +
      +
    +
  • + ); + })} +
+
+
+
+ + ); +}; + +const ChevronLeft: FC<{ + color?: 'neutral-2'; +}> = ({ color = 'neutral-2' }) => { + return ( + + + + ); +}; + +const ChevronRight: FC<{ + color?: 'neutral-2'; +}> = ({ color = 'neutral-2' }) => { + return ( + + + + ); +}; + +const ChevronDown: FC<{ + color?: 'neutral-2'; +}> = ({ color = 'neutral-2' }) => { + return ( + + + + ); +}; diff --git a/src/modules/side-nav/index.ts b/src/modules/side-nav/index.ts index 6e40a1c7b..8dfc53cf9 100644 --- a/src/modules/side-nav/index.ts +++ b/src/modules/side-nav/index.ts @@ -1,2 +1,2 @@ -export { renderSideNav } from './renderSideNav'; +export { renderSideNav, renderMobileMenuNav } from './renderSideNav'; export { sideNav } from './api'; diff --git a/src/modules/side-nav/renderSideNav.tsx b/src/modules/side-nav/renderSideNav.tsx index cc32066f9..356ff24bc 100644 --- a/src/modules/side-nav/renderSideNav.tsx +++ b/src/modules/side-nav/renderSideNav.tsx @@ -3,6 +3,7 @@ import { SideNavData, NavState } from '../../lib/types'; import { Settings } from '../shared'; import { createTheme, ThemeProviders } from '../shared'; import { SideNav } from './SideNavModule'; +import { MobileMenuNav } from './MobileMenuNavModule'; export async function renderSideNav( settings: Settings, @@ -17,3 +18,17 @@ export async function renderSideNav( container ); } + +export async function renderMobileMenuNav( + settings: Settings, + sideNavData: SideNavData, + navState: NavState, + container: HTMLElement +) { + render( + + + , + container + ); +} diff --git a/templates/article_page.hbs b/templates/article_page.hbs index 8886ffd7e..10ad0039b 100644 --- a/templates/article_page.hbs +++ b/templates/article_page.hbs @@ -99,7 +99,7 @@ \ No newline at end of file diff --git a/templates/category_page.hbs b/templates/category_page.hbs index 684463911..42f07c013 100644 --- a/templates/category_page.hbs +++ b/templates/category_page.hbs @@ -62,7 +62,7 @@ \ No newline at end of file diff --git a/templates/home_page.hbs b/templates/home_page.hbs index 661bb452f..7e9eb8ba8 100644 --- a/templates/home_page.hbs +++ b/templates/home_page.hbs @@ -111,7 +111,7 @@ \ No newline at end of file diff --git a/templates/section_page.hbs b/templates/section_page.hbs index 5d029165f..15652caa6 100644 --- a/templates/section_page.hbs +++ b/templates/section_page.hbs @@ -47,7 +47,7 @@ \ No newline at end of file