Action menu refactoring (#11454)

# Description

Closes [#696](https://github.com/twentyhq/core-team-issues/issues/696)

- `useAction` hooks have been removed for all actions
- Every action can now declare a react component
- Some standard action components have been introduced: `Action`,
`ActionLink` and `ActionModal`
- The `ActionDisplay` component uses the new `displayType` prop of the
`ActionMenuContext` to render the right component for the action
according to its container: `ActionButton`, `ActionDropdownItem` or
`ActionListItem`
- The `ActionDisplayer` wraps the action component inside a context
which gives it all the information about the action
-`actionMenuEntriesComponenState` has been removed and now all actions
are computed directly using `useRegisteredAction`
- This computation is done inside `ActionMenuContextProvider` and the
actions are passed inside a context
- `actionMenuType` gives information about the container of the action,
so the action can know wether or not to close this container upon
execution
This commit is contained in:
Raphaël Bosi
2025-04-09 15:12:49 +02:00
committed by GitHub
parent 1834b38d04
commit 9e0402e691
235 changed files with 6252 additions and 7590 deletions

View File

@ -0,0 +1,4 @@
import { ActionConfig } from '@/action-menu/actions/types/ActionConfig';
import { createContext } from 'react';
export const ActionConfigContext = createContext<ActionConfig | null>(null);

View File

@ -1,13 +1,21 @@
import { ActionConfig } from '@/action-menu/actions/types/ActionConfig';
import { createContext } from 'react';
type ActionMenuContextType = {
export type ActionMenuContextType = {
isInRightDrawer: boolean;
onActionStartedCallback?: (action: { key: string }) => Promise<void> | void;
onActionExecutedCallback?: (action: { key: string }) => Promise<void> | void;
displayType: 'button' | 'listItem' | 'dropdownItem';
actionMenuType:
| 'command-menu'
| 'show-page-action-menu'
| 'index-page-action-menu'
| 'index-page-action-menu-dropdown'
| 'command-menu-show-page-action-menu-dropdown';
actions: ActionConfig[];
};
export const ActionMenuContext = createContext<ActionMenuContextType>({
isInRightDrawer: false,
onActionStartedCallback: () => {},
onActionExecutedCallback: () => {},
actionMenuType: 'command-menu',
displayType: 'button',
actions: [],
});

View File

@ -0,0 +1,108 @@
import { ActionMenuContextType } from '@/action-menu/contexts/ActionMenuContext';
import { ActionMenuContextProviderWorkflowsEnabled } from '@/action-menu/contexts/ActionMenuContextProviderWorkflowsEnabled';
import { ActionMenuContextProviderWorkflowsEnabledSingleRecordSelection } from '@/action-menu/contexts/ActionMenuContextProviderWorkflowsEnabledSingleRecordSelection';
import { ActionMenuContextProviderWorkflowsNotEnabled } from '@/action-menu/contexts/ActionMenuContextProviderWorkflowsNotEnabled';
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
export const ActionMenuContextProvider = ({
children,
isInRightDrawer,
displayType,
actionMenuType,
}: Omit<ActionMenuContextType, 'actions'> & {
children: React.ReactNode;
}) => {
const isWorkflowEnabled = useIsFeatureEnabled(
FeatureFlagKey.IsWorkflowEnabled,
);
const localContextStoreCurrentObjectMetadataItemId =
useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemIdComponentState,
);
const mainContextStoreCurrentObjectMetadataItemId = useRecoilComponentValueV2(
contextStoreCurrentObjectMetadataItemIdComponentState,
MAIN_CONTEXT_STORE_INSTANCE_ID,
);
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const localContextStoreObjectMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.id === localContextStoreCurrentObjectMetadataItemId,
);
const mainContextStoreObjectMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.id === mainContextStoreCurrentObjectMetadataItemId,
);
const objectMetadataItem =
localContextStoreObjectMetadataItem ?? mainContextStoreObjectMetadataItem;
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
contextStoreTargetedRecordsRuleComponentState,
);
const isSingleRecordSelection =
contextStoreTargetedRecordsRule.mode === 'selection' &&
contextStoreTargetedRecordsRule.selectedRecordIds.length === 1;
const isWorkflowObject =
objectMetadataItem?.nameSingular === CoreObjectNameSingular.Workflow ||
objectMetadataItem?.nameSingular === CoreObjectNameSingular.WorkflowRun ||
objectMetadataItem?.nameSingular === CoreObjectNameSingular.WorkflowVersion;
if (
isWorkflowEnabled &&
isSingleRecordSelection &&
isDefined(objectMetadataItem) &&
(actionMenuType === 'command-menu' ||
actionMenuType === 'command-menu-show-page-action-menu-dropdown')
) {
return (
<ActionMenuContextProviderWorkflowsEnabledSingleRecordSelection
isInRightDrawer={isInRightDrawer}
displayType={displayType}
actionMenuType={actionMenuType}
objectMetadataItem={objectMetadataItem}
>
{children}
</ActionMenuContextProviderWorkflowsEnabledSingleRecordSelection>
);
}
if (isWorkflowEnabled && isDefined(objectMetadataItem) && isWorkflowObject) {
return (
<ActionMenuContextProviderWorkflowsEnabled
isInRightDrawer={isInRightDrawer}
displayType={displayType}
actionMenuType={actionMenuType}
objectMetadataItem={objectMetadataItem}
>
{children}
</ActionMenuContextProviderWorkflowsEnabled>
);
}
return (
<ActionMenuContextProviderWorkflowsNotEnabled
isInRightDrawer={isInRightDrawer}
displayType={displayType}
actionMenuType={actionMenuType}
objectMetadataItem={objectMetadataItem}
>
{children}
</ActionMenuContextProviderWorkflowsNotEnabled>
);
};

