🪛 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";
|
@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({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
|
breadcrumbs,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
|
breadcrumbs: React.ReactNode
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<html lang="ro">
|
<html lang="ro">
|
||||||
@@ -48,6 +50,7 @@ export default function RootLayout({
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
{breadcrumbs}
|
||||||
<main className="container mx-auto px-4 py-8">
|
<main className="container mx-auto px-4 py-8">
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</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" />
|
||||||
/// <reference types="next/image-types/global" />
|
/// <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
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev -p 3030",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
|
|||||||
Reference in New Issue
Block a user