584 Refactor Tabs (#11008)

Closes https://github.com/twentyhq/core-team-issues/issues/584

This PR:
- Migrates the component state `activeTabIdComponentState` from the
deprecated V1 version to V2.
- Allows the active tab state to be preserved during navigation inside
the side panel and reset when the side panel is closed.
- Allows the active tab state to be preserved when we open a record in
full page from the side panel


https://github.com/user-attachments/assets/f2329d7a-ea15-4bd8-81dc-e98ce11edbd0


https://github.com/user-attachments/assets/474bffd5-29e0-40ba-97f4-fa5e9be34dc2
This commit is contained in:
Raphaël Bosi
2025-03-19 16:53:22 +01:00
committed by GitHub
parent 0d40126a29
commit cfdb3f5778
37 changed files with 299 additions and 609 deletions

View File

@ -12,12 +12,13 @@ import { AppPath } from '@/types/AppPath';
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { TabList } from '@/ui/layout/tab/components/TabList';
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import styled from '@emotion/styled';
import { useRecoilState, useRecoilValue } from 'recoil';
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useLingui } from '@lingui/react/macro';
import { isDefined } from 'twenty-shared';
import {
@ -68,7 +69,8 @@ export const SettingsObjectDetailPage = () => {
findActiveObjectMetadataItemByNamePlural(objectNamePlural) ??
findActiveObjectMetadataItemByNamePlural(updatedObjectNamePlural);
const { activeTabId } = useTabList(
const activeTabId = useRecoilComponentValueV2(
activeTabIdComponentState,
SETTINGS_OBJECT_DETAIL_TABS.COMPONENT_INSTANCE_ID,
);
@ -169,11 +171,10 @@ export const SettingsObjectDetailPage = () => {
>
<SettingsPageContainer>
<TabList
tabListInstanceId={
tabs={tabs}
componentInstanceId={
SETTINGS_OBJECT_DETAIL_TABS.COMPONENT_INSTANCE_ID
}
tabs={tabs}
className="tab-list"
/>
<StyledContentContainer>
{renderActiveTabContent()}

View File

@ -10,7 +10,8 @@ import { RoleSettings } from '@/settings/roles/role-settings/components/RoleSett
import { SettingsPath } from '@/types/SettingsPath';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { TabList } from '@/ui/layout/tab/components/TabList';
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useGetRolesQuery } from '~/generated/graphql';
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
@ -33,9 +34,7 @@ export const SettingsRoleEdit = () => {
const role = rolesData?.getRoles.find((r) => r.id === roleId);
const { activeTabId } = useTabList(
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID,
);
const activeTabId = useRecoilComponentValueV2(activeTabIdComponentState);
useEffect(() => {
if (!rolesLoading && !role) {
@ -101,9 +100,11 @@ export const SettingsRoleEdit = () => {
{!rolesLoading && role ? (
<SettingsPageContainer>
<TabList
tabListInstanceId={SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID}
tabs={tabs}
className="tab-list"
componentInstanceId={
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID
}
/>
{renderActiveTabContent()}
</SettingsPageContainer>

View File

@ -14,7 +14,8 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { TabList } from '@/ui/layout/tab/components/TabList';
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useState } from 'react';
import { useParams } from 'react-router-dom';
@ -26,12 +27,15 @@ import { FeatureFlagKey } from '~/generated/graphql';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
const TAB_LIST_COMPONENT_ID = 'serverless-function-detail';
const SERVERLESS_FUNCTION_DETAIL_ID = 'serverless-function-detail';
export const SettingsServerlessFunctionDetail = () => {
const { serverlessFunctionId = '' } = useParams();
const { enqueueSnackBar } = useSnackBar();
const { activeTabId, setActiveTabId } = useTabList(TAB_LIST_COMPONENT_ID);
const [activeTabId, setActiveTabId] = useRecoilComponentStateV2(
activeTabIdComponentState,
SERVERLESS_FUNCTION_DETAIL_ID,
);
const [isCodeValid, setIsCodeValid] = useState(true);
const { updateOneServerlessFunction } =
useUpdateOneServerlessFunction(serverlessFunctionId);
@ -209,9 +213,9 @@ export const SettingsServerlessFunctionDetail = () => {
>
<SettingsPageContainer>
<TabList
tabListInstanceId={TAB_LIST_COMPONENT_ID}
tabs={tabs}
behaveAsLinks={false}
componentInstanceId={SERVERLESS_FUNCTION_DETAIL_ID}
/>
{renderActiveTabContent()}
</SettingsPageContainer>