feat(platform): 工作台侧边栏 - 新增滚动渐变遮罩与隐身滚动条,提升可滚动提示 (#80)

Co-authored-by: sikongyue <3155157671@qq.com>
This commit is contained in:
爱笑的毛毛虫
2026-08-02 16:17:54 +08:00
committed by GitHub
parent d222664564
commit 219809c320
2 changed files with 98 additions and 2 deletions

View File

@@ -1,5 +1,5 @@
import { Infinity as InfinityIcon } from "lucide-react";
import { Fragment, useEffect, useState, type ReactNode } from "react";
import { Fragment, useEffect, useRef, useState, type ReactNode } from "react";
import { Link, useLocation, useNavigate } from "react-router";
import { ModelSetupGuide } from "@/components/layout/model-setup-guide";
@@ -12,6 +12,11 @@ export function AppWorkspaceShell({ children }: { children: ReactNode }) {
const { pathname } = useLocation();
const navigate = useNavigate();
const [mobileSidebarExpanded, setMobileSidebarExpanded] = useState(false);
const scrollRef = useRef<HTMLElement>(null);
const [scrollState, setScrollState] = useState({
hasTopFade: false,
hasBottomFade: false,
});
const hideChrome = pathname.startsWith("/admin") || /^\/canvas\/[^/]+/.test(pathname);
const spatialWorkbench = isSpatialWorkbenchPath(pathname);
const creationWorkspace = pathname === "/create";
@@ -19,6 +24,16 @@ export function AppWorkspaceShell({ children }: { children: ReactNode }) {
const activeToolSlug = navigationTools.some((tool) => tool.slug === slug) ? (slug as NavigationToolSlug) : undefined;
const visibleNavigationTools = spatialWorkbench ? navigationTools : navigationTools.filter((tool) => tool.section === "创作空间");
const handleScroll = () => {
const element = scrollRef.current;
if (!element) return;
const { scrollTop, scrollHeight, clientHeight } = element;
setScrollState({
hasTopFade: scrollTop > 0,
hasBottomFade: scrollTop + clientHeight < scrollHeight - 1,
});
};
useEffect(() => {
const handleWorkspaceNavigation = (rawEvent: Event) => {
const event = rawEvent as CustomEvent<{ to?: string }>;
@@ -30,6 +45,11 @@ export function AppWorkspaceShell({ children }: { children: ReactNode }) {
return () => window.removeEventListener("workspace:navigate", handleWorkspaceNavigation);
}, [navigate]);
useEffect(() => {
// 初始化滚动状态
handleScroll();
}, [visibleNavigationTools.length, mobileSidebarExpanded]);
return (
<>
<div className={cn("app-workspace-shell flex h-dvh min-h-0 w-full overflow-hidden", spatialWorkbench && "is-spatial", creationWorkspace && "is-creation-workspace")}>
@@ -49,7 +69,15 @@ export function AppWorkspaceShell({ children }: { children: ReactNode }) {
</Link>
</div>
<nav className="flex min-h-0 flex-1 flex-col overflow-y-auto px-2 py-3">
<nav
ref={scrollRef}
onScroll={handleScroll}
className={cn(
"app-workspace-sidebar-scroll-area flex min-h-0 flex-1 flex-col px-2 py-3",
scrollState.hasTopFade && "has-top-fade",
scrollState.hasBottomFade && "has-bottom-fade",
)}
>
{visibleNavigationTools.map((tool, index) => {
const Icon = tool.icon;
const active = tool.slug === activeToolSlug;

View File

@@ -688,6 +688,74 @@
color: var(--foreground);
}
/* Sidebar custom scrollbar - 隐身融入设计 */
.app-workspace-sidebar-scroll-area {
position: relative; /* 为渐变遮罩定位 */
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: transparent transparent; /* 默认完全隐藏 */
transition: scrollbar-color var(--motion-state) ease-in-out;
}
.app-workspace-sidebar-scroll-area::-webkit-scrollbar {
width: 4px;
background: transparent; /* 默认轨道透明 */
}
.app-workspace-sidebar-scroll-area::-webkit-scrollbar-track {
background: transparent;
}
.app-workspace-sidebar-scroll-area::-webkit-scrollbar-thumb {
background: transparent; /* 默认滑块透明 */
border-radius: 9999px;
transition: background-color var(--motion-state) ease-in-out;
}
/* Hover state - 显示滚动条 */
.app-workspace-sidebar:hover .app-workspace-sidebar-scroll-area,
.app-workspace-sidebar-scroll-area:hover {
scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
}
.app-workspace-sidebar:hover .app-workspace-sidebar-scroll-area::-webkit-scrollbar-thumb,
.app-workspace-sidebar-scroll-area:hover::-webkit-scrollbar-thumb {
background-color: rgba(255, 255, 255, 0.2);
}
/* Dark mode scrollbar */
.dark .app-workspace-sidebar:hover .app-workspace-sidebar-scroll-area,
.dark .app-workspace-sidebar-scroll-area:hover {
scrollbar-color: rgba(255, 255, 255, 0.15) transparent;
}
.dark .app-workspace-sidebar:hover .app-workspace-sidebar-scroll-area::-webkit-scrollbar-thumb,
.dark .app-workspace-sidebar-scroll-area:hover::-webkit-scrollbar-thumb {
background-color: rgba(255, 255, 255, 0.15);
}
/* Scroll fade gradients - 光影提示设计 */
.app-workspace-sidebar-scroll-area.has-top-fade::before,
.app-workspace-sidebar-scroll-area.has-bottom-fade::after {
content: "";
position: absolute;
left: 0;
right: 0;
height: 12px;
pointer-events: none; /* 不阻塞点击事件 */
z-index: 10;
}
.app-workspace-sidebar-scroll-area.has-top-fade::before {
top: 0;
background: linear-gradient(to bottom, var(--workspace-surface-strong) 0%, transparent 100%);
}
.app-workspace-sidebar-scroll-area.has-bottom-fade::after {
bottom: 0;
background: linear-gradient(to top, var(--workspace-surface-strong) 0%, transparent 100%);
}
.workspace-signal-icon {
filter: drop-shadow(0 7px 16px color-mix(in srgb, var(--workspace-accent) 14%, transparent));
}