Skip to content

Commit 8acbfd5

Browse files
committed
type: fix type errors
1 parent 9302ef1 commit 8acbfd5

File tree

2 files changed

+164
-210
lines changed

2 files changed

+164
-210
lines changed

www/src/components/search-command-client.tsx

-176
This file was deleted.

www/src/components/search-command.tsx

+164-34
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,176 @@
1+
"use client";
2+
3+
import React from "react";
4+
import { useDocsSearch } from "fumadocs-core/search/client";
5+
import type { SortedResult } from "fumadocs-core/server";
6+
import {
7+
ChevronsUpDownIcon,
8+
CornerDownLeftIcon,
9+
FileTextIcon,
10+
HashIcon,
11+
SearchIcon,
12+
} from "lucide-react";
113
import { kekabCaseToTitle } from "@/lib/string";
2-
import { source } from "@/app/source";
3-
import { SearchCommandClient } from "./search-command-client";
4-
5-
const additionalPages = [
6-
{
7-
id: "themes",
8-
title: "Themes",
9-
headings: [],
10-
url: "/themes",
11-
},
12-
];
14+
import { Button } from "@/components/core/button";
15+
import { DialogRoot, Dialog } from "@/components/core/dialog";
16+
import { Loader } from "@/components/core/loader";
17+
import { MenuContent, MenuItem, MenuSection } from "@/components/core/menu";
18+
import { SearchFieldRoot } from "@/components/core/search-field";
19+
import { Command } from "@/registry/core/command_basic";
20+
import { Input, InputRoot } from "@/registry/core/input_basic";
21+
import { searchConfig } from "@/config";
22+
23+
interface SearchCommandProps {
24+
keyboardShortcut?: boolean;
25+
children: React.ReactNode;
26+
}
1327

