[FRONT COMPONENTS] Introduce conditionalAvailabilityExpression to command menu items (#18319)
## PR Description - Uses `expr-eval` to enable front components (SDK plugins) to define conditional availability as declarative expressions. - Moves shared types and constants to `twenty-shared` - Introduces a `conditionalAvailabilityExpression` field on `CommandMenuItemEntity`, allowing command menu items to store an `expr-eval` compatible expression string that is evaluated against a CommandMenuContext to determine if the item should be shown. - Creates an esbuild transform plugin `conditional-availability-transform-plugin` in `twenty-sdk` that converts TypeScript conditional availability expressions into `expr-eval` compatible syntax at build time, so SDK developers can write natural TS expressions that get transformed to evaluable strings. - Removes deprecated `forceRegisteredActionsByKey` state and its usage. - Creates `useCommandMenuContext` hook that builds the full `CommandMenuContext` object from React state, which is then passed to `useCommandMenuItemFrontComponentActions` for evaluating conditional availability expressions.
This commit is contained in:
+1
@@ -13,6 +13,7 @@ export const COMMAND_MENU_ITEM_FRAGMENT = gql`
|
||||
label
|
||||
icon
|
||||
isPinned
|
||||
conditionalAvailabilityExpression
|
||||
availabilityType
|
||||
availabilityObjectMetadataId
|
||||
}
|
||||
|
||||
+21
-4
@@ -13,20 +13,25 @@ import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/use
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useContext } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type CommandMenuContextApi } from 'twenty-shared/types';
|
||||
import {
|
||||
evaluateConditionalAvailabilityExpression,
|
||||
isDefined,
|
||||
} from 'twenty-shared/utils';
|
||||
import { type IconComponent, useIcons } from 'twenty-ui/display';
|
||||
|
||||
import { type HeadlessFrontComponentMountContext } from '@/front-components/states/mountedHeadlessFrontComponentMapsState';
|
||||
import { COMMAND_MENU_DEFAULT_ICON } from '@/workflow/workflow-trigger/constants/CommandMenuDefaultIcon';
|
||||
import {
|
||||
type CommandMenuItemFieldsFragment,
|
||||
CommandMenuItemAvailabilityType,
|
||||
type CommandMenuItemFieldsFragment,
|
||||
FeatureFlagKey,
|
||||
useFindManyCommandMenuItemsQuery,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
type CommandMenuItemWithFrontComponent = CommandMenuItemFieldsFragment & {
|
||||
frontComponentId: string;
|
||||
conditionalAvailabilityExpression?: string | null;
|
||||
};
|
||||
|
||||
type BuildActionFromItemParams = {
|
||||
@@ -49,8 +54,11 @@ type BuildActionFromItemParams = {
|
||||
context?: HeadlessFrontComponentMountContext,
|
||||
) => void;
|
||||
mountContext?: HeadlessFrontComponentMountContext;
|
||||
commandMenuContextApi: CommandMenuContextApi;
|
||||
};
|
||||
|
||||
// TODO: we should remove this backward compatibility logic in the future
|
||||
// once we have migrated all command menu items
|
||||
const buildActionFromItem = ({
|
||||
item,
|
||||
scope,
|
||||
@@ -60,6 +68,7 @@ const buildActionFromItem = ({
|
||||
openFrontComponentInCommandMenu,
|
||||
mountHeadlessFrontComponent,
|
||||
mountContext,
|
||||
commandMenuContextApi,
|
||||
}: BuildActionFromItemParams) => {
|
||||
const displayLabel = item.label;
|
||||
|
||||
@@ -94,7 +103,11 @@ const buildActionFromItem = ({
|
||||
position: index,
|
||||
isPinned,
|
||||
Icon,
|
||||
shouldBeRegistered: () => true,
|
||||
shouldBeRegistered: () =>
|
||||
evaluateConditionalAvailabilityExpression(
|
||||
item.conditionalAvailabilityExpression,
|
||||
commandMenuContextApi,
|
||||
),
|
||||
component: isHeadless ? (
|
||||
<HeadlessFrontComponentAction
|
||||
frontComponentId={item.frontComponentId}
|
||||
@@ -106,7 +119,9 @@ const buildActionFromItem = ({
|
||||
};
|
||||
};
|
||||
|
||||
export const useCommandMenuItemFrontComponentActions = () => {
|
||||
export const useCommandMenuItemFrontComponentActions = (
|
||||
commandMenuContextApi: CommandMenuContextApi,
|
||||
) => {
|
||||
const { getIcon } = useIcons();
|
||||
const { openFrontComponentInCommandMenu } =
|
||||
useOpenFrontComponentInCommandMenu();
|
||||
@@ -194,6 +209,7 @@ export const useCommandMenuItemFrontComponentActions = () => {
|
||||
getIcon,
|
||||
openFrontComponentInCommandMenu,
|
||||
mountHeadlessFrontComponent,
|
||||
commandMenuContextApi,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -206,6 +222,7 @@ export const useCommandMenuItemFrontComponentActions = () => {
|
||||
getIcon,
|
||||
openFrontComponentInCommandMenu,
|
||||
mountHeadlessFrontComponent,
|
||||
commandMenuContextApi,
|
||||
mountContext,
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user