This commit is contained in:
Ho Ngoc Hai
2026-01-08 10:13:36 +07:00
parent b35cf4a947
commit 4ccfb220be
115 changed files with 28 additions and 43714 deletions

View File

@@ -107,7 +107,7 @@ export default async function LocaleLayout({
const messages = await getMessages({ locale });
return (
<html lang={locale}>
<html lang={locale} suppressHydrationWarning>
<head>
<link rel="icon" href="/favicon.ico" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />

View File

@@ -4,15 +4,24 @@ import dynamic from 'next/dynamic';
import type { DocsLocale } from '@/docs/navigation';
import { defaultDocSlug } from '@/docs/registry';
// Loading component for MDX content
const LoadingDocs = () => (
<div style={{ padding: '2rem', textAlign: 'center' }}>
<p>Loading documentation...</p>
</div>
);
const docsRegistry = {
en: {
'getting-started': dynamic(() => import('../../../content/docs/en/templates/README.md'), {
ssr: false
'getting-started': dynamic(() => import('../../../../../docs/en/templates/README.md'), {
loading: () => <LoadingDocs />,
ssr: true
})
},
vi: {
'getting-started': dynamic(() => import('../../../content/docs/vi/templates/README.md'), {
ssr: false
'getting-started': dynamic(() => import('../../../../../docs/vi/templates/README.md'), {
loading: () => <LoadingDocs />,
ssr: true
})
}
} as const;

View File

@@ -1,6 +1,6 @@
'use client';
import { useState, useEffect, useRef } from 'react';
import { useState, useEffect, useRef, useMemo } from 'react';
import { useRouter, usePathname } from 'next/navigation';
import { Search, X, FileText } from 'lucide-react';
import { docsNavigation } from '@/docs/navigation';
@@ -23,7 +23,6 @@ type SearchResult = {
export default function DocsSearch({ placeholder, locale = 'en' }: DocsSearchProps) {
const [isOpen, setIsOpen] = useState(false);
const [query, setQuery] = useState('');
const [results, setResults] = useState<SearchResult[]>([]);
const [selectedIndex, setSelectedIndex] = useState(0);
const inputRef = useRef<HTMLInputElement>(null);
const router = useRouter();
@@ -33,7 +32,7 @@ export default function DocsSearch({ placeholder, locale = 'en' }: DocsSearchPro
const currentLocale = (pathname?.split('/')[1] as DocsLocale) || locale;
// Build search index from navigation
const searchIndex: SearchResult[] = docsNavigation.flatMap(group =>
const searchIndex: SearchResult[] = useMemo(() => docsNavigation.flatMap(group =>
group.items.map(item => ({
id: item.id,
slug: item.slug,
@@ -41,13 +40,12 @@ export default function DocsSearch({ placeholder, locale = 'en' }: DocsSearchPro
category: group.label[currentLocale],
href: `/${currentLocale}/docs/${item.slug}`
}))
);
), [currentLocale]);
// Search function
useEffect(() => {
// Calculate search results using useMemo instead of useEffect
const results = useMemo(() => {
if (!query.trim()) {
setResults([]);
return;
return [];
}
const searchTerm = query.toLowerCase().trim();
@@ -67,9 +65,8 @@ export default function DocsSearch({ placeholder, locale = 'en' }: DocsSearchPro
return a.title.localeCompare(b.title);
});
setResults(sorted.slice(0, 10)); // Limit to 10 results
setSelectedIndex(0);
}, [query, currentLocale]);
return sorted.slice(0, 10); // Limit to 10 results
}, [query, searchIndex]);
// Keyboard shortcuts
useEffect(() => {
@@ -149,7 +146,10 @@ export default function DocsSearch({ placeholder, locale = 'en' }: DocsSearchPro
className={styles.searchInput}
placeholder={placeholder ?? 'Search documentation…'}
value={query}
onChange={(e) => setQuery(e.target.value)}
onChange={(e) => {
setQuery(e.target.value);
setSelectedIndex(0); // Reset selected index when query changes
}}
onKeyDown={handleKeyDown}
/>
<button
@@ -168,9 +168,8 @@ export default function DocsSearch({ placeholder, locale = 'en' }: DocsSearchPro
{results.map((result, index) => (
<li key={result.id}>
<button
className={`${styles.resultItem} ${
index === selectedIndex ? styles.resultItemActive : ''
}`}
className={`${styles.resultItem} ${index === selectedIndex ? styles.resultItemActive : ''
}`}
onClick={() => handleSelect(result)}
>
<FileText size={16} className={styles.resultIcon} />