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); `;