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) }