feat(ui): update search component and improve responsive design

- Show only search icon on mobile (< 768px) in navbar
- Refactor Anchor component with better type safety and link handling
- Adjust spacing in Table of Contents and main content
- Improve responsive layout and consistency
This commit is contained in:
Wildan Nursahidan
2025-05-26 11:06:58 +07:00
parent 49d59e0476
commit bd400695ff
6 changed files with 76 additions and 28 deletions

View File

@@ -77,7 +77,7 @@ export default async function DocsPage({ params: { slug = [] } }: PageProps) {
return ( return (
<div className="flex items-start gap-10"> <div className="flex items-start gap-10">
<div className="flex-[4.5] pt-4 lg:pt-10"> <div className="flex-[4.5] pt-5">
<MobToc tocs={tocs} /> <MobToc tocs={tocs} />
<DocsBreadcrumb paths={slug} /> <DocsBreadcrumb paths={slug} />
<Typography> <Typography>

View File

@@ -8,7 +8,7 @@ export default function DocsLayout({
return ( return (
<div className="flex items-start gap-8"> <div className="flex items-start gap-8">
<Leftbar key="leftbar" /> <Leftbar key="leftbar" />
<div className="flex-[5.25] py-4 px-1"> <div className="flex-[5.25] px-1">
{children} {children}
</div> </div>
</div> </div>

View File

@@ -1,37 +1,78 @@
"use client"; "use client";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import Link from "next/link"; import Link, { LinkProps } from "next/link";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { ComponentProps, forwardRef } from "react"; import { forwardRef } from "react";
type AnchorProps = ComponentProps<typeof Link> & { type AnchorProps = LinkProps & {
absolute?: boolean; absolute?: boolean;
activeClassName?: string; activeClassName?: string;
disabled?: boolean; disabled?: boolean;
}; className?: string;
children: React.ReactNode;
} & Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, keyof LinkProps>;
const Anchor = forwardRef<HTMLAnchorElement, AnchorProps>( const Anchor = forwardRef<HTMLAnchorElement, AnchorProps>(
({ absolute, className = "", activeClassName = "", disabled, children, ...props }, ref) => { ({
absolute = false,
className = "",
activeClassName = "",
disabled = false,
children,
href,
...props
}, ref) => {
const path = usePathname(); const path = usePathname();
const href = props.href.toString(); const hrefStr = href?.toString() || '';
// Deteksi URL eksternal menggunakan regex // Check if URL is external
const isExternal = /^(https?:\/\/|\/\/)/.test(href); const isExternal = /^(https?:\/\/|\/\/)/.test(hrefStr);
let isMatch = absolute // Check if current path matches the link
? href.split("/")[1] === path.split("/")[1] const isActive = absolute
: path === href; ? hrefStr.split("/")[1] === path?.split("/")[1]
: path === hrefStr;
if (isExternal) isMatch = false; // Hindari mencocokkan URL eksternal // Apply active class only for internal links
const linkClassName = cn(
if (disabled) 'transition-colors hover:text-primary',
return ( className,
<div className={cn(className, "cursor-not-allowed")}>{children}</div> !isExternal && isActive && activeClassName
); );
if (disabled) {
return ( return (
<Link ref={ref} className={cn(className, isMatch && activeClassName)} {...props}> <span className={cn(linkClassName, "cursor-not-allowed opacity-50")}>
{children}
</span>
);
}
if (isExternal) {
return (
<a
ref={ref}
href={hrefStr}
className={linkClassName}
target="_blank"
rel="noopener noreferrer"
{...props}
>
{children}
</a>
);
}
return (
<Link
ref={ref}
href={hrefStr}
className={linkClassName}
{...props}
>
{children} {children}
</Link> </Link>
); );

View File

@@ -87,7 +87,7 @@ export default function MobToc({ tocs }: MobTocProps) {
transition={{ duration: 0.2, ease: 'easeInOut' }} transition={{ duration: 0.2, ease: 'easeInOut' }}
> >
<div className="w-full bg-background/95 backdrop-blur-sm border-b border-stone-200 dark:border-stone-800 shadow-sm"> <div className="w-full bg-background/95 backdrop-blur-sm border-b border-stone-200 dark:border-stone-800 shadow-sm">
<div className="md:px-8 px-4 py-2"> <div className="sm:px-8 px-4 py-2">
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"

View File

@@ -98,9 +98,14 @@ export default function Search() {
> >
<DialogTrigger asChild> <DialogTrigger asChild>
<div className="relative flex-1 cursor-pointer max-w-[140px]"> <div className="relative flex-1 cursor-pointer max-w-[140px]">
<div className="flex items-center">
<div className="md:hidden p-2 -ml-2">
<SearchIcon className="h-5 w-5 text-stone-500 dark:text-stone-400" />
</div>
<div className="hidden md:block w-full">
<SearchIcon className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-stone-500 dark:text-stone-400" /> <SearchIcon className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-stone-500 dark:text-stone-400" />
<Input <Input
className="md:w-full rounded-full dark:bg-background/95 bg-background border h-9 pl-10 pr-0 sm:pr-4 text-sm shadow-sm overflow-ellipsis" className="w-full rounded-full dark:bg-background/95 bg-background border h-9 pl-10 pr-0 sm:pr-4 text-sm shadow-sm overflow-ellipsis"
placeholder="Search" placeholder="Search"
type="search" type="search"
/> />
@@ -109,6 +114,8 @@ export default function Search() {
<span>K</span> <span>K</span>
</div> </div>
</div> </div>
</div>
</div>
</DialogTrigger> </DialogTrigger>
<DialogContent className="p-0 max-w-[650px] sm:top-[38%] top-[45%] !rounded-md"> <DialogContent className="p-0 max-w-[650px] sm:top-[38%] top-[45%] !rounded-md">
<DialogHeader> <DialogHeader>

View File

@@ -9,7 +9,7 @@ export default async function Toc({ path }: { path: string }) {
const tocs = await getDocsTocs(path); const tocs = await getDocsTocs(path);
return ( return (
<div className="lg:flex hidden toc flex-[1.5] min-w-[238px] py-9 sticky top-16 h-[calc(100vh-4rem)]"> <div className="lg:flex hidden toc flex-[1.5] min-w-[238px] py-5 sticky top-16 h-[calc(100vh-4rem)]">
<div className="flex flex-col h-full w-full px-2 gap-2 mb-auto"> <div className="flex flex-col h-full w-full px-2 gap-2 mb-auto">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<ListIcon className="w-4 h-4" /> <ListIcon className="w-4 h-4" />