04b0a65e73
[Figma Design](https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?node-id=81380-344641&t=FpjWNOK2gZuDQQfr-0) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Adds a side panel layout for the Command Menu, routes modals into a local container, updates the top bar and context chips, and standardizes small button sizes. > > - **Command Menu**: > - **Side Panel Layout**: Introduces `CommandMenuSidePanelLayout` with animated width, hosts `CommandMenuRouter`, and provides a modal container via `ModalContainerContext`. > - **Top Bar**: Redesign (`CommandMenuTopBar`) with back icon, optional AI sparkles action, compact height (`COMMAND_MENU_SEARCH_BAR_HEIGHT=40`), and updated placeholder. > - **Context Chips**: Adds `CommandMenuLastContextChip` and `CommandMenuRecordInfo`; extends `CommandMenuContextChip` with `page` prop; updates `CommandMenuContextChipGroups` to render last chip as record info when applicable. > - **Container Simplification**: `CommandMenuContainer` simplified to just provide contexts and `AgentChatProvider`. > - **Modal System**: > - Adds `ModalContainerContext` and updates `Modal` to portal into provided container; `Modal.Backdrop` supports `isInContainer`. > - Updates usages (e.g., `UserOrMetadataLoader`, `ActionModal`) to align with new modal behavior. > - **Page Integration**: > - Replaces `PageBody` with `CommandMenuSidePanelLayout` in `RecordShowPage` and `RecordIndexContainerGater`. > - Removes global `CommandMenuRouter` from `DefaultLayout` (keeps keyboard shortcuts). > - **UI/Styling**: > - Standardizes several buttons to `size="small"` (e.g., command actions, open record, options, reply, workflow footer). > - Adjusts `ShowPageSubContainer` styling when rendered inside command menu. > - Storybook tests updated for new placeholder text. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 81fcaa145618a2fa1c3e11e2dc88fe832c51374c. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com> Co-authored-by: Devessier <baptiste@devessier.fr> Co-authored-by: Aman Raj <92664006+araj00@users.noreply.github.com> Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com> Co-authored-by: Paul Rastoin <45004772+prastoin@users.noreply.github.com>
125 lines
4.2 KiB
TypeScript
125 lines
4.2 KiB
TypeScript
import { COMMAND_MENU_COMPONENT_INSTANCE_ID } from '@/command-menu/constants/CommandMenuComponentInstanceId';
|
|
import { SIDE_PANEL_FOCUS_ID } from '@/command-menu/constants/SidePanelFocusId';
|
|
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
|
import { useCommandMenuHistory } from '@/command-menu/hooks/useCommandMenuHistory';
|
|
import { useOpenAskAIPageInCommandMenu } from '@/command-menu/hooks/useOpenAskAIPageInCommandMenu';
|
|
import { useOpenRecordsSearchPageInCommandMenu } from '@/command-menu/hooks/useOpenRecordsSearchPageInCommandMenu';
|
|
import { useSetGlobalCommandMenuContext } from '@/command-menu/hooks/useSetGlobalCommandMenuContext';
|
|
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 { useGlobalHotkeys } from '@/ui/utilities/hotkey/hooks/useGlobalHotkeys';
|
|
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
|
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
|
import { isNonEmptyString } from '@sniptt/guards';
|
|
import { useRecoilValue } from 'recoil';
|
|
import { Key } from 'ts-key-enum';
|
|
import { FeatureFlagKey } from '~/generated/graphql';
|
|
|
|
export const useCommandMenuHotKeys = () => {
|
|
const { toggleCommandMenu } = useCommandMenu();
|
|
|
|
const { openRecordsSearchPage } = useOpenRecordsSearchPageInCommandMenu();
|
|
|
|
const { openAskAIPage } = useOpenAskAIPageInCommandMenu();
|
|
|
|
const { goBackFromCommandMenu } = useCommandMenuHistory();
|
|
|
|
const { setGlobalCommandMenuContext } = useSetGlobalCommandMenuContext();
|
|
|
|
const commandMenuSearch = useRecoilValue(commandMenuSearchState);
|
|
|
|
const { closeKeyboardShortcutMenu } = useKeyboardShortcutMenu();
|
|
|
|
const commandMenuPage = useRecoilValue(commandMenuPageState);
|
|
|
|
const isAiEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_AI_ENABLED);
|
|
|
|
const contextStoreTargetedRecordsRuleComponent = useRecoilComponentValue(
|
|
contextStoreTargetedRecordsRuleComponentState,
|
|
COMMAND_MENU_COMPONENT_INSTANCE_ID,
|
|
);
|
|
|
|
useGlobalHotkeys({
|
|
keys: ['ctrl+k', 'meta+k'],
|
|
callback: () => {
|
|
closeKeyboardShortcutMenu();
|
|
toggleCommandMenu();
|
|
},
|
|
containsModifier: true,
|
|
dependencies: [closeKeyboardShortcutMenu, toggleCommandMenu],
|
|
});
|
|
|
|
useGlobalHotkeys({
|
|
keys: ['/'],
|
|
callback: () => {
|
|
openRecordsSearchPage();
|
|
},
|
|
containsModifier: false,
|
|
dependencies: [openRecordsSearchPage],
|
|
options: {
|
|
ignoreModifiers: true,
|
|
},
|
|
});
|
|
|
|
useGlobalHotkeys({
|
|
keys: ['@'],
|
|
callback: () => {
|
|
if (isAiEnabled) {
|
|
openAskAIPage({ resetNavigationStack: true });
|
|
}
|
|
},
|
|
containsModifier: false,
|
|
dependencies: [openAskAIPage, isAiEnabled],
|
|
options: {
|
|
ignoreModifiers: true,
|
|
},
|
|
});
|
|
|
|
useHotkeysOnFocusedElement({
|
|
keys: [Key.Escape],
|
|
callback: () => {
|
|
goBackFromCommandMenu();
|
|
},
|
|
focusId: SIDE_PANEL_FOCUS_ID,
|
|
dependencies: [goBackFromCommandMenu],
|
|
});
|
|
|
|
useHotkeysOnFocusedElement({
|
|
keys: [Key.Backspace, Key.Delete],
|
|
callback: () => {
|
|
if (isNonEmptyString(commandMenuSearch)) {
|
|
return;
|
|
}
|
|
|
|
if (
|
|
commandMenuPage === CommandMenuPages.Root &&
|
|
!(
|
|
contextStoreTargetedRecordsRuleComponent.mode === 'selection' &&
|
|
contextStoreTargetedRecordsRuleComponent.selectedRecordIds.length ===
|
|
0
|
|
)
|
|
) {
|
|
setGlobalCommandMenuContext();
|
|
}
|
|
if (commandMenuPage !== CommandMenuPages.Root) {
|
|
goBackFromCommandMenu();
|
|
}
|
|
},
|
|
focusId: SIDE_PANEL_FOCUS_ID,
|
|
dependencies: [
|
|
commandMenuPage,
|
|
commandMenuSearch,
|
|
contextStoreTargetedRecordsRuleComponent,
|
|
goBackFromCommandMenu,
|
|
setGlobalCommandMenuContext,
|
|
],
|
|
options: {
|
|
preventDefault: false,
|
|
},
|
|
});
|
|
};
|