Add search records actions to the command menu (#9892)

Closes https://github.com/twentyhq/core-team-issues/issues/253 and
https://github.com/twentyhq/core-team-issues/issues/256.

- Created `CommandMenuList`, a component used at the root level of the
command menu and inside the search page of the command menu
- Refactored record agnostic actions
- Added shortcuts to the action menu entries (`/` key for the search)
and updated the design of the shortcuts
- Reordered actions at the root level of the command menu


https://github.com/user-attachments/assets/e1339cc4-ef5d-45c5-a159-6817a54b92e9
This commit is contained in:
Raphaël Bosi
2025-01-29 18:23:40 +01:00
committed by GitHub
parent 03f3ccd060
commit ce296fae4f
52 changed files with 1539 additions and 1361 deletions

View File

@ -5,11 +5,11 @@ import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectab
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { CommandMenuPages } from '@/command-menu/components/CommandMenuPages';
import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates';
import { useResetContextStoreStates } from '@/command-menu/hooks/useResetContextStoreStates';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageTitle';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
@ -19,6 +19,7 @@ import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
import { emitRightDrawerCloseEvent } from '@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent';
import { IconSearch } from 'twenty-ui';
import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState';
export const useCommandMenu = () => {
@ -71,6 +72,7 @@ export const useCommandMenu = () => {
Icon: undefined,
});
set(isCommandMenuOpenedState, false);
set(commandMenuSearchState, '');
resetSelectedItem();
goBackToPreviousHotkeyScope();
@ -110,6 +112,20 @@ export const useCommandMenu = () => {
[openCommandMenu],
);
const openRecordsSearchPage = useRecoilCallback(
({ set }) => {
return () => {
set(commandMenuPageState, CommandMenuPages.SearchRecords);
set(commandMenuPageInfoState, {
title: 'Search',
Icon: IconSearch,
});
openCommandMenu();
};
},
[openCommandMenu],
);
const setGlobalCommandMenuContext = useRecoilCallback(
({ set }) => {
return () => {
@ -161,6 +177,7 @@ export const useCommandMenu = () => {
return {
openCommandMenu,
closeCommandMenu,
openRecordsSearchPage,
openRecordInCommandMenu,
toggleCommandMenu,
setGlobalCommandMenuContext,

View File

@ -5,43 +5,25 @@ import {
} from '@/action-menu/types/ActionMenuEntry';
import { useOpenCopilotRightDrawer } from '@/activities/copilot/right-drawer/hooks/useOpenCopilotRightDrawer';
import { copilotQueryState } from '@/activities/copilot/right-drawer/states/copilotQueryState';
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
import { Note } from '@/activities/types/Note';
import { Task } from '@/activities/types/Task';
import { COMMAND_MENU_NAVIGATE_COMMANDS } from '@/command-menu/constants/CommandMenuNavigateCommands';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
import {
Command,
CommandScope,
CommandType,
} from '@/command-menu/types/Command';
import { Company } from '@/companies/types/Company';
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { getCompanyDomainName } from '@/object-metadata/utils/getCompanyDomainName';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { useMultiObjectSearch } from '@/object-record/relation-picker/hooks/useMultiObjectSearch';
import { useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap } from '@/object-record/relation-picker/hooks/useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap';
import { makeOrFilterVariables } from '@/object-record/utils/makeOrFilterVariables';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import isEmpty from 'lodash.isempty';
import { useMemo } from 'react';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { Avatar, IconCheckbox, IconNotes, IconSparkles } from 'twenty-ui';
import { IconSparkles } from 'twenty-ui';
import { useDebounce } from 'use-debounce';
import { FeatureFlagKey } from '~/generated/graphql';
import { getLogoUrlFromDomainName } from '~/utils';
export const useCommandMenuCommands = () => {
const actionMenuEntries = useRecoilComponentValueV2(
actionMenuEntriesComponentSelector,
);
const openActivityRightDrawer = useOpenActivityRightDrawer({
objectNameSingular: CoreObjectNameSingular.Note,
});
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
const commandMenuSearch = useRecoilValue(commandMenuSearchState);
const [deferredCommandMenuSearch] = useDebounce(commandMenuSearch, 300); // 200ms - 500ms
@ -78,6 +60,23 @@ export const useCommandMenuCommands = () => {
onCommandClick: actionMenuEntry.onClick,
type: CommandType.StandardAction,
scope: CommandScope.RecordSelection,
hotKeys: actionMenuEntry.hotKeys,
}));
const actionObjectCommands: Command[] = actionMenuEntries
?.filter(
(actionMenuEntry) =>
actionMenuEntry.type === ActionMenuEntryType.Standard &&
actionMenuEntry.scope === ActionMenuEntryScope.Object,
)
?.map((actionMenuEntry) => ({
id: actionMenuEntry.key,
label: actionMenuEntry.label,
Icon: actionMenuEntry.Icon,
onCommandClick: actionMenuEntry.onClick,
type: CommandType.StandardAction,
scope: CommandScope.Object,
hotKeys: actionMenuEntry.hotKeys,
}));
const actionGlobalCommands: Command[] = actionMenuEntries
@ -93,6 +92,7 @@ export const useCommandMenuCommands = () => {
onCommandClick: actionMenuEntry.onClick,
type: CommandType.StandardAction,
scope: CommandScope.Global,
hotKeys: actionMenuEntry.hotKeys,
}));
const workflowRunRecordSelectionCommands: Command[] = actionMenuEntries
@ -108,6 +108,7 @@ export const useCommandMenuCommands = () => {
onCommandClick: actionMenuEntry.onClick,
type: CommandType.WorkflowRun,
scope: CommandScope.RecordSelection,
hotKeys: actionMenuEntry.hotKeys,
}));
const workflowRunGlobalCommands: Command[] = actionMenuEntries
@ -123,193 +124,16 @@ export const useCommandMenuCommands = () => {
onCommandClick: actionMenuEntry.onClick,
type: CommandType.WorkflowRun,
scope: CommandScope.Global,
hotKeys: actionMenuEntry.hotKeys,
}));
const {
matchesSearchFilterObjectRecordsQueryResult,
matchesSearchFilterObjectRecordsLoading: loading,
} = useMultiObjectSearch({
excludedObjects: [CoreObjectNameSingular.Task, CoreObjectNameSingular.Note],
searchFilterValue: deferredCommandMenuSearch ?? undefined,
limit: 3,
});
const { objectRecordsMap: matchesSearchFilterObjectRecords } =
useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap({
multiObjectRecordsQueryResult:
matchesSearchFilterObjectRecordsQueryResult,
});
const { loading: isNotesLoading, records: notes } = useFindManyRecords<Note>({
skip: !isCommandMenuOpened,
objectNameSingular: CoreObjectNameSingular.Note,
filter: deferredCommandMenuSearch
? makeOrFilterVariables([
{ title: { ilike: `%${deferredCommandMenuSearch}%` } },
{ body: { ilike: `%${deferredCommandMenuSearch}%` } },
])
: undefined,
limit: 3,
});
const { loading: isTasksLoading, records: tasks } = useFindManyRecords<Task>({
skip: !isCommandMenuOpened,
objectNameSingular: CoreObjectNameSingular.Task,
filter: deferredCommandMenuSearch
? makeOrFilterVariables([
{ title: { ilike: `%${deferredCommandMenuSearch}%` } },
{ body: { ilike: `%${deferredCommandMenuSearch}%` } },
])
: undefined,
limit: 3,
});
const people = matchesSearchFilterObjectRecords.people?.map(
(people) => people.record,
);
const companies = matchesSearchFilterObjectRecords.companies?.map(
(companies) => companies.record,
);
const opportunities = matchesSearchFilterObjectRecords.opportunities?.map(
(opportunities) => opportunities.record,
);
const peopleCommands = useMemo(
() =>
people?.map(({ id, name: { firstName, lastName }, avatarUrl }) => ({
id,
label: `${firstName} ${lastName}`,
to: `object/person/${id}`,
shouldCloseCommandMenuOnClick: true,
Icon: () => (
<Avatar
type="rounded"
avatarUrl={avatarUrl}
placeholderColorSeed={id}
placeholder={`${firstName} ${lastName}`}
/>
),
})),
[people],
);
const companyCommands = useMemo(
() =>
companies?.map((company) => ({
id: company.id,
label: company.name ?? '',
to: `object/company/${company.id}`,
shouldCloseCommandMenuOnClick: true,
Icon: () => (
<Avatar
placeholderColorSeed={company.id}
placeholder={company.name}
avatarUrl={getLogoUrlFromDomainName(
getCompanyDomainName(company as Company),
)}
/>
),
})),
[companies],
);
const opportunityCommands = useMemo(
() =>
opportunities?.map(({ id, name }) => ({
id,
label: name ?? '',
to: `object/opportunity/${id}`,
shouldCloseCommandMenuOnClick: true,
Icon: () => (
<Avatar
type="rounded"
avatarUrl={null}
placeholderColorSeed={id}
placeholder={name ?? ''}
/>
),
})),
[opportunities],
);
const noteCommands = useMemo(
() =>
notes?.map((note) => ({
id: note.id,
label: note.title ?? '',
to: '',
onCommandClick: () => openActivityRightDrawer(note.id),
shouldCloseCommandMenuOnClick: true,
Icon: IconNotes,
})),
[notes, openActivityRightDrawer],
);
const tasksCommands = useMemo(
() =>
tasks?.map((task) => ({
id: task.id,
label: task.title ?? '',
to: '',
onCommandClick: () => openActivityRightDrawer(task.id),
shouldCloseCommandMenuOnClick: true,
Icon: IconCheckbox,
})),
[tasks, openActivityRightDrawer],
);
const customObjectRecordsMap = useMemo(() => {
return Object.fromEntries(
Object.entries(matchesSearchFilterObjectRecords).filter(
([namePlural, records]) =>
![
CoreObjectNamePlural.Person,
CoreObjectNamePlural.Opportunity,
CoreObjectNamePlural.Company,
].includes(namePlural as CoreObjectNamePlural) && !isEmpty(records),
),
);
}, [matchesSearchFilterObjectRecords]);
const customObjectCommands = useMemo(() => {
const customObjectCommandsArray: Command[] = [];
Object.values(customObjectRecordsMap).forEach((objectRecords) => {
customObjectCommandsArray.push(
...objectRecords.map((objectRecord) => ({
id: objectRecord.record.id,
label: objectRecord.recordIdentifier.name,
to: `object/${objectRecord.objectMetadataItem.nameSingular}/${objectRecord.record.id}`,
shouldCloseCommandMenuOnClick: true,
Icon: () => (
<Avatar
type="rounded"
avatarUrl={objectRecord.record.avatarUrl}
placeholderColorSeed={objectRecord.record.id}
placeholder={objectRecord.recordIdentifier.name ?? ''}
/>
),
})),
);
});
return customObjectCommandsArray;
}, [customObjectRecordsMap]);
const isLoading = loading || isNotesLoading || isTasksLoading;
return {
copilotCommands,
navigateCommands,
actionRecordSelectionCommands,
actionGlobalCommands,
actionObjectCommands,
workflowRunRecordSelectionCommands,
workflowRunGlobalCommands,
peopleCommands,
companyCommands,
opportunityCommands,
noteCommands,
tasksCommands,
customObjectCommands,
isLoading,
};
};

