style(web): 工作台入口 - 侧栏容量加硬盘图标,积分入口改为礼盒标

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ddCat
2026-09-18 10:30:08 +08:00
parent 521629c2a8
commit 1077a9106d
10 changed files with 254 additions and 50 deletions

View File

@@ -0,0 +1,44 @@
import { useId } from "react";
import { cn } from "@/lib/utils";
export function WorkspaceCreditGiftMark({ className }: { className?: string }) {
const uid = useId().replace(/:/g, "");
const box = `${uid}-box`;
const lid = `${uid}-lid`;
const ribbon = `${uid}-ribbon`;
return (
<span className={cn("app-workspace-credit-gift", className)} aria-hidden="true">
<svg viewBox="0 0 32 32" fill="none">
<defs>
<linearGradient id={box} x1="8" y1="14" x2="24" y2="28" gradientUnits="userSpaceOnUse">
<stop stopColor="#FFB34A" />
<stop offset="1" stopColor="#F26B12" />
</linearGradient>
<linearGradient id={lid} x1="6" y1="10" x2="26" y2="16" gradientUnits="userSpaceOnUse">
<stop stopColor="#FFC66A" />
<stop offset="1" stopColor="#FF8A28" />
</linearGradient>
<linearGradient id={ribbon} x1="16" y1="6" x2="16" y2="28" gradientUnits="userSpaceOnUse">
<stop stopColor="#5FE8D8" />
<stop offset="1" stopColor="#12B8A8" />
</linearGradient>
</defs>
<ellipse cx="16" cy="28.5" rx="9.2" ry="1.6" fill="#7A3A00" opacity=".22" />
<rect x="6.5" y="14" width="19" height="13.2" rx="2.4" fill={`url(#${box})`} />
<path d="M23.4 16.4v8.4c0 1.3-1 2.4-2.3 2.4h.1V14c1.3.2 2.2 1.2 2.2 2.4Z" fill="#7A3A00" opacity=".16" />
<rect x="14.4" y="14" width="3.2" height="13.2" fill={`url(#${ribbon})`} />
<rect x="6.5" y="19.3" width="19" height="2.8" fill={`url(#${ribbon})`} />
<rect x="5.4" y="10.3" width="21.2" height="5.5" rx="2.1" fill={`url(#${lid})`} />
<rect x="5.4" y="10.3" width="21.2" height="2.1" rx="2.1" fill="#fff" opacity=".24" />
<rect x="14.4" y="10.3" width="3.2" height="5.5" fill={`url(#${ribbon})`} />
<path d="M15.8 11.1C10.4 5.6 7.2 11.8 12.8 13.7c1.6.5 2.4 0 3-1.2Z" fill="#4FE3D4" />
<path d="M16.2 11.1C21.6 5.6 24.8 11.8 19.2 13.7c-1.6.5-2.4 0-3-1.2Z" fill="#14C4B4" />
<rect x="14.7" y="9.3" width="2.6" height="2.6" rx=".8" fill="#0E9E92" />
<path d="M14.9 12.1 11.6 16.4l3.8-3.1Z" fill="#3FDDD0" />
<path d="M17.1 12.1 20.4 16.4l-3.8-3.1Z" fill="#12B6A6" />
</svg>
</span>
);
}

View File

