import { ReactNode, RefObject } from 'react'; import { CSSObject } from 'styled-components'; export type DirectoryExpandedItems = Set | string[]; export type DirectoryVirtualizationConfig = { /** * External scroll container. Must be mounted (ref.current set) by the * time Directory mounts — onEndReached binds its scroll listener once * and does not retry when the element appears later. */ viewportRef?: RefObject; rowHeight?: number; sectionHeight?: number; viewportHeight?: number; overscan?: number; threshold?: number; }; export type DirectoryProps = { directoryData: DirectoryData[] | null; idPrefix?: string; activeItem?: string | null; onActiveItemChange?: (item: string | null) => void; defaultActiveItem?: string | null; iconOnlyMode?: boolean; showHierarchyLines?: boolean; hierarchyLineBorderRadius?: CSSObject['borderRadius']; expandedItems?: DirectoryExpandedItems; defaultExpandedItems?: DirectoryExpandedItems; onExpandedItemsChange?: (items: string[]) => void; onItemExpand?: (item: NavbarItem, itemPath: string) => void | Promise; /** * Called when the viewport is scrolled within endReachedThreshold pixels * of the bottom (and re-checked when the content grows), for paged / * infinite loading. */ onEndReached?: () => void | Promise; /** * Distance in pixels from the bottom at which onEndReached fires. * @default 200 */ endReachedThreshold?: number; /** * When true, clicking a parent row selects it (updates activeItem) in * addition to toggling expansion, and parent rows can render as * selected. When false, only leaf rows can be selected. * @default false */ enableParentSelection?: boolean; /** * When true, selecting an item also styles its ancestor chain as * `activePath` and every unrelated row as `muted`, so the branch you are * in reads at a glance. With nothing selected the tree renders exactly as * it does with this off. * * Requires a path-valued `activeItem` — a bare label (the backward-compat * match for id-less items) resolves no ancestors and degrades to * selected-only highlighting. * * Note: the default `muted` text colour is 4.49:1 (light) / 4.17:1 (dark), * just under the 4.5:1 WCAG AA floor; muted rows lift to the `hover` tier * on hover and focus-visible. Override `DIRECTORY` component tokens if * your product needs muted rows to clear AA at rest. * * @default false */ highlightActivePath?: boolean; enableVirtualization?: boolean; virtualization?: DirectoryVirtualizationConfig; }; export type DirectoryData = { label?: string; items?: NavbarItem[]; isCollapsible?: boolean; defaultOpen?: boolean; }; export type NavbarItem = { label: string; /** * Stable identifier used as this item's path segment (itemPath, expansion * state, virtualizer keys, data-id). Falls back to label when omitted or * empty — provide it when sibling labels can collide. Must not contain * "/" (the path separator). Items without an id also match a bare-label * activeItem for backward compatibility; items with an id match only * their id-based path. */ id?: string; items?: NavbarItem[]; leftSlot?: ReactNode; rightSlot?: ReactNode; onClick?: () => void; href?: string; isSelected?: boolean; /** * When true, the item appears in the Sidebar mobile navigation drawer. * Desktop always renders all items regardless of this flag. */ showOnMobile?: boolean; }; export type SectionProps = { section: DirectoryData; sectionIndex: number; onNavigateBetweenSections: (direction: 'up' | 'down', currentIndex: number) => void; idPrefix?: string; iconOnlyMode?: boolean; showHierarchyLines?: boolean; hierarchyLineBorderRadius?: CSSObject['borderRadius']; enableParentSelection?: boolean; highlightActivePath?: boolean; }; export type NavItemProps = { item: NavbarItem; index: number; onNavigate: (direction: 'up' | 'down', currentIndex: number) => void; itemPath?: string; iconOnlyMode?: boolean; showHierarchyLines?: boolean; hierarchyLineBorderRadius?: CSSObject['borderRadius']; isLast?: boolean; isNested?: boolean; enableParentSelection?: boolean; highlightActivePath?: boolean; pathVerticalActive?: boolean; pathElbowActive?: boolean; }; export type DirectoryFlatRow = { type: 'section'; section: DirectoryData; sectionIndex: number; } | { type: 'item'; item: NavbarItem; sectionIndex: number; itemPath: string; depth: number; isLast: boolean; ancestorIsLast: boolean[]; };