import type { IconComponent, NavItem, NavSection, } from '../../lib/navigation/nav-model'; /** A flattened, navigable destination used by the command palette search. */ export interface SearchItem { label: string; to: string; icon: IconComponent; /** Section the destination belongs to (shown as a hint on the right). */ group: string; /** A different Recomaze product: opened in a tab, never pushed at the router. */ external?: boolean; } /** The group a destination falls under when its section has no title. */ const DEFAULT_GROUP = 'General'; /** * Flattens the nav tree into the list of navigable destinations for search. * Group headers are labels, not destinations, so only the entries under them * reach the palette. * * @param {NavSection[]} sections - Top nav sections * @param {NavItem[]} bottom - Utility nav items * @returns {SearchItem[]} Every leaf destination with a `to` */ export function flattenNav( sections: NavSection[], bottom: NavItem[] ): SearchItem[] { const destinations: SearchItem[] = []; const walk = (item: NavItem, group: string): void => { if (item.to) { destinations.push({ label: item.label, to: item.to, icon: item.icon, group, external: item.external, }); } item.children?.forEach(child => walk(child, group)); }; sections.forEach(section => { section.items.forEach(item => walk(item, section.title || DEFAULT_GROUP)); }); bottom.forEach(item => walk(item, DEFAULT_GROUP)); return destinations; } /** * The destinations whose label or group contains what was typed; all of * them for a blank query. * * @param {SearchItem[]} items - Every destination * @param {string} query - What the merchant typed * @returns {SearchItem[]} The matches, in nav order */ export function searchNavItems( items: SearchItem[], query: string ): SearchItem[] { const normalizedQuery = query.trim().toLowerCase(); if (!normalizedQuery) return items; return items.filter( item => item.label.toLowerCase().includes(normalizedQuery) || item.group.toLowerCase().includes(normalizedQuery) ); }