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:
javayhu 2025-03-08 22:43:36 +08:00
parent 92d7513e3d
commit b9a5c4927e
6 changed files with 67 additions and 52 deletions

View File

@ -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>

View File

@ -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",

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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
}
]
},
];
}