lobehub/src/features/NavPanel/components/NavPanelDraggable.tsx
Innei 3bd7f1f146
🐛 fix(electron): align TabBar left padding with NavPanel width on initial load (#13981)
🐛 fix(electron): align TabBar left padding with NavPanel width on initial load

Defer DraggablePanel mount in NavPanelDraggable until `isStatusInit` flips true
so defaultSize captures the hydrated `leftPanelWidth` instead of the pre-hydration
default. Before hydration, render a placeholder div matching the store's current
width so NavigationBar's live-read width stays aligned with the DOM. Also adds
a small paddingRight to NavigationBar for visual balance.

Without this, the TabBar's left edge drifted away from the NavPanel's right edge
whenever the user's persisted panel width differed from the 320px default.
2026-04-20 01:46:05 +08:00

163 lines
4 KiB
TypeScript

'use client';
import { DraggablePanel } from '@lobehub/ui';
import { createStaticStyles, cssVar } from 'antd-style';
import { type ReactNode } from 'react';
import { memo, Suspense, useMemo, useRef } from 'react';
import { isDesktop } from '@/const/version';
import { TOGGLE_BUTTON_ID } from '@/features/NavPanel/ToggleLeftPanelButton';
import Footer from '@/routes/(main)/home/_layout/Footer';
import { USER_DROPDOWN_ICON_ID } from '@/routes/(main)/home/_layout/Header/components/User';
import { useGlobalStore } from '@/store/global';
import { systemStatusSelectors } from '@/store/global/selectors';
import { isMacOS } from '@/utils/platform';
import { useNavPanelSizeChangeHandler } from '../hooks/useNavPanel';
import { BACK_BUTTON_ID } from './BackButton';
const draggableStyles = createStaticStyles(({ css, cssVar }) => ({
content: css`
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
height: 100%;
min-height: 100%;
max-height: 100%;
`,
inner: css`
position: relative;
overflow: hidden;
flex: 1;
min-width: 240px;
max-width: 100%;
min-height: 0;
`,
layer: css`
position: absolute;
inset: 0;
overflow: hidden;
display: flex;
flex-direction: column;
min-width: 240px;
max-width: 100%;
min-height: 100%;
max-height: 100%;
`,
panel: css`
user-select: none;
height: 100%;
color: ${cssVar.colorTextSecondary};
background: ${isDesktop && isMacOS() ? 'transparent' : cssVar.colorBgLayout};
* {
user-select: none;
}
#${TOGGLE_BUTTON_ID} {
width: 0 !important;
opacity: 0;
transition:
opacity,
width 0.2s ${cssVar.motionEaseOut};
}
#${USER_DROPDOWN_ICON_ID} {
width: 0 !important;
opacity: 0;
transition:
opacity,
width 0.2s ${cssVar.motionEaseOut};
}
#${BACK_BUTTON_ID} {
width: 24px !important;
}
&:hover {
#${TOGGLE_BUTTON_ID} {
width: 32px !important;
opacity: 1;
}
#${USER_DROPDOWN_ICON_ID} {
width: 14px !important;
opacity: 1;
}
}
`,
}));
interface NavPanelDraggableProps {
activeContent: {
key: string;
node: ReactNode;
};
}
const classNames = {
content: draggableStyles.content,
};
export const NavPanelDraggable = memo<NavPanelDraggableProps>(({ activeContent }) => {
const [expand, togglePanel, isStatusInit] = useGlobalStore((s) => [
systemStatusSelectors.showLeftPanel(s),
s.toggleLeftPanel,
systemStatusSelectors.isStatusInit(s),
]);
const handleSizeChange = useNavPanelSizeChangeHandler();
// Defer DraggablePanel mount until system status hydrates; otherwise defaultSize
// captures the pre-hydration default and the DOM drifts off NavigationBar's live width.
const defaultWidthRef = useRef(0);
if (defaultWidthRef.current === 0 && isStatusInit) {
defaultWidthRef.current = systemStatusSelectors.leftPanelWidth(useGlobalStore.getState());
}
const styles = useMemo(
() => ({
background: isDesktop && isMacOS() ? 'transparent' : cssVar.colorBgLayout,
zIndex: 11,
}),
[],
);
if (defaultWidthRef.current === 0) {
const pendingWidth = systemStatusSelectors.leftPanelWidth(useGlobalStore.getState());
return <div aria-hidden style={{ flexShrink: 0, height: '100%', width: pendingWidth }} />;
}
const defaultSize = { height: '100%', width: defaultWidthRef.current };
return (
<DraggablePanel
className={draggableStyles.panel}
classNames={classNames}
defaultSize={defaultSize}
expand={expand}
expandable={false}
maxWidth={400}
minWidth={240}
placement="left"
showBorder={false}
style={styles}
onExpandChange={togglePanel}
onSizeDragging={handleSizeChange}
>
<div className={draggableStyles.inner}>
<div className={draggableStyles.layer} key={activeContent.key}>
{activeContent.node}
</div>
</div>
<Suspense>
<Footer />
</Suspense>
</DraggablePanel>
);
});