Reactivate disabled full tab widgets (#19702)
https://github.com/user-attachments/assets/14a48c7d-e731-4a15-883f-c53beb4943de
This commit is contained in:
committed by
GitHub
parent
3e48be4c31
commit
c805a351ab
+4
-2
@@ -40,8 +40,10 @@ const PageLayoutSingleTabRendererInner = () => {
|
||||
const targetRecordIdentifier = useTargetRecord();
|
||||
const { isInSidePanel } = useLayoutRenderingContext();
|
||||
|
||||
const sortedTabs = sortTabsByPosition(currentPageLayout.tabs);
|
||||
const firstTab = sortedTabs[0];
|
||||
const sortedActiveTabs = sortTabsByPosition(
|
||||
currentPageLayout.tabs.filter((tab) => tab.isActive),
|
||||
);
|
||||
const firstTab = sortedActiveTabs[0];
|
||||
|
||||
const firstTabWithVisibleWidgets = usePageLayoutTabWithVisibleWidgetsOrThrow(
|
||||
firstTab.id,
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useLingui } from '@lingui/react/macro';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { IconPlus, useIcons } from 'twenty-ui/display';
|
||||
import { IconButton } from 'twenty-ui/input';
|
||||
import { TabButton } from 'twenty-ui/input';
|
||||
|
||||
import { isPageLayoutTabDraggingComponentState } from '@/page-layout/states/isPageLayoutTabDraggingComponentState';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
@@ -29,6 +29,7 @@ import { useAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useAtomC
|
||||
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
|
||||
|
||||
import { PAGE_LAYOUT_TAB_LIST_DROPPABLE_IDS } from '@/page-layout/components/PageLayoutTabListDroppableIds';
|
||||
import { PageLayoutTabListNewTabDropdownContent } from '@/page-layout/components/PageLayoutTabListNewTabDropdownContent';
|
||||
import { PageLayoutTabListReorderableOverflowDropdown } from '@/page-layout/components/PageLayoutTabListReorderableOverflowDropdown';
|
||||
import { PageLayoutTabListVisibleTabs } from '@/page-layout/components/PageLayoutTabListVisibleTabs';
|
||||
import { STANDARD_PAGE_LAYOUT_TAB_TITLE_TRANSLATIONS } from '@/page-layout/constants/StandardPageLayoutTabTitleTranslations';
|
||||
@@ -37,9 +38,11 @@ import { useIsPageLayoutInEditMode } from '@/page-layout/hooks/useIsPageLayoutIn
|
||||
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
|
||||
import { pageLayoutTabListCurrentDragDroppableIdComponentState } from '@/page-layout/states/pageLayoutTabListCurrentDragDroppableIdComponentState';
|
||||
import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState';
|
||||
import { type PageLayoutAddTabStrategy } from '@/page-layout/types/PageLayoutAddTabStrategy';
|
||||
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
|
||||
import { shouldEnableTabEditingFeatures } from '@/page-layout/utils/shouldEnableTabEditingFeatures';
|
||||
import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { TabListDropdown } from '@/ui/layout/tab-list/components/TabListDropdown';
|
||||
import { TabListFromUrlOptionalEffect } from '@/ui/layout/tab-list/components/TabListFromUrlOptionalEffect';
|
||||
import { type SingleTabProps } from '@/ui/layout/tab-list/types/SingleTabProps';
|
||||
@@ -89,7 +92,7 @@ const StyledAddButton = styled.div`
|
||||
type PageLayoutTabListProps = Omit<TabListProps, 'tabs'> & {
|
||||
tabs: PageLayoutTab[];
|
||||
isReorderEnabled: boolean;
|
||||
onAddTab?: () => void;
|
||||
addTabStrategy?: PageLayoutAddTabStrategy;
|
||||
onReorder?: (result: DropResult, provided: ResponderProvided) => boolean;
|
||||
behaveAsLinks: boolean;
|
||||
pageLayoutType: PageLayoutType;
|
||||
@@ -103,7 +106,7 @@ export const PageLayoutTabList = ({
|
||||
className,
|
||||
componentInstanceId,
|
||||
onChangeTab,
|
||||
onAddTab,
|
||||
addTabStrategy,
|
||||
isReorderEnabled,
|
||||
onReorder,
|
||||
pageLayoutType,
|
||||
@@ -147,7 +150,7 @@ export const PageLayoutTabList = ({
|
||||
onAddButtonWidthChange,
|
||||
} = useTabListMeasurements({
|
||||
visibleTabs: tabsWithIcons,
|
||||
hasAddButton: isDefined(onAddTab),
|
||||
hasAddButton: isDefined(addTabStrategy),
|
||||
});
|
||||
|
||||
const pageLayoutId = useAvailableComponentInstanceIdOrThrow(
|
||||
@@ -155,6 +158,7 @@ export const PageLayoutTabList = ({
|
||||
);
|
||||
|
||||
const dropdownId = `tab-overflow-${componentInstanceId}`;
|
||||
const addTabDropdownId = `tab-add-${componentInstanceId}`;
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
const { openDropdown } = useOpenDropdown();
|
||||
const { toggleClickOutside } = useClickOutsideListener(dropdownId);
|
||||
@@ -358,11 +362,18 @@ export const PageLayoutTabList = ({
|
||||
loading={loading}
|
||||
onTabWidthChange={onTabWidthChange}
|
||||
onMoreButtonWidthChange={onMoreButtonWidthChange}
|
||||
onAddButtonWidthChange={onAddTab ? onAddButtonWidthChange : undefined}
|
||||
onAddButtonWidthChange={
|
||||
addTabStrategy ? onAddButtonWidthChange : undefined
|
||||
}
|
||||
addButtonMeasurement={
|
||||
onAddTab ? (
|
||||
addTabStrategy ? (
|
||||
<StyledAddButton>
|
||||
<IconButton Icon={IconPlus} size="small" variant="tertiary" />
|
||||
<TabButton
|
||||
id="add-tab"
|
||||
LeftIcon={IconPlus}
|
||||
title={t`New Tab`}
|
||||
disableTestId
|
||||
/>
|
||||
</StyledAddButton>
|
||||
) : undefined
|
||||
}
|
||||
@@ -405,13 +416,36 @@ export const PageLayoutTabList = ({
|
||||
</StyledDropdownContainer>
|
||||
)}
|
||||
|
||||
{onAddTab && (
|
||||
{addTabStrategy?.mode === 'direct' && (
|
||||
<StyledAddButton>
|
||||
<IconButton
|
||||
Icon={IconPlus}
|
||||
size="small"
|
||||
variant="tertiary"
|
||||
onClick={() => onAddTab()}
|
||||
<TabButton
|
||||
id="add-tab"
|
||||
LeftIcon={IconPlus}
|
||||
title={t`New Tab`}
|
||||
onClick={() => addTabStrategy.onCreate()}
|
||||
disableTestId
|
||||
/>
|
||||
</StyledAddButton>
|
||||
)}
|
||||
{addTabStrategy?.mode === 'dropdown' && (
|
||||
<StyledAddButton>
|
||||
<Dropdown
|
||||
dropdownId={addTabDropdownId}
|
||||
clickableComponent={
|
||||
<TabButton
|
||||
id="add-tab"
|
||||
LeftIcon={IconPlus}
|
||||
title={t`New Tab`}
|
||||
disableTestId
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<PageLayoutTabListNewTabDropdownContent
|
||||
onCreate={addTabStrategy.onCreate}
|
||||
dropdownId={addTabDropdownId}
|
||||
/>
|
||||
}
|
||||
dropdownPlacement="bottom-start"
|
||||
/>
|
||||
</StyledAddButton>
|
||||
)}
|
||||
@@ -445,13 +479,36 @@ export const PageLayoutTabList = ({
|
||||
/>
|
||||
</StyledDropdownContainer>
|
||||
)}
|
||||
{onAddTab && (
|
||||
{addTabStrategy?.mode === 'direct' && (
|
||||
<StyledAddButton>
|
||||
<IconButton
|
||||
Icon={IconPlus}
|
||||
size="small"
|
||||
variant="tertiary"
|
||||
onClick={() => onAddTab()}
|
||||
<TabButton
|
||||
id="add-tab"
|
||||
LeftIcon={IconPlus}
|
||||
title={t`New Tab`}
|
||||
onClick={() => addTabStrategy.onCreate()}
|
||||
disableTestId
|
||||
/>
|
||||
</StyledAddButton>
|
||||
)}
|
||||
{addTabStrategy?.mode === 'dropdown' && (
|
||||
<StyledAddButton>
|
||||
<Dropdown
|
||||
dropdownId={addTabDropdownId}
|
||||
clickableComponent={
|
||||
<TabButton
|
||||
id="add-tab"
|
||||
LeftIcon={IconPlus}
|
||||
title={t`New Tab`}
|
||||
disableTestId
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<PageLayoutTabListNewTabDropdownContent
|
||||
onCreate={addTabStrategy.onCreate}
|
||||
dropdownId={addTabDropdownId}
|
||||
/>
|
||||
}
|
||||
dropdownPlacement="bottom-start"
|
||||
/>
|
||||
</StyledAddButton>
|
||||
)}
|
||||
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
import { STANDARD_PAGE_LAYOUT_TAB_TITLE_TRANSLATIONS } from '@/page-layout/constants/StandardPageLayoutTabTitleTranslations';
|
||||
import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow';
|
||||
import { useIsCurrentObjectCustom } from '@/page-layout/hooks/useIsCurrentObjectCustom';
|
||||
import { useRecordPageLayoutObjectApplicationId } from '@/page-layout/hooks/useRecordPageLayoutObjectApplicationId';
|
||||
import { useUpdatePageLayoutTab } from '@/page-layout/hooks/useUpdatePageLayoutTab';
|
||||
import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState';
|
||||
import { isReactivatableTab } from '@/page-layout/utils/isReactivatableTab';
|
||||
import { sortTabsByPosition } from '@/page-layout/utils/sortTabsByPosition';
|
||||
import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuHeader } from '@/ui/layout/dropdown/components/DropdownMenuHeader/DropdownMenuHeader';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { DropdownMenuSectionLabel } from '@/ui/layout/dropdown/components/DropdownMenuSectionLabel';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { SidePanelPages } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconPlus, useIcons } from 'twenty-ui/display';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
|
||||
type PageLayoutTabListNewTabDropdownContentProps = {
|
||||
onCreate: () => void;
|
||||
dropdownId: string;
|
||||
};
|
||||
|
||||
export const PageLayoutTabListNewTabDropdownContent = ({
|
||||
onCreate,
|
||||
dropdownId,
|
||||
}: PageLayoutTabListNewTabDropdownContentProps) => {
|
||||
const { t } = useLingui();
|
||||
const { getIcon } = useIcons();
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
const { isCustom } = useIsCurrentObjectCustom();
|
||||
|
||||
const shouldTranslateTabTitles = !isCustom;
|
||||
|
||||
const { currentPageLayout } = useCurrentPageLayoutOrThrow();
|
||||
const { objectApplicationId } = useRecordPageLayoutObjectApplicationId();
|
||||
const { updatePageLayoutTab } = useUpdatePageLayoutTab();
|
||||
|
||||
const setActiveTabId = useSetAtomComponentState(activeTabIdComponentState);
|
||||
const setPageLayoutTabSettingsOpenTabId = useSetAtomComponentState(
|
||||
pageLayoutTabSettingsOpenTabIdComponentState,
|
||||
);
|
||||
const { navigatePageLayoutSidePanel } = useNavigatePageLayoutSidePanel();
|
||||
|
||||
const inactiveTabs = useMemo(
|
||||
() =>
|
||||
sortTabsByPosition(
|
||||
currentPageLayout.tabs.filter((tab) =>
|
||||
isReactivatableTab({ tab, objectApplicationId }),
|
||||
),
|
||||
),
|
||||
[currentPageLayout.tabs, objectApplicationId],
|
||||
);
|
||||
|
||||
const handleCreateEmptyTab = useCallback(() => {
|
||||
onCreate();
|
||||
closeDropdown(dropdownId);
|
||||
}, [onCreate, closeDropdown, dropdownId]);
|
||||
|
||||
const handleReactivateTab = useCallback(
|
||||
(tabId: string) => {
|
||||
updatePageLayoutTab(tabId, { isActive: true });
|
||||
setActiveTabId(tabId);
|
||||
setPageLayoutTabSettingsOpenTabId(tabId);
|
||||
navigatePageLayoutSidePanel({
|
||||
sidePanelPage: SidePanelPages.PageLayoutTabSettings,
|
||||
resetNavigationStack: true,
|
||||
});
|
||||
closeDropdown(dropdownId);
|
||||
},
|
||||
[
|
||||
updatePageLayoutTab,
|
||||
setActiveTabId,
|
||||
setPageLayoutTabSettingsOpenTabId,
|
||||
navigatePageLayoutSidePanel,
|
||||
closeDropdown,
|
||||
dropdownId,
|
||||
],
|
||||
);
|
||||
|
||||
const getTabTitle = (title: string) => {
|
||||
if (
|
||||
shouldTranslateTabTitles &&
|
||||
isDefined(STANDARD_PAGE_LAYOUT_TAB_TITLE_TRANSLATIONS[title])
|
||||
) {
|
||||
return t(STANDARD_PAGE_LAYOUT_TAB_TITLE_TRANSLATIONS[title]);
|
||||
}
|
||||
return title;
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownContent>
|
||||
<DropdownMenuHeader>{t`New tab`}</DropdownMenuHeader>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
LeftIcon={IconPlus}
|
||||
text={t`Empty tab`}
|
||||
onClick={handleCreateEmptyTab}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
{inactiveTabs.length > 0 && (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuSectionLabel label={t`Disabled`} />
|
||||
<DropdownMenuItemsContainer>
|
||||
{inactiveTabs.map((tab) => (
|
||||
<MenuItem
|
||||
key={tab.id}
|
||||
LeftIcon={isDefined(tab.icon) ? getIcon(tab.icon) : undefined}
|
||||
text={getTabTitle(tab.title)}
|
||||
onClick={() => handleReactivateTab(tab.id)}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
</>
|
||||
)}
|
||||
</DropdownContent>
|
||||
);
|
||||
};
|
||||
+4
-28
@@ -4,29 +4,24 @@ import { PageLayoutLeftPanel } from '@/page-layout/components/PageLayoutLeftPane
|
||||
import { PageLayoutTabList } from '@/page-layout/components/PageLayoutTabList';
|
||||
import { PageLayoutTabListEffect } from '@/page-layout/components/PageLayoutTabListEffect';
|
||||
import { PAGE_LAYOUT_LEFT_PANEL_CONTAINER_WIDTH } from '@/page-layout/constants/PageLayoutLeftPanelContainerWidth';
|
||||
import { useCreatePageLayoutTab } from '@/page-layout/hooks/useCreatePageLayoutTab';
|
||||
import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow';
|
||||
import { useIsPageLayoutInEditMode } from '@/page-layout/hooks/useIsPageLayoutInEditMode';
|
||||
import { usePageLayoutAddTabStrategy } from '@/page-layout/hooks/usePageLayoutAddTabStrategy';
|
||||
import { useReorderRecordPageLayoutTabs } from '@/page-layout/hooks/useReorderRecordPageLayoutTabs';
|
||||
import { PageLayoutMainContent } from '@/page-layout/PageLayoutMainContent';
|
||||
import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState';
|
||||
import { getScrollWrapperInstanceIdFromPageLayoutId } from '@/page-layout/utils/getScrollWrapperInstanceIdFromPageLayoutId';
|
||||
import { getTabListInstanceIdFromPageLayoutAndRecord } from '@/page-layout/utils/getTabListInstanceIdFromPageLayoutAndRecord';
|
||||
import { getTabsByDisplayMode } from '@/page-layout/utils/getTabsByDisplayMode';
|
||||
import { getTabsWithVisibleWidgets } from '@/page-layout/utils/getTabsWithVisibleWidgets';
|
||||
import { shouldEnableTabEditingFeatures } from '@/page-layout/utils/shouldEnableTabEditingFeatures';
|
||||
import { sortTabsByPosition } from '@/page-layout/utils/sortTabsByPosition';
|
||||
import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel';
|
||||
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { styled } from '@linaria/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { SidePanelPages } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIsMobile } from 'twenty-ui/utilities';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
@@ -67,17 +62,14 @@ export const PageLayoutTabsRenderer = () => {
|
||||
targetRecordIdentifier,
|
||||
});
|
||||
|
||||
const { createPageLayoutTab } = useCreatePageLayoutTab({
|
||||
const addTabStrategy = usePageLayoutAddTabStrategy({
|
||||
pageLayoutId: currentPageLayout.id,
|
||||
tabListInstanceId,
|
||||
});
|
||||
|
||||
const { reorderRecordPageTabs } = useReorderRecordPageLayoutTabs(
|
||||
currentPageLayout.id,
|
||||
);
|
||||
const setPageLayoutTabSettingsOpenTabId = useSetAtomComponentState(
|
||||
pageLayoutTabSettingsOpenTabIdComponentState,
|
||||
);
|
||||
const { navigatePageLayoutSidePanel } = useNavigatePageLayoutSidePanel();
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
@@ -96,22 +88,6 @@ export const PageLayoutTabsRenderer = () => {
|
||||
item.nameSingular === targetRecordIdentifier?.targetObjectNameSingular,
|
||||
)?.isSystem ?? false;
|
||||
|
||||
const handleAddTab =
|
||||
isPageLayoutInEditMode &&
|
||||
shouldEnableTabEditingFeatures(
|
||||
currentPageLayout.type,
|
||||
isRecordPageGlobalEditionEnabled,
|
||||
)
|
||||
? () => {
|
||||
const newTabId = createPageLayoutTab(t`Untitled`);
|
||||
setPageLayoutTabSettingsOpenTabId(newTabId);
|
||||
navigatePageLayoutSidePanel({
|
||||
sidePanelPage: SidePanelPages.PageLayoutTabSettings,
|
||||
focusTitleInput: true,
|
||||
});
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const canEnableTabEditing =
|
||||
isPageLayoutInEditMode &&
|
||||
shouldEnableTabEditingFeatures(
|
||||
@@ -168,7 +144,7 @@ export const PageLayoutTabsRenderer = () => {
|
||||
behaveAsLinks={!isInSidePanel && !isPageLayoutInEditMode}
|
||||
isInSidePanel={isInSidePanel}
|
||||
componentInstanceId={tabListInstanceId}
|
||||
onAddTab={handleAddTab}
|
||||
addTabStrategy={addTabStrategy}
|
||||
isReorderEnabled={canEnableTabEditing}
|
||||
onReorder={
|
||||
canEnableTabEditing
|
||||
|
||||
+7
-3
@@ -1,5 +1,5 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { type DropResult, type ResponderProvided } from '@hello-pangea/dnd';
|
||||
import { styled } from '@linaria/react';
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { ComponentWithRouterDecorator } from 'twenty-ui/testing';
|
||||
@@ -11,8 +11,8 @@ import { PageLayoutEditModeProviderContext } from '@/page-layout/contexts/PageLa
|
||||
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
|
||||
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
|
||||
import { calculateNewPosition } from '@/ui/layout/draggable-list/utils/calculateNewPosition';
|
||||
import { PageLayoutType } from '~/generated-metadata/graphql';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { PageLayoutType } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
border: 1px solid ${themeCssVariables.border.color.strong};
|
||||
@@ -170,7 +170,11 @@ const PageLayoutTabListPlayground = ({
|
||||
componentInstanceId="page-layout-tab-list-story"
|
||||
behaveAsLinks={false}
|
||||
loading={false}
|
||||
onAddTab={isReorderEnabled ? handleAddTab : undefined}
|
||||
addTabStrategy={
|
||||
isReorderEnabled
|
||||
? { mode: 'direct', onCreate: handleAddTab }
|
||||
: undefined
|
||||
}
|
||||
isReorderEnabled={isReorderEnabled}
|
||||
onReorder={isReorderEnabled ? handleReorder : undefined}
|
||||
pageLayoutType={PageLayoutType.DASHBOARD}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
|
||||
import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState';
|
||||
import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState';
|
||||
import { removeTabLayouts } from '@/page-layout/utils/removeTabLayouts';
|
||||
import { sortTabsByPosition } from '@/page-layout/utils/sortTabsByPosition';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
@@ -26,11 +24,6 @@ export const useDeletePageLayoutTab = ({
|
||||
pageLayoutId,
|
||||
);
|
||||
|
||||
const pageLayoutCurrentLayoutsState = useAtomComponentStateCallbackState(
|
||||
pageLayoutCurrentLayoutsComponentState,
|
||||
pageLayoutId,
|
||||
);
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const activeTabIdAtom = activeTabIdComponentState.atomFamily({
|
||||
@@ -40,36 +33,32 @@ export const useDeletePageLayoutTab = ({
|
||||
const deleteTab = useCallback(
|
||||
(tabId: string) => {
|
||||
const draft = store.get(pageLayoutDraftState);
|
||||
if (draft.tabs.length <= 1) {
|
||||
const activeTabs = draft.tabs.filter((t) => t.isActive);
|
||||
|
||||
if (activeTabs.length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sorted = sortTabsByPosition(draft.tabs);
|
||||
const index = sorted.findIndex((t) => t.id === tabId);
|
||||
const sortedActiveTabs = sortTabsByPosition(activeTabs);
|
||||
const index = sortedActiveTabs.findIndex((t) => t.id === tabId);
|
||||
|
||||
const activeTabId = store.get(activeTabIdAtom);
|
||||
|
||||
const allLayouts = store.get(pageLayoutCurrentLayoutsState);
|
||||
const updatedLayouts = removeTabLayouts(allLayouts, tabId);
|
||||
store.set(pageLayoutCurrentLayoutsState, updatedLayouts);
|
||||
|
||||
store.set(pageLayoutDraftState, (prev) => ({
|
||||
...prev,
|
||||
tabs: prev.tabs.filter((t) => t.id !== tabId),
|
||||
tabs: prev.tabs.map((t) =>
|
||||
t.id === tabId ? { ...t, isActive: false } : t,
|
||||
),
|
||||
}));
|
||||
|
||||
if (activeTabId === tabId) {
|
||||
const neighbor = index > 0 ? sorted[index - 1] : sorted[index + 1];
|
||||
const neighbor =
|
||||
index > 0 ? sortedActiveTabs[index - 1] : sortedActiveTabs[index + 1];
|
||||
const nextActiveId = neighbor?.id ?? null;
|
||||
store.set(activeTabIdAtom, nextActiveId);
|
||||
}
|
||||
},
|
||||
[
|
||||
pageLayoutCurrentLayoutsState,
|
||||
pageLayoutDraftState,
|
||||
activeTabIdAtom,
|
||||
store,
|
||||
],
|
||||
[pageLayoutDraftState, activeTabIdAtom, store],
|
||||
);
|
||||
|
||||
return { deleteTab };
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { useCreatePageLayoutTab } from '@/page-layout/hooks/useCreatePageLayoutTab';
|
||||
import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow';
|
||||
import { useIsPageLayoutInEditMode } from '@/page-layout/hooks/useIsPageLayoutInEditMode';
|
||||
import { useRecordPageLayoutObjectApplicationId } from '@/page-layout/hooks/useRecordPageLayoutObjectApplicationId';
|
||||
import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState';
|
||||
import { type PageLayoutAddTabStrategy } from '@/page-layout/types/PageLayoutAddTabStrategy';
|
||||
import { isReactivatableTab } from '@/page-layout/utils/isReactivatableTab';
|
||||
import { shouldEnableTabEditingFeatures } from '@/page-layout/utils/shouldEnableTabEditingFeatures';
|
||||
import { useNavigatePageLayoutSidePanel } from '@/side-panel/pages/page-layout/hooks/useNavigatePageLayoutSidePanel';
|
||||
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useCallback } from 'react';
|
||||
import { SidePanelPages } from 'twenty-shared/types';
|
||||
import { FeatureFlagKey, PageLayoutType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const usePageLayoutAddTabStrategy = ({
|
||||
pageLayoutId,
|
||||
tabListInstanceId,
|
||||
}: {
|
||||
pageLayoutId: string;
|
||||
tabListInstanceId: string;
|
||||
}): PageLayoutAddTabStrategy | undefined => {
|
||||
const { currentPageLayout } = useCurrentPageLayoutOrThrow();
|
||||
const isPageLayoutInEditMode = useIsPageLayoutInEditMode();
|
||||
const { objectApplicationId } = useRecordPageLayoutObjectApplicationId();
|
||||
|
||||
const isRecordPageGlobalEditionEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED,
|
||||
);
|
||||
|
||||
const { createPageLayoutTab } = useCreatePageLayoutTab({
|
||||
pageLayoutId,
|
||||
tabListInstanceId,
|
||||
});
|
||||
|
||||
const setPageLayoutTabSettingsOpenTabId = useSetAtomComponentState(
|
||||
pageLayoutTabSettingsOpenTabIdComponentState,
|
||||
);
|
||||
|
||||
const { navigatePageLayoutSidePanel } = useNavigatePageLayoutSidePanel();
|
||||
|
||||
const onCreate = useCallback(() => {
|
||||
const newTabId = createPageLayoutTab(t`Untitled`);
|
||||
setPageLayoutTabSettingsOpenTabId(newTabId);
|
||||
navigatePageLayoutSidePanel({
|
||||
sidePanelPage: SidePanelPages.PageLayoutTabSettings,
|
||||
focusTitleInput: true,
|
||||
});
|
||||
}, [
|
||||
createPageLayoutTab,
|
||||
setPageLayoutTabSettingsOpenTabId,
|
||||
navigatePageLayoutSidePanel,
|
||||
]);
|
||||
|
||||
const isEnabled =
|
||||
isPageLayoutInEditMode &&
|
||||
shouldEnableTabEditingFeatures(
|
||||
currentPageLayout.type,
|
||||
isRecordPageGlobalEditionEnabled,
|
||||
);
|
||||
|
||||
if (!isEnabled) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const hasInactiveTabs = currentPageLayout.tabs.some((tab) =>
|
||||
isReactivatableTab({ tab, objectApplicationId }),
|
||||
);
|
||||
|
||||
const mode =
|
||||
currentPageLayout.type === PageLayoutType.RECORD_PAGE && hasInactiveTabs
|
||||
? 'dropdown'
|
||||
: 'direct';
|
||||
|
||||
return { mode, onCreate };
|
||||
};
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
|
||||
import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const useRecordPageLayoutObjectApplicationId = (): {
|
||||
objectApplicationId: string | undefined;
|
||||
} => {
|
||||
const { currentPageLayout } = useCurrentPageLayoutOrThrow();
|
||||
const objectMetadataItems = useAtomStateValue(objectMetadataItemsSelector);
|
||||
|
||||
const objectMetadataId = currentPageLayout.objectMetadataId;
|
||||
|
||||
if (!isDefined(objectMetadataId)) {
|
||||
return { objectApplicationId: undefined };
|
||||
}
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(item) => item.id === objectMetadataId,
|
||||
);
|
||||
|
||||
return {
|
||||
objectApplicationId: objectMetadataItem?.applicationId,
|
||||
};
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import { PAGE_LAYOUT_TAB_LIST_DROPPABLE_IDS } from '@/page-layout/components/Pag
|
||||
import { useCurrentPageLayout } from '@/page-layout/hooks/useCurrentPageLayout';
|
||||
import { usePageLayoutDraftState } from '@/page-layout/hooks/usePageLayoutDraftState';
|
||||
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
|
||||
import { sortTabsByPosition } from '@/page-layout/utils/sortTabsByPosition';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { type DropResult } from '@hello-pangea/dnd';
|
||||
import { useCallback } from 'react';
|
||||
@@ -31,8 +32,8 @@ export const useReorderPageLayoutTabs = (pageLayoutIdFromProps?: string) => {
|
||||
return false;
|
||||
}
|
||||
|
||||
const sortedTabs = [...currentPageLayout.tabs].sort(
|
||||
(a, b) => a.position - b.position,
|
||||
const sortedTabs = sortTabsByPosition(
|
||||
currentPageLayout.tabs.filter((tab) => tab.isActive),
|
||||
);
|
||||
|
||||
const draggedTab = sortedTabs.find((tab) => tab.id === draggableId);
|
||||
|
||||
+1
-2
@@ -18,7 +18,6 @@ export const pageLayoutsWithRelationsSelector = createAtomSelector<
|
||||
const allFlatWidgets = get(metadataStoreState, 'pageLayoutWidgets')
|
||||
.current as FlatPageLayoutWidget[];
|
||||
|
||||
const activeFlatTabs = allFlatTabs.filter((tab) => tab.isActive);
|
||||
const activeFlatWidgets = allFlatWidgets.filter(
|
||||
(widget) => widget.isActive,
|
||||
);
|
||||
@@ -26,7 +25,7 @@ export const pageLayoutsWithRelationsSelector = createAtomSelector<
|
||||
const tabsByPageLayoutId = new Map<string, FlatPageLayoutTab[]>();
|
||||
const widgetsByTabId = new Map<string, FlatPageLayoutWidget[]>();
|
||||
|
||||
for (const tab of activeFlatTabs) {
|
||||
for (const tab of allFlatTabs) {
|
||||
const existing = tabsByPageLayoutId.get(tab.pageLayoutId);
|
||||
|
||||
if (isDefined(existing)) {
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
export type PageLayoutAddTabStrategy = {
|
||||
mode: 'direct' | 'dropdown';
|
||||
onCreate: () => void;
|
||||
};
|
||||
+1
@@ -41,6 +41,7 @@ const makeTab = (
|
||||
position: 0,
|
||||
pageLayoutId: 'layout-1',
|
||||
applicationId: 'app-1',
|
||||
isActive: true,
|
||||
layoutMode,
|
||||
widgets,
|
||||
}) as DraftPageLayout['tabs'][number];
|
||||
|
||||
+34
-32
@@ -48,38 +48,40 @@ export const convertPageLayoutDraftToUpdateInput = (
|
||||
name: pageLayoutDraft.name,
|
||||
type: pageLayoutDraft.type,
|
||||
objectMetadataId: pageLayoutDraft.objectMetadataId ?? null,
|
||||
tabs: pageLayoutDraft.tabs.map((tab) => {
|
||||
const widgets = shouldFilter
|
||||
? tab.widgets.filter((widget) => !isDynamicRelationWidget(widget))
|
||||
: tab.widgets;
|
||||
tabs: pageLayoutDraft.tabs
|
||||
.filter((tab) => tab.isActive)
|
||||
.map((tab) => {
|
||||
const widgets = shouldFilter
|
||||
? tab.widgets.filter((widget) => !isDynamicRelationWidget(widget))
|
||||
: tab.widgets;
|
||||
|
||||
return {
|
||||
id: tab.id,
|
||||
title: tab.title,
|
||||
position: tab.position,
|
||||
layoutMode: tab.layoutMode,
|
||||
widgets: widgets.map((widget, widgetIndex) => ({
|
||||
id: widget.id,
|
||||
pageLayoutTabId: widget.pageLayoutTabId,
|
||||
title: widget.title,
|
||||
type: widget.type,
|
||||
objectMetadataId: widget.objectMetadataId ?? null,
|
||||
gridPosition: {
|
||||
row: widget.gridPosition.row,
|
||||
column: widget.gridPosition.column,
|
||||
rowSpan: widget.gridPosition.rowSpan,
|
||||
columnSpan: widget.gridPosition.columnSpan,
|
||||
},
|
||||
position: buildWidgetPosition(
|
||||
widget,
|
||||
widgetIndex,
|
||||
tab.layoutMode ?? PageLayoutTabLayoutMode.GRID,
|
||||
),
|
||||
configuration: widget.configuration ?? null,
|
||||
conditionalAvailabilityExpression:
|
||||
widget.conditionalAvailabilityExpression ?? null,
|
||||
})),
|
||||
};
|
||||
}),
|
||||
return {
|
||||
id: tab.id,
|
||||
title: tab.title,
|
||||
position: tab.position,
|
||||
layoutMode: tab.layoutMode,
|
||||
widgets: widgets.map((widget, widgetIndex) => ({
|
||||
id: widget.id,
|
||||
pageLayoutTabId: widget.pageLayoutTabId,
|
||||
title: widget.title,
|
||||
type: widget.type,
|
||||
objectMetadataId: widget.objectMetadataId ?? null,
|
||||
gridPosition: {
|
||||
row: widget.gridPosition.row,
|
||||
column: widget.gridPosition.column,
|
||||
rowSpan: widget.gridPosition.rowSpan,
|
||||
columnSpan: widget.gridPosition.columnSpan,
|
||||
},
|
||||
position: buildWidgetPosition(
|
||||
widget,
|
||||
widgetIndex,
|
||||
tab.layoutMode ?? PageLayoutTabLayoutMode.GRID,
|
||||
),
|
||||
configuration: widget.configuration ?? null,
|
||||
conditionalAvailabilityExpression:
|
||||
widget.conditionalAvailabilityExpression ?? null,
|
||||
})),
|
||||
};
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
|
||||
|
||||
export const isReactivatableTab = ({
|
||||
tab,
|
||||
objectApplicationId,
|
||||
}: {
|
||||
tab: PageLayoutTab;
|
||||
objectApplicationId: string | undefined;
|
||||
}): boolean => {
|
||||
return !tab.isActive && tab.applicationId === objectApplicationId;
|
||||
};
|
||||
Reference in New Issue
Block a user