From 79b532d04599aeb851cb4154f04aff0390d75b2f Mon Sep 17 00:00:00 2001 From: Bot DocuBook Date: Tue, 5 Aug 2025 10:50:41 +0000 Subject: [PATCH] chore: Sync package version v1.14.2 --- app/page.tsx | 4 +- components/SearchModal.tsx | 198 ++++++++++++++++ components/SearchTrigger.tsx | 31 +++ components/search.tsx | 247 +++----------------- contents/docs/changelog/version-1/index.mdx | 16 ++ package.json | 2 +- 6 files changed, 274 insertions(+), 224 deletions(-) create mode 100644 components/SearchModal.tsx create mode 100644 components/SearchTrigger.tsx diff --git a/app/page.tsx b/app/page.tsx index 9d17b61..008552e 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -6,7 +6,7 @@ import { cn } from "@/lib/utils"; import AnimatedShinyText from "@/components/ui/animated-shiny-text"; import { getMetadata } from "@/app/layout"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -// v1.14.1 + export const metadata = getMetadata({ title: "Home", }); @@ -25,7 +25,7 @@ export default function Home() { )} > - 🚀 New Version - Release v1.14.1 + 🚀 New Version - Release v1.14.2 diff --git a/components/SearchModal.tsx b/components/SearchModal.tsx new file mode 100644 index 0000000..dacf697 --- /dev/null +++ b/components/SearchModal.tsx @@ -0,0 +1,198 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useEffect, useMemo, useState, useRef } from "react"; +import { ArrowUpIcon, ArrowDownIcon, CornerDownLeftIcon, FileTextIcon } from "lucide-react"; +import Anchor from "./anchor"; +import { advanceSearch, cn } from "@/lib/utils"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { page_routes } from "@/lib/routes-config"; +import { + DialogContent, + DialogHeader, + DialogFooter, + DialogClose, + DialogTitle, + DialogDescription, +} from "@/components/ui/dialog"; + +type ContextInfo = { + icon: string; + description: string; + title?: string; +}; + +type SearchResult = { + title: string; + href: string; + noLink?: boolean; + items?: undefined; + score?: number; + context?: ContextInfo; +}; + +const paddingMap = { + 1: "pl-2", + 2: "pl-4", + 3: "pl-10", +} as const; + +interface SearchModalProps { + isOpen: boolean; + setIsOpen: (open: boolean) => void; +} + +export function SearchModal({ isOpen, setIsOpen }: SearchModalProps) { + const router = useRouter(); + const [searchedInput, setSearchedInput] = useState(""); + const [selectedIndex, setSelectedIndex] = useState(0); + const itemRefs = useRef<(HTMLDivElement | null)[]>([]); + + useEffect(() => { + if (!isOpen) { + setSearchedInput(""); + } + }, [isOpen]); + + const filteredResults = useMemo(() => { + const trimmedInput = searchedInput.trim(); + + if (trimmedInput.length < 3) { + return page_routes + .filter((route) => !route.href.endsWith('/')) + .slice(0, 6) + .map((route: { title: string; href: string; noLink?: boolean; context?: ContextInfo }) => ({ + title: route.title, + href: route.href, + noLink: route.noLink, + context: route.context, + })); + } + return advanceSearch(trimmedInput) as unknown as SearchResult[]; + }, [searchedInput]); + + useEffect(() => { + setSelectedIndex(0); + }, [filteredResults]); + + useEffect(() => { + const handleNavigation = (event: KeyboardEvent) => { + if (!isOpen || filteredResults.length === 0) return; + + if (event.key === "ArrowDown") { + event.preventDefault(); + setSelectedIndex((prev) => (prev + 1) % filteredResults.length); + } else if (event.key === "ArrowUp") { + event.preventDefault(); + setSelectedIndex((prev) => (prev - 1 + filteredResults.length) % filteredResults.length); + } else if (event.key === "Enter") { + event.preventDefault(); + const selectedItem = filteredResults[selectedIndex]; + if (selectedItem) { + router.push(`/docs${selectedItem.href}`); + setIsOpen(false); + } + } + }; + + window.addEventListener("keydown", handleNavigation); + return () => window.removeEventListener("keydown", handleNavigation); + }, [isOpen, filteredResults, selectedIndex, router, setIsOpen]); + + useEffect(() => { + if (itemRefs.current[selectedIndex]) { + itemRefs.current[selectedIndex]?.scrollIntoView({ + behavior: "smooth", + block: "nearest", + }); + } + }, [selectedIndex]); + + return ( + + + Search Documentation + Search through the documentation + + + setSearchedInput(e.target.value)} + placeholder="Type something to search..." + autoFocus + className="h-14 px-6 bg-transparent border-b text-[14px] outline-none w-full" + aria-label="Search documentation" + /> + + {filteredResults.length == 0 && searchedInput && ( +