@@ -1,3 +1,4 @@
import { HardDrive } from "lucide-react";
import { Link } from "react-router";
import { useAccountFileStorageUsage } from "@/hooks/use-account-file-storage-usage";
@@ -5,6 +6,14 @@ import { accountStorageMeter } from "@/lib/account-storage-usage";
import { cn } from "@/lib/utils";
import { preloadWorkspaceRoute } from "@/lib/workspace-route-modules";
function StorageGlyph() {
return (
<span className="app-workspace-sidebar-storage-icon" aria-hidden="true">
<HardDrive strokeWidth={1.75} />
</span>
);
}
export function WorkspaceSidebarStorageMeter({ collapsed }: { collapsed: boolean }) {
const query = useAccountFileStorageUsage();
const meter = accountStorageMeter(query.data);
@@ -13,13 +22,30 @@ export function WorkspaceSidebarStorageMeter({ collapsed }: { collapsed: boolean
const totalText = query.data ? `共 ${meter.totalLabel}` : "";
const summary = query.data ? `${usedText},${remainingText},${totalText}` : usedText;
const track = (
<span
className="app-workspace-sidebar-storage-track"
role="progressbar"
aria-label="账号文件容量使用进度"
aria-valuemin={0}
aria-valuemax={query.data?.totalBytes ?? 0}
aria-valuenow={query.data ? Math.min(query.data.usedBytes, query.data.totalBytes) : 0}
aria-valuetext={summary}
>
<span style={{ width: `${meter.percent}%` }} />
</span>
);
if (query.isError && !query.data) {
return (
<div className={cn("app-workspace-sidebar-storage is-error", collapsed && "is-collapsed")}>
{collapsed ? (
<button type="button" title="容量统计暂时不可用,点击重试" aria-label="重试加载账号容量" onClick={() => void query.refetch()} />
<button type="button" title="容量统计暂时不可用,点击重试" aria-label="重试加载账号容量" onClick={() => void query.refetch()}>
<StorageGlyph />
</button>
) : (
<>
<StorageGlyph />
<span>容量暂不可用</span>
<button type="button" onClick={() => void query.refetch()}>
重试
@@ -47,26 +73,23 @@ export function WorkspaceSidebarStorageMeter({ collapsed }: { collapsed: boolean
onFocus={() => preloadWorkspaceRoute("/assets")}
onPointerEnter={() => preloadWorkspaceRoute("/assets")}
>
{collapsed ? null : (
<span className="app-workspace-sidebar-storage-copy">
<span className="app-workspace-sidebar-storage-meta">
<span className="app-workspace-sidebar-storage-used">{usedText}</span>
{totalText ? <span className="app-workspace-sidebar-storage-total">{totalText}</span> : null}
<StorageGlyph />
{collapsed ? (
track
) : (
<span className="app-workspace-sidebar-storage-body">
<span className="app-workspace-sidebar-storage-copy">
<span className="app-workspace-sidebar-storage-meta">
<span className="app-workspace-sidebar-storage-used">{usedText}</span>
{totalText ? <span className="app-workspace-sidebar-storage-total">{totalText}</span> : null}
</span>
</span>
<span className="app-workspace-sidebar-storage-foot">
{track}
{remainingText ? <span className="app-workspace-sidebar-storage-remain">{remainingText}</span> : null}
</span>
{remainingText ? <span className="app-workspace-sidebar-storage-remain">{remainingText}</span> : null}
</span>
)}
<span
className="app-workspace-sidebar-storage-track"
role="progressbar"
aria-label="账号文件容量使用进度"
aria-valuemin={0}
aria-valuemax={query.data?.totalBytes ?? 0}
aria-valuenow={query.data ? Math.min(query.data.usedBytes, query.data.totalBytes) : 0}
aria-valuetext={summary}
>
<span style={{ width: `${meter.percent}%` }} />
</span>
</Link>
);
}

View File

@@ -1,8 +1,9 @@
import { Coins, PanelLeftClose, PanelLeftOpen } from "lucide-react";
import { PanelLeftClose, PanelLeftOpen } from "lucide-react";
import { Link, useLocation } from "react-router";
import { SystemAnnouncementCenter } from "@/components/layout/system-announcement-center";
import { WorkspaceAccountMenu } from "@/components/layout/workspace-account-menu";
import { WorkspaceCreditGiftMark } from "@/components/layout/workspace-credit-gift-mark";
import { WorkspaceTopBarExtensionSlot } from "@/components/layout/workspace-top-bar-extension";
import { AnimatedThemeToggler } from "@/components/ui/animated-theme-toggler";
import { useThemeStore } from "@/stores/use-theme-store";
@@ -48,7 +49,7 @@ export function WorkspaceTopBar({ sidebarOpen, onToggleSidebar }: { sidebarOpen:
<WorkspaceTopBarExtensionSlot />
<div className="app-workspace-topbar-actions">
{creditsEnabled ? <button type="button" className="app-workspace-topbar-credit-pill" aria-label={`打开积分中心,可用 ${balance} 积分`} onClick={() => openWorkspaceWallet()}>
<Coins aria-hidden="true" />
<WorkspaceCreditGiftMark />
<span>积分</span>
<strong>{balance}</strong>
</button> : null}