feat: 增加沉浸模式切换过渡动画

This commit is contained in:
JSR
2026-03-27 21:47:47 +08:00
parent e6d1e03238
commit 56d56e34ab
3 changed files with 114 additions and 35 deletions
+47 -10
View File
@@ -7,6 +7,8 @@ import { ContentArea } from "./components/ContentArea"
import { OOBE } from "./components/OOBE/OOBE" import { OOBE } from "./components/OOBE/OOBE"
import { ThemeProvider, useTheme } from "./contexts/ThemeContext" import { ThemeProvider, useTheme } from "./contexts/ThemeContext"
const IMMERSIVE_TRANSITION_MS = 260
function MainContent(): React.JSX.Element { function MainContent(): React.JSX.Element {
const { t } = useTranslation() const { t } = useTranslation()
const navigate = useNavigate() const navigate = useNavigate()
@@ -15,6 +17,8 @@ function MainContent(): React.JSX.Element {
const [messageApi, contextHolder] = message.useMessage() const [messageApi, contextHolder] = message.useMessage()
const { isIosDevice, isAndroidDevice, defaultPortraitMode } = useMemo(getMobileDeviceInfo, []) const { isIosDevice, isAndroidDevice, defaultPortraitMode } = useMemo(getMobileDeviceInfo, [])
const [immersiveMode, setImmersiveMode] = useState(false) const [immersiveMode, setImmersiveMode] = useState(false)
const [sidebarVisible, setSidebarVisible] = useState(true)
const sidebarTransitionTimerRef = useRef<number | null>(null)
useEffect(() => { useEffect(() => {
const api = (window as any).api const api = (window as any).api
@@ -350,6 +354,30 @@ function MainContent(): React.JSX.Element {
}) })
} }
useEffect(() => {
if (sidebarTransitionTimerRef.current) {
window.clearTimeout(sidebarTransitionTimerRef.current)
sidebarTransitionTimerRef.current = null
}
if (!immersiveMode) {
setSidebarVisible(true)
return
}
sidebarTransitionTimerRef.current = window.setTimeout(() => {
setSidebarVisible(false)
sidebarTransitionTimerRef.current = null
}, IMMERSIVE_TRANSITION_MS)
return () => {
if (sidebarTransitionTimerRef.current) {
window.clearTimeout(sidebarTransitionTimerRef.current)
sidebarTransitionTimerRef.current = null
}
}
}, [immersiveMode])
const isDark = currentTheme?.mode === "dark" const isDark = currentTheme?.mode === "dark"
const brandColor = currentTheme?.config?.tdesign?.brandColor || "#0052D9" const brandColor = currentTheme?.config?.tdesign?.brandColor || "#0052D9"
@@ -366,16 +394,25 @@ function MainContent(): React.JSX.Element {
> >
{contextHolder} {contextHolder}
<Layout style={{ height: "100%", flexDirection: "row", overflow: "hidden" }}> <Layout style={{ height: "100%", flexDirection: "row", overflow: "hidden" }}>
{!immersiveMode && ( {sidebarVisible && (
<Sidebar <div
activeMenu={activeMenu} className={`ss-immersive-sidebar ${immersiveMode ? "is-hidden" : "is-visible"}`}
permission={permission} style={
onMenuChange={onMenuChange} {
collapsed={sidebarCollapsed} "--ss-sidebar-width": `${sidebarCollapsed ? 64 : 200}px`,
floatingExpand={isPortraitMode} } as React.CSSProperties
floatingExpanded={floatingSidebarExpanded} }
onFloatingExpandedChange={setFloatingSidebarExpanded} >
/> <Sidebar
activeMenu={activeMenu}
permission={permission}
onMenuChange={onMenuChange}
collapsed={sidebarCollapsed}
floatingExpand={isPortraitMode}
floatingExpanded={floatingSidebarExpanded}
onFloatingExpandedChange={setFloatingSidebarExpanded}
/>
</div>
)} )}
<ContentArea <ContentArea
permission={permission} permission={permission}
+42
View File
@@ -225,3 +225,45 @@ html.platform-macos #root {
.ss-group-board-modal .ant-modal-body { .ss-group-board-modal .ant-modal-body {
overflow: hidden; overflow: hidden;
} }
.ss-immersive-sidebar {
--ss-sidebar-width: 200px;
width: var(--ss-sidebar-width);
overflow: hidden;
opacity: 1;
transform: translateX(0);
transition:
width 260ms cubic-bezier(0.22, 1, 0.36, 1),
opacity 220ms ease,
transform 260ms cubic-bezier(0.22, 1, 0.36, 1);
will-change: width, opacity, transform;
}
.ss-immersive-sidebar.is-hidden {
width: 0;
opacity: 0;
transform: translateX(-10px);
pointer-events: none;
}
.ss-immersive-toolbar {
opacity: 1;
transform: translateX(-50%) translateY(0) scale(1);
transition:
opacity 200ms ease,
transform 260ms cubic-bezier(0.22, 1, 0.36, 1);
will-change: opacity, transform;
}
.ss-immersive-toolbar.is-hidden {
opacity: 0;
transform: translateX(-50%) translateY(16px) scale(0.98);
pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
.ss-immersive-sidebar,
.ss-immersive-toolbar {
transition: none !important;
}
}
+25 -25
View File
@@ -2472,29 +2472,30 @@ export const Home: React.FC<HomeProps> = ({
)} )}
</Modal> </Modal>
{immersiveMode && ( <div
<div ref={immersiveToolbarRef}
ref={immersiveToolbarRef} data-immersive-toolbar="true"
data-immersive-toolbar="true" className={`ss-immersive-toolbar ${immersiveMode ? "is-visible" : "is-hidden"}`}
style={{ aria-hidden={!immersiveMode}
position: "fixed", style={{
left: "50%", position: "fixed",
bottom: isPortraitMode ? "12px" : "16px", left: "50%",
transform: "translateX(-50%)", bottom: isPortraitMode ? "12px" : "16px",
zIndex: 1100, transform: "translateX(-50%)",
width: "max-content", zIndex: 1100,
maxWidth: "calc(100vw - 20px)", width: "max-content",
borderRadius: "999px", maxWidth: "calc(100vw - 20px)",
border: "1px solid color-mix(in srgb, var(--ss-border-color) 80%, transparent)", borderRadius: "999px",
backgroundColor: "var(--ss-card-bg)", border: "1px solid color-mix(in srgb, var(--ss-border-color) 80%, transparent)",
background: "color-mix(in srgb, var(--ss-card-bg) 62%, transparent)", backgroundColor: "var(--ss-card-bg)",
backdropFilter: "blur(16px)", background: "color-mix(in srgb, var(--ss-card-bg) 62%, transparent)",
WebkitBackdropFilter: "blur(16px)", backdropFilter: "blur(16px)",
boxShadow: "0 8px 30px rgba(0, 0, 0, 0.16)", WebkitBackdropFilter: "blur(16px)",
padding: "10px", boxShadow: "0 8px 30px rgba(0, 0, 0, 0.16)",
overflow: "visible", padding: "10px",
}} overflow: "visible",
> }}
>
<div <div
ref={searchAreaRef} ref={searchAreaRef}
style={{ style={{
@@ -2634,8 +2635,7 @@ export const Home: React.FC<HomeProps> = ({
</div> </div>
)} )}
</div> </div>
</div> </div>
)}
{isPortraitMode ? ( {isPortraitMode ? (
<Drawer <Drawer