feat: update navigation and add new block components

- Disabled development indicators in next.config.ts for cleaner output.
- Updated English and Chinese message files to include new block titles for logo, login, signup, and contact sections.
- Refactored routes to include new paths for login, signup, and contact blocks.
- Introduced new layout and page components for marketing sections, including logo cloud and various call-to-action pages.
- Enhanced blocks navigation component to utilize updated paths and improve user experience.
This commit is contained in:
javayhu 2025-03-25 23:57:51 +08:00
parent aefe37068b
commit af94ab6dd0
78 changed files with 237 additions and 135 deletions

View File

@ -227,26 +227,47 @@
"hero": {
"title": "Hero Blocks"
},
"pricing": {
"title": "Pricing Blocks"
"logo": {
"title": "Logo Cloud Blocks"
},
"features": {
"title": "Features Blocks"
},
"faq": {
"title": "FAQ Blocks"
},
"testimonials": {
"title": "Testimonials Blocks"
"content": {
"title": "Content Blocks"
},
"stats": {
"title": "Stats Blocks"
},
"team": {
"title": "Team Blocks"
},
"testimonials": {
"title": "Testimonials Blocks"
},
"callToAction": {
"title": "Call to Action Blocks"
},
"content": {
"title": "Content Blocks"
"footer": {
"title": "Footer Blocks"
},
"pricing": {
"title": "Pricing Blocks"
},
"comparator": {
"title": "Comparator Blocks"
},
"faq": {
"title": "FAQ Blocks"
},
"login": {
"title": "Login Blocks"
},
"signup": {
"title": "Signup Blocks"
},
"contact": {
"title": "Contact Blocks"
}
}
}

View File

@ -222,26 +222,47 @@
"hero": {
"title": "Hero组件"
},
"pricing": {
"title": "Pricing组件"
"logoCloud": {
"title": "Logo Cloud组件"
},
"features": {
"title": "Features组件"
},
"faq": {
"title": "FAQ组件"
},
"testimonials": {
"title": "Testimonials组件"
"content": {
"title": "Content组件"
},
"stats": {
"title": "Stats组件"
},
"team": {
"title": "Team组件"
},
"testimonials": {
"title": "Testimonials组件"
},
"callToAction": {
"title": "Call to Action组件"
},
"content": {
"title": "Content组件"
"footer": {
"title": "Footer组件"
},
"pricing": {
"title": "Pricing组件"
},
"comparator": {
"title": "Comparator组件"
},
"faqs": {
"title": "FAQs组件"
},
"login": {
"title": "Login组件"
},
"signup": {
"title": "Signup组件"
},
"contact": {
"title": "Contact组件"
}
}
}

View File

@ -7,6 +7,7 @@ import { withContentCollections } from "@content-collections/next";
*/
const nextConfig: NextConfig = {
/* config options here */
devIndicators: false,
// https://nextjs.org/docs/architecture/nextjs-compiler#remove-console
// Remove all console.* calls in production only

View File

@ -1,15 +0,0 @@
import CategoryNavigation from '@/components/blocks/blocks-nav';
import { categories } from '@/components/blocks/blocks';
export default function CategoryLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<>
<CategoryNavigation categories={categories} />
<main>{children}</main>
</>
);
}

View File

@ -1,51 +0,0 @@
import BlockPreview from '@/components/blocks/block-preview';
import { blocks, categories } from '@/components/blocks/blocks';
import { notFound } from 'next/navigation';
interface PageProps {
params: Promise<{ category: string }>;
}
export const dynamic = 'force-static';
export const revalidate = 3600;
export async function generateStaticParams() {
return categories.map((category) => ({
category: category,
}));
}
export async function generateMetadata({ params }: PageProps) {
const { category } = await params;
return {
title: `Shadcn ${category} Blocks`,
};
}
export default async function CategoryPage({ params }: PageProps) {
const { category } = await params;
const categoryBlocks = blocks.filter((b) => b.category === category);
if (categoryBlocks.length === 0) {
notFound();
}
return (
<>
<section>
<h1 className="sr-only text-3xl font-bold sm:text-4xl md:text-nowrap">
Shadcn <span className="capitalize">{category}</span> blocks
</h1>
<p className="sr-only text-base md:text-lg">
Speed up your workflow with responsive, pre-built UI blocks designed
for marketing websites.
</p>
<div className="h-6 w-full bg-[repeating-linear-gradient(-45deg,var(--color-border),var(--color-border)_1px,transparent_1px,transparent_6px)] opacity-35"></div>
</section>
{categoryBlocks.map((block, index) => (
<BlockPreview {...block} key={index} />
))}
</>
);
}

View File

@ -0,0 +1,12 @@
import { categories } from '@/components/blocks/blocks';
import BlocksNav from '@/components/blocks/blocks-nav';
import { PropsWithChildren } from 'react';
export default function CategoryLayout({ children }: PropsWithChildren) {
return (
<>
<BlocksNav categories={categories} />
<main>{children}</main>
</>
);
}

View File

@ -0,0 +1,54 @@
import BlockPreview from '@/components/blocks/block-preview';
import { blocks, categories } from '@/components/blocks/blocks';
import { constructMetadata } from '@/lib/metadata';
import { getBaseUrlWithLocale } from '@/lib/urls/get-base-url';
import { Metadata } from 'next';
import { Locale } from 'next-intl';
import { getTranslations } from 'next-intl/server';
import { notFound } from 'next/navigation';
interface BlockCategoryPageProps {
params: Promise<{ category: string }>;
}
export const dynamic = 'force-static';
export const revalidate = 3600;
export async function generateStaticParams() {
return categories.map((category) => ({
category: category,
}));
}
export async function generateMetadata({
params,
}: {
params: Promise<{ locale: Locale; category: string }>;
}): Promise<Metadata | undefined> {
const { locale, category } = await params;
const t = await getTranslations({ locale, namespace: 'Metadata' });
return constructMetadata({
title: category + ' | ' + t('title'),
description: t('description'),
canonicalUrl: `${getBaseUrlWithLocale(locale)}/blocks/${category}`,
});
}
export default async function BlockCategoryPage({
params,
}: BlockCategoryPageProps) {
const { category } = await params;
const categoryBlocks = blocks.filter((b) => b.category === category);
if (categoryBlocks.length === 0) {
notFound();
}
return (
<>
{categoryBlocks.map((block, index) => (
<BlockPreview {...block} key={index} />
))}
</>
);
}

View File

@ -6,9 +6,9 @@ import { notFound } from 'next/navigation';
import { ReactNode } from 'react';
import { Toaster } from 'sonner';
import { Providers } from './providers';
import { TailwindIndicator } from '@/components/tailwind-indicator';
import '@/styles/globals.css';
import { TailwindIndicator } from '@/components/tailwind-indicator';
interface LocaleLayoutProps {
children: ReactNode;
@ -52,7 +52,7 @@ export default async function LocaleLayout({
<Toaster richColors position="top-right" offset={64} />
<TailwindIndicator />
{/* <TailwindIndicator /> */}
</Providers>
</NextIntlClientProvider>
</body>

View File

@ -1,43 +1,46 @@
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { LocaleLink, useLocalePathname } from '@/i18n/navigation';
import { cn } from '@/lib/utils';
const BlocksNav = ({ categories }: { categories: string[] }) => {
const pathname = usePathname();
export default function BlocksNav({ categories }: { categories: string[] }) {
const pathname = useLocalePathname();
return (
<div className="dark:border-border/50 relative z-50 border-b">
<div className="mt-4 dark:border-border/50 relative z-20 border-t">
<div className="mx-auto max-w-7xl">
<nav className="flex items-center lg:-mx-3">
<ul className="relative -mb-px flex h-11 snap-x snap-proximity scroll-px-6 items-center gap-6 overflow-x-auto overflow-y-hidden px-6 lg:scroll-px-2 lg:gap-5">
{categories.map((category) => (
<li
key={category}
className={cn(
'flex h-full snap-start items-center border-b border-b-transparent',
pathname === `/nsui/${category}` && 'border-primary'
)}
>
<Link
href={`/nsui/${category}`}
prefetch={true}
<ul className="relative -mb-px flex h-12 snap-x snap-proximity scroll-px-6 items-center gap-6 overflow-x-auto overflow-y-hidden px-6 lg:scroll-px-2 lg:gap-5">
{categories.map((category) => {
const href = `/blocks/${category}`;
const isActive = pathname.startsWith(href);
return (
<li
key={category}
className={cn(
pathname === `/nsui/${category}` && 'text-foreground!',
'hover:bg-muted dark:text-muted-foreground hover:text-foreground flex h-7 w-fit items-center text-nowrap rounded-full px-1 text-sm text-zinc-700 lg:-mx-2 lg:px-3'
'flex h-full snap-start items-center border-b border-b-transparent',
isActive && 'border-primary'
)}
>
<span className="block w-max text-nowrap capitalize">
{category}
</span>
</Link>
</li>
))}
<LocaleLink
href={href}
prefetch={true}
className={cn(
isActive && 'text-foreground!',
'hover:bg-muted dark:text-muted-foreground hover:text-foreground flex h-7 w-fit items-center text-nowrap rounded-full px-2 text-sm text-zinc-700 lg:-mx-2 lg:px-3'
)}
>
<span className="block w-max text-nowrap capitalize">
{category}
</span>
</LocaleLink>
</li>
)
})}
</ul>
</nav>
</div>
</div>
);
};
export default BlocksNav;
}

View File

@ -35,7 +35,13 @@ import {
SquareKanbanIcon,
SquarePenIcon,
ThumbsUpIcon,
WandSparklesIcon
WandSparklesIcon,
SquareCodeIcon,
UsersIcon,
FootprintsIcon,
SplitSquareVerticalIcon,
LogInIcon,
UserPlusIcon
} from 'lucide-react';
import { useTranslations } from 'next-intl';
@ -199,9 +205,9 @@ export function getMenuLinks(): NestedMenuItem[] {
external: false,
},
{
title: t('Marketing.navbar.blocks.items.pricing.title'),
icon: <CircleDollarSignIcon className="site-4 shrink-0" />,
href: Routes.PricingBlocks,
title: t('Marketing.navbar.blocks.items.logo.title'),
icon: <SquareCodeIcon className="site-4 shrink-0" />,
href: Routes.LogoBlocks,
external: false,
},
{
@ -211,15 +217,9 @@ export function getMenuLinks(): NestedMenuItem[] {
external: false,
},
{
title: t('Marketing.navbar.blocks.items.faq.title'),
icon: <CircleHelpIcon className="site-4 shrink-0" />,
href: Routes.FAQBlocks,
external: false,
},
{
title: t('Marketing.navbar.blocks.items.testimonials.title'),
icon: <ThumbsUpIcon className="site-4 shrink-0" />,
href: Routes.TestimonialsBlocks,
title: t('Marketing.navbar.blocks.items.content.title'),
icon: <NewspaperIcon className="site-4 shrink-0" />,
href: Routes.ContentBlocks,
external: false,
},
{
@ -228,6 +228,18 @@ export function getMenuLinks(): NestedMenuItem[] {
href: Routes.StatsBlocks,
external: false,
},
{
title: t('Marketing.navbar.blocks.items.team.title'),
icon: <UsersIcon className="site-4 shrink-0" />,
href: Routes.TeamBlocks,
external: false,
},
{
title: t('Marketing.navbar.blocks.items.testimonials.title'),
icon: <ThumbsUpIcon className="site-4 shrink-0" />,
href: Routes.TestimonialsBlocks,
external: false,
},
{
title: t('Marketing.navbar.blocks.items.callToAction.title'),
icon: <RocketIcon className="site-4 shrink-0" />,
@ -235,9 +247,45 @@ export function getMenuLinks(): NestedMenuItem[] {
external: false,
},
{
title: t('Marketing.navbar.blocks.items.content.title'),
icon: <NewspaperIcon className="site-4 shrink-0" />,
href: Routes.ContentBlocks,
title: t('Marketing.navbar.blocks.items.footer.title'),
icon: <FootprintsIcon className="site-4 shrink-0" />,
href: Routes.FooterBlocks,
external: false,
},
{
title: t('Marketing.navbar.blocks.items.pricing.title'),
icon: <CircleDollarSignIcon className="site-4 shrink-0" />,
href: Routes.PricingBlocks,
external: false,
},
{
title: t('Marketing.navbar.blocks.items.comparator.title'),
icon: <SplitSquareVerticalIcon className="site-4 shrink-0" />,
href: Routes.ComparatorBlocks,
external: false,
},
{
title: t('Marketing.navbar.blocks.items.faq.title'),
icon: <CircleHelpIcon className="site-4 shrink-0" />,
href: Routes.FAQBlocks,
external: false,
},
{
title: t('Marketing.navbar.blocks.items.login.title'),
icon: <LogInIcon className="site-4 shrink-0" />,
href: Routes.LoginBlocks,
external: false,
},
{
title: t('Marketing.navbar.blocks.items.signup.title'),
icon: <UserPlusIcon className="site-4 shrink-0" />,
href: Routes.SignupBlocks,
external: false,
},
{
title: t('Marketing.navbar.blocks.items.contact.title'),
icon: <MailIcon className="site-4 shrink-0" />,
href: Routes.ContactBlocks,
external: false,
},
],

View File

@ -38,14 +38,22 @@ export enum Routes {
AIVideo = '/ai/video',
AIAudio = '/ai/audio',
// Block routes
HeroBlocks = '/blocks/hero-section',
PricingBlocks = '/blocks/pricing',
LogoBlocks = '/blocks/logo-cloud',
FeaturesBlocks = '/blocks/features',
FAQBlocks = '/blocks/faq',
TestimonialsBlocks = '/blocks/testimonials',
StatsBlocks = '/blocks/stats',
CallToActionBlocks = '/blocks/call-to-action',
ContentBlocks = '/blocks/content',
StatsBlocks = '/blocks/stats',
TeamBlocks = '/blocks/team',
TestimonialsBlocks = '/blocks/testimonials',
CallToActionBlocks = '/blocks/call-to-action',
FooterBlocks = '/blocks/footer',
PricingBlocks = '/blocks/pricing',
ComparatorBlocks = '/blocks/comparator',
FAQBlocks = '/blocks/faqs',
LoginBlocks = '/blocks/login',
SignupBlocks = '/blocks/signup',
ContactBlocks = '/blocks/contact',
}
/**