491 save the page component instance id for side panel navigation (#10700)
Closes https://github.com/twentyhq/core-team-issues/issues/491 This PR: - Duplicates the right drawer pages for the command menu and replace all the states used in these pages by component states (The right drawer pages will be deleted when we deprecate the command menu v1) - Wraps those pages into a component instance provider - We store the component instance id upon navigation to restore the states when we navigate back to a page The only pages which are not updated for now are the pages related to the workflow objects, this will be done in another PR. In another PR, to improve the navigation experience I will replace the icons and titles of the chips by the label identifier and the avatar if the page is a record page. https://github.com/user-attachments/assets/a76d3345-01f3-4db9-8a55-331cca8b87e0 --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
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 { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { MenuItem } from 'twenty-ui';
|
||||
@ -14,6 +15,8 @@ export const CommandMenuContextChipGroups = ({
|
||||
}: {
|
||||
contextChips: CommandMenuContextChipProps[];
|
||||
}) => {
|
||||
const { closeDropdown } = useDropdownV2();
|
||||
|
||||
if (contextChips.length === 0) {
|
||||
return null;
|
||||
}
|
||||
@ -34,6 +37,7 @@ export const CommandMenuContextChipGroups = ({
|
||||
}
|
||||
|
||||
const firstChips = contextChips.slice(0, -1);
|
||||
const firstThreeChips = firstChips.slice(0, 3);
|
||||
const lastChip = contextChips.at(-1);
|
||||
|
||||
return (
|
||||
@ -42,8 +46,9 @@ export const CommandMenuContextChipGroups = ({
|
||||
<Dropdown
|
||||
clickableComponent={
|
||||
<CommandMenuContextChip
|
||||
Icons={firstChips.map((chip) => chip.Icons?.[0])}
|
||||
Icons={firstThreeChips.map((chip) => chip.Icons?.[0])}
|
||||
onClick={() => {}}
|
||||
text={`${firstChips.length}`}
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
@ -52,7 +57,10 @@ export const CommandMenuContextChipGroups = ({
|
||||
<MenuItem
|
||||
LeftComponent={chip.Icons}
|
||||
text={chip.text}
|
||||
onClick={chip.onClick}
|
||||
onClick={() => {
|
||||
closeDropdown(COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID);
|
||||
chip.onClick?.();
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuItemsContainer>
|
||||
|
||||
@ -4,7 +4,7 @@ import { getSelectedRecordsContextText } from '@/command-menu/utils/getRecordCon
|
||||
import { useFindManyRecordsSelectedInContextStore } from '@/context-store/hooks/useFindManyRecordsSelectedInContextStore';
|
||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||
|
||||
export const CommandMenuContextRecordChip = ({
|
||||
export const CommandMenuContextRecordsChip = ({
|
||||
objectMetadataItemId,
|
||||
instanceId,
|
||||
}: {
|
||||
@ -1,7 +1,9 @@
|
||||
import { CommandMenuContainer } from '@/command-menu/components/CommandMenuContainer';
|
||||
import { CommandMenuTopBar } from '@/command-menu/components/CommandMenuTopBar';
|
||||
import { COMMAND_MENU_PAGES_CONFIG } from '@/command-menu/constants/CommandMenuPagesConfig';
|
||||
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
|
||||
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
||||
import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { motion } from 'framer-motion';
|
||||
@ -16,6 +18,8 @@ const StyledCommandMenuContent = styled.div`
|
||||
export const CommandMenuRouter = () => {
|
||||
const commandMenuPage = useRecoilValue(commandMenuPageState);
|
||||
|
||||
const commandMenuPageInfo = useRecoilValue(commandMenuPageInfoState);
|
||||
|
||||
const commandMenuPageComponent = isDefined(commandMenuPage) ? (
|
||||
COMMAND_MENU_PAGES_CONFIG.get(commandMenuPage)
|
||||
) : (
|
||||
@ -26,20 +30,24 @@ export const CommandMenuRouter = () => {
|
||||
|
||||
return (
|
||||
<CommandMenuContainer>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{
|
||||
duration: theme.animation.duration.instant,
|
||||
delay: 0.1,
|
||||
}}
|
||||
<CommandMenuPageComponentInstanceContext.Provider
|
||||
value={{ instanceId: commandMenuPageInfo.instanceId }}
|
||||
>
|
||||
<CommandMenuTopBar />
|
||||
</motion.div>
|
||||
<StyledCommandMenuContent>
|
||||
{commandMenuPageComponent}
|
||||
</StyledCommandMenuContent>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{
|
||||
duration: theme.animation.duration.instant,
|
||||
delay: 0.1,
|
||||
}}
|
||||
>
|
||||
<CommandMenuTopBar />
|
||||
</motion.div>
|
||||
<StyledCommandMenuContent>
|
||||
{commandMenuPageComponent}
|
||||
</StyledCommandMenuContent>
|
||||
</CommandMenuPageComponentInstanceContext.Provider>
|
||||
</CommandMenuContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@ -128,16 +128,21 @@ export const CommandMenuTopBar = () => {
|
||||
(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
|
||||
return filteredCommandMenuNavigationStack.map((page, index) => {
|
||||
const isLastChip =
|
||||
index === filteredCommandMenuNavigationStack.length - 1;
|
||||
|
||||
return {
|
||||
page,
|
||||
Icons: [<page.pageIcon size={theme.icon.size.sm} />],
|
||||
text: page.pageTitle,
|
||||
onClick: isLastChip
|
||||
? undefined
|
||||
: () => {
|
||||
navigateCommandMenuHistory(index);
|
||||
},
|
||||
}));
|
||||
};
|
||||
});
|
||||
}, [
|
||||
commandMenuNavigationStack,
|
||||
navigateCommandMenuHistory,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { CommandMenuContextRecordChip } from '@/command-menu/components/CommandMenuContextRecordChip';
|
||||
import { CommandMenuContextRecordsChip } from '@/command-menu/components/CommandMenuContextRecordsChip';
|
||||
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
|
||||
import { RESET_CONTEXT_TO_SELECTION } from '@/command-menu/constants/ResetContextToSelection';
|
||||
import { useResetPreviousCommandMenuContext } from '@/command-menu/hooks/useResetPreviousCommandMenuContext';
|
||||
@ -46,7 +46,7 @@ export const ResetContextToSelectionCommandButton = () => {
|
||||
Icon={IconArrowBackUp}
|
||||
label={t`Reset to`}
|
||||
RightComponent={
|
||||
<CommandMenuContextRecordChip
|
||||
<CommandMenuContextRecordsChip
|
||||
objectMetadataItemId={objectMetadataItem.id}
|
||||
instanceId="command-menu-previous"
|
||||
/>
|
||||
|
||||
@ -100,6 +100,7 @@ const meta: Meta<typeof CommandMenu> = {
|
||||
page: CommandMenuPages.Root,
|
||||
pageTitle: 'Command Menu',
|
||||
pageIcon: IconDotsVertical,
|
||||
pageId: '1',
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import { Decorator, Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { CommandMenuContextRecordChip } from '@/command-menu/components/CommandMenuContextRecordChip';
|
||||
import { CommandMenuContextRecordsChip } from '@/command-menu/components/CommandMenuContextRecordsChip';
|
||||
import { PreComputedChipGeneratorsContext } from '@/object-metadata/contexts/PreComputedChipGeneratorsContext';
|
||||
import { RecordChipData } from '@/object-record/record-field/types/RecordChipData';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
@ -207,9 +207,9 @@ const ContextStoreDecorator: Decorator = (Story) => {
|
||||
);
|
||||
};
|
||||
|
||||
const meta: Meta<typeof CommandMenuContextRecordChip> = {
|
||||
const meta: Meta<typeof CommandMenuContextRecordsChip> = {
|
||||
title: 'Modules/CommandMenu/CommandMenuContextRecordChip',
|
||||
component: CommandMenuContextRecordChip,
|
||||
component: CommandMenuContextRecordsChip,
|
||||
decorators: [
|
||||
ContextStoreDecorator,
|
||||
ChipGeneratorsDecorator,
|
||||
@ -221,7 +221,7 @@ const meta: Meta<typeof CommandMenuContextRecordChip> = {
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof CommandMenuContextRecordChip>;
|
||||
type Story = StoryObj<typeof CommandMenuContextRecordsChip>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user