Skip to content

Commit 7ddef7d

Browse files
committed
feat: add breadcrumb component
1 parent 5f0272d commit 7ddef7d

File tree

4 files changed

+144
-19
lines changed

4 files changed

+144
-19
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696
"typescript": "^5.1.6"
9797
},
9898
"lint-staged": {
99-
"(apps|packages)/**/*.{js,ts,jsx,tsx}": [
99+
"**/*.{js,ts,jsx,tsx}": [
100100
"prettier --write",
101101
"eslint --fix"
102102
],

src/app/[...slug]/page.tsx

+30-16
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ import { notFound } from "next/navigation";
55
import { ArrowRightIcon, ChevronRightIcon, SearchIcon } from "lucide-react";
66
import { Mdx } from "@/components/mdx/mdx-remote";
77
import { Badge } from "@/components/ui/badge";
8+
import {
9+
Breadcrumb,
10+
BreadcrumbItem,
11+
BreadcrumbLink,
12+
BreadcrumbList,
13+
BreadcrumbPage,
14+
BreadcrumbSeparator,
15+
} from "@/components/ui/breadcrumb";
816
import { Input } from "@/components/ui/input";
917
import { ScrollArea } from "@/components/ui/scroll-area";
1018
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
@@ -50,22 +58,28 @@ export default async function Page({ params }: PageProps) {
5058
<main>
5159
{/* breadcrumbs */}
5260
{metadata.breadcrumbs.length > 1 && (
53-
<p className="flex items-center text-muted-foreground">
54-
{metadata.breadcrumbs.map((breadcrumb, index) => (
55-
<React.Fragment key={index}>
56-
<Link
57-
href={breadcrumb.href}
58-
className={cn("hover:underline", {
59-
"font-semibold text-foreground":
60-
index === metadata.breadcrumbs.length - 1,
61-
})}
62-
>
63-
{breadcrumb.label}
64-
</Link>
65-
{index < metadata.breadcrumbs.length - 1 && <ChevronRightIcon />}
66-
</React.Fragment>
67-
))}
68-
</p>
61+
<Breadcrumb>
62+
<BreadcrumbList>
63+
{metadata.breadcrumbs.map((breadcrumb, index) => (
64+
<React.Fragment key={index}>
65+
{index === metadata.breadcrumbs.length - 1 ? (
66+
<BreadcrumbPage>{breadcrumb.label}</BreadcrumbPage>
67+
) : (
68+
<BreadcrumbItem>
69+
<BreadcrumbLink href={breadcrumb.href}>
70+
{breadcrumb.label}
71+
</BreadcrumbLink>
72+
</BreadcrumbItem>
73+
)}
74+
{index < metadata.breadcrumbs.length - 1 && (
75+
<BreadcrumbSeparator>
76+
<ChevronRightIcon />
77+
</BreadcrumbSeparator>
78+
)}
79+
</React.Fragment>
80+
))}
81+
</BreadcrumbList>
82+
</Breadcrumb>
6983
)}
7084
<h1 className="mt-2 text-4xl font-bold">{metadata.title}</h1>
7185
<p className="mt-2 text-muted-foreground">{metadata.description}</p>

src/components/ui/breadcrumb.tsx

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import * as React from "react";
2+
import { Slot } from "@radix-ui/react-slot";
3+
import { ChevronRight, MoreHorizontal } from "lucide-react";
4+
import { cn } from "@/utils/classes";
5+
6+
const Breadcrumb = React.forwardRef<
7+
HTMLElement,
8+
React.ComponentPropsWithoutRef<"nav"> & {
9+
separator?: React.ReactNode;
10+
}
11+
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />);
12+
Breadcrumb.displayName = "Breadcrumb";
13+
14+
const BreadcrumbList = React.forwardRef<
15+
HTMLOListElement,
16+
React.ComponentPropsWithoutRef<"ol">
17+
>(({ className, ...props }, ref) => (
18+
<ol
19+
ref={ref}
20+
className={cn(
21+
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground",
22+
className
23+
)}
24+
{...props}
25+
/>
26+
));
27+
BreadcrumbList.displayName = "BreadcrumbList";
28+
29+
const BreadcrumbItem = React.forwardRef<
30+
HTMLLIElement,
31+
React.ComponentPropsWithoutRef<"li">
32+
>(({ className, ...props }, ref) => (
33+
<li
34+
ref={ref}
35+
className={cn("inline-flex items-center gap-1.5", className)}
36+
{...props}
37+
/>
38+
));
39+
BreadcrumbItem.displayName = "BreadcrumbItem";
40+
41+
const BreadcrumbLink = React.forwardRef<
42+
HTMLAnchorElement,
43+
React.ComponentPropsWithoutRef<"a"> & {
44+
asChild?: boolean;
45+
}
46+
>(({ asChild, className, ...props }, ref) => {
47+
const Comp = asChild ? Slot : "a";
48+
49+
return (
50+
<Comp
51+
ref={ref}
52+
className={cn("transition-colors hover:text-foreground", className)}
53+
{...props}
54+
/>
55+
);
56+
});
57+
BreadcrumbLink.displayName = "BreadcrumbLink";
58+
59+
const BreadcrumbPage = React.forwardRef<
60+
HTMLSpanElement,
61+
React.ComponentPropsWithoutRef<"span">
62+
>(({ className, ...props }, ref) => (
63+
<span
64+
ref={ref}
65+
role="link"
66+
aria-disabled="true"
67+
aria-current="page"
68+
className={cn("font-normal text-foreground", className)}
69+
{...props}
70+
/>
71+
));
72+
BreadcrumbPage.displayName = "BreadcrumbPage";
73+
74+
const BreadcrumbSeparator = ({
75+
children,
76+
className,
77+
...props
78+
}: React.ComponentProps<"li">) => (
79+
<li
80+
role="presentation"
81+
aria-hidden="true"
82+
className={cn("[&>svg]:size-3.5", className)}
83+
{...props}
84+
>
85+
{children ?? <ChevronRight />}
86+
</li>
87+
);
88+
BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
89+
90+
const BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<"span">) => (
91+
<span
92+
role="presentation"
93+
aria-hidden="true"
94+
className={cn("flex h-9 w-9 items-center justify-center", className)}
95+
{...props}
96+
>
97+
<MoreHorizontal className="h-4 w-4" />
98+
<span className="sr-only">More</span>
99+
</span>
100+
);
101+
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis";
102+
103+
export {
104+
Breadcrumb,
105+
BreadcrumbList,
106+
BreadcrumbItem,
107+
BreadcrumbLink,
108+
BreadcrumbPage,
109+
BreadcrumbSeparator,
110+
BreadcrumbEllipsis,
111+
};

src/styles/globals.css

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@
5555
}
5656
body {
5757
@apply bg-background text-foreground;
58-
white-space: pre-wrap;
59-
word-wrap: break-word;
58+
/* white-space: pre-wrap; */
59+
/* word-wrap: break-word; */
6060
}
6161
}
6262

0 commit comments

Comments
 (0)