Files
twenty/front/src/modules/command-menu/components/CommandMenu.tsx
T
279630052f 2294 feat(frontend): styling shortcut keys (#2336)
* 2294 feat(frontend): styling shortcut keys

* 2294 fix(front): pr requested changes

* Fix component interface

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2023-11-09 15:18:51 +01:00

208 lines
6.0 KiB
TypeScript

import { useState } from 'react';
import { useRecoilValue } from 'recoil';
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
import { IconNotes } from '@/ui/display/icon';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { Avatar } from '@/users/components/Avatar';
import {
QueryMode,
useSearchActivityQuery,
useSearchCompanyQuery,
useSearchPeopleQuery,
} from '~/generated/graphql';
import { getLogoUrlFromDomainName } from '~/utils';
import { useCommandMenu } from '../hooks/useCommandMenu';
import { commandMenuCommandsState } from '../states/commandMenuCommandsState';
import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState';
import { Command, CommandType } from '../types/Command';
import { CommandGroup } from './CommandGroup';
import { CommandMenuItem } from './CommandMenuItem';
import {
StyledDialog,
StyledEmpty,
StyledInput,
StyledList,
} from './CommandMenuStyles';
export const CommandMenu = () => {
const { openCommandMenu, closeCommandMenu } = useCommandMenu();
const openActivityRightDrawer = useOpenActivityRightDrawer();
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
const [search, setSearch] = useState('');
const commandMenuCommands = useRecoilValue(commandMenuCommandsState);
useScopedHotkeys(
'ctrl+k,meta+k',
() => {
setSearch('');
openCommandMenu();
},
AppHotkeyScope.CommandMenu,
[openCommandMenu, setSearch],
);
const { data: peopleData } = useSearchPeopleQuery({
skip: !isCommandMenuOpened,
variables: {
where: {
OR: [
{ firstName: { contains: search, mode: QueryMode.Insensitive } },
{ lastName: { contains: search, mode: QueryMode.Insensitive } },
],
},
limit: 3,
},
});
const people = peopleData?.searchResults ?? [];
const { data: companyData } = useSearchCompanyQuery({
skip: !isCommandMenuOpened,
variables: {
where: {
OR: [{ name: { contains: search, mode: QueryMode.Insensitive } }],
},
limit: 3,
},
});
const companies = companyData?.searchResults ?? [];
const { data: activityData } = useSearchActivityQuery({
skip: !isCommandMenuOpened,
variables: {
where: {
OR: [
{ title: { contains: search, mode: QueryMode.Insensitive } },
{ body: { contains: search, mode: QueryMode.Insensitive } },
],
},
limit: 3,
},
});
const activities = activityData?.searchResults ?? [];
const checkInShortcuts = (cmd: Command, search: string) => {
return (cmd.firstHotKey + (cmd.secondHotKey ?? ''))
.toLowerCase()
.includes(search.toLowerCase());
};
const checkInLabels = (cmd: Command, search: string) => {
if (cmd.label) {
return cmd.label.toLowerCase().includes(search.toLowerCase());
}
return false;
};
const matchingNavigateCommand = commandMenuCommands.filter(
(cmd) =>
(search.length > 0
? checkInShortcuts(cmd, search) || checkInLabels(cmd, search)
: true) && cmd.type === CommandType.Navigate,
);
const matchingCreateCommand = commandMenuCommands.filter(
(cmd) =>
(search.length > 0
? checkInShortcuts(cmd, search) || checkInLabels(cmd, search)
: true) && cmd.type === CommandType.Create,
);
return (
<StyledDialog
open={isCommandMenuOpened}
onOpenChange={(opened) => {
if (!opened) {
closeCommandMenu();
}
}}
shouldFilter={false}
label="Global Command Menu"
>
<StyledInput
value={search}
placeholder="Search"
onValueChange={setSearch}
/>
<StyledList>
<StyledEmpty>No results found.</StyledEmpty>
<CommandGroup heading="Create">
{matchingCreateCommand.map((cmd) => (
<CommandMenuItem
to={cmd.to}
key={cmd.label}
Icon={cmd.Icon}
label={cmd.label}
onClick={cmd.onCommandClick}
firstHotKey={cmd.firstHotKey}
secondHotKey={cmd.secondHotKey}
/>
))}
</CommandGroup>
<CommandGroup heading="Navigate">
{matchingNavigateCommand.map((cmd) => (
<CommandMenuItem
to={cmd.to}
key={cmd.label}
label={cmd.label}
Icon={cmd.Icon}
onClick={cmd.onCommandClick}
firstHotKey={cmd.firstHotKey}
secondHotKey={cmd.secondHotKey}
/>
))}
</CommandGroup>
<CommandGroup heading="People">
{people.map((person) => (
<CommandMenuItem
key={person.id}
to={`person/${person.id}`}
label={person.displayName}
Icon={() => (
<Avatar
type="rounded"
avatarUrl={null}
colorId={person.id}
placeholder={person.displayName}
/>
)}
/>
))}
</CommandGroup>
<CommandGroup heading="Companies">
{companies.map((company) => (
<CommandMenuItem
key={company.id}
label={company.name}
to={`companies/${company.id}`}
Icon={() => (
<Avatar
colorId={company.id}
placeholder={company.name}
avatarUrl={getLogoUrlFromDomainName(company.domainName)}
/>
)}
/>
))}
</CommandGroup>
<CommandGroup heading="Notes">
{activities.map((activity) => (
<CommandMenuItem
Icon={IconNotes}
key={activity.id}
label={activity.title ?? ''}
onClick={() => openActivityRightDrawer(activity.id)}
/>
))}
</CommandGroup>
</StyledList>
</StyledDialog>
);
};