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:
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
Reference in New Issue
Block a user