🪛 04 breadcrumbs
This commit is contained in:
15
app/@breadcrumbs/about/page.tsx
Normal file
15
app/@breadcrumbs/about/page.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Breadcrumbs } from '@/components/layout/Breadcrumbs';
|
||||
|
||||
export default function AboutBreadcrumb() {
|
||||
return (
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{
|
||||
label: 'Despre',
|
||||
href: '/about',
|
||||
current: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
53
app/@breadcrumbs/blog/[...slug]/page.tsx
Normal file
53
app/@breadcrumbs/blog/[...slug]/page.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Breadcrumbs } from '@/components/layout/Breadcrumbs';
|
||||
import { getPostBySlug } from '@/lib/markdown';
|
||||
|
||||
interface BreadcrumbItem {
|
||||
label: string;
|
||||
href: string;
|
||||
current?: boolean;
|
||||
}
|
||||
|
||||
function formatDirectoryName(name: string): string {
|
||||
const directoryNames: { [key: string]: string } = {
|
||||
tech: 'Tehnologie',
|
||||
design: 'Design',
|
||||
tutorial: 'Tutoriale',
|
||||
};
|
||||
|
||||
return directoryNames[name] || name.charAt(0).toUpperCase() + name.slice(1);
|
||||
}
|
||||
|
||||
export default async function BlogPostBreadcrumb({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ slug: string[] }>;
|
||||
}) {
|
||||
const { slug } = await params;
|
||||
const slugPath = slug.join('/');
|
||||
const post = getPostBySlug(slugPath);
|
||||
|
||||
const items: BreadcrumbItem[] = [
|
||||
{
|
||||
label: 'Blog',
|
||||
href: '/blog',
|
||||
},
|
||||
];
|
||||
|
||||
if (slug.length > 1) {
|
||||
for (let i = 0; i < slug.length - 1; i++) {
|
||||
const segmentPath = slug.slice(0, i + 1).join('/');
|
||||
items.push({
|
||||
label: formatDirectoryName(slug[i]),
|
||||
href: `/blog/${segmentPath}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
items.push({
|
||||
label: post ? post.frontmatter.title : slug[slug.length - 1],
|
||||
href: `/blog/${slugPath}`,
|
||||
current: true,
|
||||
});
|
||||
|
||||
return <Breadcrumbs items={items} />;
|
||||
}
|
||||
15
app/@breadcrumbs/blog/page.tsx
Normal file
15
app/@breadcrumbs/blog/page.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Breadcrumbs } from '@/components/layout/Breadcrumbs';
|
||||
|
||||
export default function BlogBreadcrumb() {
|
||||
return (
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{
|
||||
label: 'Blog',
|
||||
href: '/blog',
|
||||
current: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
7
app/@breadcrumbs/default.tsx
Normal file
7
app/@breadcrumbs/default.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { Breadcrumbs } from '@/components/layout/Breadcrumbs';
|
||||
|
||||
export default function DefaultBreadcrumb() {
|
||||
return <Breadcrumbs />;
|
||||
}
|
||||
29
app/@breadcrumbs/tags/[tag]/page.tsx
Normal file
29
app/@breadcrumbs/tags/[tag]/page.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Breadcrumbs } from '@/components/layout/Breadcrumbs';
|
||||
|
||||
export default async function TagBreadcrumb({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ tag: string }>;
|
||||
}) {
|
||||
const { tag } = await params;
|
||||
const tagName = tag
|
||||
.split('-')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
|
||||
return (
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{
|
||||
label: 'Tag-uri',
|
||||
href: '/tags',
|
||||
},
|
||||
{
|
||||
label: tagName,
|
||||
href: `/tags/${tag}`,
|
||||
current: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
15
app/@breadcrumbs/tags/page.tsx
Normal file
15
app/@breadcrumbs/tags/page.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Breadcrumbs } from '@/components/layout/Breadcrumbs';
|
||||
|
||||
export default function TagsBreadcrumb() {
|
||||
return (
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{
|
||||
label: 'Tag-uri',
|
||||
href: '/tags',
|
||||
current: true,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1 +1,11 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
@layer utilities {
|
||||
.scrollbar-hide {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.scrollbar-hide::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,10 @@ export const metadata: Metadata = {
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
breadcrumbs,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
breadcrumbs: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="ro">
|
||||
@@ -48,6 +50,7 @@ export default function RootLayout({
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
{breadcrumbs}
|
||||
<main className="container mx-auto px-4 py-8">
|
||||
{children}
|
||||
</main>
|
||||
|
||||
145
components/layout/Breadcrumbs.tsx
Normal file
145
components/layout/Breadcrumbs.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import { Fragment } from 'react';
|
||||
import { BreadcrumbsSchema } from './BreadcrumbsSchema';
|
||||
|
||||
interface BreadcrumbItem {
|
||||
label: string;
|
||||
href: string;
|
||||
current?: boolean;
|
||||
}
|
||||
|
||||
function HomeIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function ChevronIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M9 5l7 7-7 7"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function formatSegmentLabel(segment: string): string {
|
||||
const specialCases: { [key: string]: string } = {
|
||||
blog: 'Blog',
|
||||
tags: 'Tag-uri',
|
||||
about: 'Despre',
|
||||
};
|
||||
|
||||
if (specialCases[segment]) {
|
||||
return specialCases[segment];
|
||||
}
|
||||
|
||||
return segment
|
||||
.split('-')
|
||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
export function Breadcrumbs({ items }: { items?: BreadcrumbItem[] }) {
|
||||
const pathname = usePathname();
|
||||
|
||||
let breadcrumbs: BreadcrumbItem[] = items || [];
|
||||
|
||||
if (!items) {
|
||||
const segments = pathname.split('/').filter(Boolean);
|
||||
breadcrumbs = segments.map((segment, index) => {
|
||||
const href = '/' + segments.slice(0, index + 1).join('/');
|
||||
const label = formatSegmentLabel(segment);
|
||||
const current = index === segments.length - 1;
|
||||
|
||||
return { label, href, current };
|
||||
});
|
||||
}
|
||||
|
||||
if (pathname === '/') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const schemaItems = [
|
||||
{ position: 1, name: 'Acasă', item: '/' },
|
||||
...breadcrumbs.map((item, index) => ({
|
||||
position: index + 2,
|
||||
name: item.label,
|
||||
item: item.href,
|
||||
})),
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<nav
|
||||
aria-label="Breadcrumb"
|
||||
className="bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700"
|
||||
>
|
||||
<div className="container mx-auto px-4 py-3">
|
||||
<ol className="flex items-center space-x-2 text-sm overflow-x-auto scrollbar-hide">
|
||||
<li className="flex-shrink-0">
|
||||
<Link
|
||||
href="/"
|
||||
className="flex items-center text-gray-500 hover:text-primary-600 transition"
|
||||
aria-label="Acasă"
|
||||
>
|
||||
<HomeIcon className="w-4 h-4" />
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
{breadcrumbs.map((item) => (
|
||||
<Fragment key={item.href}>
|
||||
<li className="text-gray-400 flex-shrink-0">
|
||||
<ChevronIcon className="w-4 h-4" />
|
||||
</li>
|
||||
<li className="flex-shrink-0">
|
||||
{item.current ? (
|
||||
<span
|
||||
className="font-medium text-gray-700 dark:text-gray-300 truncate max-w-[150px] sm:max-w-none block"
|
||||
aria-current="page"
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
) : (
|
||||
<Link
|
||||
href={item.href}
|
||||
className="text-gray-500 hover:text-primary-600 transition truncate max-w-[150px] sm:max-w-none block"
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
</Fragment>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
</nav>
|
||||
<BreadcrumbsSchema items={schemaItems} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
25
components/layout/BreadcrumbsSchema.tsx
Normal file
25
components/layout/BreadcrumbsSchema.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
interface BreadcrumbSchemaItem {
|
||||
position: number;
|
||||
name: string;
|
||||
item: string;
|
||||
}
|
||||
|
||||
export function BreadcrumbsSchema({ items }: { items: BreadcrumbSchemaItem[] }) {
|
||||
const structuredData = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BreadcrumbList',
|
||||
itemListElement: items.map((item) => ({
|
||||
'@type': 'ListItem',
|
||||
position: item.position,
|
||||
name: item.name,
|
||||
item: `http://localhost:3000${item.item}`,
|
||||
})),
|
||||
};
|
||||
|
||||
return (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: JSON.stringify(structuredData) }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
2
next-env.d.ts
vendored
2
next-env.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"dev": "next dev -p 3030",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
|
||||
Reference in New Issue
Block a user