Skip to content

Commit

Permalink
Add nested links to mobile nav (#1973)
Browse files Browse the repository at this point in the history
* init

* Fix product navigation to display submenus

* Fix types

* Remove logs

* Add smooth scroll for active link
  • Loading branch information
khadni authored Jun 10, 2024
1 parent 9458fac commit 66ddcd0
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 158 deletions.
32 changes: 20 additions & 12 deletions src/components/Header/Nav/ProductNavigation/Mobile/Category.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import React from "react"
import { ProductItem } from "../../config"
import { ProductItem, SubProducts, SubProductItem } from "../../config"
import { clsx } from "../../utils"
import { CaretRightIcon } from "./CaretRightIcon"
import styles from "./category.module.css"
import { SubProducts } from "./ProductNavigation"

type ListItemProps = {
item: ProductItem
Expand All @@ -20,20 +19,29 @@ const Item = React.forwardRef<HTMLAnchorElement, ListItemProps>(
</span>
</>
)

const handleProductClick = () => {
const subProductItems = subProducts as unknown as SubProductItem[]
const mappedSubProducts: SubProducts = {
label,
items: subProductItems.map((subProductItem) => ({
label: subProductItem.label,
href: subProductItem.href || "#",
pages: subProductItem.items.map((item) => ({
label: item.label,
href: item.href || "/",
children: item.children || [],
})),
})),
}
onProductClick(mappedSubProducts)
}

return subProducts ? (
<button
className={clsx(styles.link, "product-link")}
style={{ marginTop: "var(--space-0x)" }}
onClick={() =>
onProductClick({
...subProducts,
items:
subProducts.items?.map((item) => ({
...item,
href: item.href || "/",
})) || [],
})
}
onClick={handleProductClick}
data-testid="sub-product-navigation-trigger-mobile"
>
{itemComponent}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ProductsNav } from "../../config"
import { ProductsNav, SubProducts } from "../../config"
import { Category } from "./Category"
import { SubProducts } from "./ProductNavigation"

type Props = {
onProductClick: (subProducts: SubProducts) => void
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
import * as Dialog from "@radix-ui/react-dialog"
import React, { useEffect } from "react"
import { ProductsNav } from "../../config"
import { ProductsNav, SubProducts } from "../../config"
import { SearchTrigger } from "../../NavBar"
import { isMatchedPath } from "../../isMatchedPath"
import { clsx } from "../../utils"
import { CaretIcon } from "../CaretIcon"
import { extendRadixComponent } from "../extendRadixComponent"
import { BottomBar } from "./BottomBar"
import { ProductContent } from "./ProductContent"
import styles from "./productNavigation.module.css"
import { SubProductContent } from "./SubProductContent"

type Page = {
label: string
href: string
}

export type SubProducts = {
label: string
items: { label: string; href: string; pages?: Page[] }[]
}
import styles from "./productNavigation.module.css"

type Props = {
searchTrigger?: SearchTrigger
Expand All @@ -36,7 +26,7 @@ export function ProductNavigation({ productsNav, path }: Props) {
const [open, setOpen] = React.useState(false)
const [subProducts, setSubProducts] = React.useState<SubProducts | undefined>(undefined)
const [showSearch, setShowSearch] = React.useState(false)
const [producsSlidePosition, setProductsSlidePosition] = React.useState<"main" | "submenu">("main")
const [productsSlidePosition, setProductsSlidePosition] = React.useState<"main" | "submenu">("main")
const closeButtonRef = React.useRef(null)

useEffect(() => {
Expand All @@ -47,23 +37,27 @@ export function ProductNavigation({ productsNav, path }: Props) {
if (foundSubProduct) {
const subProduct = foundSubProduct.items.find((item) => item.subProducts && isMatchedPath(path, item.href))

if (subProduct?.subProducts?.items) {
const safeSubProducts: SubProducts = {
label: subProduct.subProducts.label,
items: subProduct.subProducts.items.map((item) => ({
label: item.label,
href: item.href || "#",
pages:
item.pages?.map((page) => ({
label: page.label,
href: page.href,
})) || [],
if (subProduct?.subProducts && Array.isArray(subProduct.subProducts)) {
const items = subProduct.subProducts.map((subProductItem) => ({
label: subProductItem.label,
href: "#",
pages: subProductItem.items.map((page) => ({
label: page.label,
href: page.href,
children: page.children || [],
})),
}))

const safeSubProducts: SubProducts = {
label: subProduct.label,
items,
}

setSubProducts(safeSubProducts)
setProductsSlidePosition("submenu")
}
} else {
setSubProducts(undefined)
}
}, [path, productsNav])

Expand All @@ -74,13 +68,15 @@ export function ProductNavigation({ productsNav, path }: Props) {

const onSubproductClick = () => {
setProductsSlidePosition("main")
setSubProducts(undefined)
}

const handleOpenChange = (newOpenState: boolean) => {
setOpen(newOpenState)
if (!newOpenState) {
setProductsSlidePosition("main")
setShowSearch(false)
setSubProducts(undefined)
}
}

Expand Down Expand Up @@ -114,7 +110,7 @@ export function ProductNavigation({ productsNav, path }: Props) {
overflow: "hidden",
}}
>
<div className={clsx(styles.content, styles[producsSlidePosition])}>
<div className={clsx(styles.content, styles[productsSlidePosition])}>
<ul className={clsx(styles.productContent)}>
<ProductContent onProductClick={onProductClick} productsNav={productsNav} />
</ul>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import { clsx } from "../../utils"
import React, { useEffect, useRef } from "react"
import { BackArrowIcon } from "./BackArrowIcon"
import { Page } from "../../config"
import styles from "./subProductContent.module.css"

type Page = {
label: string
href: string
}

type Props = {
onSubproductClick: () => void
subProducts?: {
Expand All @@ -16,8 +12,41 @@ type Props = {
currentPath: string
}

const renderPages = (pages: Page[], currentPath: string, indent: boolean) => {
return pages.map(({ label, href, children }) => {
const adjustedHref = "/" + href
const isActive = currentPath.replace(/\/$/, "") === adjustedHref.replace(/\/$/, "")

const linkRef = useRef<HTMLAnchorElement>(null)

useEffect(() => {
if (isActive && linkRef.current) {
linkRef.current.scrollIntoView({ behavior: "smooth", block: "center" })
}
}, [isActive])

const linkStyle = {
backgroundColor: isActive ? "var(--blue-100)" : "transparent",
color: isActive ? "var(--blue-600)" : "inherit",
fontWeight: isActive ? "500" : "normal",
marginLeft: indent ? "20px" : "0",
}

return (
<React.Fragment key={label}>
<a ref={linkRef} style={linkStyle} className={`${styles.link} subproduct-link`} href={adjustedHref}>
{label}
</a>
{children && renderPages(children, currentPath, true)}
</React.Fragment>
)
})
}

export const SubProductContent = ({ subProducts, onSubproductClick, currentPath }: Props) => {
if (!subProducts) return null
if (!subProducts) {
return null
}

return (
<>
Expand All @@ -28,22 +57,7 @@ export const SubProductContent = ({ subProducts, onSubproductClick, currentPath
{subProducts.items.map(({ label, pages }) => (
<div key={label}>
<h3 className={styles.section}>{label}</h3>
{pages?.map(({ label, href }) => {
const adjustedHref = "/" + href
const isActive = currentPath.replace(/\/$/, "") === adjustedHref.replace(/\/$/, "")

const linkStyle = {
backgroundColor: isActive ? "var(--blue-100)" : "transparent",
color: isActive ? "var(--blue-600)" : "inherit",
fontWeight: isActive ? "500" : "normal",
}

return (
<a key={label} style={linkStyle} className={`${styles.link} subproduct-link`} href={adjustedHref}>
{label}
</a>
)
})}
{pages && renderPages(pages, currentPath, false)}
</div>
))}
</>
Expand Down
26 changes: 20 additions & 6 deletions src/components/Header/Nav/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,26 @@ type LinksConfig = {
actionButton?: { label: string; href: string }
}

export type Item = { label: string; icon?: string; href: string }

export type Page = {
label: string
href: string
children?: Page[]
}

export type ProductItem = Item & {
subProducts?: {
export type Item = { label: string; icon?: string; href: string; children?: Page[] }

export type SubProductItem = {
label: string
href?: string
items: {
label: string
items?: { label: string; href?: string; pages?: Page[] }[]
}
href: string
children?: Page[]
}[]
}

export type ProductItem = Item & {
subProducts?: SubProductItem[]
}

export type ProductsNav = {
Expand All @@ -26,11 +34,17 @@ export type ProductsNav = {
}[]
}

export type SubProducts = {
label: string
items: { label: string; href: string; pages?: Page[] }[]
}

export type SubProductsNavItem = {
label: string
icon?: string
href: string
hideFromDropdown?: boolean
pages?: Page[]
}

export type SubProductsNav = SubProductsNavItem[]
Expand Down
Loading

0 comments on commit 66ddcd0

Please sign in to comment.