refactor: streamline settings navigation chrome

This commit is contained in:
Your Name 2026-05-29 15:19:06 +08:00
parent aea1d84c88
commit 1307095960
2 changed files with 80 additions and 97 deletions

View File

@ -9,7 +9,7 @@ type AdminLayoutShellProps = ComponentProps<typeof TopNavbar> & {
};
function shouldHideSidebarChrome(pathname: string) {
return pathname === "/settings";
return pathname === "/settings" || pathname.startsWith("/settings/");
}
export function AdminLayoutShell({

View File

@ -1,16 +1,8 @@
"use client";
import Link from "next/link";
import { ArrowLeft, ChevronDown } from "lucide-react";
import { ArrowLeft } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
Select,
SelectContent,
@ -25,7 +17,6 @@ import { Separator } from "@/components/ui/separator";
import { ADMIN_SETTINGS_NAV_GROUPS } from "../_lib/settings-navigation";
import type { AdminSettingsNavGroup, AdminSettingsNavItem } from "../_lib/types";
import { cn } from "@/lib/utils";
import { SettingStatusBadge } from "./setting-status-badge";
function getActiveGroup(activeTab: string) {
return ADMIN_SETTINGS_NAV_GROUPS.find((group) =>
@ -33,78 +24,48 @@ function getActiveGroup(activeTab: string) {
);
}
function SettingsGroupDropdown({
activeTab,
function SettingsGroupTab({
group,
isActive,
onTabChange,
}: {
activeTab: string;
group: AdminSettingsNavGroup;
isActive: boolean;
onTabChange: (value: string) => void;
}) {
const hasActiveTab = group.items.some((item) => item.value === activeTab);
const defaultItem = group.items[0];
const hasHref = Boolean(defaultItem?.href);
const className = cn(
"h-10 w-full justify-center rounded-2xl border px-4 text-sm font-medium shadow-none transition-colors",
isActive
? "border-foreground/10 bg-foreground text-background hover:bg-foreground/90 hover:text-background dark:border-white/20 dark:bg-white dark:text-black dark:hover:bg-white/90"
: "border-border/70 bg-zinc-50/80 text-muted-foreground hover:bg-background hover:text-foreground"
);
if (!defaultItem) return null;
if (hasHref) {
return (
<Button
asChild
className={className}
size="sm"
variant="ghost"
>
<Link href={defaultItem.href!}>{group.title}</Link>
</Button>
);
}
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
className={cn(
"h-9 rounded-full border px-3.5 text-sm shadow-none",
hasActiveTab
? "border-foreground/10 bg-foreground text-background hover:bg-foreground/90 hover:text-background dark:border-white/20 dark:bg-white dark:text-black dark:hover:bg-white/90"
: "border-border/70 bg-background/90 text-muted-foreground hover:bg-background hover:text-foreground"
)}
size="sm"
variant="ghost"
>
<span>{group.title}</span>
<ChevronDown className="h-3.5 w-3.5 opacity-70" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-80">
<DropdownMenuLabel className="space-y-1">
<div>{group.title}</div>
<div className="font-normal text-muted-foreground text-xs">
{group.description}
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
{group.items.map((item) => {
const Icon = item.icon;
const itemContent = (
<>
<Icon className="h-4 w-4 text-muted-foreground" />
<div className="min-w-0 flex-1">
<div className="font-medium text-sm">{item.label}</div>
<div className="truncate text-muted-foreground text-xs">
{item.description}
</div>
</div>
{item.value === activeTab ? (
<SettingStatusBadge tone="enabled">当前</SettingStatusBadge>
) : null}
</>
);
if (item.href) {
return (
<DropdownMenuItem asChild key={item.value}>
<Link href={item.href}>{itemContent}</Link>
</DropdownMenuItem>
);
}
return (
<DropdownMenuItem
key={item.value}
onSelect={() => onTabChange(item.value)}
>
{itemContent}
</DropdownMenuItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
<Button
className={className}
onClick={() => onTabChange(defaultItem.value)}
size="sm"
variant="ghost"
>
{group.title}
</Button>
);
}
@ -112,28 +73,33 @@ function SettingsQuickAction({
activeTab,
item,
onTabChange,
stretched = false,
}: {
activeTab: string;
item: AdminSettingsNavItem;
onTabChange: (value: string) => void;
stretched?: boolean;
}) {
const Icon = item.icon;
const className =
item.value === activeTab
? "border-foreground/10 bg-foreground text-background shadow-sm hover:bg-foreground/90 dark:border-white/20 dark:bg-white dark:text-black dark:hover:bg-white/90"
: "border-transparent bg-transparent text-muted-foreground hover:border-border/80 hover:bg-background hover:text-foreground";
const layoutClassName = stretched
? "h-10 w-full min-w-0 justify-center gap-2 rounded-2xl border px-4"
: "h-9 shrink-0 gap-2 rounded-full border px-3.5";
if (item.href) {
return (
<Button
asChild
className={`h-9 shrink-0 gap-2 rounded-full border px-3.5 ${className}`}
className={`${layoutClassName} ${className}`}
size="sm"
variant="ghost"
>
<Link href={item.href}>
<Icon className="h-4 w-4" />
<span>{item.label}</span>
<span className={stretched ? "truncate" : ""}>{item.label}</span>
</Link>
</Button>
);
@ -141,13 +107,13 @@ function SettingsQuickAction({
return (
<Button
className={`h-9 shrink-0 gap-2 rounded-full border px-3.5 ${className}`}
className={`${layoutClassName} ${className}`}
onClick={() => onTabChange(item.value)}
size="sm"
variant="ghost"
>
<Icon className="h-4 w-4" />
<span>{item.label}</span>
<span className={stretched ? "truncate" : ""}>{item.label}</span>
</Button>
);
}
@ -160,6 +126,10 @@ export function AdminSettingsNavigation({
onTabChange: (value: string) => void;
}) {
const activeGroup = getActiveGroup(activeTab);
const groupButtons = ADMIN_SETTINGS_NAV_GROUPS.map((group) => ({
group,
isActive: group.items.some((item) => item.value === activeTab),
}));
return (
<div className="rounded-2xl border bg-background px-4 py-3 shadow-sm">
@ -229,26 +199,39 @@ export function AdminSettingsNavigation({
<Separator className="mx-1 h-5 shrink-0" orientation="vertical" />
<div className="flex min-w-0 flex-1 items-center gap-1 overflow-x-auto">
{ADMIN_SETTINGS_NAV_GROUPS.map((group) => (
<SettingsGroupDropdown
activeTab={activeTab}
group={group}
key={group.title}
onTabChange={onTabChange}
/>
))}
<div className="flex min-w-0 flex-1 items-center gap-3 overflow-hidden">
<div className="grid w-[420px] min-w-[420px] grid-cols-3 gap-2">
{groupButtons.map(({ group, isActive }) => (
<SettingsGroupTab
group={group}
isActive={isActive}
key={group.title}
onTabChange={onTabChange}
/>
))}
</div>
<Separator className="mx-1 h-5 shrink-0" orientation="vertical" />
<Separator className="h-6 shrink-0" orientation="vertical" />
{(activeGroup?.items ?? []).map((item) => (
<SettingsQuickAction
activeTab={activeTab}
item={item}
key={item.value}
onTabChange={onTabChange}
/>
))}
<div
className="grid min-w-0 flex-1 gap-2"
style={{
gridTemplateColumns: `repeat(${Math.max(
activeGroup?.items.length || 1,
1
)}, minmax(0, 1fr))`,
}}
>
{(activeGroup?.items ?? []).map((item) => (
<SettingsQuickAction
activeTab={activeTab}
item={item}
key={item.value}
onTabChange={onTabChange}
stretched
/>
))}
</div>
</div>
<Button