import React, { useCallback, useMemo, useRef } from "react";
import { useForwardedRef } from "@bedrock-layout/use-forwarded-ref";
import { useStatefulRef } from "@bedrock-layout/use-stateful-ref";
import { Box } from "../box";
import { Icons } from "../icons";
import { useIsomorphicLayoutEffect } from "../../utils";
import type { Breadcrumbs } from "./";
const defaultDivider = (
);
// eslint-disable-next-line max-lines-per-function
export const useBreadcrumbs = ({
crumbs,
customDivider,
ref,
}: {
customDivider: React.ComponentProps["divider"];
ref: React.ComponentProps["ref"];
crumbs: React.ComponentProps["crumbs"];
}) => {
const divider = useMemo(() => customDivider ?? defaultDivider, [customDivider]);
const rootRef = useForwardedRef(ref);
const overflowAtIndexRef = useStatefulRef(undefined);
const isOverflowRef = useRef(typeof overflowAtIndexRef.current === "number");
isOverflowRef.current = typeof overflowAtIndexRef.current === "number";
const previousContainerWidth = useRef(0);
// eslint-disable-next-line max-statements, max-lines-per-function
const handleOverflow = useCallback(() => {
const rootElem = rootRef.current;
const crumbsListCloneElem = rootElem.children[1]?.children[0];
const firstCrumb = crumbsListCloneElem?.children[0];
const dividerElem = crumbsListCloneElem?.children[1];
const lastCrumb = crumbsListCloneElem?.children[crumbsListCloneElem.children.length - 1];
const subCrumbsListTrigger = rootElem.children[0]?.children[0]?.children[2];
if (
!(crumbsListCloneElem instanceof HTMLElement) ||
!(firstCrumb instanceof HTMLElement) ||
!(dividerElem instanceof HTMLElement) ||
!(lastCrumb instanceof HTMLElement) ||
!(subCrumbsListTrigger instanceof HTMLElement)
) {
return;
}
const firstCrumbWidth = Math.ceil(firstCrumb.getBoundingClientRect().width);
const lastCrumbWidth = Math.ceil(lastCrumb.getBoundingClientRect().width);
const dividerWidth = Math.ceil(dividerElem.getBoundingClientRect().width);
const gap = parseInt(getComputedStyle(crumbsListCloneElem).gap);
const staticallyOccupiedContainerSpace =
firstCrumbWidth + gap + dividerWidth + gap + gap + dividerWidth + gap + lastCrumbWidth;
const subCrumbsListTriggerWidth = subCrumbsListTrigger.offsetWidth;
const rootElemStyle = getComputedStyle(rootElem);
const rootElemPaddingLeft = parseInt(rootElemStyle.paddingLeft);
const rootElemPaddingRight = parseInt(rootElemStyle.paddingRight);
let shadowListContainerWidth =
parseInt(rootElemStyle.width) -
rootElemPaddingLeft -
rootElemPaddingRight -
staticallyOccupiedContainerSpace;
const isGrowing = shadowListContainerWidth > previousContainerWidth.current;
previousContainerWidth.current = shadowListContainerWidth;
const crumbsCount = crumbsListCloneElem.children.length;
shadowListContainerWidth -=
isOverflowRef.current && ((overflowAtIndexRef.current ?? 0) > 1 || !isGrowing)
? subCrumbsListTriggerWidth + gap
: 0;
let totalWidth = 0;
for (let i = crumbsCount - 3; i > 1; i--) {
const crumb = crumbsListCloneElem.children[i];
if (!(crumb instanceof HTMLElement)) return;
const crumbWidth =
Math.ceil(crumb.getBoundingClientRect().width) + (i !== crumbsCount - 3 ? gap : 0);
totalWidth += crumbWidth;
if (totalWidth > shadowListContainerWidth) {
overflowAtIndexRef.current = Math.ceil(i / 2);
return;
}
}
overflowAtIndexRef.current = undefined;
}, [rootRef, overflowAtIndexRef]);
const observerRef = useRef(
typeof ResizeObserver === "undefined"
? undefined
: new ResizeObserver(() => {
handleOverflow();
})
);
useIsomorphicLayoutEffect(() => {
const observer = observerRef.current;
const rootElem = rootRef.current;
observer?.observe(rootElem);
handleOverflow();
return () => {
observer?.unobserve(rootElem);
};
}, [rootRef, crumbs.length, handleOverflow]);
return { rootRef, overflowAtIndex: overflowAtIndexRef.current, divider };
};