import React from 'react';
import type {
BreadcrumbItem,
BreadcrumbPressEvent,
BreadcrumbsProps,
} from '../../../../types/breadcrumbs';
import { Button } from '../../../button/public';
import { Icon } from '../../../icon/public';
import { resolveIconSize } from '../../../icon/utils/resolveIconSize';
import { View } from '../../../layout/public';
import { withZoraThemeScope } from '../../../theme/adapters/inbound/withZoraThemeScope';
import { useZoraTheme } from '../../../theme/composition/useZoraTheme';
import { Text } from '../../../typography/public';
/*** Renders a breadcrumb trail whose final item represents the current location. */
export const Breadcrumbs = withZoraThemeScope(BreadcrumbsInner);
type BreadcrumbRenderItem =
| {
item: BreadcrumbItem;
type: 'item';
}
| {
type: 'ellipsis';
};
/*** Connects serializable breadcrumb items to the component-level item press event. */
function BreadcrumbsInner({
themeId: _themeId,
mode: _mode,
interactionPolicy,
items,
separator = '/',
maxItems,
compact = false,
disabled = false,
onItemPress,
testID,
}: BreadcrumbsProps) {
const normalizedMaxItems = normalizeMaxItems(maxItems);
const renderItems = createRenderItems(items, normalizedMaxItems);
const currentItemId = items.at(-1)?.id;
if (items.length === 0) return null;
return (
{renderItems.map((renderItem, index) => {
const showSeparator = index !== renderItems.length - 1;
if (renderItem.type === 'ellipsis') {
return (
…
{showSeparator ? renderSeparator(separator) : null}
);
}
return (
);
})}
);
}
/*** Renders one breadcrumb item and its optional following separator. */
function BreadcrumbItemView({
compact,
current,
disabled,
interactionPolicy,
item,
onItemPress,
separator,
testID,
}: {
compact: boolean;
current: boolean;
disabled: boolean;
interactionPolicy: BreadcrumbsProps['interactionPolicy'];
item: BreadcrumbItem;
onItemPress: ((event: BreadcrumbPressEvent) => void) | undefined;
separator: string | undefined;
testID: string | undefined;
}) {
const interactive = !current && !disabled && !item.disabled && onItemPress !== undefined;
return (
<>
{interactive ? (
) : (
)}
{separator === undefined ? null : renderSeparator(separator)}
>
);
}
/*** Renders one non-interactive breadcrumb label. */
function BreadcrumbLabel({ item, compact }: { item: BreadcrumbItem; compact: boolean }) {
const { theme } = useZoraTheme();
return (
{item.icon ? (
) : null}
{item.label}
);
}
/*** Renders the configured separator with muted breadcrumb styling. */
function renderSeparator(separator: string): React.ReactNode {
return (
{separator}
);
}
/*** Normalizes an optional maximum breadcrumb item count. */
function normalizeMaxItems(value: number | undefined): number | undefined {
if (value === undefined || !Number.isFinite(value)) return undefined;
return Math.max(0, Math.trunc(value));
}
/*** Collapses middle breadcrumb items into one ellipsis when required. */
function createRenderItems(
items: readonly BreadcrumbItem[],
maxItems: number | undefined,
): readonly BreadcrumbRenderItem[] {
if (maxItems === undefined || maxItems === 0 || items.length <= maxItems || maxItems < 3) {
return items.map((item) => ({ item, type: 'item' }));
}
const firstItem = items.at(0);
if (firstItem === undefined) return [];
const trailingCount = maxItems - 2;
const trailingItems = items.slice(items.length - trailingCount);
return [
{ item: firstItem, type: 'item' },
{ type: 'ellipsis' },
...trailingItems.map((item) => ({ item, type: 'item' }) satisfies BreadcrumbRenderItem),
];
}