diff --git a/apps/www/src/components/docs/search.tsx b/apps/www/src/components/docs/search.tsx index 31755b7ab..5e77f0cea 100644 --- a/apps/www/src/components/docs/search.tsx +++ b/apps/www/src/components/docs/search.tsx @@ -15,7 +15,13 @@ import { SpaceBetweenVerticallyIcon, TextIcon } from '@radix-ui/react-icons'; -import { Command, Dialog, EmptyState, IconButton } from '@raystack/apsara'; +import { + Command, + Dialog, + EmptyState, + IconButton, + Spinner +} from '@raystack/apsara'; import { flattenTree, type Item as PageItem, @@ -36,10 +42,7 @@ type SearchItems = { items: Item[]; }; -// Docs pages carry no icon in their data, so map known page slugs to icons -// (overview + foundations pages get meaningful ones). Everything else — -// components and any future pages — falls back to a generic icon so every -// top-level row stays icon-aligned. +/* Map known page slugs to icons; everything else falls back to a generic one. */ const PAGE_ICONS: Record = { docs: ReaderIcon, 'getting-started': RocketIcon, @@ -59,8 +62,7 @@ const getPageIcon = (url: string): typeof MagnifyingGlassIcon => export default function DocsSearch({ pageTree }: { pageTree: Root }) { const router = useRouter(); const [open, setOpen] = useState(false); - // Per-result manual expand/collapse overrides; reset whenever the query - // changes so the auto-open rule re-applies for the fresh result set. + /* Manual expand/collapse overrides; reset on query change. */ const [openOverrides, setOpenOverrides] = useState>( {} ); @@ -86,9 +88,8 @@ export default function DocsSearch({ pageTree }: { pageTree: Root }) { const flattened = flattenTree(pageTree.children); if (!flattened.length) return []; - // Default view shows the overview + foundations (theme) pages grouped by - // folder. Components are intentionally excluded — there are dozens of them, - // so they only surface once the user actually searches. + /* Default view: overview + foundations grouped by folder; components are + excluded until the user searches. */ const items = flattened.reduce>((acc, item) => { const folder = getFolderFromUrl(item.url); if (folder === 'components') return acc; @@ -106,6 +107,7 @@ export default function DocsSearch({ pageTree }: { pageTree: Root }) { const trimmedQuery = search.trim(); const isSearching = trimmedQuery.length > 0; + const isLoading = isSearching && query.isLoading; const results = isSearching && query.data && query.data !== 'empty' ? query.data : []; @@ -152,10 +154,8 @@ export default function DocsSearch({ pageTree }: { pageTree: Root }) { const items = !isSearching ? defaultItems : searchResults; - // The `items` prop opts the Command out of its built-in per-item filtering - // and group-unwrapping: results are already filtered by fumadocs, and the - // grouped layout must stay intact while searching. An empty array is still - // truthy, so `hasItems` stays true even when there are no results. + /* The `items` prop opts Command out of built-in filtering/unwrapping — + results are pre-filtered by fumadocs and the grouped layout stays intact. */ const itemValues = useMemo( () => items.flatMap(section => @@ -167,10 +167,8 @@ export default function DocsSearch({ pageTree }: { pageTree: Root }) { [items] ); - // A result's sub-details collapse behaves like an accordion: open it only - // when the match came from a sub-detail rather than the page title (e.g. - // "Toolbar" surfacing for "button" via its Button section). When the title - // itself matches, the page is the hit, so keep its sub-details collapsed. + /* Auto-open sub-details only when the match came from a sub-detail, not the + page title; a title match keeps its sub-details collapsed. */ const queryLower = trimmedQuery.toLowerCase(); const titleMatches = (item: Item) => typeof item.name === 'string' && @@ -229,12 +227,16 @@ export default function DocsSearch({ pageTree }: { pageTree: Root }) { - } - /> + {isLoading ? ( + + ) : ( + } + /> + )} {items.map((section, index) => (