import * as DropdownMenu from '@radix-ui/react-dropdown-menu'; import { AnimatePresence, motion, type Variants } from 'framer-motion'; import { memo, useEffect, useRef, useState } from 'react'; import type { FileMap } from '~/lib/stores/files'; import { classNames } from '~/utils/classNames'; import { WORK_DIR } from '~/utils/constants'; import { cubicEasingFn } from '~/utils/easings'; import { renderLogger } from '~/utils/logger'; import FileTree from './FileTree'; const WORK_DIR_REGEX = new RegExp(`^${WORK_DIR.split('/').slice(0, -1).join('/').replaceAll('/', '\\/')}/`); interface FileBreadcrumbProps { files?: FileMap; pathSegments?: string[]; onFileSelect?: (filePath: string) => void; } const contextMenuVariants = { open: { y: 0, opacity: 1, transition: { duration: 0.15, ease: cubicEasingFn, }, }, close: { y: 6, opacity: 0, transition: { duration: 0.15, ease: cubicEasingFn, }, }, } satisfies Variants; export const FileBreadcrumb = memo(({ files, pathSegments = [], onFileSelect }) => { renderLogger.trace('FileBreadcrumb'); const [activeIndex, setActiveIndex] = useState(null); const contextMenuRef = useRef(null); const segmentRefs = useRef<(HTMLSpanElement | null)[]>([]); const handleSegmentClick = (index: number) => { setActiveIndex((prevIndex) => (prevIndex === index ? null : index)); }; useEffect(() => { const handleOutsideClick = (event: MouseEvent) => { if ( activeIndex !== null && !contextMenuRef.current?.contains(event.target as Node) && !segmentRefs.current.some((ref) => ref?.contains(event.target as Node)) ) { setActiveIndex(null); } }; document.addEventListener('mousedown', handleOutsideClick); return () => { document.removeEventListener('mousedown', handleOutsideClick); }; }, [activeIndex]); if (files === undefined || pathSegments.length === 0) { return null; } return (
{pathSegments.map((segment, index) => { const isLast = index === pathSegments.length - 1; const path = pathSegments.slice(0, index).join('/'); if (!WORK_DIR_REGEX.test(path)) { return null; } const isActive = activeIndex === index; return (
(segmentRefs.current[index] = ref)} className={classNames('flex items-center gap-1.5 cursor-pointer shrink-0', { 'text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary': !isActive, 'text-bolt-elements-textPrimary underline': isActive, 'pr-4': isLast, })} onClick={() => handleSegmentClick(index)} > {isLast &&
} {segment} {index > 0 && !isLast && } {isActive && (
{ setActiveIndex(null); onFileSelect?.(filePath); }} />
)}
); })}
); });