1428
export function SearchCommand({
15-
children,
1629
keyboardShortcut,
17-
}: {
18-
children: React.ReactNode;
19-
keyboardShortcut?: boolean;
20-
}) {
21-
const pages = [
22-
...source.getPages().map((page) => ({
23-
id: page.url,
24-
title: page.data.title,
25-
headings: page.data.structuredData.headings,
26-
url: page.url,
27-
})),
28-
...additionalPages,
29-
];
30+
children,
31+
}: SearchCommandProps) {
32+
const { search, setSearch, query } = useDocsSearch({ type: "fetch" });
33+
const results =
34+
search === "" || query.data === "empty"
35+
? [
36+
{
37+
id: "suggestions",
38+
name: "Suggestions",
39+
results: searchConfig.defaultResults.map((elem) => ({
40+
id: elem.href,
41+
content: elem.name,
42+
url: elem.href,
43+
type: "page",
44+
})),
45+
},
46+
]
47+
: groupByCategory(query.data);
3048

49+
return (
50+
<SearchCommandDialog keyboardShortcut={keyboardShortcut} trigger={children}>
51+
<Command inputValue={search} onInputChange={setSearch} className="h-72">
52+
<div className="p-1">
53+
<SearchFieldRoot placeholder="Search" autoFocus className="w-full">
54+
<InputRoot className="focus-within:ring-1">
55+
{query.isLoading ? <Loader /> : <SearchIcon />}
56+
<Input />
57+
</InputRoot>
58+
</SearchFieldRoot>
59+
</div>
60+
<MenuContent className="h-full overflow-y-scroll py-1">
61+
{results.map((group) => (
62+
<MenuSection key={group.id} title={group.name}>
63+
{group.results.map((item) => (
64+
<MenuItem
65+
key={item.id}
66+
href={item.url}
67+
textValue={item.content}
68+
prefix={item.type === "page" ? <FileTextIcon /> : undefined}
69+
className={
70+
item.type === "page"
71+
? "[&_svg]:text-fg-muted gap-3 py-2"
72+
: "py-0 pl-2.5"
73+
}
74+
>
75+
{item.type === "page" ? (
76+
item.content
77+
) : (
78+
<div className="[&_svg]:text-fg-muted ml-2 flex items-center gap-3 border-l pl-4 [&_svg]:size-4">
79+
<HashIcon />
80+
<p className="flex-1 truncate py-2">{item.content}</p>
81+
</div>
82+
)}
83+
</MenuItem>
84+
))}
85+
</MenuSection>
86+
))}
87+
</MenuContent>
88+
<div className="text-fg-muted flex items-center justify-end gap-4 rounded-b-[inherit] border-t p-3 text-xs [&_svg]:size-4">
89+
<div className="flex items-center gap-1">
90+
<ChevronsUpDownIcon />
91+
<span>Navigate</span>
92+
</div>
93+
<div className="flex items-center gap-1">
94+
<CornerDownLeftIcon />
95+
<span>Go</span>
96+
</div>
97+
</div>
98+
</Command>
99+
</SearchCommandDialog>
100+
);
101+
}
102+
103+
type GroupedResults = {
104+
id: string;
105+
name: string;
106+
results: SortedResult[];
107+
}[];
108+
const groupByCategory = (results?: SortedResult[]): GroupedResults => {
109+
// We will get the category from the url and group the results by category
110+
// eg url: /docs/components/buttons/button -> category: components
111+
if (!results) return [];
31112
const uniqueCategories = Array.from(
32-
new Set(pages.map((item) => item.url.split("/")[2]))
113+
new Set(results.map((result) => result.url.split("/")[2]!))
33114
).filter(Boolean);
34115

35-
const items = uniqueCategories.map((category) => ({
36-
// @ts-expect-error TODO fix later
37-
title: kekabCaseToTitle(category),
38-
items: pages.filter((item) => item.url.split("/")[2] === category),
116+
const groupedResults: GroupedResults = uniqueCategories.map((category) => ({
117+
id: category,
118+
name: kekabCaseToTitle(category),
119+
results: results.filter((result) => result.url.split("/")[2] === category),
39120
}));
40121

122+
return groupedResults;
123+
};
124+
125+
const SearchCommandDialog = ({
126+
keyboardShortcut = false,
127+
trigger,
128+
children,
129+
}: {
130+
keyboardShortcut?: boolean;
131+
children: React.ReactNode;
132+
trigger: React.ReactNode;
133+
}) => {
134+
const [isOpen, setIsOpen] = React.useState(false);
135+
136+
React.useEffect(() => {
137+
if (!keyboardShortcut) return;
138+
139+
const down = (e: KeyboardEvent) => {
140+
if ((e.key === "k" && (e.metaKey || e.ctrlKey)) || e.key === "/") {
141+
if (
142+
(e.target instanceof HTMLElement && e.target.isContentEditable) ||
143+
e.target instanceof HTMLInputElement ||
144+
e.target instanceof HTMLTextAreaElement ||
145+
e.target instanceof HTMLSelectElement
146+
) {
147+
return;
148+
}
149+
150+
e.preventDefault();
151+
setIsOpen((open) => !open);
152+
}
153+
};
154+
155+
document.addEventListener("keydown", down);
156+
return () => document.removeEventListener("keydown", down);
157+
}, [keyboardShortcut]);
158+
41159
return (
42-
<SearchCommandClient keyboardShortcut={keyboardShortcut} items={items}>
43-
{children}
44-
</SearchCommandClient>
160+
<DialogRoot isOpen={isOpen} onOpenChange={setIsOpen}>
161+
{trigger}
162+
<Dialog className="p-0!">
163+
{children}
164+
<Button
165+
slot="close"
166+
variant="outline"
167+
shape="rectangle"
168+
size="sm"
169+
className="absolute right-2 top-2 h-7 px-2 text-xs font-normal"
170+
>
171+
Esc
172+
</Button>
173+
</Dialog>
174+
</DialogRoot>
45175
);
46-
}
176+
};

0 commit comments

Comments
 (0)