View File

@ -0,0 +1,50 @@
import {
ActionMenuContext,
ActionMenuContextType,
} from '@/action-menu/contexts/ActionMenuContext';
import { useRegisteredActions } from '@/action-menu/hooks/useRegisteredActions';
import { useShouldActionBeRegisteredParams } from '@/action-menu/hooks/useShouldActionBeRegisteredParams';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
export const ActionMenuContextProviderWorkflowsEnabled = ({
objectMetadataItem,
isInRightDrawer,
displayType,
actionMenuType,
children,
}: {
objectMetadataItem: ObjectMetadataItem;
isInRightDrawer: ActionMenuContextType['isInRightDrawer'];
displayType: ActionMenuContextType['displayType'];
actionMenuType: ActionMenuContextType['actionMenuType'];
children: React.ReactNode;
}) => {
const params = useShouldActionBeRegisteredParams({
objectMetadataItem,
});
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(
params.selectedRecord?.id,
);
const shouldBeRegisteredParams = {
...params,
workflowWithCurrentVersion,
};
const actions = useRegisteredActions(shouldBeRegisteredParams);
return (
<ActionMenuContext.Provider
value={{
isInRightDrawer,
displayType,
actionMenuType,
actions,
}}
>
{children}
</ActionMenuContext.Provider>
);
};

View File

@ -0,0 +1,55 @@
import { useWorkflowRunRecordActions } from '@/action-menu/actions/record-actions/workflow-run-record-actions/hooks/useWorkflowRunRecordActions';
import {
ActionMenuContext,
ActionMenuContextType,
} from '@/action-menu/contexts/ActionMenuContext';
import { useRegisteredActions } from '@/action-menu/hooks/useRegisteredActions';
import { useShouldActionBeRegisteredParams } from '@/action-menu/hooks/useShouldActionBeRegisteredParams';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
export const ActionMenuContextProviderWorkflowsEnabledSingleRecordSelection = ({
objectMetadataItem,
isInRightDrawer,
displayType,
actionMenuType,
children,
}: {
objectMetadataItem: ObjectMetadataItem;
isInRightDrawer: ActionMenuContextType['isInRightDrawer'];
displayType: ActionMenuContextType['displayType'];
actionMenuType: ActionMenuContextType['actionMenuType'];
children: React.ReactNode;
}) => {
const params = useShouldActionBeRegisteredParams({
objectMetadataItem,
});
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(
params.selectedRecord?.id,
);
const shouldBeRegisteredParams = {
...params,
workflowWithCurrentVersion,
};
const actions = useRegisteredActions(shouldBeRegisteredParams);
const workflowRunRecordActions = useWorkflowRunRecordActions({
objectMetadataItem,
});
return (
<ActionMenuContext.Provider
value={{
isInRightDrawer,
displayType,
actionMenuType,
actions: [...actions, ...workflowRunRecordActions],
}}
>
{children}
</ActionMenuContext.Provider>
);
};

View File

@ -0,0 +1,40 @@
import {
ActionMenuContext,
ActionMenuContextType,
} from '@/action-menu/contexts/ActionMenuContext';
import { useRegisteredActions } from '@/action-menu/hooks/useRegisteredActions';
import { useShouldActionBeRegisteredParams } from '@/action-menu/hooks/useShouldActionBeRegisteredParams';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
export const ActionMenuContextProviderWorkflowsNotEnabled = ({
objectMetadataItem,
isInRightDrawer,
displayType,
actionMenuType,
children,
}: {
objectMetadataItem?: ObjectMetadataItem;
isInRightDrawer: ActionMenuContextType['isInRightDrawer'];
displayType: ActionMenuContextType['displayType'];
actionMenuType: ActionMenuContextType['actionMenuType'];
children: React.ReactNode;
}) => {
const params = useShouldActionBeRegisteredParams({
objectMetadataItem,
});
const actions = useRegisteredActions(params);
return (
<ActionMenuContext.Provider
value={{
isInRightDrawer,
displayType,
actionMenuType,
actions,
}}
>
{children}
</ActionMenuContext.Provider>
);
};