+ No results found for{" "} + {`"${searchedInput}"`} +

+ )} + +
+ {filteredResults.map((item, index) => { + const level = (item.href.split("/").slice(1).length - 1) as keyof typeof paddingMap; + const paddingClass = paddingMap[level] || 'pl-2'; + const isActive = index === selectedIndex; + + return ( + + { + itemRefs.current[index] = el as HTMLDivElement | null; + }} + className={cn( + "dark:hover:bg-accent/15 hover:bg-accent/10 w-full px-3 rounded-sm text-sm flex items-center gap-2.5", + isActive && "bg-primary/20 dark:bg-primary/30", + paddingClass + )} + href={`/docs${item.href}`} + tabIndex={-1} + > +
1 && "border-l pl-4" + )} + > +
+ + {item.title} +
+ {isActive && ( +
+ Return + +
+ )} +
+
+
+ ); + })} +
+
+ +
+ + + + + + +

to navigate

+ + + +

to select

+ + esc + +

to close

+
+
+
+ ); +} \ No newline at end of file diff --git a/components/SearchTrigger.tsx b/components/SearchTrigger.tsx new file mode 100644 index 0000000..055d407 --- /dev/null +++ b/components/SearchTrigger.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { CommandIcon, SearchIcon } from "lucide-react"; +import { DialogTrigger } from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; + +export function SearchTrigger() { + return ( + +
+
+
+ +
+
+ + +
+ + K +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/components/search.tsx b/components/search.tsx index 7509d88..119fe14 100644 --- a/components/search.tsx +++ b/components/search.tsx @@ -1,52 +1,28 @@ "use client"; -import { useRouter } from "next/navigation"; -import { useEffect, useMemo, useState, useRef } from "react"; -import { ArrowUpIcon, ArrowDownIcon, CommandIcon, FileTextIcon, SearchIcon, CornerDownLeftIcon } from "lucide-react"; -import { Input } from "@/components/ui/input"; -import { - Dialog, - DialogContent, - DialogHeader, - DialogFooter, - DialogTrigger, - DialogClose, - DialogTitle, - DialogDescription, -} from "@/components/ui/dialog"; -import Anchor from "./anchor"; -import { advanceSearch, cn } from "@/lib/utils"; -import { ScrollArea } from "@/components/ui/scroll-area"; -import { page_routes } from "@/lib/routes-config"; +import { useState, useEffect } from "react"; +import { Dialog } from "@/components/ui/dialog"; +import { SearchTrigger } from "@/components/SearchTrigger"; +import { SearchModal } from "@/components/SearchModal"; -// Define the ContextInfo type to match the one in routes-config -type ContextInfo = { - icon: string; - description: string; - title?: string; -}; +// Define props for the Search component +interface SearchProps { + /** + * Specify the type of search engine to use. + * @default 'default' + */ + type?: "default" | "algolia"; +} -type SearchResult = { - title: string; - href: string; - noLink?: boolean; - items?: undefined; - score?: number; - context?: ContextInfo; -}; - -export default function Search() { - const router = useRouter(); - const [searchedInput, setSearchedInput] = useState(""); +export default function Search({ type = "default" }: SearchProps) { const [isOpen, setIsOpen] = useState(false); - const [selectedIndex, setSelectedIndex] = useState(0); - const itemRefs = useRef<(HTMLDivElement | null)[]>([]); + // Effect to handle keyboard shortcut (Cmd/Ctrl + K) useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if ((event.ctrlKey || event.metaKey) && event.key === "k") { event.preventDefault(); - setIsOpen(true); + setIsOpen((open) => !open); } }; @@ -56,191 +32,20 @@ export default function Search() { }; }, []); - const filteredResults = useMemo(() => { - const trimmedInput = searchedInput.trim(); - - // If search input is empty or less than 3 characters, show initial suggestions - if (trimmedInput.length < 3) { - return page_routes - .filter((route: { href: string }) => !route.href.endsWith('/')) // Filter out directory routes - .slice(0, 6) // Limit to 6 posts - .map((route: { title: string; href: string; noLink?: boolean; context?: ContextInfo }) => ({ - title: route.title, - href: route.href, - noLink: route.noLink, - context: route.context - })); - } - - // For search with 3 or more characters, use the advance search - return advanceSearch(trimmedInput) as unknown as SearchResult[]; - }, [searchedInput]); - - useEffect(() => { - setSelectedIndex(0); - }, [filteredResults]); - - useEffect(() => { - const handleNavigation = (event: KeyboardEvent) => { - if (!isOpen || filteredResults.length === 0) return; - - if (event.key === "ArrowDown") { - event.preventDefault(); - setSelectedIndex((prev) => (prev + 1) % filteredResults.length); - } - - if (event.key === "ArrowUp") { - event.preventDefault(); - setSelectedIndex((prev) => (prev - 1 + filteredResults.length) % filteredResults.length); - } - - if (event.key === "Enter") { - event.preventDefault(); - const selectedItem = filteredResults[selectedIndex]; - if (selectedItem) { - router.push(`/docs${selectedItem.href}`); - setIsOpen(false); - } - } - }; - - window.addEventListener("keydown", handleNavigation); - return () => { - window.removeEventListener("keydown", handleNavigation); - }; - }, [isOpen, filteredResults, selectedIndex, router]); - - useEffect(() => { - if (itemRefs.current[selectedIndex]) { - itemRefs.current[selectedIndex]?.scrollIntoView({ - behavior: "smooth", - block: "nearest", - }); - } - }, [selectedIndex]); + // Here you can add logic for different search types if needed in the future + if (type === "algolia") { + // return ; // Example for future implementation + console.warn("Tipe pencarian 'algolia' belum diimplementasikan."); + // For now, we will fall back to the default search implementation + } + // Render the default search components return (
- { - if (!open) setSearchedInput(""); - setIsOpen(open); - }} - > - -
-
-
- -
-
- - -
- - K -
-
-
-
-
- - - Search Documentation - - - Search through the documentation - - setSearchedInput(e.target.value)} - placeholder="Type something to search..." - autoFocus - className="h-14 px-6 bg-transparent border-b text-[14px] outline-none w-full" - aria-label="Search documentation" - /> - {filteredResults.length == 0 && searchedInput && ( -

- No results found for{" "} - {`"${searchedInput}"`} -

- )} - -
- {filteredResults.map((item, index) => { - const level = (item.href.split("/").slice(1).length - 1) as keyof typeof paddingMap; - const paddingClass = paddingMap[level]; - const isActive = index === selectedIndex; - - return ( - - { - itemRefs.current[index] = el as HTMLDivElement | null; - }} - className={cn( - "dark:hover:bg-accent/15 hover:bg-accent/10 w-full px-3 rounded-sm text-sm flex items-center gap-2.5", - isActive && "bg-primary/20 dark:bg-primary/30", - paddingClass - )} - href={`/docs${item.href}`} - tabIndex={0} - > -
1 && "border-l pl-4" - )} - > -
- - {item.title} -
- {isActive && ( -
- Return - -
- )} -
-
-
- ); - })} -
-
- -
- - - - - - -

