322 compact command chips dropdown (#10456)
Closes https://github.com/twentyhq/core-team-issues/issues/322 https://github.com/user-attachments/assets/d4806f04-e217-40f5-9707-93334bbd49ea --------- Co-authored-by: Devessier <baptiste@devessier.fr>
This commit is contained in:
@ -1,4 +1,9 @@
|
||||
import { COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID } from '@/command-menu/constants/CommandMenuContextChipGroupsDropdownId';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { MenuItem } from 'twenty-ui';
|
||||
import {
|
||||
CommandMenuContextChip,
|
||||
CommandMenuContextChipProps,
|
||||
@ -34,9 +39,30 @@ export const CommandMenuContextChipGroups = ({
|
||||
return (
|
||||
<>
|
||||
{firstChips.length > 0 && (
|
||||
<CommandMenuContextChip
|
||||
Icons={firstChips.map((chip) => chip.Icons?.[0])}
|
||||
/>
|
||||
<Dropdown
|
||||
clickableComponent={
|
||||
<CommandMenuContextChip
|
||||
Icons={firstChips.map((chip) => chip.Icons?.[0])}
|
||||
onClick={() => {}}
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
{firstChips.map((chip) => (
|
||||
<MenuItem
|
||||
LeftComponent={chip.Icons}
|
||||
text={chip.text}
|
||||
onClick={chip.onClick}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
}
|
||||
dropdownHotkeyScope={{
|
||||
scope: AppHotkeyScope.CommandMenu,
|
||||
}}
|
||||
dropdownId={COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID}
|
||||
dropdownPlacement="bottom-start"
|
||||
></Dropdown>
|
||||
)}
|
||||
|
||||
{isDefined(lastChip) && (
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { CommandMenuContextChipGroups } from '@/command-menu/components/CommandMenuContextChipGroups';
|
||||
import { CommandMenuContextRecordChipAvatars } from '@/command-menu/components/CommandMenuContextRecordChipAvatars';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { getSelectedRecordsContextText } from '@/command-menu/utils/getRecordContextText';
|
||||
import { useFindManyRecordsSelectedInContextStore } from '@/context-store/hooks/useFindManyRecordsSelectedInContextStore';
|
||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||
@ -22,6 +23,8 @@ export const CommandMenuContextChipGroupsWithRecordSelection = ({
|
||||
limit: 3,
|
||||
});
|
||||
|
||||
const { openRootCommandMenu } = useCommandMenu();
|
||||
|
||||
if (loading) {
|
||||
return null;
|
||||
}
|
||||
@ -43,6 +46,7 @@ export const CommandMenuContextChipGroupsWithRecordSelection = ({
|
||||
totalCount,
|
||||
),
|
||||
Icons: Avatars,
|
||||
onClick: contextChips.length > 0 ? openRootCommandMenu : undefined,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
@ -99,7 +100,11 @@ export const CommandMenuTopBar = () => {
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const { closeCommandMenu, goBackFromCommandMenu } = useCommandMenu();
|
||||
const {
|
||||
closeCommandMenu,
|
||||
goBackFromCommandMenu,
|
||||
navigateCommandMenuHistory,
|
||||
} = useCommandMenu();
|
||||
|
||||
const contextStoreCurrentObjectMetadataItem = useRecoilComponentValueV2(
|
||||
contextStoreCurrentObjectMetadataItemComponentState,
|
||||
@ -118,35 +123,56 @@ export const CommandMenuTopBar = () => {
|
||||
);
|
||||
|
||||
const contextChips = useMemo(() => {
|
||||
return commandMenuNavigationStack
|
||||
.filter((page) => page.page !== CommandMenuPages.Root)
|
||||
.map((page) => {
|
||||
return {
|
||||
Icons: [<page.pageIcon size={theme.icon.size.sm} />],
|
||||
text: page.pageTitle,
|
||||
};
|
||||
});
|
||||
}, [commandMenuNavigationStack, theme.icon.size.sm]);
|
||||
const filteredCommandMenuNavigationStack =
|
||||
commandMenuNavigationStack.filter(
|
||||
(page) => page.page !== CommandMenuPages.Root,
|
||||
);
|
||||
|
||||
return filteredCommandMenuNavigationStack.map((page, index) => ({
|
||||
Icons: [<page.pageIcon size={theme.icon.size.sm} />],
|
||||
text: page.pageTitle,
|
||||
onClick:
|
||||
index === filteredCommandMenuNavigationStack.length - 1
|
||||
? undefined
|
||||
: () => {
|
||||
navigateCommandMenuHistory(index);
|
||||
},
|
||||
}));
|
||||
}, [
|
||||
commandMenuNavigationStack,
|
||||
navigateCommandMenuHistory,
|
||||
theme.icon.size.sm,
|
||||
]);
|
||||
|
||||
const location = useLocation();
|
||||
const isButtonVisible =
|
||||
!location.pathname.startsWith('/objects/') &&
|
||||
!location.pathname.startsWith('/object/');
|
||||
|
||||
const backButtonAnimationDuration =
|
||||
contextChips.length > 0 ? theme.animation.duration.instant : 0;
|
||||
|
||||
return (
|
||||
<StyledInputContainer>
|
||||
<StyledContentContainer>
|
||||
{isCommandMenuV2Enabled && (
|
||||
<>
|
||||
{commandMenuPage !== CommandMenuPages.Root && (
|
||||
<CommandMenuContextChip
|
||||
Icons={[<IconChevronLeft size={theme.icon.size.sm} />]}
|
||||
onClick={() => {
|
||||
goBackFromCommandMenu();
|
||||
}}
|
||||
testId="command-menu-go-back-button"
|
||||
/>
|
||||
)}
|
||||
<AnimatePresence>
|
||||
{commandMenuPage !== CommandMenuPages.Root && (
|
||||
<motion.div
|
||||
exit={{ opacity: 0, width: 0 }}
|
||||
transition={{
|
||||
duration: backButtonAnimationDuration,
|
||||
}}
|
||||
>
|
||||
<CommandMenuContextChip
|
||||
Icons={[<IconChevronLeft size={theme.icon.size.sm} />]}
|
||||
onClick={goBackFromCommandMenu}
|
||||
testId="command-menu-go-back-button"
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
{isDefined(contextStoreCurrentObjectMetadataItem) &&
|
||||
commandMenuPage !== CommandMenuPages.SearchRecords ? (
|
||||
<CommandMenuContextChipGroupsWithRecordSelection
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
export const COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID =
|
||||
'command-menu-context-chip-groups-dropdown';
|
||||
@ -7,6 +7,7 @@ import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousH
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { IconDotsVertical, IconSearch, useIcons } from 'twenty-ui';
|
||||
|
||||
import { COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID } from '@/command-menu/constants/CommandMenuContextChipGroupsDropdownId';
|
||||
import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates';
|
||||
import { useResetContextStoreStates } from '@/command-menu/hooks/useResetContextStoreStates';
|
||||
import {
|
||||
@ -25,6 +26,7 @@ import { mainContextStoreComponentInstanceIdState } from '@/context-store/states
|
||||
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 { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
||||
import { emitRightDrawerCloseEvent } from '@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useCallback } from 'react';
|
||||
@ -46,6 +48,8 @@ export const useCommandMenu = () => {
|
||||
const { copyContextStoreStates } = useCopyContextStoreStates();
|
||||
const { resetContextStoreStates } = useResetContextStoreStates();
|
||||
|
||||
const { closeDropdown } = useDropdownV2();
|
||||
|
||||
const openCommandMenu = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
() => {
|
||||
@ -53,6 +57,8 @@ export const useCommandMenu = () => {
|
||||
.getLoadable(isCommandMenuOpenedState)
|
||||
.getValue();
|
||||
|
||||
setHotkeyScopeAndMemorizePreviousScope(AppHotkeyScope.CommandMenuOpen);
|
||||
|
||||
if (isCommandMenuOpened) {
|
||||
return;
|
||||
}
|
||||
@ -63,7 +69,6 @@ export const useCommandMenu = () => {
|
||||
});
|
||||
|
||||
set(isCommandMenuOpenedState, true);
|
||||
setHotkeyScopeAndMemorizePreviousScope(AppHotkeyScope.CommandMenuOpen);
|
||||
set(hasUserSelectedCommandState, false);
|
||||
},
|
||||
[
|
||||
@ -77,8 +82,9 @@ export const useCommandMenu = () => {
|
||||
({ set }) =>
|
||||
() => {
|
||||
set(isCommandMenuOpenedState, false);
|
||||
closeDropdown(COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID);
|
||||
},
|
||||
[],
|
||||
[closeDropdown],
|
||||
);
|
||||
|
||||
const onCommandMenuCloseAnimationComplete = useRecoilCallback(
|
||||
@ -115,6 +121,7 @@ export const useCommandMenu = () => {
|
||||
}: CommandMenuNavigationStackItem & {
|
||||
resetNavigationStack?: boolean;
|
||||
}) => {
|
||||
closeDropdown(COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID);
|
||||
set(commandMenuPageState, page);
|
||||
set(commandMenuPageInfoState, {
|
||||
title: pageTitle,
|
||||
@ -136,7 +143,7 @@ export const useCommandMenu = () => {
|
||||
openCommandMenu();
|
||||
};
|
||||
},
|
||||
[openCommandMenu],
|
||||
[closeDropdown, openCommandMenu],
|
||||
);
|
||||
|
||||
const openRootCommandMenu = useCallback(() => {
|
||||
@ -144,6 +151,7 @@ export const useCommandMenu = () => {
|
||||
page: CommandMenuPages.Root,
|
||||
pageTitle: 'Command Menu',
|
||||
pageIcon: IconDotsVertical,
|
||||
resetNavigationStack: true,
|
||||
});
|
||||
}, [navigateCommandMenu]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user