import React from 'react';
import styled from 'styled-components';
import type { JSX } from 'react';
import type { BreadcrumbItem } from '@redocly/config';
import { useThemeHooks } from '@redocly/theme/core/hooks';
import { Breadcrumb } from '@redocly/theme/components/Breadcrumbs/Breadcrumb';
import { BreadcrumbDropdown } from '@redocly/theme/components/Breadcrumbs/BreadcrumbDropdown';
import { BreadcrumbIcon } from '@redocly/theme/components/Breadcrumbs/BreadcrumbIcon';
import { trimText } from '@redocly/theme/core/utils';
import { BREADCRUMB_MAX_LENGTH } from '@redocly/theme/core/constants';
import { GenericIcon } from '@redocly/theme/icons/GenericIcon/GenericIcon';
export function Breadcrumbs(props: {
className?: string;
additionalBreadcrumbs?: BreadcrumbItem[];
}): JSX.Element | null {
const { useBreadcrumbs, useTelemetry, useTranslate } = useThemeHooks();
const { breadcrumbs: fileBreadcrumbs, currentItemSiblings } = useBreadcrumbs();
const telemetry = useTelemetry();
const { translate } = useTranslate();
const breadcrumbs = React.useMemo(() => {
const extra = props.additionalBreadcrumbs ?? [];
return [...(fileBreadcrumbs || []), ...extra];
}, [fileBreadcrumbs, props.additionalBreadcrumbs]);
const shouldCollapse = breadcrumbs.length > 4;
const isInDropdown = (index: number, total: number, collapsed: boolean): boolean =>
collapsed && index > 0 && index < total - 2;
const items = React.useMemo(() => {
const total = breadcrumbs.length;
return breadcrumbs.map((b, i) => ({
breadcrumb: b,
idx: i,
isLast: i === total - 1,
inDropdown: isInDropdown(i, total, shouldCollapse),
}));
}, [breadcrumbs, shouldCollapse]);
if (breadcrumbs.length === 0) {
return null;
}
const renderBreadcrumb = (
breadcrumb: BreadcrumbItem,
idx: number,
isActive: boolean,
): JSX.Element => {
const isParentOfActive =
idx === breadcrumbs.length - 2 && currentItemSiblings && currentItemSiblings.length > 0;
if (isParentOfActive) {
const currentActiveBreadcrumb = breadcrumbs[breadcrumbs.length - 1];
const siblingsWithActive = [
{ ...currentActiveBreadcrumb, isActive: true },
...currentItemSiblings,
];
const translatedLabel = translate(breadcrumb.labelTranslationKey, breadcrumb.label);
return (
telemetry.sendBreadcrumbClickedMessage([
{
object: 'breadcrumb',
link: item.link,
position: itemIdx + 1,
totalBreadcrumbs: siblingsWithActive.length,
},
])
}
>
{trimText(translatedLabel, BREADCRUMB_MAX_LENGTH)}
);
}
return (
telemetry.sendBreadcrumbClickedMessage([
{
object: 'breadcrumb',
link: breadcrumb.link,
position: idx + 1,
totalBreadcrumbs: breadcrumbs.length,
},
])
}
/>
);
};
return (
{items.map(({ breadcrumb, idx, isLast, inDropdown }) => {
if (inDropdown) return null;
if (shouldCollapse && idx === 0) {
const collapsedBreadcrumbs = breadcrumbs.slice(1, -2);
return (
{renderBreadcrumb(breadcrumb, idx, isLast)}
/
telemetry.sendBreadcrumbClickedMessage([
{
object: 'breadcrumb',
link: item.link,
position: itemIdx + 1,
totalBreadcrumbs: breadcrumbs.length,
},
])
}
>
...
/
);
}
return (
{renderBreadcrumb(breadcrumb, idx, isLast)}
{isLast ? null : /}
);
})}
);
}
const BreadcrumbsWrapper = styled.div`
display: flex;
flex-direction: row;
align-items: center;
color: var(--breadcrumbs-text-color);
font-size: var(--breadcrumbs-font-size);
flex-wrap: wrap;
`;
const BreadcrumbSeparator = styled.span`
padding: var(--breadcrumbs-padding);
`;