import { CommandGroup } from '@/command-menu/components/CommandGroup'; import { CommandMenuDefaultSelectionEffect } from '@/command-menu/components/CommandMenuDefaultSelectionEffect'; import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem'; import { CommandMenuTopBar } from '@/command-menu/components/CommandMenuTopBar'; 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 { useCommandMenuHotKeys } from '@/command-menu/hooks/useCommandMenuHotKeys'; import { useMatchingCommandMenuCommands } from '@/command-menu/hooks/useMatchingCommandMenuCommands'; import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState'; import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem'; import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList'; import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import styled from '@emotion/styled'; import { useRef } from 'react'; import { useRecoilState } from 'recoil'; import { isDefined } from 'twenty-ui'; const MOBILE_NAVIGATION_BAR_HEIGHT = 64; type CommandGroupConfig = { heading: string; items?: any[]; }; const StyledCommandMenu = styled.div` background: ${({ theme }) => theme.background.secondary}; border-left: 1px solid ${({ theme }) => theme.border.color.medium}; box-shadow: ${({ theme }) => theme.boxShadow.strong}; font-family: ${({ theme }) => theme.font.family}; height: 100%; overflow: hidden; padding: 0; position: fixed; right: 0%; top: 0%; width: ${() => (useIsMobile() ? '100%' : '500px')}; z-index: 1000; `; const StyledList = styled.div` background: ${({ theme }) => theme.background.secondary}; overscroll-behavior: contain; transition: 100ms ease; transition-property: height; `; const StyledInnerList = styled.div<{ isMobile: boolean }>` max-height: ${({ isMobile }) => isMobile ? `calc(100dvh - ${COMMAND_MENU_SEARCH_BAR_HEIGHT}px - ${ COMMAND_MENU_SEARCH_BAR_PADDING * 2 }px - ${MOBILE_NAVIGATION_BAR_HEIGHT}px)` : `calc(100dvh - ${COMMAND_MENU_SEARCH_BAR_HEIGHT}px - ${ COMMAND_MENU_SEARCH_BAR_PADDING * 2 }px)`}; padding-left: ${({ theme }) => theme.spacing(2)}; padding-right: ${({ theme }) => theme.spacing(2)}; padding-top: ${({ theme }) => theme.spacing(1)}; width: calc(100% - ${({ theme }) => theme.spacing(4)}); `; const StyledEmpty = styled.div` align-items: center; color: ${({ theme }) => theme.font.color.light}; display: flex; font-size: ${({ theme }) => theme.font.size.md}; height: 64px; justify-content: center; white-space: pre-wrap; `; export const CommandMenu = () => { const { onItemClick, closeCommandMenu } = useCommandMenu(); const commandMenuRef = useRef(null); const [commandMenuSearch, setCommandMenuSearch] = useRecoilState( commandMenuSearchState, ); const isMobile = useIsMobile(); useCommandMenuHotKeys(); useListenClickOutside({ refs: [commandMenuRef], callback: closeCommandMenu, listenerId: 'COMMAND_MENU_LISTENER_ID', hotkeyScope: AppHotkeyScope.CommandMenuOpen, }); const { isNoResults, isLoading, copilotCommands, matchingStandardActionRecordSelectionCommands, matchingWorkflowRunRecordSelectionCommands, matchingStandardActionGlobalCommands, matchingWorkflowRunGlobalCommands, matchingNavigateCommand, peopleCommands, companyCommands, opportunityCommands, noteCommands, tasksCommands, customObjectCommands, } = useMatchingCommandMenuCommands({ commandMenuSearch, }); const selectableItems = copilotCommands .concat(matchingStandardActionRecordSelectionCommands) .concat(matchingWorkflowRunRecordSelectionCommands) .concat(matchingStandardActionGlobalCommands) .concat(matchingWorkflowRunGlobalCommands) .concat(matchingNavigateCommand) .concat(peopleCommands) .concat(companyCommands) .concat(opportunityCommands) .concat(noteCommands) .concat(tasksCommands) .concat(customObjectCommands) .filter(isDefined); const selectableItemIds = selectableItems.map((item) => item.id); const commandGroups: CommandGroupConfig[] = [ { heading: 'Copilot', items: copilotCommands, }, { heading: 'Record Selection', items: matchingStandardActionRecordSelectionCommands, }, { heading: 'Workflow Record Selection', items: matchingWorkflowRunRecordSelectionCommands, }, { heading: 'View', items: matchingStandardActionGlobalCommands, }, { heading: 'Workflows', items: matchingWorkflowRunGlobalCommands, }, { heading: 'Navigate', items: matchingNavigateCommand, }, { heading: 'People', items: peopleCommands, }, { heading: 'Companies', items: companyCommands, }, { heading: 'Opportunities', items: opportunityCommands, }, { heading: 'Notes', items: noteCommands, }, { heading: 'Tasks', items: tasksCommands, }, { heading: 'Custom Objects', items: customObjectCommands, }, ]; return ( <> { const command = selectableItems.find( (item) => item.id === itemId, ); if (isDefined(command)) { const { to, onCommandClick, shouldCloseCommandMenuOnClick, } = command; onItemClick({ shouldCloseCommandMenuOnClick, onClick: onCommandClick, to, }); } }} > {isNoResults && !isLoading && ( No results found )} {commandGroups.map(({ heading, items }) => items?.length ? ( {items.map((item) => { return ( ); })} ) : null, )} ); };