import * as React from "react"
import {
Breadcrumb,
BreadcrumbEllipsis,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@workspace/ui/components/breadcrumb"
import { Button } from "@workspace/ui/components/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@workspace/ui/components/dropdown-menu"
import { uiMessages, useMessage } from "@workspace/ui/components/i18n"
import { cn } from "@workspace/ui/lib/utils"
import { isRenderablePageInfoValue, usePageInfo } from "./page-info"
export interface PageBreadcrumbItem {
/** Native destination. Use `render` instead for a router-specific Link. */
href?: string
key?: React.Key
label: React.ReactNode
/** Router-specific link element, such as ``. */
render?: React.ReactElement
}
export interface PageBreadcrumbProps extends Omit<
React.ComponentProps,
"children"
> {
children?: React.ReactNode
/** Breadcrumb path in root-to-current-page order. */
items?: readonly PageBreadcrumbItem[]
/** Maximum visible entries, including the collapsed-items trigger. */
maxItems?: number
separator?: React.ReactNode
}
export function PageBreadcrumb({
children,
className,
items,
maxItems = 4,
separator,
...props
}: PageBreadcrumbProps) {
const pageInfo = usePageInfo()
const resolvedItems =
items === undefined && children === undefined ? pageInfo.breadcrumb : items
const resolvedChildren = items === undefined ? children : undefined
if (
resolvedItems !== undefined
? resolvedItems.length === 0
: !isRenderablePageInfoValue(resolvedChildren)
) {
return null
}
return (
{resolvedItems ? (
) : (
resolvedChildren
)}
)
}
function PageBreadcrumbItems({
items,
maxItems,
separator,
}: {
items: readonly PageBreadcrumbItem[]
maxItems: number
separator?: React.ReactNode
}) {
const displayItems = getDisplayItems(items, maxItems)
return (
{displayItems.map((displayItem, index) => (
{isCollapsedItems(displayItem) ? (
) : (
)}
{index < displayItems.length - 1 && (
{separator}
)}
))}
)
}
function PageBreadcrumbEntry({
current,
item,
}: {
current: boolean
item: PageBreadcrumbItem
}) {
if (current) {
return {item.label}
}
const render = getItemRender(item)
if (render) {
return {item.label}
}
return {item.label}
}
function PageBreadcrumbMenu({
items,
}: {
items: readonly PageBreadcrumbItem[]
}) {
const menuLabel = useMessage(uiMessages.morePages)
return (
}
>
{items.map((item, index) => {
const render = getItemRender(item)
return (
{item.label}
)
})}
)
}
function getDisplayItems(
items: readonly PageBreadcrumbItem[],
maxItems: number
): Array {
const visibleCount = Number.isFinite(maxItems)
? Math.max(3, Math.floor(maxItems))
: 4
if (items.length <= visibleCount) return [...items]
const trailingCount = visibleCount - 2
return [
items[0]!,
items.slice(1, -trailingCount),
...items.slice(-trailingCount),
]
}
function getItemRender(item: PageBreadcrumbItem) {
return item.render ?? (item.href ? : undefined)
}
function getItemKey(item: PageBreadcrumbItem, index: number): React.Key {
return item.key ?? item.href ?? index
}
function getDisplayItemKey(
item: PageBreadcrumbItem | readonly PageBreadcrumbItem[],
index: number
): React.Key {
if (isCollapsedItems(item)) return `collapsed-${index}`
return getItemKey(item, index)
}
function isCollapsedItems(
item: PageBreadcrumbItem | readonly PageBreadcrumbItem[]
): item is readonly PageBreadcrumbItem[] {
return Array.isArray(item)
}