View File

@ -1,7 +1,7 @@
import { CommandMenuPages } from '@/command-menu/components/CommandMenuPages';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
@ -12,8 +12,12 @@ import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum';
export const useCommandMenuHotKeys = () => {
const { closeCommandMenu, toggleCommandMenu, setGlobalCommandMenuContext } =
useCommandMenu();
const {
closeCommandMenu,
openRecordsSearchPage,
toggleCommandMenu,
setGlobalCommandMenuContext,
} = useCommandMenu();
const commandMenuSearch = useRecoilValue(commandMenuSearchState);
@ -36,6 +40,18 @@ export const useCommandMenuHotKeys = () => {
[toggleCommandMenu],
);
useScopedHotkeys(
['/'],
() => {
openRecordsSearchPage();
},
AppHotkeyScope.KeyboardShortcutMenu,
[openRecordsSearchPage],
{
ignoreModifiers: true,
},
);
useScopedHotkeys(
[Key.Escape],
() => {

View File

@ -10,9 +10,10 @@ export const useMatchCommands = ({
const [deferredCommandMenuSearch] = useDebounce(commandMenuSearch, 300); // 200ms - 500ms
const checkInShortcuts = (cmd: Command, search: string) => {
return (cmd.firstHotKey + (cmd.secondHotKey ?? ''))
const concatenatedString = cmd.hotKeys?.join('') ?? '';
return concatenatedString
.toLowerCase()
.includes(search.toLowerCase());
.includes(search.toLowerCase().trim());
};
const checkInLabels = (cmd: Command, search: string) => {

View File

@ -12,24 +12,21 @@ export const useMatchingCommandMenuCommands = ({
copilotCommands,
navigateCommands,
actionRecordSelectionCommands,
actionObjectCommands,
actionGlobalCommands,
workflowRunRecordSelectionCommands,
workflowRunGlobalCommands,
peopleCommands,
companyCommands,
opportunityCommands,
noteCommands,
tasksCommands,
customObjectCommands,
isLoading,
} = useCommandMenuCommands();
const matchingNavigateCommand = matchCommands(navigateCommands);
const matchingNavigateCommands = matchCommands(navigateCommands);
const matchingStandardActionRecordSelectionCommands = matchCommands(
actionRecordSelectionCommands,
);
const matchingStandardActionObjectCommands =
matchCommands(actionObjectCommands);
const matchingStandardActionGlobalCommands =
matchCommands(actionGlobalCommands);
@ -41,33 +38,22 @@ export const useMatchingCommandMenuCommands = ({
workflowRunGlobalCommands,
);
const isNoResults =
const noResults =
!matchingStandardActionRecordSelectionCommands.length &&
!matchingWorkflowRunRecordSelectionCommands.length &&
!matchingStandardActionGlobalCommands.length &&
!matchingWorkflowRunGlobalCommands.length &&
!matchingNavigateCommand.length &&
!peopleCommands?.length &&
!companyCommands?.length &&
!opportunityCommands?.length &&
!noteCommands?.length &&
!tasksCommands?.length &&
!customObjectCommands?.length;
!matchingStandardActionObjectCommands.length &&
!matchingNavigateCommands.length;
return {
isNoResults,
isLoading,
noResults,
copilotCommands,
matchingStandardActionRecordSelectionCommands,
matchingStandardActionObjectCommands,
matchingWorkflowRunRecordSelectionCommands,
matchingStandardActionGlobalCommands,
matchingWorkflowRunGlobalCommands,
matchingNavigateCommand,
peopleCommands,
companyCommands,
opportunityCommands,
noteCommands,
tasksCommands,
customObjectCommands,
matchingNavigateCommands,
};
};

View File

@ -0,0 +1,229 @@
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
import { Note } from '@/activities/types/Note';
import { Task } from '@/activities/types/Task';
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
import { Company } from '@/companies/types/Company';
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { getCompanyDomainName } from '@/object-metadata/utils/getCompanyDomainName';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { useMultiObjectSearch } from '@/object-record/relation-picker/hooks/useMultiObjectSearch';
import { useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap } from '@/object-record/relation-picker/hooks/useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap';
import { makeOrFilterVariables } from '@/object-record/utils/makeOrFilterVariables';
import { t } from '@lingui/core/macro';
import isEmpty from 'lodash.isempty';
import { useMemo } from 'react';
import { useRecoilValue } from 'recoil';
import { Avatar, IconCheckbox, IconNotes } from 'twenty-ui';
import { useDebounce } from 'use-debounce';
import { getLogoUrlFromDomainName } from '~/utils';
const MAX_SEARCH_RESULTS_PER_OBJECT = 8;
export const useSearchRecords = () => {
const commandMenuSearch = useRecoilValue(commandMenuSearchState);
const [deferredCommandMenuSearch] = useDebounce(commandMenuSearch, 300);
const {
matchesSearchFilterObjectRecordsQueryResult,
matchesSearchFilterObjectRecordsLoading: loading,
} = useMultiObjectSearch({
excludedObjects: [CoreObjectNameSingular.Task, CoreObjectNameSingular.Note],
searchFilterValue: deferredCommandMenuSearch ?? undefined,
limit: MAX_SEARCH_RESULTS_PER_OBJECT,
});
const { objectRecordsMap: matchesSearchFilterObjectRecords } =
useMultiObjectSearchQueryResultFormattedAsObjectRecordsMap({
multiObjectRecordsQueryResult:
matchesSearchFilterObjectRecordsQueryResult,
});
const { loading: isNotesLoading, records: notes } = useFindManyRecords<Note>({
objectNameSingular: CoreObjectNameSingular.Note,
filter: deferredCommandMenuSearch
? makeOrFilterVariables([
{ title: { ilike: `%${deferredCommandMenuSearch}%` } },
{ body: { ilike: `%${deferredCommandMenuSearch}%` } },
])
: undefined,
limit: MAX_SEARCH_RESULTS_PER_OBJECT,
});
const { loading: isTasksLoading, records: tasks } = useFindManyRecords<Task>({
objectNameSingular: CoreObjectNameSingular.Task,
filter: deferredCommandMenuSearch
? makeOrFilterVariables([
{ title: { ilike: `%${deferredCommandMenuSearch}%` } },
{ body: { ilike: `%${deferredCommandMenuSearch}%` } },
])
: undefined,
limit: MAX_SEARCH_RESULTS_PER_OBJECT,
});
const people = matchesSearchFilterObjectRecords.people?.map(
(people) => people.record,
);
const companies = matchesSearchFilterObjectRecords.companies?.map(
(companies) => companies.record,
);
const opportunities = matchesSearchFilterObjectRecords.opportunities?.map(
(opportunities) => opportunities.record,
);
const peopleCommands = useMemo(
() =>
people?.map(({ id, name: { firstName, lastName }, avatarUrl }) => ({
id,
label: `${firstName} ${lastName}`,
to: `object/person/${id}`,
shouldCloseCommandMenuOnClick: true,
Icon: () => (
<Avatar
type="rounded"
avatarUrl={avatarUrl}
placeholderColorSeed={id}
placeholder={`${firstName} ${lastName}`}
/>
),
})),
[people],
);
const companyCommands = useMemo(
() =>
companies?.map((company) => ({
id: company.id,
label: company.name ?? '',
to: `object/company/${company.id}`,
shouldCloseCommandMenuOnClick: true,
Icon: () => (
<Avatar
placeholderColorSeed={company.id}
placeholder={company.name}
avatarUrl={getLogoUrlFromDomainName(
getCompanyDomainName(company as Company),
)}
/>
),
})),
[companies],
);
const opportunityCommands = useMemo(
() =>
opportunities?.map(({ id, name }) => ({
id,
label: name ?? '',
to: `object/opportunity/${id}`,
shouldCloseCommandMenuOnClick: true,
Icon: () => (
<Avatar
type="rounded"
avatarUrl={null}
placeholderColorSeed={id}
placeholder={name ?? ''}
/>
),
})),
[opportunities],
);
const openNoteRightDrawer = useOpenActivityRightDrawer({
objectNameSingular: CoreObjectNameSingular.Note,
});
const openTaskRightDrawer = useOpenActivityRightDrawer({
objectNameSingular: CoreObjectNameSingular.Task,
});
const noteCommands = useMemo(
() =>
notes?.map((note) => ({
id: note.id,
label: note.title ?? '',
to: '',
onCommandClick: () => openNoteRightDrawer(note.id),
shouldCloseCommandMenuOnClick: true,
Icon: IconNotes,
})),
[notes, openNoteRightDrawer],
);
const tasksCommands = useMemo(
() =>
tasks?.map((task) => ({
id: task.id,
label: task.title ?? '',
to: '',
onCommandClick: () => openTaskRightDrawer(task.id),
shouldCloseCommandMenuOnClick: true,
Icon: IconCheckbox,
})),
[tasks, openTaskRightDrawer],
);
const customObjectRecordsMap = useMemo(() => {
return Object.fromEntries(
Object.entries(matchesSearchFilterObjectRecords).filter(
([namePlural, records]) =>
![
CoreObjectNamePlural.Person,
CoreObjectNamePlural.Opportunity,
CoreObjectNamePlural.Company,
].includes(namePlural as CoreObjectNamePlural) && !isEmpty(records),
),
);
}, [matchesSearchFilterObjectRecords]);
const customObjectCommands = useMemo(() => {
return Object.values(customObjectRecordsMap).flatMap((objectRecords) =>
objectRecords.map((objectRecord) => ({
id: objectRecord.record.id,
label: objectRecord.recordIdentifier.name,
to: `object/${objectRecord.objectMetadataItem.nameSingular}/${objectRecord.record.id}`,
shouldCloseCommandMenuOnClick: true,
Icon: () => (
<Avatar
type="rounded"
avatarUrl={objectRecord.record.avatarUrl}
placeholderColorSeed={objectRecord.record.id}
placeholder={objectRecord.recordIdentifier.name ?? ''}
/>
),
})),
);
}, [customObjectRecordsMap]);
const commands = [
...(peopleCommands ?? []),
...(companyCommands ?? []),
...(opportunityCommands ?? []),
...(noteCommands ?? []),
...(tasksCommands ?? []),
...(customObjectCommands ?? []),
];
const noResults =
!peopleCommands?.length &&
!companyCommands?.length &&
!opportunityCommands?.length &&
!noteCommands?.length &&
!tasksCommands?.length &&
!customObjectCommands?.length;
return {
loading: loading || isNotesLoading || isTasksLoading,
noResults,
commandGroups: [
{
heading: t`Results`,
items: commands,
},
],
hasMore: false,
pageSize: 0,
onLoadMore: () => {},
};
};