to navigate

- - - -

to select

- - esc - -

to close

-
-
-
+ + +
); -} - -const paddingMap = { - 1: "pl-2", - 2: "pl-4", - 3: "pl-10", -} as const; +} \ No newline at end of file diff --git a/contents/docs/changelog/version-1/index.mdx b/contents/docs/changelog/version-1/index.mdx index 5c29a33..0d3c033 100644 --- a/contents/docs/changelog/version-1/index.mdx +++ b/contents/docs/changelog/version-1/index.mdx @@ -8,6 +8,22 @@ date: 02-08-2025 This changelog contains a list of all the changes made to the DocuBook template. It will be updated with each new release and will include information about new features, bug fixes, and other improvements. +
+### v 1.14.2 +
+ + + + - Refactor Search component into three distinct components: Search, SearchTrigger, and SearchModal for better maintainability and scalability. + - New SearchTrigger components + - New SearchModal components + + + - Resolve TypeScript error for missing 'noLink' property on the 'Page' type after refactoring. + - Fix TypeScript error for missing 'context' property by providing a complete inline type annotation in the SearchModal component. + + +
### v 1.14.0
diff --git a/package.json b/package.json index a8f2866..a9b7c61 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "docubook", - "version": "1.14.1", + "version": "1.14.2", "private": true, "scripts": { "dev": "next dev",