9426 migrate workflow pages to command menu (#9515)
Closes twentyhq/core-team-issues#53 - Removes command menu top bar text input when the user is not on root page - Fixes bug when resetting command menu context - Added animations on command menu open and close - Refactored workflow visualizer code to remove unnecessary rerenders and props drilling https://github.com/user-attachments/assets/1da3adb8-220b-407b-9279-30354d3100d3
This commit is contained in:
@ -3,20 +3,25 @@ import { RecordAgnosticActionsSetterEffect } from '@/action-menu/actions/record-
|
||||
import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals';
|
||||
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { COMMAND_MENU_ANIMATION_VARIANTS } from '@/command-menu/constants/CommandMenuAnimationVariants';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKeys';
|
||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||
import { CommandMenuAnimationVariant } from '@/command-menu/types/CommandMenuAnimationVariant';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { workflowReactFlowRefState } from '@/workflow/workflow-diagram/states/workflowReactFlowRefState';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useRef } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useIsMobile } from 'twenty-ui';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
|
||||
const StyledCommandMenu = styled.div`
|
||||
const StyledCommandMenu = styled(motion.div)`
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border-left: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
box-shadow: ${({ theme }) => theme.boxShadow.strong};
|
||||
@ -27,8 +32,9 @@ const StyledCommandMenu = styled.div`
|
||||
position: fixed;
|
||||
right: 0%;
|
||||
top: 0%;
|
||||
width: ${() => (useIsMobile() ? '100%' : '500px')};
|
||||
z-index: 30;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
export const CommandMenuContainer = ({
|
||||
@ -45,15 +51,28 @@ export const CommandMenuContainer = ({
|
||||
|
||||
const commandMenuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const workflowReactFlowRef = useRecoilValue(workflowReactFlowRefState);
|
||||
|
||||
useCommandMenuHotKeys();
|
||||
|
||||
useListenClickOutside({
|
||||
refs: [commandMenuRef],
|
||||
refs: [
|
||||
commandMenuRef,
|
||||
...(workflowReactFlowRef ? [workflowReactFlowRef] : []),
|
||||
],
|
||||
callback: closeCommandMenu,
|
||||
listenerId: 'COMMAND_MENU_LISTENER_ID',
|
||||
hotkeyScope: AppHotkeyScope.CommandMenuOpen,
|
||||
});
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const targetVariantForAnimation: CommandMenuAnimationVariant = isMobile
|
||||
? 'fullScreen'
|
||||
: 'normal';
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'command-menu' }}
|
||||
@ -71,7 +90,17 @@ export const CommandMenuContainer = ({
|
||||
{isWorkflowEnabled && <RecordAgnosticActionsSetterEffect />}
|
||||
<ActionMenuConfirmationModals />
|
||||
{isCommandMenuOpened && (
|
||||
<StyledCommandMenu ref={commandMenuRef} className="command-menu">
|
||||
<StyledCommandMenu
|
||||
ref={commandMenuRef}
|
||||
className="command-menu"
|
||||
animate={targetVariantForAnimation}
|
||||
initial="closed"
|
||||
exit="closed"
|
||||
variants={COMMAND_MENU_ANIMATION_VARIANTS}
|
||||
transition={{
|
||||
duration: theme.animation.duration.normal,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</StyledCommandMenu>
|
||||
)}
|
||||
|
||||
@ -3,4 +3,8 @@ export enum CommandMenuPages {
|
||||
ViewRecord = 'view-record',
|
||||
ViewEmailThread = 'view-email-thread',
|
||||
ViewCalendarEvent = 'view-calendar-event',
|
||||
WorkflowStepSelectTriggerType = 'workflow-step-select-trigger-type',
|
||||
WorkflowStepSelectAction = 'workflow-step-select-action',
|
||||
WorkflowStepView = 'workflow-step-view',
|
||||
WorkflowStepEdit = 'workflow-step-edit',
|
||||
}
|
||||
|
||||
@ -2,9 +2,15 @@ import { CommandMenuContainer } from '@/command-menu/components/CommandMenuConta
|
||||
import { CommandMenuTopBar } from '@/command-menu/components/CommandMenuTopBar';
|
||||
import { COMMAND_MENU_PAGES_CONFIG } from '@/command-menu/constants/CommandMenuPagesConfig';
|
||||
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
const StyledCommandMenuContent = styled.div`
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
export const CommandMenuRouter = () => {
|
||||
const commandMenuPage = useRecoilValue(commandMenuPageState);
|
||||
|
||||
@ -17,7 +23,9 @@ export const CommandMenuRouter = () => {
|
||||
return (
|
||||
<CommandMenuContainer>
|
||||
<CommandMenuTopBar />
|
||||
{commandMenuPageComponent}
|
||||
<StyledCommandMenuContent>
|
||||
{commandMenuPageComponent}
|
||||
</StyledCommandMenuContent>
|
||||
</CommandMenuContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
import { CommandMenuContextRecordChip } from '@/command-menu/components/CommandMenuContextRecordChip';
|
||||
import { CommandMenuPages } from '@/command-menu/components/CommandMenuPages';
|
||||
import { COMMAND_MENU_SEARCH_BAR_HEIGHT } from '@/command-menu/constants/CommandMenuSearchBarHeight';
|
||||
import { COMMAND_MENU_SEARCH_BAR_PADDING } from '@/command-menu/constants/CommandMenuSearchBarPadding';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
||||
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
|
||||
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { IconX, LightIconButton, isDefined, useIsMobile } from 'twenty-ui';
|
||||
|
||||
const StyledInputContainer = styled.div`
|
||||
@ -17,6 +19,7 @@ const StyledInputContainer = styled.div`
|
||||
border-radius: 0;
|
||||
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: ${({ theme }) => theme.font.size.lg};
|
||||
height: ${COMMAND_MENU_SEARCH_BAR_HEIGHT}px;
|
||||
margin: 0;
|
||||
@ -25,6 +28,7 @@ const StyledInputContainer = styled.div`
|
||||
|
||||
padding: 0 ${({ theme }) => theme.spacing(COMMAND_MENU_SEARCH_BAR_PADDING)};
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
const StyledInput = styled.input`
|
||||
@ -45,6 +49,13 @@ const StyledInput = styled.input`
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledContentContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledCloseButtonContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
@ -69,19 +80,25 @@ export const CommandMenuTopBar = () => {
|
||||
contextStoreCurrentObjectMetadataIdComponentState,
|
||||
);
|
||||
|
||||
const commandMenuPage = useRecoilValue(commandMenuPageState);
|
||||
|
||||
return (
|
||||
<StyledInputContainer>
|
||||
{isDefined(contextStoreCurrentObjectMetadataId) && (
|
||||
<CommandMenuContextRecordChip
|
||||
objectMetadataItemId={contextStoreCurrentObjectMetadataId}
|
||||
/>
|
||||
)}
|
||||
<StyledInput
|
||||
autoFocus
|
||||
value={commandMenuSearch}
|
||||
placeholder="Type anything"
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
<StyledContentContainer>
|
||||
{isDefined(contextStoreCurrentObjectMetadataId) && (
|
||||
<CommandMenuContextRecordChip
|
||||
objectMetadataItemId={contextStoreCurrentObjectMetadataId}
|
||||
/>
|
||||
)}
|
||||
{commandMenuPage === CommandMenuPages.Root && (
|
||||
<StyledInput
|
||||
autoFocus
|
||||
value={commandMenuSearch}
|
||||
placeholder="Type anything"
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
)}
|
||||
</StyledContentContainer>
|
||||
{!isMobile && (
|
||||
<StyledCloseButtonContainer>
|
||||
<LightIconButton
|
||||
|
||||
Reference in New Issue
Block a user