Command menu refactoring (#9257)

Refactored the `CommandMenu` component to make it more readable and
easier to refactor.
The file was way too big so I introduced a few hooks and eliminated code
duplication.

Introduced:
- `useMatchCommands` hook to match commands with the search
- `useCommandMenuCommands` which returns all command menu commands
- `useMatchingCommandMenuCommands` to return the commands matched with
the search
- `CommandMenuContainer` to simplify the `DefaultLayout`

- Unmounted the `CommandMenu` when it wasn't opened to improve
performances

I also introduced a new behavior: Automatically select the first item
when opening the command menu:

https://github.com/user-attachments/assets/4b683d49-570e-47c9-8939-99f42ed8691c
This commit is contained in:
Raphaël Bosi
2024-12-30 15:22:49 +01:00
committed by GitHub
parent 0fa59d7718
commit 1091bc657d
24 changed files with 827 additions and 980 deletions

View File

@ -1,12 +1,5 @@
import { RecordActionMenuEntriesSetter } from '@/action-menu/actions/record-actions/components/RecordActionMenuEntriesSetter';
import { RecordAgnosticActionsSetterEffect } from '@/action-menu/actions/record-agnostic-actions/components/RecordAgnosticActionsSetterEffect';
import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals';
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
import { AuthModal } from '@/auth/components/AuthModal';
import { CommandMenu } from '@/command-menu/components/CommandMenu';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
import { CommandMenuContainer } from '@/command-menu/components/CommandMenuContainer';
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
import { KeyboardShortcutMenu } from '@/keyboard-shortcut-menu/components/KeyboardShortcutMenu';
import { AppNavigationDrawer } from '@/navigation/components/AppNavigationDrawer';
@ -18,8 +11,7 @@ import { SignInBackgroundMockPage } from '@/sign-in-background-mock/components/S
import { useShowAuthModal } from '@/ui/layout/hooks/useShowAuthModal';
import { NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/NavDrawerWidths';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { css, Global, useTheme } from '@emotion/react';
import { Global, css, useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { AnimatePresence, LayoutGroup, motion } from 'framer-motion';
import { Outlet } from 'react-router-dom';
@ -77,9 +69,6 @@ export const DefaultLayout = () => {
const theme = useTheme();
const windowsWidth = useScreenSize().width;
const showAuthModal = useShowAuthModal();
const { toggleCommandMenu } = useCommandMenu();
const isWorkflowEnabled = useIsFeatureEnabled('IS_WORKFLOW_ENABLED');
return (
<>
@ -93,25 +82,7 @@ export const DefaultLayout = () => {
<StyledLayout>
{!showAuthModal && (
<>
<ContextStoreComponentInstanceContext.Provider
value={{ instanceId: 'command-menu' }}
>
<ActionMenuComponentInstanceContext.Provider
value={{ instanceId: 'command-menu' }}
>
<ActionMenuContext.Provider
value={{
isInRightDrawer: false,
onActionExecutedCallback: toggleCommandMenu,
}}
>
<RecordActionMenuEntriesSetter />
{isWorkflowEnabled && <RecordAgnosticActionsSetterEffect />}
<ActionMenuConfirmationModals />
<CommandMenu />
</ActionMenuContext.Provider>
</ActionMenuComponentInstanceContext.Provider>
</ContextStoreComponentInstanceContext.Provider>
<CommandMenuContainer />
<KeyboardShortcutMenu />
</>
)}

View File

@ -26,7 +26,7 @@ export const SelectableList = ({
}: SelectableListProps) => {
useSelectableListHotKeys(selectableListId, hotkeyScope);
const { setSelectableItemIds, setSelectableListOnEnter } =
const { setSelectableItemIds, setSelectableListOnEnter, setSelectedItemId } =
useSelectableList(selectableListId);
useEffect(() => {
@ -47,7 +47,12 @@ export const SelectableList = ({
if (isDefined(selectableItemIdArray)) {
setSelectableItemIds(arrayToChunks(selectableItemIdArray, 1));
}
}, [selectableItemIdArray, selectableItemIdMatrix, setSelectableItemIds]);
}, [
selectableItemIdArray,
selectableItemIdMatrix,
setSelectableItemIds,
setSelectedItemId,
]);
return (
<SelectableListScope selectableListScopeId={selectableListId}>

View File

@ -33,11 +33,23 @@ export const useSelectableList = (selectableListId?: string) => {
[selectedItemIdState, isSelectedItemIdSelector],
);
const setSelectedItemId = useRecoilCallback(
({ set }) =>
(itemId: string) => {
resetSelectedItem();
set(selectedItemIdState, itemId);
set(isSelectedItemIdSelector(itemId), true);
},
[resetSelectedItem, selectedItemIdState, isSelectedItemIdSelector],
);
return {
selectableListId: scopeId,
setSelectableItemIds,
isSelectedItemIdSelector,
setSelectableListOnEnter,
resetSelectedItem,
setSelectedItemId,
selectedItemIdState,
};
};