Side Panel Sub Page Framework® (#18579)

Replace hard-coded implementations for sub pages in the side panel with
a proper framework
This commit is contained in:
Baptiste Devessier
2026-03-12 16:17:29 +01:00
committed by GitHub
parent 5bfa4c5c39
commit 2a6fcfcfb3
45 changed files with 824 additions and 551 deletions
@@ -5,6 +5,9 @@ import {
OverflowingTextWithTooltip,
} from 'twenty-ui/display';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
import { SidePanelAskAIInfo } from '@/side-panel/components/SidePanelAskAIInfo';
import { SidePanelFolderInfo } from '@/side-panel/components/SidePanelFolderInfo';
import { SidePanelLinkInfo } from '@/side-panel/components/SidePanelLinkInfo';
@@ -14,9 +17,6 @@ import { SidePanelPageInfoLayout } from '@/side-panel/components/SidePanelPageIn
import { SidePanelPageLayoutInfo } from '@/side-panel/components/SidePanelPageLayoutInfo';
import { SidePanelRecordInfo } from '@/side-panel/components/SidePanelRecordInfo';
import { SidePanelWorkflowStepInfo } from '@/side-panel/components/SidePanelWorkflowStepInfo';
import { NavigationMenuItemType } from '@/navigation-menu-item/constants/NavigationMenuItemType';
import { useWorkspaceSectionItems } from '@/navigation-menu-item/hooks/useWorkspaceSectionItems';
import { selectedNavigationMenuItemInEditModeState } from '@/navigation-menu-item/states/selectedNavigationMenuItemInEditModeState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { SidePanelPages } from 'twenty-shared/types';
@@ -98,11 +98,9 @@ export const SidePanelPageInfo = ({ pageChip }: SidePanelPageInfoProps) => {
? [
SidePanelPages.PageLayoutWidgetTypeSelect,
SidePanelPages.PageLayoutGraphTypeSelect,
SidePanelPages.PageLayoutGraphFilter,
SidePanelPages.PageLayoutIframeSettings,
SidePanelPages.PageLayoutTabSettings,
SidePanelPages.PageLayoutFieldsSettings,
SidePanelPages.PageLayoutFieldsLayout,
].includes(pageChip.page?.page)
: false;
@@ -1,5 +1,6 @@
import { CommandMenuContextProvider } from '@/command-menu-item/contexts/CommandMenuContextProvider';
import { SidePanelContainer } from '@/side-panel/components/SidePanelContainer';
import { SidePanelSubPageRouter } from '@/side-panel/components/SidePanelSubPageRouter';
import { SidePanelTopBar } from '@/side-panel/components/SidePanelTopBar';
import { SIDE_PANEL_PAGES_CONFIG } from '@/side-panel/constants/SidePanelPagesConfig';
import { SidePanelPageComponentInstanceContext } from '@/side-panel/states/contexts/SidePanelPageComponentInstanceContext';
@@ -56,7 +57,9 @@ export const SidePanelRouter = () => {
displayType="listItem"
containerType="command-menu-list"
>
{sidePanelPageComponent}
<SidePanelSubPageRouter>
{sidePanelPageComponent}
</SidePanelSubPageRouter>
</CommandMenuContextProvider>
</StyledSidePanelContent>
</SidePanelPageComponentInstanceContext.Provider>
@@ -0,0 +1,54 @@
import { SIDE_PANEL_SUB_PAGES_CONFIG } from '@/side-panel/constants/SidePanelSubPagesConfig';
import { useSidePanelSubPageHistory } from '@/side-panel/hooks/useSidePanelSubPageHistory';
import { SidePanelSubPageNavigationHeader } from '@/side-panel/pages/common/components/SidePanelSubPageNavigationHeader';
import { styled } from '@linaria/react';
import React, { type ReactNode } from 'react';
import { isDefined } from 'twenty-shared/utils';
const StyledSubPageContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
`;
type SidePanelSubPageRouterProps = {
children: ReactNode;
};
export const SidePanelSubPageRouter = ({
children,
}: SidePanelSubPageRouterProps) => {
const { currentSidePanelSubPage, goBackFromSidePanelSubPage } =
useSidePanelSubPageHistory();
if (!isDefined(currentSidePanelSubPage)) {
return <>{children}</>;
}
const subPageComponent = SIDE_PANEL_SUB_PAGES_CONFIG.get(
currentSidePanelSubPage.subPage,
);
if (!isDefined(subPageComponent)) {
throw new Error(
`Missing side panel sub-page config for "${currentSidePanelSubPage.subPage}". ` +
'Please add it to SIDE_PANEL_SUB_PAGES_CONFIG.',
);
}
const keyedSubPageComponent = React.isValidElement(subPageComponent)
? React.cloneElement(subPageComponent, {
key: currentSidePanelSubPage.id,
})
: subPageComponent;
return (
<StyledSubPageContainer>
<SidePanelSubPageNavigationHeader
title={currentSidePanelSubPage.title}
onBackClick={goBackFromSidePanelSubPage}
/>
{keyedSubPageComponent}
</StyledSubPageContainer>
);
};
@@ -1,7 +1,6 @@
import { styled } from '@linaria/react';
import { type ReactNode } from 'react';
import { SidePanelSubPageNavigationHeader } from '@/side-panel/pages/common/components/SidePanelSubPageNavigationHeader';
import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledSubViewContainer = styled.div`
@@ -50,8 +49,6 @@ const StyledScrollableListWrapper = styled.div`
`;
type SidePanelSubViewWithSearchProps = {
backBarTitle: string;
onBack: () => void;
searchPlaceholder: string;
searchValue: string;
onSearchChange: (value: string) => void;
@@ -60,8 +57,6 @@ type SidePanelSubViewWithSearchProps = {
};
export const SidePanelSubViewWithSearch = ({
backBarTitle,
onBack,
searchPlaceholder,
searchValue,
onSearchChange,
@@ -69,10 +64,6 @@ export const SidePanelSubViewWithSearch = ({
children,
}: SidePanelSubViewWithSearchProps) => (
<StyledSubViewContainer>
<SidePanelSubPageNavigationHeader
title={backBarTitle}
onBackClick={onBack}
/>
<StyledSearchContainer>
<StyledSearchInput
placeholder={searchPlaceholder}
@@ -1,9 +1,10 @@
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
import { GRAPH_TYPE_INFORMATION } from '@/side-panel/pages/page-layout/constants/GraphTypeInformation';
import { getCurrentGraphTypeFromConfig } from '@/side-panel/pages/page-layout/utils/getCurrentGraphTypeFromConfig';
import { isWidgetConfigurationOfTypeGraph } from '@/side-panel/pages/page-layout/utils/isWidgetConfigurationOfTypeGraph';
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget';
import { t } from '@lingui/core/macro';
import { useContext } from 'react';
import { SidePanelPages } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
@@ -14,7 +15,6 @@ import {
type IconComponent,
} from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme-constants';
import { useContext } from 'react';
type PageLayoutHeaderInfo = {
headerIcon: IconComponent | undefined;
@@ -105,8 +105,7 @@ export const usePageLayoutHeaderInfo = ({
};
}
case SidePanelPages.PageLayoutGraphTypeSelect:
case SidePanelPages.PageLayoutGraphFilter: {
case SidePanelPages.PageLayoutGraphTypeSelect: {
if (!isDefined(pageLayoutEditingWidgetId)) {
return null;
}
@@ -127,12 +126,6 @@ export const usePageLayoutHeaderInfo = ({
widgetInEditMode.configuration,
);
const graphTypeInfo = GRAPH_TYPE_INFORMATION[currentGraphType];
const graphTypeLabel = t(graphTypeInfo.label);
const headerType =
sidePanelPage === SidePanelPages.PageLayoutGraphFilter
? graphTypeLabel
: t`Chart`;
const title = isDefined(editedTitle)
? editedTitle
@@ -143,16 +136,15 @@ export const usePageLayoutHeaderInfo = ({
return {
headerIcon: graphTypeInfo.icon,
headerIconColor: iconColor,
headerType,
headerType: t`Chart`,
title,
isReadonly: sidePanelPage === SidePanelPages.PageLayoutGraphFilter,
isReadonly: false,
tab: undefined,
widgetInEditMode,
};
}
case SidePanelPages.PageLayoutFieldsSettings:
case SidePanelPages.PageLayoutFieldsLayout: {
case SidePanelPages.PageLayoutFieldsSettings: {
if (!isDefined(pageLayoutEditingWidgetId)) {
return null;
}