improve : search & anchor

This commit is contained in:
2025-02-27 21:47:17 +07:00
parent d73d28a114
commit 68383379da
4 changed files with 256 additions and 9 deletions

View File

@@ -14,11 +14,16 @@ type AnchorProps = ComponentProps<typeof Link> & {
const Anchor = forwardRef<HTMLAnchorElement, AnchorProps>(
({ absolute, className = "", activeClassName = "", disabled, children, ...props }, ref) => {
const path = usePathname();
let isMatch = absolute
? props.href.toString().split("/")[1] == path.split("/")[1]
: path === props.href;
const href = props.href.toString();
if (props.href.toString().includes("http")) isMatch = false;
// Deteksi URL eksternal menggunakan regex
const isExternal = /^(https?:\/\/|\/\/)/.test(href);
let isMatch = absolute
? href.split("/")[1] === path.split("/")[1]
: path === href;
if (isExternal) isMatch = false; // Hindari mencocokkan URL eksternal
if (disabled)
return (
@@ -32,7 +37,7 @@ const Anchor = forwardRef<HTMLAnchorElement, AnchorProps>(
);
}
);
// ✅ Tambahkan display name agar tidak error saat build
Anchor.displayName = "Anchor";
export default Anchor;

View File

@@ -1,7 +1,7 @@
"use client";
import { useRouter } from "next/navigation";
import { useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useState, useRef } from "react";
import { ArrowUpIcon, ArrowDownIcon, CommandIcon, FileTextIcon, SearchIcon, CornerDownLeftIcon } from "lucide-react";
import { Input } from "@/components/ui/input";
import {
@@ -22,6 +22,7 @@ export default function Search() {
const [searchedInput, setSearchedInput] = useState("");
const [isOpen, setIsOpen] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(0);
const itemRefs = useRef<(HTMLDivElement | null)[]>([]);
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
@@ -32,7 +33,6 @@ export default function Search() {
};
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
@@ -72,12 +72,20 @@ export default function Search() {
};
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]);
return (
<div>
<Dialog
@@ -92,7 +100,7 @@ export default function Search() {
<SearchIcon className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-stone-500 dark:text-stone-400" />
<Input
className="md:w-full rounded-md dark:bg-background/95 bg-background border h-9 pl-10 pr-0 sm:pr-4 text-sm shadow-sm overflow-ellipsis"
placeholder="Search documentation..."
placeholder="Search"
type="search"
/>
<div className="sm:flex hidden absolute top-1/2 -translate-y-1/2 right-2 text-xs font-medium font-mono items-center gap-0.5 dark:bg-black dark:border dark:border-white/20 bg-stone-200/50 border border-black/40 p-1 rounded-sm">
@@ -128,6 +136,9 @@ export default function Search() {
return (
<DialogClose key={item.href} asChild>
<Anchor
ref={(el) => {
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",