mirror of
https://github.com/lobehub/lobehub
synced 2026-04-21 17:47:27 +00:00
🐛 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.
163 lines
4 KiB
TypeScript
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>
|
|
);
|
|
});
|