feat(platform): 工作台侧边栏 - 新增滚动渐变遮罩与隐身滚动条,提升可滚动提示 (#80)
Co-authored-by: sikongyue <3155157671@qq.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user