refactor: improve UI consistency and enhance component interactions
- Update blog post page with refined category link styling - Modify blog table of contents to add hover state for links - Enhance locale selector with optional locale name display - Refactor navbar mobile and desktop components with improved layout and styling - Reorganize marketing menu links and update icon usage - Adjust font weights and hover states across navigation components
This commit is contained in:
parent
92d7513e3d
commit
b9a5c4927e
@ -114,7 +114,7 @@ export default async function BlogPostPage(props: NextPageProps) {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* blog post date */}
|
||||
{/* blog post date and reading time */}
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<CalendarIcon className="size-4 text-muted-foreground" />
|
||||
@ -122,7 +122,9 @@ export default async function BlogPostPage(props: NextPageProps) {
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<ClockIcon className="size-4 text-muted-foreground" />
|
||||
<p className="text-sm text-muted-foreground">{estimateReadingTime(post.body.raw)}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{estimateReadingTime(post.body.raw)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -173,7 +175,7 @@ export default async function BlogPostPage(props: NextPageProps) {
|
||||
<li key={category.slug}>
|
||||
<LocaleLink
|
||||
href={`/blog/category/${category.slug}`}
|
||||
className="text-sm link-underline-animation"
|
||||
className="text-sm font-medium hover:text-primary"
|
||||
>
|
||||
{category.name}
|
||||
</LocaleLink>
|
||||
|
@ -93,7 +93,7 @@ function Tree({ tree, level = 1, activeItem }: TreeProps) {
|
||||
<a
|
||||
href={item.url}
|
||||
className={cn(
|
||||
"inline-block text-sm no-underline",
|
||||
"inline-block text-sm no-underline hover:text-primary",
|
||||
item.url === `#${activeItem}`
|
||||
? "font-medium text-primary"
|
||||
: "text-muted-foreground",
|
||||
|
@ -29,7 +29,7 @@ import { useEffect, useTransition } from "react";
|
||||
*
|
||||
* https://next-intl.dev/docs/routing/navigation#userouter
|
||||
*/
|
||||
export default function LocaleSelector() {
|
||||
export default function LocaleSelector({showLocaleName = true}: {showLocaleName?: boolean}) {
|
||||
const router = useLocaleRouter();
|
||||
const pathname = useLocalePathname();
|
||||
const params = useParams();
|
||||
@ -66,6 +66,7 @@ export default function LocaleSelector() {
|
||||
{currentLocale && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-lg">{LOCALE_LIST[currentLocale].flag}</span>
|
||||
{showLocaleName && <span>{LOCALE_LIST[currentLocale].name}</span>}
|
||||
</div>
|
||||
)}
|
||||
</SelectValue>
|
||||
|
@ -16,7 +16,13 @@ import { authClient } from '@/lib/auth-client';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Routes } from '@/routes';
|
||||
import { Portal } from '@radix-ui/react-portal';
|
||||
import { ArrowUpRightIcon, ChevronDownIcon, ChevronUpIcon, MenuIcon, XIcon } from 'lucide-react';
|
||||
import {
|
||||
ArrowUpRightIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronUpIcon,
|
||||
MenuIcon,
|
||||
XIcon
|
||||
} from 'lucide-react';
|
||||
import { useTranslations } from "next-intl";
|
||||
import * as React from 'react';
|
||||
import { RemoveScroll } from 'react-remove-scroll';
|
||||
@ -72,7 +78,7 @@ export function NavbarMobile({
|
||||
</LocaleLink>
|
||||
|
||||
{/* navbar right shows menu icon */}
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center justify-end gap-4">
|
||||
{/* show user button if user is logged in */}
|
||||
{user ? <UserButton /> : null}
|
||||
|
||||
@ -82,7 +88,8 @@ export function NavbarMobile({
|
||||
aria-expanded={open}
|
||||
aria-label="Toggle Mobile Menu"
|
||||
onClick={handleToggleMobileMenu}
|
||||
className="flex aspect-square h-fit select-none items-center justify-center rounded-md border"
|
||||
className="flex aspect-square h-fit select-none items-center
|
||||
justify-center rounded-md border"
|
||||
>
|
||||
{open ? (
|
||||
<XIcon className="size-8" />
|
||||
@ -96,7 +103,7 @@ export function NavbarMobile({
|
||||
{/* mobile menu */}
|
||||
{open && (
|
||||
<Portal asChild>
|
||||
<RemoveScroll allowPinchZoom enabled>
|
||||
<RemoveScroll allowPinchZoom removeScrollBar enabled>
|
||||
<MainMobileMenu onLinkClicked={handleToggleMobileMenu} />
|
||||
</RemoveScroll>
|
||||
</Portal>
|
||||
@ -118,10 +125,11 @@ function MainMobileMenu({ onLinkClicked }: MainMobileMenuProps) {
|
||||
const localePathname = useLocalePathname();
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 mt-[72px] overflow-y-auto bg-background backdrop-blur-md animate-in fade-in-0">
|
||||
<div className="fixed w-full inset-0 z-50 mt-[72px] overflow-y-auto
|
||||
bg-background backdrop-blur-md animate-in fade-in-0">
|
||||
<div className="flex size-full flex-col items-start space-y-4 p-4">
|
||||
{/* action buttons */}
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
<LocaleLink
|
||||
href={Routes.Login}
|
||||
onClick={onLinkClicked}
|
||||
@ -154,7 +162,9 @@ function MainMobileMenu({ onLinkClicked }: MainMobileMenuProps) {
|
||||
<ul className="w-full">
|
||||
{menuLinks && menuLinks.map((item) => {
|
||||
const isActive = item.href ? localePathname.startsWith(item.href) :
|
||||
item.items?.some(subItem => subItem.href && localePathname.startsWith(subItem.href));
|
||||
item.items?.some(subItem =>
|
||||
subItem.href && localePathname.startsWith(subItem.href)
|
||||
);
|
||||
|
||||
return (
|
||||
<li key={item.title} className="py-2">
|
||||
@ -229,7 +239,8 @@ function MainMobileMenu({ onLinkClicked }: MainMobileMenuProps) {
|
||||
)}
|
||||
</div>
|
||||
{subItem.external && (
|
||||
<ArrowUpRightIcon className="size-4 shrink-0 text-muted-foreground transition-colors group-hover:text-primary" />
|
||||
<ArrowUpRightIcon className="size-4 shrink-0 text-muted-foreground
|
||||
transition-colors group-hover:text-primary" />
|
||||
)}
|
||||
</LocaleLink>
|
||||
</li>
|
||||
@ -247,7 +258,7 @@ function MainMobileMenu({ onLinkClicked }: MainMobileMenuProps) {
|
||||
buttonVariants({ variant: 'ghost' }),
|
||||
'w-full justify-start',
|
||||
"text-muted-foreground hover:text-primary",
|
||||
isActive && "font-bold text-primary dark:text-primary-foreground"
|
||||
isActive && "font-semibold text-primary dark:text-primary-foreground"
|
||||
)}
|
||||
onClick={onLinkClicked}
|
||||
>
|
||||
@ -260,7 +271,7 @@ function MainMobileMenu({ onLinkClicked }: MainMobileMenuProps) {
|
||||
</ul>
|
||||
|
||||
{/* bottom buttons */}
|
||||
<div className="flex w-full items-center justify-between gap-4 border-t border-border/40 py-4">
|
||||
<div className="flex w-full items-center justify-between gap-4 border-t border-border/50 py-4">
|
||||
<LocaleSelector />
|
||||
<ThemeSwitcherHorizontal />
|
||||
</div>
|
||||
|
@ -35,7 +35,7 @@ const customNavigationMenuTriggerStyle = cn(
|
||||
navigationMenuTriggerStyle(),
|
||||
"relative bg-transparent text-muted-foreground",
|
||||
"hover:bg-transparent hover:text-primary focus:bg-transparent focus:text-primary",
|
||||
"data-[active]:font-bold data-[active]:bg-transparent data-[active]:text-primary",
|
||||
"data-[active]:font-semibold data-[active]:bg-transparent data-[active]:text-primary",
|
||||
"data-[state=open]:bg-transparent data-[state=open]:text-primary",
|
||||
"dark:hover:text-primary dark:data-[active]:text-primary-foreground"
|
||||
);
|
||||
@ -180,7 +180,7 @@ export function Navbar({ scroll }: NavBarProps) {
|
||||
)}
|
||||
|
||||
<ThemeSwitcher />
|
||||
<LocaleSelector />
|
||||
<LocaleSelector showLocaleName={false} />
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
|
@ -20,7 +20,7 @@ import {
|
||||
MailboxIcon,
|
||||
MailIcon,
|
||||
SettingsIcon,
|
||||
ShieldIcon,
|
||||
ShieldCheckIcon,
|
||||
SquareKanbanIcon,
|
||||
SquarePenIcon
|
||||
} from 'lucide-react';
|
||||
@ -46,6 +46,7 @@ export function createTranslator(t: any): TranslationFunction {
|
||||
|
||||
/**
|
||||
* Get menu links with translations
|
||||
*
|
||||
* @param t - The translation function
|
||||
* @returns The menu links with translated titles and descriptions
|
||||
*/
|
||||
@ -66,39 +67,6 @@ export function getMenuLinks(t: TranslationFunction): NestedMenuItem[] {
|
||||
href: Routes.Blog,
|
||||
external: false
|
||||
},
|
||||
{
|
||||
title: t('Marketing.menu.ai.title'),
|
||||
items: [
|
||||
{
|
||||
title: t('Marketing.menu.ai.items.text.title'),
|
||||
description: t('Marketing.menu.ai.items.text.description'),
|
||||
icon: <SquarePenIcon className="size-5 shrink-0" />,
|
||||
href: Routes.AIText,
|
||||
external: false
|
||||
},
|
||||
{
|
||||
title: t('Marketing.menu.ai.items.image.title'),
|
||||
description: t('Marketing.menu.ai.items.image.description'),
|
||||
icon: <ImageIcon className="size-5 shrink-0" />,
|
||||
href: Routes.AIImage,
|
||||
external: false
|
||||
},
|
||||
{
|
||||
title: t('Marketing.menu.ai.items.video.title'),
|
||||
description: t('Marketing.menu.ai.items.video.description'),
|
||||
icon: <FilmIcon className="size-5 shrink-0" />,
|
||||
href: Routes.AIVideo,
|
||||
external: false
|
||||
},
|
||||
{
|
||||
title: t('Marketing.menu.ai.items.audio.title'),
|
||||
description: t('Marketing.menu.ai.items.audio.description'),
|
||||
icon: <AudioLinesIcon className="size-5 shrink-0" />,
|
||||
href: Routes.AIAudio,
|
||||
external: false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: t('Marketing.menu.pages.title'),
|
||||
items: [
|
||||
@ -147,7 +115,7 @@ export function getMenuLinks(t: TranslationFunction): NestedMenuItem[] {
|
||||
{
|
||||
title: t('Marketing.menu.pages.items.privacyPolicy.title'),
|
||||
description: t('Marketing.menu.pages.items.privacyPolicy.description'),
|
||||
icon: <ShieldIcon className="size-5 shrink-0" />,
|
||||
icon: <ShieldCheckIcon className="size-5 shrink-0" />,
|
||||
href: Routes.PrivacyPolicy,
|
||||
external: false
|
||||
},
|
||||
@ -160,6 +128,39 @@ export function getMenuLinks(t: TranslationFunction): NestedMenuItem[] {
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: t('Marketing.menu.ai.title'),
|
||||
items: [
|
||||
{
|
||||
title: t('Marketing.menu.ai.items.text.title'),
|
||||
description: t('Marketing.menu.ai.items.text.description'),
|
||||
icon: <SquarePenIcon className="size-5 shrink-0" />,
|
||||
href: Routes.AIText,
|
||||
external: false
|
||||
},
|
||||
{
|
||||
title: t('Marketing.menu.ai.items.image.title'),
|
||||
description: t('Marketing.menu.ai.items.image.description'),
|
||||
icon: <ImageIcon className="size-5 shrink-0" />,
|
||||
href: Routes.AIImage,
|
||||
external: false
|
||||
},
|
||||
{
|
||||
title: t('Marketing.menu.ai.items.video.title'),
|
||||
description: t('Marketing.menu.ai.items.video.description'),
|
||||
icon: <FilmIcon className="size-5 shrink-0" />,
|
||||
href: Routes.AIVideo,
|
||||
external: false
|
||||
},
|
||||
{
|
||||
title: t('Marketing.menu.ai.items.audio.title'),
|
||||
description: t('Marketing.menu.ai.items.audio.description'),
|
||||
icon: <AudioLinesIcon className="size-5 shrink-0" />,
|
||||
href: Routes.AIAudio,
|
||||
external: false
|
||||
}
|
||||
]
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user