import React, { isValidElement, useState } from 'react'; import { getWebProps } from 'react-native-unistyles/web'; import { breadcrumbStyles } from './Breadcrumb.styles'; import type { BreadcrumbProps, BreadcrumbItem as BreadcrumbItemType } from './types'; import { IconSvg } from '../Icon/IconSvg/IconSvg.web'; import { IconRegistry } from '../Icon/IconRegistry'; import { isIconName } from '../Icon/icon-resolver'; import Menu from '../Menu/Menu.web'; import type { MenuItem } from '../Menu/types'; import { mdiDotsHorizontal } from '@mdi/js'; // Self-register icons used internally by Breadcrumb IconRegistry.registerMany({ 'dots-horizontal': mdiDotsHorizontal, }); interface BreadcrumbItemProps { item: BreadcrumbItemType; isLast: boolean; size: BreadcrumbProps['size']; intent: BreadcrumbProps['intent']; itemStyle?: BreadcrumbProps['itemStyle']; } const BreadcrumbItem: React.FC = ({ item, isLast, size, intent, itemStyle }) => { const isClickable = !!item.onPress && !item.disabled; const isDisabled = item.disabled || false; breadcrumbStyles.useVariants({ size, intent, active: isLast, disabled: isDisabled, }); const itemProps = getWebProps([breadcrumbStyles.item]); const itemTextProps = getWebProps([breadcrumbStyles.itemText, itemStyle]); const iconProps = getWebProps([breadcrumbStyles.icon]); const handleClick = () => { if (!item.disabled && item.onPress) { item.onPress(); } }; const renderIcon = () => { if (!item.icon) return null; if (isIconName(item.icon)) { return ( ); } else if (isValidElement(item.icon)) { return item.icon; } return null; }; if (isClickable) { return ( ); } return (
{item.icon && ( {renderIcon()} )} {item.label}
); }; interface BreadcrumbSeparatorProps { separator: React.ReactNode; size: BreadcrumbProps['size']; separatorStyle?: BreadcrumbProps['separatorStyle']; } const BreadcrumbSeparator: React.FC = ({ separator, size, separatorStyle }) => { breadcrumbStyles.useVariants({ size }); const isTextSeparator = typeof separator === 'string'; if (isTextSeparator) { const separatorProps = getWebProps([breadcrumbStyles.separator, separatorStyle]); return ( ); } const separatorIconProps = getWebProps([breadcrumbStyles.separatorIcon, separatorStyle]); return ( ); }; interface BreadcrumbEllipsisProps { size: BreadcrumbProps['size']; intent: BreadcrumbProps['intent']; } const BreadcrumbEllipsis: React.FC = ({ size, intent }) => { breadcrumbStyles.useVariants({ size, intent }); const ellipsisProps = getWebProps([breadcrumbStyles.ellipsis]); const iconProps = getWebProps([breadcrumbStyles.ellipsisIcon]); return ( ); }; /** * Navigation trail showing the current page location within a hierarchy. * Supports custom separators, responsive collapsing, and icon prefixes. */ const Breadcrumb: React.FC = ({ items, separator = '/', maxItems, intent = 'primary', size = 'md', style, itemStyle, separatorStyle, testID, responsive = false, minVisibleItems = 3, id, }) => { const [menuOpen, setMenuOpen] = useState(false); breadcrumbStyles.useVariants({ size, intent }); const containerProps = getWebProps([breadcrumbStyles.container, style as any]); const menuButtonProps = getWebProps([breadcrumbStyles.menuButton]); const menuIconProps = getWebProps([breadcrumbStyles.menuButtonIcon]); // Handle responsive collapsing let displayItems = items; let collapsedItems: BreadcrumbItemType[] = []; let showMenu = false; let showEllipsis = false; if (responsive && items.length > minVisibleItems) { // Responsive mode: show first item + menu + last (minVisibleItems - 2) items showMenu = true; const lastItemCount = Math.max(1, minVisibleItems - 2); displayItems = [ items[0], ...items.slice(-lastItemCount), ]; collapsedItems = items.slice(1, -(lastItemCount)); } else if (maxItems && items.length > maxItems) { // Legacy truncation mode showEllipsis = true; const firstItems = items.slice(0, 1); const lastItems = items.slice(-(maxItems - 1)); displayItems = [...firstItems, ...lastItems]; } // Convert collapsed breadcrumb items to menu items const menuItems: MenuItem[] = collapsedItems.map((item, index) => ({ id: `collapsed-${index}`, label: item.label, onClick: item.onPress, disabled: item.disabled, icon: isIconName(item.icon) ? item.icon : undefined, })); return ( ); }; export default Breadcrumb;