Baptiste Devessier
2025-11-18 12:25:18 +01:00
committed by GitHub
parent 76cc07a5f7
commit 6209ba539b
13 changed files with 107 additions and 26 deletions
@@ -9,7 +9,7 @@ import {
} from '@hello-pangea/dnd';
import { useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { IconPlus } from 'twenty-ui/display';
import { IconPlus, useIcons } from 'twenty-ui/display';
import { IconButton } from 'twenty-ui/input';
import { isPageLayoutTabDraggingComponentState } from '@/page-layout/states/isPageLayoutTabDraggingComponentState';
@@ -35,8 +35,10 @@ import { PageLayoutTabListStaticOverflowDropdown } from '@/page-layout/component
import { PageLayoutTabListVisibleTabs } from '@/page-layout/components/PageLayoutTabListVisibleTabs';
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
import { isPageLayoutInEditModeComponentState } from '@/page-layout/states/isPageLayoutInEditModeComponentState';
import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState';
import { pageLayoutTabListCurrentDragDroppableIdComponentState } from '@/page-layout/states/pageLayoutTabListCurrentDragDroppableIdComponentState';
import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState';
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
import { type SingleTabProps } from '@/ui/layout/tab-list/types/SingleTabProps';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { isDefined } from 'twenty-shared/utils';
@@ -67,7 +69,8 @@ const StyledAddButton = styled.div`
margin-left: ${TAB_LIST_GAP}px;
`;
type PageLayoutTabListProps = TabListProps & {
type PageLayoutTabListProps = Omit<TabListProps, 'tabs'> & {
tabs: PageLayoutTab[];
isReorderEnabled: boolean;
onAddTab?: () => void;
onReorder?: (result: DropResult, provided: ResponderProvided) => boolean;
@@ -85,7 +88,14 @@ export const PageLayoutTabList = ({
isReorderEnabled,
onReorder,
}: PageLayoutTabListProps) => {
const visibleTabs = useMemo(() => tabs.filter((tab) => !tab.hide), [tabs]);
const { getIcon } = useIcons();
const tabsWithIcons: SingleTabProps[] = tabs.map((tab) => ({
id: tab.id,
title: tab.title,
Icon: tab.icon ? getIcon(tab.icon) : undefined,
}));
const navigate = useNavigate();
const [activeTabId, setActiveTabId] = useRecoilComponentState(
@@ -103,7 +113,7 @@ export const PageLayoutTabList = ({
onMoreButtonWidthChange,
onAddButtonWidthChange,
} = useTabListMeasurements({
visibleTabs,
visibleTabs: tabsWithIcons,
hasAddButton: isDefined(onAddTab),
});
@@ -238,7 +248,7 @@ export const PageLayoutTabList = ({
],
);
if (visibleTabs.length === 0) {
if (tabsWithIcons.length === 0) {
return null;
}
@@ -254,12 +264,12 @@ export const PageLayoutTabList = ({
>
<TabListFromUrlOptionalEffect
isInRightDrawer={!!isInRightDrawer}
tabListIds={tabs.map((tab) => tab.id)}
tabListIds={tabsWithIcons.map((tab) => tab.id)}
/>
{visibleTabs.length > 1 && (
{tabsWithIcons.length > 1 && (
<TabListHiddenMeasurements
visibleTabs={visibleTabs}
visibleTabs={tabsWithIcons}
activeTabId={activeTabId}
loading={loading}
onTabWidthChange={onTabWidthChange}
@@ -284,7 +294,7 @@ export const PageLayoutTabList = ({
>
<StyledContainer className={className}>
<PageLayoutTabListVisibleTabs
visibleTabs={visibleTabs}
visibleTabs={tabsWithIcons}
visibleTabCount={visibleTabCount}
activeTabId={activeTabId}
behaveAsLinks={behaveAsLinks}
@@ -323,7 +333,7 @@ export const PageLayoutTabList = ({
) : (
<StyledContainer className={className}>
<PageLayoutTabListVisibleTabs
visibleTabs={visibleTabs}
visibleTabs={tabsWithIcons}
visibleTabCount={visibleTabCount}
activeTabId={activeTabId}
behaveAsLinks={behaveAsLinks}
@@ -1,3 +1,4 @@
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { type TabListProps } from '@/ui/layout/tab-list/types/TabListProps';
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
@@ -5,8 +6,10 @@ import { useEffect } from 'react';
type PageLayoutTabListEffectProps = Pick<
TabListProps,
'componentInstanceId' | 'tabs' | 'onChangeTab'
>;
'componentInstanceId' | 'onChangeTab'
> & {
tabs: PageLayoutTab[];
};
export const PageLayoutTabListEffect = ({
tabs,
@@ -18,10 +21,8 @@ export const PageLayoutTabListEffect = ({
componentInstanceId,
);
const visibleTabs = tabs.filter((tab) => !tab.hide);
const activeTabExists = visibleTabs.some((tab) => tab.id === activeTabId);
const initialActiveTabId = activeTabExists ? activeTabId : visibleTabs[0]?.id;
const activeTabExists = tabs.some((tab) => tab.id === activeTabId);
const initialActiveTabId = activeTabExists ? activeTabId : tabs[0]?.id;
useEffect(() => {
setActiveTabId(initialActiveTabId);
@@ -3,7 +3,6 @@ import { type DropResult, type ResponderProvided } from '@hello-pangea/dnd';
import type { Meta, StoryObj } from '@storybook/react';
import { useMemo, useState } from 'react';
import { RecoilRoot } from 'recoil';
import { IconPlus } from 'twenty-ui/display';
import { ComponentWithRouterDecorator } from 'twenty-ui/testing';
import { calculateNewPosition } from '@/favorites/utils/calculateNewPosition';
@@ -11,7 +10,7 @@ import { PageLayoutTabList } from '@/page-layout/components/PageLayoutTabList';
import { PAGE_LAYOUT_TAB_LIST_DROPPABLE_IDS } from '@/page-layout/components/PageLayoutTabListDroppableIds';
import { PageLayoutTabListEffect } from '@/page-layout/components/PageLayoutTabListEffect';
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
import { type SingleTabProps } from '@/ui/layout/tab-list/types/SingleTabProps';
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
const StyledContainer = styled.div`
border: 1px solid ${({ theme }) => theme.border.color.strong};
@@ -19,14 +18,41 @@ const StyledContainer = styled.div`
width: 720px;
`;
type PageLayoutTab = SingleTabProps & {
position: number;
};
const createInitialTabs = (): PageLayoutTab[] => [
{ id: 'overview', title: 'Overview', position: 0, Icon: IconPlus },
{ id: 'revenue', title: 'Revenue', position: 1 },
{ id: 'forecasts', title: 'Forecasts', position: 2 },
{
__typename: 'PageLayoutTab',
id: 'overview',
title: 'Overview',
position: 0,
icon: 'IconPlus',
pageLayoutId: 'test-layout',
widgets: [],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
deletedAt: null,
},
{
__typename: 'PageLayoutTab',
id: 'revenue',
title: 'Revenue',
position: 1,
pageLayoutId: 'test-layout',
widgets: [],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
deletedAt: null,
},
{
__typename: 'PageLayoutTab',
id: 'forecasts',
title: 'Forecasts',
position: 2,
pageLayoutId: 'test-layout',
widgets: [],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
deletedAt: null,
},
];
const PageLayoutTabListPlayground = ({
@@ -45,9 +71,15 @@ const PageLayoutTabListPlayground = ({
setTabs((prev) => [
...prev,
{
__typename: 'PageLayoutTab',
id: `new-tab-${nextIndex}`,
title: `New Tab ${nextIndex}`,
position: nextIndex,
pageLayoutId: 'test-layout',
widgets: [],
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
deletedAt: null,
},
]);
setNextIndex((value) => value + 1);
@@ -16,6 +16,7 @@ export const DEFAULT_COMPANY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'company-tab-fields',
title: 'Fields',
icon: 'IconList',
position: 100,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_COMPANY_RECORD_PAGE_LAYOUT_ID,
@@ -48,6 +49,7 @@ export const DEFAULT_COMPANY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'company-tab-timeline',
title: 'Timeline',
icon: 'IconTimelineEvent',
position: 200,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_COMPANY_RECORD_PAGE_LAYOUT_ID,
@@ -80,6 +82,7 @@ export const DEFAULT_COMPANY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'company-tab-tasks',
title: 'Tasks',
icon: 'IconCheckbox',
position: 300,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_COMPANY_RECORD_PAGE_LAYOUT_ID,
@@ -112,6 +115,7 @@ export const DEFAULT_COMPANY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'company-tab-notes',
title: 'Notes',
icon: 'IconNotes',
position: 400,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_COMPANY_RECORD_PAGE_LAYOUT_ID,
@@ -144,6 +148,7 @@ export const DEFAULT_COMPANY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'company-tab-files',
title: 'Files',
icon: 'IconPaperclip',
position: 500,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_COMPANY_RECORD_PAGE_LAYOUT_ID,
@@ -176,6 +181,7 @@ export const DEFAULT_COMPANY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'company-tab-emails',
title: 'Emails',
icon: 'IconMail',
position: 600,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_COMPANY_RECORD_PAGE_LAYOUT_ID,
@@ -208,6 +214,7 @@ export const DEFAULT_COMPANY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'company-tab-calendar',
title: 'Calendar',
icon: 'IconCalendarEvent',
position: 700,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_COMPANY_RECORD_PAGE_LAYOUT_ID,
@@ -16,6 +16,7 @@ export const DEFAULT_NOTE_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'note-tab-fields',
title: 'Fields',
icon: 'IconList',
position: 100,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_NOTE_RECORD_PAGE_LAYOUT_ID,
@@ -113,6 +114,7 @@ export const DEFAULT_NOTE_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'note-tab-timeline',
title: 'Timeline',
icon: 'IconTimelineEvent',
position: 200,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_NOTE_RECORD_PAGE_LAYOUT_ID,
@@ -145,6 +147,7 @@ export const DEFAULT_NOTE_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'note-tab-files',
title: 'Files',
icon: 'IconPaperclip',
position: 300,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_NOTE_RECORD_PAGE_LAYOUT_ID,
@@ -16,6 +16,7 @@ export const DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'opportunity-tab-fields',
title: 'Fields',
icon: 'IconList',
position: 100,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT_ID,
@@ -48,6 +49,7 @@ export const DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'opportunity-tab-timeline',
title: 'Timeline',
icon: 'IconTimelineEvent',
position: 200,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT_ID,
@@ -80,6 +82,7 @@ export const DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'opportunity-tab-tasks',
title: 'Tasks',
icon: 'IconCheckbox',
position: 300,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT_ID,
@@ -112,6 +115,7 @@ export const DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'opportunity-tab-notes',
title: 'Notes',
icon: 'IconNotes',
position: 400,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT_ID,
@@ -144,6 +148,7 @@ export const DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'opportunity-tab-files',
title: 'Files',
icon: 'IconPaperclip',
position: 500,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT_ID,
@@ -176,6 +181,7 @@ export const DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'opportunity-tab-emails',
title: 'Emails',
icon: 'IconMail',
position: 600,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT_ID,
@@ -208,6 +214,7 @@ export const DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'opportunity-tab-calendar',
title: 'Calendar',
icon: 'IconCalendarEvent',
position: 700,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_OPPORTUNITY_RECORD_PAGE_LAYOUT_ID,
@@ -16,6 +16,7 @@ export const DEFAULT_PERSON_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'person-tab-fields',
title: 'Fields',
icon: 'IconList',
position: 100,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_PERSON_RECORD_PAGE_LAYOUT_ID,
@@ -48,6 +49,7 @@ export const DEFAULT_PERSON_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'person-tab-timeline',
title: 'Timeline',
icon: 'IconTimelineEvent',
position: 200,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_PERSON_RECORD_PAGE_LAYOUT_ID,
@@ -80,6 +82,7 @@ export const DEFAULT_PERSON_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'person-tab-tasks',
title: 'Tasks',
icon: 'IconCheckbox',
position: 300,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_PERSON_RECORD_PAGE_LAYOUT_ID,
@@ -112,6 +115,7 @@ export const DEFAULT_PERSON_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'person-tab-notes',
title: 'Notes',
icon: 'IconNotes',
position: 400,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_PERSON_RECORD_PAGE_LAYOUT_ID,
@@ -144,6 +148,7 @@ export const DEFAULT_PERSON_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'person-tab-files',
title: 'Files',
icon: 'IconPaperclip',
position: 500,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_PERSON_RECORD_PAGE_LAYOUT_ID,
@@ -176,6 +181,7 @@ export const DEFAULT_PERSON_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'person-tab-emails',
title: 'Emails',
icon: 'IconMail',
position: 600,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_PERSON_RECORD_PAGE_LAYOUT_ID,
@@ -208,6 +214,7 @@ export const DEFAULT_PERSON_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'person-tab-calendar',
title: 'Calendar',
icon: 'IconCalendarEvent',
position: 700,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_PERSON_RECORD_PAGE_LAYOUT_ID,
@@ -16,6 +16,7 @@ export const DEFAULT_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'default-tab-fields',
title: 'Fields',
icon: 'IconList',
position: 100,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_RECORD_PAGE_LAYOUT_ID,
@@ -48,6 +49,7 @@ export const DEFAULT_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'default-tab-timeline',
title: 'Timeline',
icon: 'IconTimelineEvent',
position: 200,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_RECORD_PAGE_LAYOUT_ID,
@@ -80,6 +82,7 @@ export const DEFAULT_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'default-tab-tasks',
title: 'Tasks',
icon: 'IconCheckbox',
position: 300,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_RECORD_PAGE_LAYOUT_ID,
@@ -112,6 +115,7 @@ export const DEFAULT_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'default-tab-notes',
title: 'Notes',
icon: 'IconNotes',
position: 400,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_RECORD_PAGE_LAYOUT_ID,
@@ -144,6 +148,7 @@ export const DEFAULT_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'default-tab-files',
title: 'Files',
icon: 'IconPaperclip',
position: 500,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_RECORD_PAGE_LAYOUT_ID,
@@ -16,6 +16,7 @@ export const DEFAULT_TASK_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'task-tab-fields',
title: 'Fields',
icon: 'IconList',
position: 100,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_TASK_RECORD_PAGE_LAYOUT_ID,
@@ -113,6 +114,7 @@ export const DEFAULT_TASK_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'task-tab-timeline',
title: 'Timeline',
icon: 'IconTimelineEvent',
position: 200,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_TASK_RECORD_PAGE_LAYOUT_ID,
@@ -145,6 +147,7 @@ export const DEFAULT_TASK_RECORD_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'task-tab-files',
title: 'Files',
icon: 'IconPaperclip',
position: 300,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_TASK_RECORD_PAGE_LAYOUT_ID,
@@ -23,6 +23,7 @@ export const DEFAULT_WORKFLOW_PAGE_LAYOUT: PageLayout = {
title: 'Flow',
position: 100,
layoutMode: 'canvas',
icon: 'IconSettings',
pageLayoutId: DEFAULT_WORKFLOW_PAGE_LAYOUT_ID,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
@@ -23,6 +23,7 @@ export const DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT: PageLayout = {
title: 'Fields',
position: 100,
layoutMode: 'vertical-list',
icon: 'IconList',
pageLayoutId: DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT_ID,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
@@ -56,6 +57,7 @@ export const DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT: PageLayout = {
title: 'Flow',
position: 200,
layoutMode: 'canvas',
icon: 'IconSettings',
pageLayoutId: DEFAULT_WORKFLOW_RUN_PAGE_LAYOUT_ID,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
@@ -21,6 +21,7 @@ export const DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'workflow-version-tab-fields',
title: 'Fields',
icon: 'IconList',
position: 100,
layoutMode: 'vertical-list',
pageLayoutId: DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT_ID,
@@ -54,6 +55,7 @@ export const DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT: PageLayout = {
__typename: 'PageLayoutTab',
id: 'workflow-version-tab-flow',
title: 'Flow',
icon: 'IconSettings',
position: 200,
layoutMode: 'canvas',
pageLayoutId: DEFAULT_WORKFLOW_VERSION_PAGE_LAYOUT_ID,
@@ -18,4 +18,5 @@ export type PageLayoutTab = Omit<PageLayoutTabGenerated, 'widgets'> & {
* Only available behind IS_RECORD_PAGE_LAYOUT_ENABLED for now.
*/
layoutMode?: PageLayoutTabLayoutMode;
icon?: string;
};