fix(SW-1877): new responsive breadcrumbs
This commit is contained in:
@@ -1,18 +1,61 @@
|
||||
"use client"
|
||||
|
||||
import { cx } from "class-variance-authority"
|
||||
import { type PropsWithChildren, useEffect, useState } from "react"
|
||||
import {
|
||||
Breadcrumb as AriaBreadcrumb,
|
||||
Breadcrumbs as AriaBreadCrumbs,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
OverlayArrow,
|
||||
Popover,
|
||||
} from "react-aria-components"
|
||||
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import Link from "@/components/TempDesignSystem/Link"
|
||||
import Footnote from "@/components/TempDesignSystem/Text/Footnote"
|
||||
import { debounce } from "@/utils/debounce"
|
||||
|
||||
import { Arrow } from "../Popover/Arrow"
|
||||
import { breadcrumbsVariants } from "./variants"
|
||||
|
||||
import styles from "./breadcrumbs.module.css"
|
||||
|
||||
import type { BreadcrumbsProps } from "@/components/TempDesignSystem/Breadcrumbs/breadcrumbs"
|
||||
import type {
|
||||
Breadcrumb,
|
||||
BreadcrumbsProps,
|
||||
} from "@/components/TempDesignSystem/Breadcrumbs/breadcrumbs"
|
||||
|
||||
export default function Breadcrumbs({
|
||||
breadcrumbs,
|
||||
variant,
|
||||
}: BreadcrumbsProps) {
|
||||
// using a ref instead to detect when the element is available and forcing a render
|
||||
const [element, attachRef] = useState<HTMLButtonElement | null>(null)
|
||||
const [isTooltipDisabled, setIsTooltipDisabled] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const observer = new ResizeObserver(
|
||||
debounce(([entry]) => {
|
||||
const el = entry.target
|
||||
setIsTooltipDisabled(el.clientWidth >= el.scrollWidth)
|
||||
}, 200)
|
||||
)
|
||||
|
||||
if (element) {
|
||||
observer.observe(element)
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (element) {
|
||||
observer.unobserve(element)
|
||||
}
|
||||
}
|
||||
}, [element])
|
||||
|
||||
if (!breadcrumbs?.length) {
|
||||
return null
|
||||
}
|
||||
@@ -21,60 +64,137 @@ export default function Breadcrumbs({
|
||||
variant,
|
||||
})
|
||||
|
||||
const homeBreadcrumb = breadcrumbs.shift()
|
||||
const [homeBreadcrumb, remainingBreadcrumbs, lastBreadcrumb] =
|
||||
splitBreadcrumbs(breadcrumbs)
|
||||
|
||||
return (
|
||||
<nav className={classNames}>
|
||||
<ul className={styles.list}>
|
||||
{homeBreadcrumb ? (
|
||||
<li className={styles.listItem}>
|
||||
<Link
|
||||
className={styles.homeLink}
|
||||
color="peach80"
|
||||
href={homeBreadcrumb.href!}
|
||||
variant="breadcrumb"
|
||||
aria-label={homeBreadcrumb.title}
|
||||
<nav aria-label="Breadcrumbs" className={classNames}>
|
||||
<AriaBreadCrumbs className={styles.list}>
|
||||
<Breadcrumb
|
||||
href={homeBreadcrumb.href}
|
||||
aria-label={homeBreadcrumb.title}
|
||||
>
|
||||
<MaterialIcon icon="home" size={16} color="CurrentColor" />
|
||||
</Breadcrumb>
|
||||
|
||||
{remainingBreadcrumbs.length >= 3 ? (
|
||||
<>
|
||||
<Breadcrumb
|
||||
href={remainingBreadcrumbs[0].href!}
|
||||
className={styles.mobile}
|
||||
>
|
||||
<MaterialIcon icon="home" size={16} color="CurrentColor" />
|
||||
</Link>
|
||||
<MaterialIcon
|
||||
icon="chevron_right"
|
||||
size={20}
|
||||
aria-hidden="true"
|
||||
color="Icon/Interactive/Secondary"
|
||||
/>
|
||||
</li>
|
||||
) : null}
|
||||
|
||||
{breadcrumbs.map((breadcrumb, index) => {
|
||||
if (breadcrumb.href && index < breadcrumbs.length - 1) {
|
||||
return (
|
||||
<li key={breadcrumb.uid} className={styles.listItem}>
|
||||
<Link
|
||||
color="peach80"
|
||||
href={breadcrumb.href}
|
||||
variant="breadcrumb"
|
||||
>
|
||||
{breadcrumb.title}
|
||||
</Link>
|
||||
<MaterialIcon
|
||||
icon="chevron_right"
|
||||
size={20}
|
||||
aria-hidden="true"
|
||||
color="Icon/Interactive/Secondary"
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={breadcrumb.uid} className={styles.listItem}>
|
||||
<Footnote color="burgundy" type="bold">
|
||||
{breadcrumb.title}
|
||||
</Footnote>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
{remainingBreadcrumbs[0].title}
|
||||
</Breadcrumb>
|
||||
<AriaBreadcrumb className={cx(styles.listItem, styles.mobile)}>
|
||||
<DialogTrigger>
|
||||
<Footnote color="burgundy" type="bold" asChild>
|
||||
<Button className={styles.button}>...</Button>
|
||||
</Footnote>
|
||||
<Popover>
|
||||
<Dialog className={styles.dialog}>
|
||||
{remainingBreadcrumbs.slice(1).map((breadcrumb) => (
|
||||
<Link
|
||||
href={breadcrumb.href!}
|
||||
size="tiny"
|
||||
key={breadcrumb.uid}
|
||||
className={styles.dialogLink}
|
||||
>
|
||||
{breadcrumb.title}
|
||||
</Link>
|
||||
))}
|
||||
</Dialog>
|
||||
</Popover>
|
||||
</DialogTrigger>
|
||||
<MaterialIcon
|
||||
icon="chevron_right"
|
||||
size={20}
|
||||
aria-hidden="true"
|
||||
color="Icon/Interactive/Secondary"
|
||||
/>
|
||||
</AriaBreadcrumb>
|
||||
</>
|
||||
) : (
|
||||
remainingBreadcrumbs.map((breadcrumb) => (
|
||||
<Breadcrumb key={breadcrumb.uid} href={breadcrumb.href}>
|
||||
{breadcrumb.title}
|
||||
</Breadcrumb>
|
||||
))
|
||||
)}
|
||||
{remainingBreadcrumbs.map((breadcrumb) => (
|
||||
<Breadcrumb
|
||||
key={breadcrumb.uid}
|
||||
href={breadcrumb.href}
|
||||
className={styles.desktop}
|
||||
>
|
||||
{breadcrumb.title}
|
||||
</Breadcrumb>
|
||||
))}
|
||||
<Breadcrumb>
|
||||
<DialogTrigger>
|
||||
<Footnote color="burgundy" type="bold" asChild>
|
||||
<Button
|
||||
className={styles.button}
|
||||
ref={attachRef}
|
||||
isDisabled={isTooltipDisabled}
|
||||
>
|
||||
{lastBreadcrumb.title}
|
||||
</Button>
|
||||
</Footnote>
|
||||
<Popover placement="bottom" offset={16}>
|
||||
<Dialog className={styles.tooltip}>
|
||||
<OverlayArrow>
|
||||
<Arrow color="black" size="small" />
|
||||
</OverlayArrow>
|
||||
<Typography variant="Label/xsRegular">
|
||||
<span>{lastBreadcrumb.title}</span>
|
||||
</Typography>
|
||||
</Dialog>
|
||||
</Popover>
|
||||
</DialogTrigger>
|
||||
</Breadcrumb>
|
||||
</AriaBreadCrumbs>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
function Breadcrumb({
|
||||
className = "",
|
||||
href,
|
||||
children,
|
||||
...props
|
||||
}: PropsWithChildren<{
|
||||
className?: string
|
||||
href?: string
|
||||
}>) {
|
||||
return (
|
||||
<AriaBreadcrumb
|
||||
className={cx(styles.listItem, { [styles.last]: !href }, className)}
|
||||
{...props}
|
||||
>
|
||||
{href ? (
|
||||
<>
|
||||
<Link color="peach80" href={href} variant="breadcrumb">
|
||||
{children}
|
||||
</Link>
|
||||
<MaterialIcon
|
||||
icon="chevron_right"
|
||||
size={20}
|
||||
aria-hidden="true"
|
||||
color="Icon/Interactive/Secondary"
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</AriaBreadcrumb>
|
||||
)
|
||||
}
|
||||
|
||||
function splitBreadcrumbs(
|
||||
breadcrumbs: Breadcrumb[]
|
||||
): [Breadcrumb, Breadcrumb[], Breadcrumb] {
|
||||
const copy = breadcrumbs.slice(0)
|
||||
const first = copy.shift()!
|
||||
const last = copy.pop()!
|
||||
return [first, copy, last]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user