mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-27 13:29:15 +07:00
♻️ refactor(nav): remove devOnly mode from nav layout and stabilize Footer (#13101)
* ♻️ refactor(nav): remove devOnly mode from nav layout and stabilize Footer during panel transitions - Remove devOnly filtering from useNavLayout, treat all items as non-dev mode - Move Pages to top nav position, remove video/image/settings/memory nav items - Extract Footer from SideBarLayout into NavPanelDraggable outside animation layer - Show settings ActionIcon in Footer when dev mode is enabled (hidden on settings page) * 🔧 fix(footer): update settings icon in Footer component - Replace Settings2 icon with Settings icon in the Footer when dev mode is enabled, ensuring consistency in the user interface. Signed-off-by: Innei <tukon479@gmail.com> --------- Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
@@ -3,15 +3,13 @@ import { type ReactNode } from 'react';
|
||||
import { memo, Suspense } from 'react';
|
||||
|
||||
import SkeletonList, { SkeletonItem } from '@/features/NavPanel/components/SkeletonList';
|
||||
import Footer from '@/routes/(main)/home/_layout/Footer';
|
||||
|
||||
interface SidebarLayoutProps {
|
||||
body?: ReactNode;
|
||||
footer?: ReactNode;
|
||||
header?: ReactNode;
|
||||
}
|
||||
|
||||
const SideBarLayout = memo<SidebarLayoutProps>(({ header, body, footer }) => {
|
||||
const SideBarLayout = memo<SidebarLayoutProps>(({ header, body }) => {
|
||||
return (
|
||||
<Flexbox gap={4} style={{ height: '100%', overflow: 'hidden' }}>
|
||||
<Suspense fallback={<SkeletonItem height={44} style={{ marginTop: 8 }} />}>{header}</Suspense>
|
||||
@@ -20,7 +18,6 @@ const SideBarLayout = memo<SidebarLayoutProps>(({ header, body, footer }) => {
|
||||
<Suspense fallback={<SkeletonList paddingBlock={8} />}>{body}</Suspense>
|
||||
</TooltipGroup>
|
||||
</ScrollShadow>
|
||||
<Suspense>{footer || <Footer />}</Suspense>
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -4,10 +4,11 @@ import { DraggablePanel, Freeze } from '@lobehub/ui';
|
||||
import { createStaticStyles, cssVar } from 'antd-style';
|
||||
import { AnimatePresence, motion, useIsPresent } from 'motion/react';
|
||||
import { type ReactNode } from 'react';
|
||||
import { memo, useLayoutEffect, useMemo, useRef } from 'react';
|
||||
import { memo, Suspense, useLayoutEffect, 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';
|
||||
@@ -53,6 +54,7 @@ const draggableStyles = createStaticStyles(({ css, cssVar }) => ({
|
||||
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
@@ -66,8 +68,7 @@ const draggableStyles = createStaticStyles(({ css, cssVar }) => ({
|
||||
|
||||
min-width: 240px;
|
||||
max-width: 100%;
|
||||
min-height: 100%;
|
||||
max-height: 100%;
|
||||
min-height: 0;
|
||||
`,
|
||||
layer: css`
|
||||
will-change: opacity, transform;
|
||||
@@ -245,6 +246,9 @@ export const NavPanelDraggable = memo<NavPanelDraggableProps>(({ activeContent }
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Suspense>
|
||||
<Footer />
|
||||
</Suspense>
|
||||
</DraggablePanel>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -5,19 +5,9 @@ import { useTranslation } from 'react-i18next';
|
||||
import { getRouteById } from '@/config/routes';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { SidebarTabKey } from '@/store/global/initialState';
|
||||
import {
|
||||
featureFlagsSelectors,
|
||||
serverConfigSelectors,
|
||||
useServerConfigStore,
|
||||
} from '@/store/serverConfig';
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { userGeneralSettingsSelectors } from '@/store/user/slices/settings/selectors';
|
||||
import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
|
||||
|
||||
export interface NavItem {
|
||||
/**
|
||||
* true = dev mode only, false = simplified mode only, undefined = always
|
||||
*/
|
||||
devOnly?: boolean;
|
||||
hidden?: boolean;
|
||||
icon: any;
|
||||
isNew?: boolean;
|
||||
@@ -42,121 +32,72 @@ export interface NavLayout {
|
||||
};
|
||||
}
|
||||
|
||||
const filterByMode = (items: NavItem[], isDevMode: boolean): NavItem[] =>
|
||||
items.filter((item) => item.devOnly === undefined || item.devOnly === isDevMode);
|
||||
|
||||
export const useNavLayout = (): NavLayout => {
|
||||
const { t } = useTranslation('common');
|
||||
const toggleCommandMenu = useGlobalStore((s) => s.toggleCommandMenu);
|
||||
const { showMarket, showAiImage, hideGitHub } = useServerConfigStore(featureFlagsSelectors);
|
||||
const enableBusinessFeatures = useServerConfigStore(serverConfigSelectors.enableBusinessFeatures);
|
||||
const isDevMode = useUserStore((s) => userGeneralSettingsSelectors.config(s).isDevMode);
|
||||
const { showMarket, hideGitHub } = useServerConfigStore(featureFlagsSelectors);
|
||||
|
||||
const topNavItems = useMemo(
|
||||
() =>
|
||||
filterByMode(
|
||||
[
|
||||
{
|
||||
icon: SearchIcon,
|
||||
key: 'search',
|
||||
onClick: () => toggleCommandMenu(true),
|
||||
title: t('tab.search'),
|
||||
},
|
||||
{
|
||||
icon: HomeIcon,
|
||||
key: SidebarTabKey.Home,
|
||||
title: t('tab.home'),
|
||||
url: '/',
|
||||
},
|
||||
{
|
||||
devOnly: true,
|
||||
icon: getRouteById('page')!.icon,
|
||||
key: SidebarTabKey.Pages,
|
||||
title: t('tab.pages'),
|
||||
url: '/page',
|
||||
},
|
||||
{
|
||||
devOnly: true,
|
||||
hidden: !enableBusinessFeatures,
|
||||
icon: getRouteById('video')!.icon,
|
||||
key: SidebarTabKey.Video,
|
||||
title: t('tab.video'),
|
||||
url: '/video',
|
||||
},
|
||||
{
|
||||
devOnly: true,
|
||||
hidden: !showAiImage,
|
||||
icon: getRouteById('image')!.icon,
|
||||
key: SidebarTabKey.Image,
|
||||
title: t('tab.aiImage'),
|
||||
url: '/image',
|
||||
},
|
||||
{
|
||||
hidden: !showMarket,
|
||||
icon: getRouteById('community')!.icon,
|
||||
key: SidebarTabKey.Community,
|
||||
title: t('tab.marketplace'),
|
||||
url: '/community',
|
||||
},
|
||||
],
|
||||
isDevMode,
|
||||
),
|
||||
[t, toggleCommandMenu, showMarket, isDevMode, enableBusinessFeatures, showAiImage],
|
||||
[
|
||||
{
|
||||
icon: SearchIcon,
|
||||
key: 'search',
|
||||
onClick: () => toggleCommandMenu(true),
|
||||
title: t('tab.search'),
|
||||
},
|
||||
{
|
||||
icon: HomeIcon,
|
||||
key: SidebarTabKey.Home,
|
||||
title: t('tab.home'),
|
||||
url: '/',
|
||||
},
|
||||
{
|
||||
icon: getRouteById('page')!.icon,
|
||||
key: SidebarTabKey.Pages,
|
||||
title: t('tab.pages'),
|
||||
url: '/page',
|
||||
},
|
||||
{
|
||||
hidden: !showMarket,
|
||||
icon: getRouteById('community')!.icon,
|
||||
key: SidebarTabKey.Community,
|
||||
title: t('tab.marketplace'),
|
||||
url: '/community',
|
||||
},
|
||||
] as NavItem[],
|
||||
[t, toggleCommandMenu, showMarket],
|
||||
);
|
||||
|
||||
const bottomMenuItems = useMemo(
|
||||
() =>
|
||||
filterByMode(
|
||||
[
|
||||
{
|
||||
devOnly: true,
|
||||
icon: getRouteById('settings')!.icon,
|
||||
key: SidebarTabKey.Setting,
|
||||
title: t('tab.setting'),
|
||||
url: '/settings',
|
||||
},
|
||||
{
|
||||
icon: getRouteById('resource')!.icon,
|
||||
key: SidebarTabKey.Resource,
|
||||
title: t('tab.resource'),
|
||||
url: '/resource',
|
||||
},
|
||||
{
|
||||
devOnly: true,
|
||||
icon: getRouteById('memory')!.icon,
|
||||
key: SidebarTabKey.Memory,
|
||||
title: t('tab.memory'),
|
||||
url: '/memory',
|
||||
},
|
||||
{
|
||||
devOnly: false,
|
||||
icon: getRouteById('page')!.icon,
|
||||
key: SidebarTabKey.Pages,
|
||||
title: t('tab.pages'),
|
||||
url: '/page',
|
||||
},
|
||||
],
|
||||
isDevMode,
|
||||
),
|
||||
[t, isDevMode],
|
||||
[
|
||||
{
|
||||
icon: getRouteById('resource')!.icon,
|
||||
key: SidebarTabKey.Resource,
|
||||
title: t('tab.resource'),
|
||||
url: '/resource',
|
||||
},
|
||||
] as NavItem[],
|
||||
[t],
|
||||
);
|
||||
|
||||
const footer = useMemo(
|
||||
() => ({
|
||||
hideGitHub: !!hideGitHub,
|
||||
layout: (isDevMode ? 'expanded' : 'compact') as 'expanded' | 'compact',
|
||||
showEvalEntry: isDevMode,
|
||||
showSettingsEntry: !isDevMode,
|
||||
layout: 'compact' as const,
|
||||
showEvalEntry: false,
|
||||
showSettingsEntry: true,
|
||||
}),
|
||||
[isDevMode, hideGitHub],
|
||||
[hideGitHub],
|
||||
);
|
||||
|
||||
const userPanel = useMemo(
|
||||
() => ({
|
||||
showDataImporter: isDevMode,
|
||||
showMemory: !isDevMode,
|
||||
showDataImporter: false,
|
||||
showMemory: true,
|
||||
}),
|
||||
[isDevMode],
|
||||
[],
|
||||
);
|
||||
|
||||
return { bottomMenuItems, footer, topNavItems, userPanel };
|
||||
|
||||
@@ -13,11 +13,12 @@ import {
|
||||
FlaskConical,
|
||||
Github,
|
||||
Rocket,
|
||||
Settings,
|
||||
Settings2,
|
||||
} from 'lucide-react';
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
|
||||
import ChangelogModal from '@/components/ChangelogModal';
|
||||
import HighlightNotification from '@/components/HighlightNotification';
|
||||
@@ -27,6 +28,8 @@ import { useFeedbackModal } from '@/hooks/useFeedbackModal';
|
||||
import { useNavLayout } from '@/hooks/useNavLayout';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { systemStatusSelectors } from '@/store/global/selectors/systemStatus';
|
||||
import { useUserStore } from '@/store/user';
|
||||
import { userGeneralSettingsSelectors } from '@/store/user/slices/settings/selectors';
|
||||
|
||||
const PRODUCT_HUNT_NOTIFICATION = {
|
||||
actionHref: 'https://www.producthunt.com/products/lobehub?launch=lobehub',
|
||||
@@ -40,6 +43,9 @@ const Footer = memo(() => {
|
||||
const { t } = useTranslation('common');
|
||||
const { analytics } = useAnalytics();
|
||||
const { footer } = useNavLayout();
|
||||
const isDevMode = useUserStore((s) => userGeneralSettingsSelectors.config(s).isDevMode);
|
||||
const location = useLocation();
|
||||
const isSettingsPage = location.pathname.startsWith('/settings');
|
||||
const [shouldLoadChangelog, setShouldLoadChangelog] = useState(false);
|
||||
const [isChangelogModalOpen, setIsChangelogModalOpen] = useState(false);
|
||||
const [isProductHuntCardOpen, setIsProductHuntCardOpen] = useState(false);
|
||||
@@ -119,7 +125,7 @@ const Footer = memo(() => {
|
||||
|
||||
const helpMenuItems: MenuProps['items'] = useMemo(
|
||||
() => [
|
||||
...(footer.showSettingsEntry
|
||||
...(footer.showSettingsEntry && !isDevMode
|
||||
? [
|
||||
{
|
||||
icon: <Icon icon={Settings2} />,
|
||||
@@ -202,6 +208,7 @@ const Footer = memo(() => {
|
||||
footer.layout,
|
||||
footer.hideGitHub,
|
||||
footer.showEvalEntry,
|
||||
isDevMode,
|
||||
t,
|
||||
handleOpenFeedbackModal,
|
||||
isWithinTimeWindow,
|
||||
@@ -233,6 +240,11 @@ const Footer = memo(() => {
|
||||
<DropdownMenu items={helpMenuItems} placement="topLeft">
|
||||
<ActionIcon aria-label={t('userPanel.help')} icon={CircleHelp} size={16} />
|
||||
</DropdownMenu>
|
||||
{isDevMode && !isSettingsPage && (
|
||||
<Link to="/settings">
|
||||
<ActionIcon aria-label={t('userPanel.setting')} icon={Settings} size={16} />
|
||||
</Link>
|
||||
)}
|
||||
</Flexbox>
|
||||
)}
|
||||
<ChangelogModal
|
||||
|
||||
@@ -4,13 +4,12 @@ import SideBarLayout from '@/features/NavPanel/SideBarLayout';
|
||||
|
||||
import Body from './Body';
|
||||
import { AgentModalProvider } from './Body/Agent/ModalProvider';
|
||||
import Footer from './Footer';
|
||||
import Header from './Header';
|
||||
|
||||
const Sidebar = memo(() => {
|
||||
return (
|
||||
<AgentModalProvider>
|
||||
<SideBarLayout body={<Body />} footer={<Footer />} header={<Header />} />
|
||||
<SideBarLayout body={<Body />} header={<Header />} />
|
||||
</AgentModalProvider>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user