blodemd 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dev-server/app/[[...slug]]/page.tsx +139 -0
- package/dev-server/app/blodemd-dev/invalidate/route.ts +12 -0
- package/dev-server/app/blodemd-dev/static/[...path]/route.ts +32 -0
- package/dev-server/app/blodemd-dev/version/route.ts +14 -0
- package/dev-server/app/blodemd-internal/proxy/route.ts +86 -0
- package/dev-server/app/error.tsx +24 -0
- package/dev-server/app/favicon.ico +0 -0
- package/dev-server/app/globals.css +4 -0
- package/dev-server/app/layout.tsx +38 -0
- package/dev-server/app/not-found.tsx +18 -0
- package/dev-server/app/search/route.ts +17 -0
- package/dev-server/components/dev-reload-script.tsx +86 -0
- package/dev-server/components/providers.tsx +15 -0
- package/dev-server/lib/dev-state.ts +8 -0
- package/dev-server/lib/local-content-source.ts +103 -0
- package/dev-server/lib/local-runtime.tsx +558 -0
- package/dev-server/next-env.d.ts +5 -0
- package/dev-server/next.config.js +46 -0
- package/dev-server/package.json +57 -0
- package/dev-server/postcss.config.mjs +7 -0
- package/dev-server/public/glide-variable.woff2 +0 -0
- package/dev-server/tsconfig.json +50 -0
- package/dist/cli.mjs +311 -86
- package/dist/cli.mjs.map +1 -1
- package/docs/app/globals.css +457 -0
- package/docs/components/api/api-playground.tsx +295 -0
- package/docs/components/api/api-reference.tsx +121 -0
- package/docs/components/content/collection-index.tsx +114 -0
- package/docs/components/docs/contextual-menu.tsx +406 -0
- package/docs/components/docs/copy-page-menu.tsx +255 -0
- package/docs/components/docs/doc-header.tsx +210 -0
- package/docs/components/docs/doc-shell.tsx +313 -0
- package/docs/components/docs/doc-sidebar.tsx +211 -0
- package/docs/components/docs/doc-toc.tsx +45 -0
- package/docs/components/docs/mobile-nav.tsx +205 -0
- package/docs/components/icons/doc-icon.tsx +96 -0
- package/docs/components/mdx/accordion.tsx +83 -0
- package/docs/components/mdx/badge.tsx +79 -0
- package/docs/components/mdx/callout.tsx +88 -0
- package/docs/components/mdx/card.tsx +110 -0
- package/docs/components/mdx/code-block.tsx +75 -0
- package/docs/components/mdx/code-group.tsx +94 -0
- package/docs/components/mdx/color.tsx +87 -0
- package/docs/components/mdx/columns.tsx +25 -0
- package/docs/components/mdx/expandable.tsx +45 -0
- package/docs/components/mdx/field-layout.tsx +77 -0
- package/docs/components/mdx/frame.tsx +23 -0
- package/docs/components/mdx/get-text-content.ts +18 -0
- package/docs/components/mdx/icon.tsx +12 -0
- package/docs/components/mdx/index.tsx +107 -0
- package/docs/components/mdx/installer.tsx +20 -0
- package/docs/components/mdx/panel.tsx +11 -0
- package/docs/components/mdx/param-field.tsx +56 -0
- package/docs/components/mdx/preview.tsx +36 -0
- package/docs/components/mdx/prompt.tsx +63 -0
- package/docs/components/mdx/request-example.tsx +27 -0
- package/docs/components/mdx/response-field.tsx +42 -0
- package/docs/components/mdx/steps.tsx +92 -0
- package/docs/components/mdx/tabs.tsx +88 -0
- package/docs/components/mdx/tile.tsx +43 -0
- package/docs/components/mdx/tooltip.tsx +71 -0
- package/docs/components/mdx/tree.tsx +120 -0
- package/docs/components/mdx/type-table.tsx +71 -0
- package/docs/components/mdx/update.tsx +44 -0
- package/docs/components/mdx/video.tsx +12 -0
- package/docs/components/mdx/view.tsx +66 -0
- package/docs/components/providers.tsx +15 -0
- package/docs/components/ui/breadcrumb.tsx +92 -0
- package/docs/components/ui/button.tsx +90 -0
- package/docs/components/ui/card.tsx +92 -0
- package/docs/components/ui/command.tsx +139 -0
- package/docs/components/ui/dialog.tsx +97 -0
- package/docs/components/ui/field.tsx +237 -0
- package/docs/components/ui/input.tsx +105 -0
- package/docs/components/ui/label.tsx +22 -0
- package/docs/components/ui/popover.tsx +72 -0
- package/docs/components/ui/search.tsx +384 -0
- package/docs/components/ui/separator.tsx +26 -0
- package/docs/components/ui/sheet.tsx +104 -0
- package/docs/components/ui/sidebar.tsx +433 -0
- package/docs/components/ui/theme-toggle.tsx +62 -0
- package/docs/components/ui/tooltip.tsx +53 -0
- package/docs/lib/contextual-options.ts +193 -0
- package/docs/lib/docs-collection.ts +22 -0
- package/docs/lib/mdx.ts +87 -0
- package/docs/lib/navigation.ts +288 -0
- package/docs/lib/openapi.ts +158 -0
- package/docs/lib/routes.ts +44 -0
- package/docs/lib/server-cache.ts +83 -0
- package/docs/lib/shiki.ts +40 -0
- package/docs/lib/theme.ts +29 -0
- package/docs/lib/toc.ts +2 -0
- package/docs/lib/utils.ts +5 -0
- package/package.json +43 -6
- package/packages/@repo/common/dist/index.d.ts +9 -0
- package/packages/@repo/common/dist/index.d.ts.map +1 -0
- package/packages/@repo/common/dist/index.js +42 -0
- package/packages/@repo/common/package.json +34 -0
- package/packages/@repo/common/src/index.ts +51 -0
- package/packages/@repo/contracts/dist/api-key.d.ts +30 -0
- package/packages/@repo/contracts/dist/api-key.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/api-key.js +20 -0
- package/packages/@repo/contracts/dist/dates.d.ts +4 -0
- package/packages/@repo/contracts/dist/dates.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/dates.js +2 -0
- package/packages/@repo/contracts/dist/deployment.d.ts +71 -0
- package/packages/@repo/contracts/dist/deployment.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/deployment.js +46 -0
- package/packages/@repo/contracts/dist/domain.d.ts +94 -0
- package/packages/@repo/contracts/dist/domain.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/domain.js +36 -0
- package/packages/@repo/contracts/dist/ids.d.ts +14 -0
- package/packages/@repo/contracts/dist/ids.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/ids.js +10 -0
- package/packages/@repo/contracts/dist/index.d.ts +10 -0
- package/packages/@repo/contracts/dist/index.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/index.js +11 -0
- package/packages/@repo/contracts/dist/pagination.d.ts +23 -0
- package/packages/@repo/contracts/dist/pagination.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/pagination.js +15 -0
- package/packages/@repo/contracts/dist/project.d.ts +25 -0
- package/packages/@repo/contracts/dist/project.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/project.js +23 -0
- package/packages/@repo/contracts/dist/tenant.d.ts +111 -0
- package/packages/@repo/contracts/dist/tenant.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/tenant.js +56 -0
- package/packages/@repo/contracts/dist/user.d.ts +9 -0
- package/packages/@repo/contracts/dist/user.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/user.js +9 -0
- package/packages/@repo/contracts/package.json +37 -0
- package/packages/@repo/contracts/src/api-key.ts +27 -0
- package/packages/@repo/contracts/src/dates.ts +4 -0
- package/packages/@repo/contracts/src/deployment.ts +73 -0
- package/packages/@repo/contracts/src/domain.ts +51 -0
- package/packages/@repo/contracts/src/ids.ts +22 -0
- package/packages/@repo/contracts/src/index.ts +11 -0
- package/packages/@repo/contracts/src/pagination.ts +21 -0
- package/packages/@repo/contracts/src/project.ts +30 -0
- package/packages/@repo/contracts/src/tenant.ts +92 -0
- package/packages/@repo/contracts/src/user.ts +12 -0
- package/packages/@repo/models/dist/docs-config.d.ts +985 -0
- package/packages/@repo/models/dist/docs-config.d.ts.map +1 -0
- package/packages/@repo/models/dist/docs-config.js +548 -0
- package/packages/@repo/models/dist/index.d.ts +3 -0
- package/packages/@repo/models/dist/index.d.ts.map +1 -0
- package/packages/@repo/models/dist/index.js +3 -0
- package/packages/@repo/models/dist/tenant.d.ts +25 -0
- package/packages/@repo/models/dist/tenant.d.ts.map +1 -0
- package/packages/@repo/models/dist/tenant.js +1 -0
- package/packages/@repo/models/package.json +37 -0
- package/packages/@repo/models/src/docs-config.ts +648 -0
- package/packages/@repo/models/src/index.ts +3 -0
- package/packages/@repo/models/src/tenant.ts +29 -0
- package/packages/@repo/prebuild/dist/index.d.ts +2 -0
- package/packages/@repo/prebuild/dist/index.d.ts.map +1 -0
- package/packages/@repo/prebuild/dist/index.js +2 -0
- package/packages/@repo/prebuild/dist/openapi.d.ts +43 -0
- package/packages/@repo/prebuild/dist/openapi.d.ts.map +1 -0
- package/packages/@repo/prebuild/dist/openapi.js +58 -0
- package/packages/@repo/prebuild/package.json +39 -0
- package/packages/@repo/prebuild/src/index.ts +2 -0
- package/packages/@repo/prebuild/src/openapi.ts +116 -0
- package/packages/@repo/previewing/dist/blob-source.d.ts +16 -0
- package/packages/@repo/previewing/dist/blob-source.d.ts.map +1 -0
- package/packages/@repo/previewing/dist/blob-source.js +110 -0
- package/packages/@repo/previewing/dist/content-source.d.ts +12 -0
- package/packages/@repo/previewing/dist/content-source.d.ts.map +1 -0
- package/packages/@repo/previewing/dist/content-source.js +1 -0
- package/packages/@repo/previewing/dist/fs-source.d.ts +11 -0
- package/packages/@repo/previewing/dist/fs-source.d.ts.map +1 -0
- package/packages/@repo/previewing/dist/fs-source.js +72 -0
- package/packages/@repo/previewing/dist/index.d.ts +120 -0
- package/packages/@repo/previewing/dist/index.d.ts.map +1 -0
- package/packages/@repo/previewing/dist/index.js +984 -0
- package/packages/@repo/previewing/package.json +41 -0
- package/packages/@repo/previewing/src/blob-source.ts +167 -0
- package/packages/@repo/previewing/src/content-source.ts +12 -0
- package/packages/@repo/previewing/src/fs-source.ts +104 -0
- package/packages/@repo/previewing/src/index.ts +1490 -0
- package/packages/@repo/validation/dist/index.d.ts +12 -0
- package/packages/@repo/validation/dist/index.d.ts.map +1 -0
- package/packages/@repo/validation/dist/index.js +30 -0
- package/packages/@repo/validation/package.json +37 -0
- package/packages/@repo/validation/src/index.ts +59 -0
- package/packages/@repo/validation/src/mintlify-docs-schema.json +5016 -0
- package/scripts/prepare-package.mjs +39 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { SearchIcon } from "blode-icons-react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import {
|
|
6
|
+
startTransition,
|
|
7
|
+
useCallback,
|
|
8
|
+
useDeferredValue,
|
|
9
|
+
useEffect,
|
|
10
|
+
useMemo,
|
|
11
|
+
useRef,
|
|
12
|
+
useState,
|
|
13
|
+
} from "react";
|
|
14
|
+
import type {
|
|
15
|
+
ChangeEvent,
|
|
16
|
+
KeyboardEvent as ReactKeyboardEvent,
|
|
17
|
+
MouseEvent as ReactMouseEvent,
|
|
18
|
+
} from "react";
|
|
19
|
+
|
|
20
|
+
import { isExternalHref, resolveHref, toDocHref } from "@/lib/routes";
|
|
21
|
+
|
|
22
|
+
export interface SearchItem {
|
|
23
|
+
href?: string;
|
|
24
|
+
title: string;
|
|
25
|
+
path: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface SearchResponse {
|
|
29
|
+
items: SearchItem[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const MAX_RESULTS = 12;
|
|
33
|
+
|
|
34
|
+
const isEditableTarget = (target: EventTarget | null) =>
|
|
35
|
+
(target instanceof HTMLElement && target.isContentEditable) ||
|
|
36
|
+
target instanceof HTMLInputElement ||
|
|
37
|
+
target instanceof HTMLTextAreaElement ||
|
|
38
|
+
target instanceof HTMLSelectElement;
|
|
39
|
+
|
|
40
|
+
const searchMatches = (item: SearchItem, query: string) => {
|
|
41
|
+
if (!query) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const haystack =
|
|
46
|
+
`${item.title} ${item.path} ${item.href ?? ""}`.toLowerCase();
|
|
47
|
+
return haystack.includes(query);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const getWrappedNextIndex = (current: number, length: number) => {
|
|
51
|
+
if (length === 0 || current >= length - 1) {
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return current + 1;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const getWrappedPrevIndex = (current: number, length: number) => {
|
|
59
|
+
if (length === 0 || current <= 0) {
|
|
60
|
+
return length - 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return current - 1;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const Search = ({ basePath }: { basePath: string }) => {
|
|
67
|
+
const router = useRouter();
|
|
68
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
69
|
+
const requestRef = useRef<Promise<void> | null>(null);
|
|
70
|
+
const loadedRef = useRef(false);
|
|
71
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
72
|
+
const [items, setItems] = useState<SearchItem[]>([]);
|
|
73
|
+
const [open, setOpen] = useState(false);
|
|
74
|
+
const [query, setQuery] = useState("");
|
|
75
|
+
const [status, setStatus] = useState<"idle" | "loading" | "ready" | "error">(
|
|
76
|
+
"idle"
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const deferredQuery = useDeferredValue(query.trim().toLowerCase());
|
|
80
|
+
|
|
81
|
+
const loadSearchItems = useCallback(() => {
|
|
82
|
+
if (loadedRef.current) {
|
|
83
|
+
return Promise.resolve();
|
|
84
|
+
}
|
|
85
|
+
if (requestRef.current) {
|
|
86
|
+
return requestRef.current;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
setStatus("loading");
|
|
90
|
+
const request = (async () => {
|
|
91
|
+
try {
|
|
92
|
+
const response = await fetch(toDocHref("search", basePath), {
|
|
93
|
+
headers: {
|
|
94
|
+
accept: "application/json",
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (!response.ok) {
|
|
99
|
+
throw new Error(`Failed to load search index: ${response.status}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const payload = (await response.json()) as SearchResponse;
|
|
103
|
+
const nextItems = Array.isArray(payload.items) ? payload.items : [];
|
|
104
|
+
loadedRef.current = true;
|
|
105
|
+
startTransition(() => {
|
|
106
|
+
setItems(nextItems);
|
|
107
|
+
setStatus("ready");
|
|
108
|
+
});
|
|
109
|
+
} catch {
|
|
110
|
+
setStatus("error");
|
|
111
|
+
} finally {
|
|
112
|
+
requestRef.current = null;
|
|
113
|
+
}
|
|
114
|
+
})();
|
|
115
|
+
|
|
116
|
+
requestRef.current = request;
|
|
117
|
+
return request;
|
|
118
|
+
}, [basePath]);
|
|
119
|
+
|
|
120
|
+
const filteredItems = useMemo(
|
|
121
|
+
() =>
|
|
122
|
+
items
|
|
123
|
+
.filter((item) => searchMatches(item, deferredQuery))
|
|
124
|
+
.slice(0, MAX_RESULTS),
|
|
125
|
+
[deferredQuery, items]
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const closeSearch = useCallback(() => {
|
|
129
|
+
setOpen(false);
|
|
130
|
+
setQuery("");
|
|
131
|
+
setActiveIndex(0);
|
|
132
|
+
}, []);
|
|
133
|
+
|
|
134
|
+
const runSelection = useCallback(
|
|
135
|
+
(item: SearchItem) => {
|
|
136
|
+
closeSearch();
|
|
137
|
+
const href = item.href
|
|
138
|
+
? resolveHref(item.href, basePath)
|
|
139
|
+
: toDocHref(item.path, basePath);
|
|
140
|
+
if (item.href && isExternalHref(item.href)) {
|
|
141
|
+
window.open(href, "_blank", "noopener,noreferrer");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
router.push(href);
|
|
145
|
+
},
|
|
146
|
+
[basePath, closeSearch, router]
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const openSearch = useCallback(async () => {
|
|
150
|
+
setOpen(true);
|
|
151
|
+
await loadSearchItems();
|
|
152
|
+
}, [loadSearchItems]);
|
|
153
|
+
|
|
154
|
+
const warmSearch = useCallback(async () => {
|
|
155
|
+
try {
|
|
156
|
+
await loadSearchItems();
|
|
157
|
+
} catch {
|
|
158
|
+
// Ignore warm-up failures and let the explicit open path show the error state.
|
|
159
|
+
}
|
|
160
|
+
}, [loadSearchItems]);
|
|
161
|
+
|
|
162
|
+
const handleQueryChange = useCallback(
|
|
163
|
+
(event: ChangeEvent<HTMLInputElement>) => {
|
|
164
|
+
setQuery(event.target.value);
|
|
165
|
+
},
|
|
166
|
+
[]
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const handleResultClick = useCallback(
|
|
170
|
+
(event: ReactMouseEvent<HTMLButtonElement>) => {
|
|
171
|
+
const index = Number(event.currentTarget.dataset.index);
|
|
172
|
+
const item = filteredItems[index];
|
|
173
|
+
if (!item) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
runSelection(item);
|
|
178
|
+
},
|
|
179
|
+
[filteredItems, runSelection]
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const handleResultMouseEnter = useCallback(
|
|
183
|
+
(event: ReactMouseEvent<HTMLButtonElement>) => {
|
|
184
|
+
const index = Number(event.currentTarget.dataset.index);
|
|
185
|
+
if (Number.isNaN(index)) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
setActiveIndex(index);
|
|
190
|
+
},
|
|
191
|
+
[]
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
if (!open) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
inputRef.current?.focus();
|
|
200
|
+
const previousOverflow = document.body.style.overflow;
|
|
201
|
+
document.body.style.overflow = "hidden";
|
|
202
|
+
|
|
203
|
+
return () => {
|
|
204
|
+
document.body.style.overflow = previousOverflow;
|
|
205
|
+
};
|
|
206
|
+
}, [open]);
|
|
207
|
+
|
|
208
|
+
useEffect(() => {
|
|
209
|
+
setActiveIndex(0);
|
|
210
|
+
}, [deferredQuery, open]);
|
|
211
|
+
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
const handleKeydown = async (event: KeyboardEvent) => {
|
|
214
|
+
if (
|
|
215
|
+
(event.key === "k" && (event.metaKey || event.ctrlKey)) ||
|
|
216
|
+
event.key === "/"
|
|
217
|
+
) {
|
|
218
|
+
if (isEditableTarget(event.target)) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
event.preventDefault();
|
|
223
|
+
if (open) {
|
|
224
|
+
closeSearch();
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
await openSearch();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (!open || event.key !== "Escape") {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
event.preventDefault();
|
|
236
|
+
closeSearch();
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
document.addEventListener("keydown", handleKeydown);
|
|
240
|
+
return () => document.removeEventListener("keydown", handleKeydown);
|
|
241
|
+
}, [closeSearch, open, openSearch]);
|
|
242
|
+
|
|
243
|
+
const handleDialogKeyDown = useCallback(
|
|
244
|
+
(event: ReactKeyboardEvent<HTMLDivElement>) => {
|
|
245
|
+
if (event.key === "ArrowDown") {
|
|
246
|
+
event.preventDefault();
|
|
247
|
+
setActiveIndex((current) =>
|
|
248
|
+
getWrappedNextIndex(current, filteredItems.length)
|
|
249
|
+
);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (event.key === "ArrowUp") {
|
|
254
|
+
event.preventDefault();
|
|
255
|
+
setActiveIndex((current) =>
|
|
256
|
+
getWrappedPrevIndex(current, filteredItems.length)
|
|
257
|
+
);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (event.key === "Enter") {
|
|
262
|
+
const activeItem = filteredItems[activeIndex];
|
|
263
|
+
if (!activeItem) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
event.preventDefault();
|
|
268
|
+
runSelection(activeItem);
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
[activeIndex, filteredItems, runSelection]
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
return (
|
|
275
|
+
<>
|
|
276
|
+
<button
|
|
277
|
+
aria-label="Search documentation"
|
|
278
|
+
className="inline-flex size-8 items-center justify-center rounded-md hover:bg-accent hover:text-accent-foreground md:hidden"
|
|
279
|
+
onClick={openSearch}
|
|
280
|
+
onFocus={warmSearch}
|
|
281
|
+
onMouseEnter={warmSearch}
|
|
282
|
+
type="button"
|
|
283
|
+
>
|
|
284
|
+
<SearchIcon className="size-4.5" />
|
|
285
|
+
</button>
|
|
286
|
+
<button
|
|
287
|
+
className="relative hidden h-8 w-full items-center justify-start rounded-lg border border-border bg-muted/50 pl-3 text-sm font-normal text-foreground shadow-none transition-colors hover:bg-muted/80 md:flex md:w-48 lg:w-56 xl:w-64 dark:bg-card"
|
|
288
|
+
onClick={openSearch}
|
|
289
|
+
onFocus={warmSearch}
|
|
290
|
+
onMouseEnter={warmSearch}
|
|
291
|
+
type="button"
|
|
292
|
+
>
|
|
293
|
+
<span className="hidden lg:inline-flex">Search documentation...</span>
|
|
294
|
+
<span className="inline-flex lg:hidden">Search...</span>
|
|
295
|
+
<span className="ml-auto hidden pr-3 text-[11px] text-muted-foreground sm:inline-flex">
|
|
296
|
+
Cmd K
|
|
297
|
+
</span>
|
|
298
|
+
</button>
|
|
299
|
+
{open ? (
|
|
300
|
+
<div className="fixed inset-0 z-50">
|
|
301
|
+
<button
|
|
302
|
+
aria-label="Close search"
|
|
303
|
+
className="absolute inset-0 bg-background/80 backdrop-blur-sm"
|
|
304
|
+
onClick={closeSearch}
|
|
305
|
+
type="button"
|
|
306
|
+
/>
|
|
307
|
+
<div
|
|
308
|
+
aria-modal="true"
|
|
309
|
+
className="relative mx-auto mt-[10vh] flex w-[calc(100%-2rem)] max-w-2xl flex-col overflow-hidden rounded-2xl border border-border bg-background shadow-2xl"
|
|
310
|
+
onKeyDown={handleDialogKeyDown}
|
|
311
|
+
role="dialog"
|
|
312
|
+
>
|
|
313
|
+
<div className="flex items-center gap-3 border-b border-border px-4 py-3">
|
|
314
|
+
<SearchIcon className="size-4 text-muted-foreground" />
|
|
315
|
+
<input
|
|
316
|
+
aria-label="Search documentation"
|
|
317
|
+
className="w-full bg-transparent text-sm text-foreground outline-none placeholder:text-muted-foreground"
|
|
318
|
+
onChange={handleQueryChange}
|
|
319
|
+
placeholder="Search docs..."
|
|
320
|
+
ref={inputRef}
|
|
321
|
+
type="text"
|
|
322
|
+
value={query}
|
|
323
|
+
/>
|
|
324
|
+
<button
|
|
325
|
+
className="rounded-md border border-border px-2 py-1 text-[11px] text-muted-foreground hover:bg-accent hover:text-foreground"
|
|
326
|
+
onClick={closeSearch}
|
|
327
|
+
type="button"
|
|
328
|
+
>
|
|
329
|
+
Esc
|
|
330
|
+
</button>
|
|
331
|
+
</div>
|
|
332
|
+
<div className="max-h-[min(70vh,32rem)] overflow-y-auto p-2">
|
|
333
|
+
{status === "loading" ? (
|
|
334
|
+
<div className="px-3 py-10 text-center text-sm text-muted-foreground">
|
|
335
|
+
Loading search index...
|
|
336
|
+
</div>
|
|
337
|
+
) : null}
|
|
338
|
+
{status === "error" ? (
|
|
339
|
+
<div className="px-3 py-10 text-center text-sm text-muted-foreground">
|
|
340
|
+
Search is temporarily unavailable.
|
|
341
|
+
</div>
|
|
342
|
+
) : null}
|
|
343
|
+
{status === "ready" && filteredItems.length === 0 ? (
|
|
344
|
+
<div className="px-3 py-10 text-center text-sm text-muted-foreground">
|
|
345
|
+
No results found.
|
|
346
|
+
</div>
|
|
347
|
+
) : null}
|
|
348
|
+
{status === "ready" && filteredItems.length > 0 ? (
|
|
349
|
+
<div className="grid gap-1">
|
|
350
|
+
{filteredItems.map((item, index) => {
|
|
351
|
+
const isActive = index === activeIndex;
|
|
352
|
+
const href = item.href
|
|
353
|
+
? resolveHref(item.href, basePath)
|
|
354
|
+
: toDocHref(item.path, basePath);
|
|
355
|
+
|
|
356
|
+
return (
|
|
357
|
+
<button
|
|
358
|
+
className={`grid gap-1 rounded-xl px-3 py-2 text-left transition-colors ${
|
|
359
|
+
isActive
|
|
360
|
+
? "bg-accent text-foreground"
|
|
361
|
+
: "text-muted-foreground hover:bg-accent/70 hover:text-foreground"
|
|
362
|
+
}`}
|
|
363
|
+
data-index={index}
|
|
364
|
+
key={`${item.path}-${item.href ?? "internal"}`}
|
|
365
|
+
onClick={handleResultClick}
|
|
366
|
+
onMouseEnter={handleResultMouseEnter}
|
|
367
|
+
type="button"
|
|
368
|
+
>
|
|
369
|
+
<span className="text-sm font-medium text-foreground">
|
|
370
|
+
{item.title}
|
|
371
|
+
</span>
|
|
372
|
+
<span className="text-xs">{href}</span>
|
|
373
|
+
</button>
|
|
374
|
+
);
|
|
375
|
+
})}
|
|
376
|
+
</div>
|
|
377
|
+
) : null}
|
|
378
|
+
</div>
|
|
379
|
+
</div>
|
|
380
|
+
</div>
|
|
381
|
+
) : null}
|
|
382
|
+
</>
|
|
383
|
+
);
|
|
384
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Separator as SeparatorPrimitive } from "radix-ui";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "@/lib/utils";
|
|
7
|
+
|
|
8
|
+
const Separator = ({
|
|
9
|
+
className,
|
|
10
|
+
orientation = "horizontal",
|
|
11
|
+
decorative = true,
|
|
12
|
+
...props
|
|
13
|
+
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) => (
|
|
14
|
+
<SeparatorPrimitive.Root
|
|
15
|
+
data-slot="separator"
|
|
16
|
+
decorative={decorative}
|
|
17
|
+
orientation={orientation}
|
|
18
|
+
className={cn(
|
|
19
|
+
"shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
|
|
20
|
+
className
|
|
21
|
+
)}
|
|
22
|
+
{...props}
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
export { Separator };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { XIcon } from "blode-icons-react";
|
|
4
|
+
import { Dialog as SheetPrimitive } from "radix-ui";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
|
|
7
|
+
import { cn } from "@/lib/utils";
|
|
8
|
+
|
|
9
|
+
const Sheet = ({
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof SheetPrimitive.Root>) => (
|
|
12
|
+
<SheetPrimitive.Root data-slot="sheet" {...props} />
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
const SheetPortal = ({
|
|
16
|
+
...props
|
|
17
|
+
}: React.ComponentProps<typeof SheetPrimitive.Portal>) => (
|
|
18
|
+
<SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const SheetOverlay = ({
|
|
22
|
+
className,
|
|
23
|
+
...props
|
|
24
|
+
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) => (
|
|
25
|
+
<SheetPrimitive.Overlay
|
|
26
|
+
data-slot="sheet-overlay"
|
|
27
|
+
className={cn(
|
|
28
|
+
"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
|
29
|
+
className
|
|
30
|
+
)}
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const SheetContent = ({
|
|
36
|
+
className,
|
|
37
|
+
children,
|
|
38
|
+
side = "right",
|
|
39
|
+
showCloseButton = true,
|
|
40
|
+
...props
|
|
41
|
+
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
|
42
|
+
side?: "top" | "right" | "bottom" | "left";
|
|
43
|
+
showCloseButton?: boolean;
|
|
44
|
+
}) => (
|
|
45
|
+
<SheetPortal>
|
|
46
|
+
<SheetOverlay />
|
|
47
|
+
<SheetPrimitive.Content
|
|
48
|
+
data-slot="sheet-content"
|
|
49
|
+
className={cn(
|
|
50
|
+
"fixed z-50 flex flex-col gap-4 bg-background shadow-lg transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500",
|
|
51
|
+
side === "right" &&
|
|
52
|
+
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
|
53
|
+
side === "left" &&
|
|
54
|
+
"inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
|
55
|
+
side === "top" &&
|
|
56
|
+
"inset-x-0 top-0 h-auto border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
|
57
|
+
side === "bottom" &&
|
|
58
|
+
"inset-x-0 bottom-0 h-auto border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
|
59
|
+
className
|
|
60
|
+
)}
|
|
61
|
+
{...props}
|
|
62
|
+
>
|
|
63
|
+
{children}
|
|
64
|
+
{showCloseButton && (
|
|
65
|
+
<SheetPrimitive.Close className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-secondary">
|
|
66
|
+
<XIcon className="size-4" />
|
|
67
|
+
<span className="sr-only">Close</span>
|
|
68
|
+
</SheetPrimitive.Close>
|
|
69
|
+
)}
|
|
70
|
+
</SheetPrimitive.Content>
|
|
71
|
+
</SheetPortal>
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const SheetHeader = ({ className, ...props }: React.ComponentProps<"div">) => (
|
|
75
|
+
<div
|
|
76
|
+
data-slot="sheet-header"
|
|
77
|
+
className={cn("flex flex-col gap-1.5 p-4", className)}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const SheetTitle = ({
|
|
83
|
+
className,
|
|
84
|
+
...props
|
|
85
|
+
}: React.ComponentProps<typeof SheetPrimitive.Title>) => (
|
|
86
|
+
<SheetPrimitive.Title
|
|
87
|
+
data-slot="sheet-title"
|
|
88
|
+
className={cn("font-semibold text-foreground", className)}
|
|
89
|
+
{...props}
|
|
90
|
+
/>
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const SheetDescription = ({
|
|
94
|
+
className,
|
|
95
|
+
...props
|
|
96
|
+
}: React.ComponentProps<typeof SheetPrimitive.Description>) => (
|
|
97
|
+
<SheetPrimitive.Description
|
|
98
|
+
data-slot="sheet-description"
|
|
99
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
100
|
+
{...props}
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
export { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription };
|