Skip to content

Commit eb21a6c

Browse files
authored
feat(spotlight-search): added spinner for loading state during loading state in spotlight search (#835)
* feat(spotlight-search): added spinner for loading state during loading state in spotlight search * feat(spotlight-search): fixed comments
1 parent e58e59e commit eb21a6c

1 file changed

Lines changed: 26 additions & 24 deletions

File tree

apps/www/src/components/docs/search.tsx

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@ import {
1515
SpaceBetweenVerticallyIcon,
1616
TextIcon
1717
} from '@radix-ui/react-icons';
18-
import { Command, Dialog, EmptyState, IconButton } from '@raystack/apsara';
18+
import {
19+
Command,
20+
Dialog,
21+
EmptyState,
22+
IconButton,
23+
Spinner
24+
} from '@raystack/apsara';
1925
import {
2026
flattenTree,
2127
type Item as PageItem,
@@ -36,10 +42,7 @@ type SearchItems = {
3642
items: Item[];
3743
};
3844

39-
// Docs pages carry no icon in their data, so map known page slugs to icons
40-
// (overview + foundations pages get meaningful ones). Everything else —
41-
// components and any future pages — falls back to a generic icon so every
42-
// top-level row stays icon-aligned.
45+
/* Map known page slugs to icons; everything else falls back to a generic one. */
4346
const PAGE_ICONS: Record<string, typeof MagnifyingGlassIcon> = {
4447
docs: ReaderIcon,
4548
'getting-started': RocketIcon,
@@ -59,8 +62,7 @@ const getPageIcon = (url: string): typeof MagnifyingGlassIcon =>
5962
export default function DocsSearch({ pageTree }: { pageTree: Root }) {
6063
const router = useRouter();
6164
const [open, setOpen] = useState(false);
62-
// Per-result manual expand/collapse overrides; reset whenever the query
63-
// changes so the auto-open rule re-applies for the fresh result set.
65+
/* Manual expand/collapse overrides; reset on query change. */
6466
const [openOverrides, setOpenOverrides] = useState<Record<string, boolean>>(
6567
{}
6668
);
@@ -86,9 +88,8 @@ export default function DocsSearch({ pageTree }: { pageTree: Root }) {
8688
const flattened = flattenTree(pageTree.children);
8789
if (!flattened.length) return [];
8890

89-
// Default view shows the overview + foundations (theme) pages grouped by
90-
// folder. Components are intentionally excluded — there are dozens of them,
91-
// so they only surface once the user actually searches.
91+
/* Default view: overview + foundations grouped by folder; components are
92+
excluded until the user searches. */
9293
const items = flattened.reduce<Record<string, Item[]>>((acc, item) => {
9394
const folder = getFolderFromUrl(item.url);
9495
if (folder === 'components') return acc;
@@ -106,6 +107,7 @@ export default function DocsSearch({ pageTree }: { pageTree: Root }) {
106107

107108
const trimmedQuery = search.trim();
108109
const isSearching = trimmedQuery.length > 0;
110+
const isLoading = isSearching && query.isLoading;
109111
const results =
110112
isSearching && query.data && query.data !== 'empty' ? query.data : [];
111113

@@ -152,10 +154,8 @@ export default function DocsSearch({ pageTree }: { pageTree: Root }) {
152154

153155
const items = !isSearching ? defaultItems : searchResults;
154156

155-
// The `items` prop opts the Command out of its built-in per-item filtering
156-
// and group-unwrapping: results are already filtered by fumadocs, and the
157-
// grouped layout must stay intact while searching. An empty array is still
158-
// truthy, so `hasItems` stays true even when there are no results.
157+
/* The `items` prop opts Command out of built-in filtering/unwrapping —
158+
results are pre-filtered by fumadocs and the grouped layout stays intact. */
159159
const itemValues = useMemo(
160160
() =>
161161
items.flatMap(section =>
@@ -167,10 +167,8 @@ export default function DocsSearch({ pageTree }: { pageTree: Root }) {
167167
[items]
168168
);
169169

170-
// A result's sub-details collapse behaves like an accordion: open it only
171-
// when the match came from a sub-detail rather than the page title (e.g.
172-
// "Toolbar" surfacing for "button" via its Button section). When the title
173-
// itself matches, the page is the hit, so keep its sub-details collapsed.
170+
/* Auto-open sub-details only when the match came from a sub-detail, not the
171+
page title; a title match keeps its sub-details collapsed. */
174172
const queryLower = trimmedQuery.toLowerCase();
175173
const titleMatches = (item: Item) =>
176174
typeof item.name === 'string' &&
@@ -229,12 +227,16 @@ export default function DocsSearch({ pageTree }: { pageTree: Root }) {
229227
</div>
230228
<Command.Content className={styles.searchList}>
231229
<Command.Empty className={styles.searchEmpty}>
232-
<EmptyState
233-
variant='empty1'
234-
heading='No result found'
235-
subHeading='The keyword you’re searching for isn’t in the document—try using a different term.'
236-
icon={<ExclamationTriangleIcon />}
237-
/>
230+
{isLoading ? (
231+
<Spinner size={5} aria-label='Searching docs' />
232+
) : (
233+
<EmptyState
234+
variant='empty1'
235+
heading='No result found'
236+
subHeading='The keyword you’re searching for isn’t in the document—try using a different term.'
237+
icon={<ExclamationTriangleIcon />}
238+
/>
239+
)}
238240
</Command.Empty>
239241
{items.map((section, index) => (
240242
<Fragment key={section.heading}>

0 commit comments

Comments
 (0)