Compare commits

...

1 Commits

Author SHA1 Message Date
google-labs-jules[bot]
fb3e914a95 Refine mobile menu UI/UX
- Convert nav links to block elements with padding
- Improve Login/Register button layout
- Update LanguageToggle and ReleaseChannelSelector to use semantic tokens

Co-authored-by: cloud-neutral <4133689+cloud-neutral@users.noreply.github.com>
2026-01-29 09:52:38 +00:00
7 changed files with 10055 additions and 15260 deletions

2
next-env.d.ts vendored
View File

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

View File

@ -100,4 +100,4 @@
"glob": "10.5.0"
},
"packageManager": "yarn@4.12.0"
}
}

View File

@ -51,6 +51,7 @@ export function AskAIDialog({
new Map<string, { answer: string; sources: any[]; timestamp: number }>()
)
const requestIdRef = useRef(0)
const requestIdRef = useRef(0)
const processedInitialRef = useRef<number | null>(null)
const { language } = useLanguage()

View File

@ -1,18 +1,21 @@
'use client'
import { useLanguage } from '../i18n/LanguageProvider'
export default function LanguageToggle() {
type Props = {
className?: string
}
export default function LanguageToggle({ className = '' }: Props) {
const { language, setLanguage } = useLanguage()
return (
<select
value={language}
onChange={(e) => setLanguage(e.target.value as 'en' | 'zh')}
className="bg-gray-100 text-gray-900 border border-gray-300 px-2 py-1 rounded text-sm"
className={`rounded border border-surface-border bg-surface px-2 py-1 text-sm text-text ${className}`}
>
<option value="en">English</option>
<option value="zh"></option>
</select>
)
}
// This component provides a dropdown to toggle between English and Chinese languages.

View File

@ -387,12 +387,12 @@ export default function Navbar() {
inputClassName="py-2 pr-12"
/>
*/}
<div className="flex flex-col gap-2 text-sm font-medium">
<div className="flex flex-col gap-1 text-sm font-medium">
{mainLinks.map((link) => (
<Link
key={link.key}
href={link.href}
className="py-2 text-sm opacity-80 transition hover:opacity-100"
className="block rounded-md px-3 py-3 text-base font-medium text-text opacity-80 transition-colors hover:bg-surface-muted hover:text-primary hover:opacity-100"
onClick={() => setMenuOpen(false)}
>
{link.label}
@ -400,7 +400,7 @@ export default function Navbar() {
))}
<Link
href="/about"
className="py-2 text-sm opacity-80 transition hover:opacity-100"
className="block rounded-md px-3 py-3 text-base font-medium text-text opacity-80 transition-colors hover:bg-surface-muted hover:text-primary hover:opacity-100"
onClick={() => setMenuOpen(false)}
>
{labels.about}
@ -408,7 +408,7 @@ export default function Navbar() {
<Link
key={servicesLink.key}
href={servicesLink.href}
className="py-2 text-sm opacity-80 transition hover:opacity-100"
className="block rounded-md px-3 py-3 text-base font-medium text-text opacity-80 transition-colors hover:bg-surface-muted hover:text-primary hover:opacity-100"
onClick={() => setMenuOpen(false)}
>
{servicesLink.label}
@ -425,41 +425,42 @@ export default function Navbar() {
<p className="text-xs text-text-muted">{user.email}</p>
</div>
</div>
<Link
href="/panel"
className="mt-3 inline-flex items-center justify-center rounded-md border border-surface-border bg-surface px-3 py-1.5 text-xs font-semibold text-primary transition hover:border-primary/50 hover:bg-primary/10"
onClick={() => setMenuOpen(false)}
>
{accountCopy.userCenter}
</Link>
<Link
href="/logout"
className="mt-3 inline-flex items-center justify-center rounded-md border border-surface-border px-3 py-1.5 text-xs font-semibold text-danger transition hover:border-danger/60 hover:bg-danger/10 focus:outline-none focus:ring-2 focus:ring-danger/30 focus:ring-offset-2 focus:ring-offset-background"
onClick={() => setMenuOpen(false)}
>
{accountCopy.logout}
</Link>
<div className="mt-4 flex flex-col gap-3">
<Link
href="/panel"
className="flex w-full items-center justify-center rounded-lg border border-surface-border bg-surface px-4 py-2.5 text-sm font-medium text-primary transition hover:border-primary/50 hover:bg-primary/10"
onClick={() => setMenuOpen(false)}
>
{accountCopy.userCenter}
</Link>
<Link
href="/logout"
className="flex w-full items-center justify-center rounded-lg border border-surface-border px-4 py-2.5 text-sm font-medium text-danger transition hover:border-danger/60 hover:bg-danger/10 focus:outline-none focus:ring-2 focus:ring-danger/30 focus:ring-offset-2 focus:ring-offset-background"
onClick={() => setMenuOpen(false)}
>
{accountCopy.logout}
</Link>
</div>
</div>
) : (
<div className="flex items-center gap-3 text-sm font-medium">
<div className="mt-2 grid grid-cols-2 gap-4 text-sm font-medium">
<Link
href="/login"
className="py-2 text-sm opacity-80 transition hover:opacity-100"
className="flex w-full items-center justify-center rounded-lg border border-surface-border bg-surface px-4 py-2.5 text-sm font-medium text-text transition hover:bg-surface-muted"
onClick={() => setMenuOpen(false)}
>
{nav.account.login}
</Link>
<span className="h-3 w-px bg-surface-border" aria-hidden="true" />
<Link
href="/register"
className="rounded-md border border-surface-border px-3 py-1.5 text-primary transition hover:border-primary/50 hover:bg-surface-muted"
className="flex w-full items-center justify-center rounded-lg bg-primary px-4 py-2.5 text-sm font-medium text-white shadow-sm transition hover:bg-primary/90"
onClick={() => setMenuOpen(false)}
>
{nav.account.register}
</Link>
</div>
)}
<div className="flex flex-col gap-2">
<div className="mt-4 flex flex-col gap-3">
<ReleaseChannelSelector selected={selectedChannels} onToggle={toggleChannel} />
<LanguageToggle />
</div>

View File

@ -53,7 +53,7 @@ export default function ReleaseChannelSelector({
<button
type="button"
onClick={() => setOpen((prev) => !prev)}
className={`relative flex items-center gap-2 rounded-md border border-gray-200 bg-white/80 text-sm text-gray-700 shadow-sm transition hover:border-purple-300 hover:text-purple-600 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2 ${
className={`relative flex items-center gap-2 rounded-md border border-surface-border bg-surface text-sm text-text shadow-sm transition hover:border-primary/50 hover:text-primary focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 ${
isIcon
? 'h-9 w-9 justify-center p-0'
: isCompact
@ -66,7 +66,7 @@ export default function ReleaseChannelSelector({
>
{isIcon ? (
<svg
className="h-5 w-5 text-purple-600"
className="h-5 w-5 text-primary"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
@ -86,14 +86,14 @@ export default function ReleaseChannelSelector({
</svg>
) : (
<>
<span className={`font-medium ${isCompact ? '' : 'text-gray-700'}`}>{labels.label}</span>
<span className={`font-medium ${isCompact ? '' : 'text-text'}`}>{labels.label}</span>
{!isCompact && (
<span className="text-xs text-gray-500">
<span className="text-xs text-text-muted">
{labels.summaryPrefix}: {summary}
</span>
)}
<svg
className={`h-4 w-4 text-gray-400 transition-transform ${open ? 'rotate-180' : ''}`}
className={`h-4 w-4 text-text-subtle transition-transform ${open ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
@ -105,37 +105,37 @@ export default function ReleaseChannelSelector({
</>
)}
{isIcon && hasPreviewSelection && (
<span className="absolute -top-1 -right-1 h-2 w-2 rounded-full bg-purple-500" />
<span className="absolute -top-1 -right-1 h-2 w-2 rounded-full bg-primary" />
)}
</button>
{(isCompact || isIcon) && (
<div className="pointer-events-none absolute bottom-full left-1/2 z-40 mb-2 hidden w-56 -translate-x-1/2 rounded-md bg-gray-900 px-3 py-2 text-xs text-white opacity-0 transition group-hover:block group-hover:opacity-100 group-focus-within:block group-focus-within:opacity-100">
<div className="pointer-events-none absolute bottom-full left-1/2 z-40 mb-2 hidden w-56 -translate-x-1/2 rounded-md bg-surface-inverted px-3 py-2 text-xs text-background opacity-0 transition group-hover:block group-hover:opacity-100 group-focus-within:block group-focus-within:opacity-100">
<div className="font-semibold">{labels.label}</div>
<div className="mt-1 text-gray-200">
<div className="mt-1 text-background-muted">
{labels.summaryPrefix}: {summary}
</div>
</div>
)}
{open && (
<div className="absolute right-0 z-50 mt-2 w-64 rounded-md border border-gray-200 bg-white shadow-lg">
<ul className="py-2 text-sm text-gray-700" role="listbox" aria-label={labels.label}>
<div className="absolute right-0 z-50 mt-2 w-64 rounded-md border border-surface-border bg-surface shadow-lg">
<ul className="py-2 text-sm text-text" role="listbox" aria-label={labels.label}>
{CHANNEL_ORDER.map((channel) => {
const channelLabels = labels[channel]
const checked = selected.includes(channel)
const isStable = channel === 'stable'
return (
<li key={channel}>
<label className="flex cursor-pointer items-start gap-3 px-3 py-2 hover:bg-gray-50">
<label className="flex cursor-pointer items-start gap-3 px-3 py-2 hover:bg-surface-muted">
<input
type="checkbox"
className="mt-1 h-4 w-4 rounded border-gray-300 text-purple-600 focus:ring-purple-500"
className="mt-1 h-4 w-4 rounded border-surface-border text-primary focus:ring-primary"
checked={checked}
onChange={() => (!isStable ? onToggle(channel) : undefined)}
disabled={isStable}
/>
<div>
<div className="font-medium text-gray-900">{channelLabels.name}</div>
<p className="text-xs text-gray-500">{channelLabels.description}</p>
<div className="font-medium text-text">{channelLabels.name}</div>
<p className="text-xs text-text-muted">{channelLabels.description}</p>
</div>
</label>
</li>

25226
yarn.lock

File diff suppressed because it is too large Load Diff