Files
twenty/packages/twenty-front/src/modules/command-menu/components/hooks/usePageLayoutHeaderInfo.ts
T
Abdul Rahman 04b0a65e73 feat: fix Command Menu Side Panel Layout (#15883)
[Figma
Design](https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?node-id=81380-344641&t=FpjWNOK2gZuDQQfr-0)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Adds a side panel layout for the Command Menu, routes modals into a
local container, updates the top bar and context chips, and standardizes
small button sizes.
> 
> - **Command Menu**:
> - **Side Panel Layout**: Introduces `CommandMenuSidePanelLayout` with
animated width, hosts `CommandMenuRouter`, and provides a modal
container via `ModalContainerContext`.
> - **Top Bar**: Redesign (`CommandMenuTopBar`) with back icon, optional
AI sparkles action, compact height
(`COMMAND_MENU_SEARCH_BAR_HEIGHT=40`), and updated placeholder.
> - **Context Chips**: Adds `CommandMenuLastContextChip` and
`CommandMenuRecordInfo`; extends `CommandMenuContextChip` with `page`
prop; updates `CommandMenuContextChipGroups` to render last chip as
record info when applicable.
> - **Container Simplification**: `CommandMenuContainer` simplified to
just provide contexts and `AgentChatProvider`.
> - **Modal System**:
> - Adds `ModalContainerContext` and updates `Modal` to portal into
provided container; `Modal.Backdrop` supports `isInContainer`.
> - Updates usages (e.g., `UserOrMetadataLoader`, `ActionModal`) to
align with new modal behavior.
> - **Page Integration**:
> - Replaces `PageBody` with `CommandMenuSidePanelLayout` in
`RecordShowPage` and `RecordIndexContainerGater`.
> - Removes global `CommandMenuRouter` from `DefaultLayout` (keeps
keyboard shortcuts).
> - **UI/Styling**:
> - Standardizes several buttons to `size="small"` (e.g., command
actions, open record, options, reply, workflow footer).
> - Adjusts `ShowPageSubContainer` styling when rendered inside command
menu.
>   - Storybook tests updated for new placeholder text.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
81fcaa145618a2fa1c3e11e2dc88fe832c51374c. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
Co-authored-by: Devessier <baptiste@devessier.fr>
Co-authored-by: Aman Raj <92664006+araj00@users.noreply.github.com>
Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
Co-authored-by: Paul Rastoin <45004772+prastoin@users.noreply.github.com>
2025-11-21 16:12:41 +01:00

152 lines
4.1 KiB
TypeScript

import { GRAPH_TYPE_INFORMATION } from '@/command-menu/pages/page-layout/constants/GraphTypeInformation';
import { isChartWidget } from '@/command-menu/pages/page-layout/utils/isChartWidget';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
import { useTheme } from '@emotion/react';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import {
IconAppWindow,
IconFrame,
type IconComponent,
} from 'twenty-ui/display';
import { type PageLayoutWidget } from '~/generated/graphql';
type PageLayoutHeaderInfo = {
headerIcon: IconComponent | undefined;
headerIconColor: string;
headerType: string;
title: string;
isReadonly: boolean;
tab: PageLayoutTab | undefined;
widgetInEditMode: PageLayoutWidget | undefined;
};
type UsePageLayoutHeaderInfoParams = {
commandMenuPage: CommandMenuPages;
draftPageLayout: {
tabs: PageLayoutTab[];
};
pageLayoutEditingWidgetId: string | null | undefined;
openTabId: string | null | undefined;
editedTitle: string | null | undefined;
};
export const usePageLayoutHeaderInfo = ({
commandMenuPage,
draftPageLayout,
pageLayoutEditingWidgetId,
openTabId,
editedTitle,
}: UsePageLayoutHeaderInfoParams): PageLayoutHeaderInfo | null => {
const theme = useTheme();
const iconColor = theme.font.color.tertiary;
switch (commandMenuPage) {
case CommandMenuPages.PageLayoutTabSettings: {
if (!isDefined(openTabId)) {
return null;
}
const tab = draftPageLayout.tabs.find((t) => t.id === openTabId);
if (!isDefined(tab)) {
return null;
}
const title = isDefined(editedTitle)
? editedTitle
: isDefined(tab.title) && tab.title !== ''
? tab.title
: '';
return {
headerIcon: IconAppWindow,
headerIconColor: iconColor,
headerType: t`Tab`,
title,
isReadonly: false,
tab,
widgetInEditMode: undefined,
};
}
case CommandMenuPages.PageLayoutIframeSettings: {
if (!isDefined(pageLayoutEditingWidgetId)) {
return null;
}
const widgetInEditMode = draftPageLayout.tabs
.flatMap((tab) => tab.widgets)
.find((widget) => widget.id === pageLayoutEditingWidgetId);
if (!isDefined(widgetInEditMode)) {
return null;
}
const title = isDefined(editedTitle)
? editedTitle
: isDefined(widgetInEditMode.title) && widgetInEditMode.title !== ''
? widgetInEditMode.title
: '';
return {
headerIcon: IconFrame,
headerIconColor: iconColor,
headerType: t`iFrame Widget`,
title,
isReadonly: false,
tab: undefined,
widgetInEditMode,
};
}
case CommandMenuPages.PageLayoutGraphTypeSelect:
case CommandMenuPages.PageLayoutGraphFilter: {
if (!isDefined(pageLayoutEditingWidgetId)) {
return null;
}
const widgetInEditMode = draftPageLayout.tabs
.flatMap((tab) => tab.widgets)
.find((widget) => widget.id === pageLayoutEditingWidgetId);
if (!isDefined(widgetInEditMode)) {
return null;
}
if (!isChartWidget(widgetInEditMode)) {
return null;
}
const currentGraphType = widgetInEditMode.configuration.graphType;
const graphTypeInfo = GRAPH_TYPE_INFORMATION[currentGraphType];
const graphTypeLabel = t(graphTypeInfo.label);
const headerType =
commandMenuPage === CommandMenuPages.PageLayoutGraphFilter
? t`${graphTypeLabel} Chart`
: t`Chart`;
const title = isDefined(editedTitle)
? editedTitle
: isDefined(widgetInEditMode.title) && widgetInEditMode.title !== ''
? widgetInEditMode.title
: '';
return {
headerIcon: graphTypeInfo.icon,
headerIconColor: iconColor,
headerType,
title,
isReadonly: commandMenuPage === CommandMenuPages.PageLayoutGraphFilter,
tab: undefined,
widgetInEditMode,
};
}
default:
return null;
}
};