[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
This commit is contained in:
+1
@@ -8,6 +8,7 @@ export const COMMAND_MENU_ITEM_FRAGMENT = gql`
|
||||
frontComponent {
|
||||
id
|
||||
name
|
||||
isHeadless
|
||||
}
|
||||
label
|
||||
icon
|
||||
|
||||
+22
-8
@@ -6,6 +6,7 @@ import { useOpenFrontComponentInCommandMenu } from '@/command-menu/hooks/useOpen
|
||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||
import { contextStoreIsPageInEditModeComponentState } from '@/context-store/states/contextStoreIsPageInEditModeComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { useMountHeadlessFrontComponent } from '@/front-components/hooks/useMountHeadlessFrontComponent';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useContext } from 'react';
|
||||
@@ -35,6 +36,7 @@ type BuildActionFromItemParams = {
|
||||
pageTitle: string;
|
||||
pageIcon: IconComponent;
|
||||
}) => void;
|
||||
mountHeadlessFrontComponent: (frontComponentId: string) => void;
|
||||
};
|
||||
|
||||
const buildActionFromItem = ({
|
||||
@@ -44,11 +46,26 @@ const buildActionFromItem = ({
|
||||
isPinned,
|
||||
getIcon,
|
||||
openFrontComponentInCommandMenu,
|
||||
mountHeadlessFrontComponent,
|
||||
}: BuildActionFromItemParams) => {
|
||||
const displayLabel = item.label;
|
||||
|
||||
const Icon = getIcon(item.icon, COMMAND_MENU_DEFAULT_ICON);
|
||||
|
||||
const isHeadless = item.frontComponent?.isHeadless === true;
|
||||
|
||||
const handleClick = () => {
|
||||
if (isHeadless) {
|
||||
mountHeadlessFrontComponent(item.frontComponentId);
|
||||
} else {
|
||||
openFrontComponentInCommandMenu({
|
||||
frontComponentId: item.frontComponentId,
|
||||
pageTitle: displayLabel,
|
||||
pageIcon: Icon,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
type: ActionType.FrontComponent,
|
||||
key: `command-menu-item-front-component-${item.id}`,
|
||||
@@ -61,14 +78,8 @@ const buildActionFromItem = ({
|
||||
shouldBeRegistered: () => true,
|
||||
component: (
|
||||
<Action
|
||||
onClick={() =>
|
||||
openFrontComponentInCommandMenu({
|
||||
frontComponentId: item.frontComponentId,
|
||||
pageTitle: displayLabel,
|
||||
pageIcon: Icon,
|
||||
})
|
||||
}
|
||||
closeSidePanelOnCommandMenuListActionExecution={false}
|
||||
onClick={handleClick}
|
||||
closeSidePanelOnCommandMenuListActionExecution={isHeadless}
|
||||
/>
|
||||
),
|
||||
};
|
||||
@@ -78,6 +89,7 @@ export const useCommandMenuItemFrontComponentActions = () => {
|
||||
const { getIcon } = useIcons();
|
||||
const { openFrontComponentInCommandMenu } =
|
||||
useOpenFrontComponentInCommandMenu();
|
||||
const mountHeadlessFrontComponent = useMountHeadlessFrontComponent();
|
||||
|
||||
const isPageInEditMode = useRecoilComponentValue(
|
||||
contextStoreIsPageInEditModeComponentState,
|
||||
@@ -140,6 +152,7 @@ export const useCommandMenuItemFrontComponentActions = () => {
|
||||
isPinned: !isPageInEditMode && item.isPinned,
|
||||
getIcon,
|
||||
openFrontComponentInCommandMenu,
|
||||
mountHeadlessFrontComponent,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -151,6 +164,7 @@ export const useCommandMenuItemFrontComponentActions = () => {
|
||||
isPinned: !isPageInEditMode && item.isPinned,
|
||||
getIcon,
|
||||
openFrontComponentInCommandMenu,
|
||||
mountHeadlessFrontComponent,
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user