import { useAtomValue } from "jotai"; import { treeDataAtom } from "@/features/page/tree/atoms/tree-data-atom.ts"; import React, { useCallback, useEffect, useState } from "react"; import { computeBreadcrumbState } from "./breadcrumb.utils"; import { Button, Anchor, Popover, Breadcrumbs, ActionIcon, Text, Tooltip, } from "@mantine/core"; import { IconCornerDownRightDouble, IconDots } from "@tabler/icons-react"; import { Link, useParams } from "react-router-dom"; import classes from "./breadcrumb.module.css"; import { SpaceTreeNode } from "@/features/page/tree/types.ts"; import { IPage } from "@/features/page/types/page.types.ts"; import { buildPageUrl } from "@/features/page/page.utils.ts"; import { usePageQuery, usePageBreadcrumbsQuery, } from "@/features/page/queries/page-query.ts"; import { extractPageSlugId } from "@/lib"; import { useMediaQuery } from "@mantine/hooks"; import { useTranslation } from "react-i18next"; function getTitle(name: string, icon: string) { if (icon) { return `${icon} ${name}`; } return name; } export default function Breadcrumb() { const { t } = useTranslation(); const treeData = useAtomValue(treeDataAtom); const [breadcrumbNodes, setBreadcrumbNodes] = useState< SpaceTreeNode[] | null >(null); const { pageSlug, spaceSlug } = useParams(); const { data: currentPage } = usePageQuery({ pageId: extractPageSlugId(pageSlug), }); // The page's own ancestor chain, fetched independently of the lazily-built // sidebar tree so a deep page doesn't render a blank breadcrumb for seconds // while the tree backfills (#218). const { data: ancestors } = usePageBreadcrumbsQuery(currentPage?.id); const isMobile = useMediaQuery("(max-width: 48em)"); useEffect(() => { if (!currentPage) return; // Selection/mapping + stale-clearing live in a pure, unit-tested helper // (#218). It resolves the correct chain when possible and, on a transient // miss, clears a chain left over from a previously-viewed page instead of // showing the wrong trail — while keeping a chain already resolved for THIS // page to avoid a blank flash. setBreadcrumbNodes((previous) => computeBreadcrumbState( treeData, ancestors as IPage[] | undefined, currentPage.id, previous, ), ); }, [currentPage?.id, treeData, ancestors]); const HiddenNodesTooltipContent = () => breadcrumbNodes?.slice(1, -1).map((node) => ( )); const MobileHiddenNodesTooltipContent = () => breadcrumbNodes?.map((node) => ( )); const renderAnchor = useCallback( (node: SpaceTreeNode, isCurrent = false) => ( {getTitle(node.name, node.icon)} ), [spaceSlug], ); const getBreadcrumbItems = () => { if (!breadcrumbNodes) return []; if (breadcrumbNodes.length > 3) { const firstNode = breadcrumbNodes[0]; //const secondLastNode = breadcrumbNodes[breadcrumbNodes.length - 2]; const lastNode = breadcrumbNodes[breadcrumbNodes.length - 1]; return [ renderAnchor(firstNode), , //renderAnchor(secondLastNode), renderAnchor(lastNode, true), ]; } return breadcrumbNodes.map((node, i) => renderAnchor(node, i === breadcrumbNodes.length - 1), ); }; const getMobileBreadcrumbItems = () => { if (!breadcrumbNodes) return []; if (breadcrumbNodes.length > 0) { return [ , ]; } return breadcrumbNodes.map((node, i) => renderAnchor(node, i === breadcrumbNodes.length - 1), ); }; return ( ); }