Files
twenty/packages/twenty-front/src/modules/command-menu/pages/page-layout/components/CommandMenuPageLayoutFieldsSettings.tsx
T
Raphaël Bosi 7da8450075 [FRONT COMPONENTS] Headless components (#18096)
## Description

- Add `isHeadless` field to `FrontComponent` entity so front components
can run without rendering UI in the command menu
- Introduce headless front component mounting logic:
`HeadlessFrontComponentMountRoot` at the application root,
`useMountHeadlessFrontComponent`, and `useUnmountHeadlessFrontComponent`
hooks to mount/unmount headless components
- Expand the SDK with new action components (`Action`, `ActionLink`,
`ActionOpenSidePanelPage`) and host communication functions
(`openSidePanelPage`, `unmountFrontComponent`)
- Move `CommandMenuPages` type to twenty-shared so the SDK can reference
it for side panel navigation

## Video QA



https://github.com/user-attachments/assets/4f9e3bb1-fcd1-42be-b3f4-a97e80c2add2
2026-02-20 14:14:42 +00:00

103 lines
3.4 KiB
TypeScript

import { CommandGroup } from '@/command-menu/components/CommandGroup';
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
import { CommandMenuList } from '@/command-menu/components/CommandMenuList';
import { WidgetSettingsFooter } from '@/command-menu/pages/page-layout/components/WidgetSettingsFooter';
import { useNavigatePageLayoutCommandMenu } from '@/command-menu/pages/page-layout/hooks/useNavigatePageLayoutCommandMenu';
import { usePageLayoutIdForRecordPageLayoutFromContextStoreTargetedRecord } from '@/command-menu/pages/page-layout/hooks/usePageLayoutIdForRecordPageLayoutFromContextStoreTargetedRecord';
import { useWidgetInEditMode } from '@/command-menu/pages/page-layout/hooks/useWidgetInEditMode';
import { useFieldsWidgetGroups } from '@/page-layout/widgets/fields/hooks/useFieldsWidgetGroups';
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { CommandMenuPages } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { IconLayoutSidebarRight } from 'twenty-ui/display';
import { type FieldsConfiguration } from '~/generated-metadata/graphql';
const StyledContainer = styled.div`
display: flex;
flex-direction: column;
height: 100%;
`;
const StyledCommandMenuContainer = styled.div`
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
`;
export const CommandMenuPageLayoutFieldsSettings = () => {
const { t } = useLingui();
const { navigatePageLayoutCommandMenu } = useNavigatePageLayoutCommandMenu();
const { pageLayoutId, objectNameSingular } =
usePageLayoutIdForRecordPageLayoutFromContextStoreTargetedRecord();
const { widgetInEditMode } = useWidgetInEditMode(pageLayoutId);
const fieldsConfiguration = widgetInEditMode?.configuration as
| FieldsConfiguration
| undefined;
const { groups } = useFieldsWidgetGroups({
viewId: fieldsConfiguration?.viewId ?? null,
objectNameSingular,
});
if (!isDefined(widgetInEditMode)) {
return null;
}
const totalFieldsCount = groups.reduce(
(count, group) => count + group.fields.length,
0,
);
const handleNavigateToLayout = () => {
navigatePageLayoutCommandMenu({
commandMenuPage: CommandMenuPages.PageLayoutFieldsLayout,
});
};
const selectableItemIds = [
'layout',
'display-more-fields-button',
'action-button',
'move-down',
'move-up',
'move-to-tab',
'add-widget-above',
'add-widget-below',
'delete',
];
return (
<StyledContainer>
<StyledCommandMenuContainer>
<CommandMenuList
commandGroups={[]}
selectableItemIds={selectableItemIds}
>
<CommandGroup heading={t`Customize`}>
<SelectableListItem
itemId="layout"
onEnter={handleNavigateToLayout}
>
<CommandMenuItem
id="layout"
label={t`Layout`}
Icon={IconLayoutSidebarRight}
hasSubMenu
onClick={handleNavigateToLayout}
description={t`${totalFieldsCount} fields`}
contextualTextPosition="right"
/>
</SelectableListItem>
</CommandGroup>
</CommandMenuList>
</StyledCommandMenuContainer>
<WidgetSettingsFooter pageLayoutId={pageLayoutId} />
</StyledContainer>
);
};