refactor(command-menu-item): rename Actions to CommandMenuItem (#18489)
actions are being renamed to command menu item, they will be migrated to server and will be served as headless front components --------- Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com>
This commit is contained in:
+47
@@ -0,0 +1,47 @@
|
||||
import { CommandMenuItemComponent } from '@/command-menu-item/display/components/CommandMenuItemComponent';
|
||||
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { styled } from '@linaria/react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useContext } from 'react';
|
||||
import { ThemeContext } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledActionContainer = styled(motion.div)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
export const PageHeaderCommandMenuButtons = () => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const { commandMenuItems } = useContext(CommandMenuContext);
|
||||
const pinnedActions = commandMenuItems
|
||||
.filter((entry) => entry.isPinned)
|
||||
.toReversed();
|
||||
|
||||
const actionsWithPositionForAnimation = pinnedActions.map(
|
||||
(action, index) => ({
|
||||
action,
|
||||
position: pinnedActions.length - index - 1,
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{actionsWithPositionForAnimation.map(({ action, position }) => (
|
||||
<StyledActionContainer
|
||||
key={position}
|
||||
layout
|
||||
initial={{ width: 0, opacity: 0 }}
|
||||
animate={{ width: 'unset', opacity: 1 }}
|
||||
exit={{ width: 0, opacity: 0 }}
|
||||
transition={{
|
||||
duration: theme.animation.duration.instant,
|
||||
ease: 'easeInOut',
|
||||
}}
|
||||
>
|
||||
<CommandMenuItemComponent action={action} />
|
||||
</StyledActionContainer>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import { PageHeaderCommandMenuButtons } from '@/command-menu-item/components/PageHeaderCommandMenuButtons';
|
||||
import { RecordIndexCommandMenuDropdown } from '@/command-menu-item/components/RecordIndexCommandMenuDropdown';
|
||||
import { CommandMenuContextProvider } from '@/command-menu-item/contexts/CommandMenuContextProvider';
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useIsMobile } from 'twenty-ui/utilities';
|
||||
|
||||
export const RecordIndexCommandMenu = () => {
|
||||
const contextStoreCurrentObjectMetadataItemId = useAtomComponentStateValue(
|
||||
contextStoreCurrentObjectMetadataItemIdComponentState,
|
||||
MAIN_CONTEXT_STORE_INSTANCE_ID,
|
||||
);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<>
|
||||
{contextStoreCurrentObjectMetadataItemId && (
|
||||
<>
|
||||
<CommandMenuContextProvider
|
||||
isInSidePanel={false}
|
||||
displayType="button"
|
||||
containerType="index-page-header"
|
||||
>
|
||||
{!isMobile && <PageHeaderCommandMenuButtons />}
|
||||
</CommandMenuContextProvider>
|
||||
<CommandMenuContextProvider
|
||||
isInSidePanel={false}
|
||||
displayType="dropdownItem"
|
||||
containerType="index-page-dropdown"
|
||||
>
|
||||
<RecordIndexCommandMenuDropdown />
|
||||
</CommandMenuContextProvider>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
import { CommandMenuItemComponent } from '@/command-menu-item/display/components/CommandMenuItemComponent';
|
||||
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
|
||||
import { CommandMenuItemType } from '@/command-menu-item/types/CommandMenuItemType';
|
||||
import { COMMAND_MENU_DROPDOWN_CLICK_OUTSIDE_ID } from '@/command-menu-item/constants/CommandMenuDropdownClickOutsideId';
|
||||
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext';
|
||||
import { recordIndexCommandMenuDropdownPositionComponentState } from '@/command-menu-item/states/recordIndexCommandMenuDropdownPositionComponentState';
|
||||
import { getCommandMenuDropdownIdFromCommandMenuId } from '@/command-menu-item/utils/getCommandMenuDropdownIdFromCommandMenuId';
|
||||
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { SelectableListItem } from '@/ui/layout/selectable-list/components/SelectableListItem';
|
||||
import { selectedItemIdComponentState } from '@/ui/layout/selectable-list/states/selectedItemIdComponentState';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useContext } from 'react';
|
||||
import { IconLayoutSidebarRightExpand } from 'twenty-ui/display';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
|
||||
const StyledDropdownMenuContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const RecordIndexCommandMenuDropdown = () => {
|
||||
const { t } = useLingui();
|
||||
const { commandMenuItems } = useContext(CommandMenuContext);
|
||||
|
||||
const recordIndexActions = commandMenuItems.filter(
|
||||
(action) =>
|
||||
action.type === CommandMenuItemType.Standard &&
|
||||
action.scope === CommandMenuItemScope.RecordSelection,
|
||||
);
|
||||
|
||||
const commandMenuId = useAvailableComponentInstanceIdOrThrow(
|
||||
CommandMenuComponentInstanceContext,
|
||||
);
|
||||
|
||||
const dropdownId = getCommandMenuDropdownIdFromCommandMenuId(commandMenuId);
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const recordIndexCommandMenuDropdownPosition = useAtomComponentStateValue(
|
||||
recordIndexCommandMenuDropdownPositionComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
const { openSidePanelMenu } = useSidePanelMenu();
|
||||
|
||||
const selectedItemIdArray = [
|
||||
...recordIndexActions.map((action) => action.key),
|
||||
'more-actions',
|
||||
];
|
||||
|
||||
const selectedItemId = useAtomComponentStateValue(
|
||||
selectedItemIdComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
dropdownId={dropdownId}
|
||||
data-select-disable
|
||||
dropdownPlacement="bottom-start"
|
||||
dropdownStrategy="absolute"
|
||||
dropdownOffset={{
|
||||
x: recordIndexCommandMenuDropdownPosition.x ?? 0,
|
||||
y: recordIndexCommandMenuDropdownPosition.y ?? 0,
|
||||
}}
|
||||
dropdownComponents={
|
||||
<DropdownContent>
|
||||
<StyledDropdownMenuContainer
|
||||
data-click-outside-id={COMMAND_MENU_DROPDOWN_CLICK_OUTSIDE_ID}
|
||||
>
|
||||
<DropdownMenuItemsContainer>
|
||||
<SelectableList
|
||||
focusId={dropdownId}
|
||||
selectableItemIdArray={selectedItemIdArray}
|
||||
selectableListInstanceId={dropdownId}
|
||||
>
|
||||
{recordIndexActions.map((action) => (
|
||||
<CommandMenuItemComponent action={action} key={action.key} />
|
||||
))}
|
||||
<SelectableListItem
|
||||
itemId="more-actions"
|
||||
key="more-actions"
|
||||
onEnter={() => {
|
||||
closeDropdown(dropdownId);
|
||||
openSidePanelMenu();
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
LeftIcon={IconLayoutSidebarRightExpand}
|
||||
onClick={() => {
|
||||
closeDropdown(dropdownId);
|
||||
openSidePanelMenu();
|
||||
}}
|
||||
focused={selectedItemId === 'more-actions'}
|
||||
text={t`More actions`}
|
||||
/>
|
||||
</SelectableListItem>
|
||||
</SelectableList>
|
||||
</DropdownMenuItemsContainer>
|
||||
</StyledDropdownMenuContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { RecordPageSidePanelCommandMenuDropdown } from '@/command-menu-item/components/RecordPageSidePanelCommandMenuDropdown';
|
||||
import { CommandMenuContextProvider } from '@/command-menu-item/contexts/CommandMenuContextProvider';
|
||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
|
||||
export const RecordPageSidePanelCommandMenu = () => {
|
||||
const contextStoreCurrentObjectMetadataItemId = useAtomComponentStateValue(
|
||||
contextStoreCurrentObjectMetadataItemIdComponentState,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{contextStoreCurrentObjectMetadataItemId && (
|
||||
<CommandMenuContextProvider
|
||||
isInSidePanel={true}
|
||||
displayType="dropdownItem"
|
||||
containerType="command-menu-show-page-dropdown"
|
||||
>
|
||||
<RecordPageSidePanelCommandMenuDropdown />
|
||||
</CommandMenuContextProvider>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import { CommandMenuItemComponent } from '@/command-menu-item/display/components/CommandMenuItemComponent';
|
||||
import { CommandMenuItemScope } from '@/command-menu-item/types/CommandMenuItemScope';
|
||||
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext';
|
||||
import { getSidePanelCommandMenuDropdownIdFromCommandMenuId } from '@/command-menu-item/utils/getSidePanelCommandMenuDropdownIdFromCommandMenuId';
|
||||
import { OptionsDropdownMenu } from '@/ui/layout/dropdown/components/OptionsDropdownMenu';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useContext } from 'react';
|
||||
|
||||
export const RecordPageSidePanelCommandMenuDropdown = () => {
|
||||
const { commandMenuItems } = useContext(CommandMenuContext);
|
||||
|
||||
const commandMenuId = useAvailableComponentInstanceIdOrThrow(
|
||||
CommandMenuComponentInstanceContext,
|
||||
);
|
||||
|
||||
const dropdownId =
|
||||
getSidePanelCommandMenuDropdownIdFromCommandMenuId(commandMenuId);
|
||||
|
||||
const recordSelectionActions = commandMenuItems.filter(
|
||||
(action) => action.scope === CommandMenuItemScope.RecordSelection,
|
||||
);
|
||||
|
||||
const selectableItemIdArray = recordSelectionActions.map(
|
||||
(action) => action.key,
|
||||
);
|
||||
|
||||
return (
|
||||
<OptionsDropdownMenu
|
||||
dropdownId={dropdownId}
|
||||
selectableListId={commandMenuId}
|
||||
selectableItemIdArray={selectableItemIdArray}
|
||||
>
|
||||
{recordSelectionActions.map((action) => (
|
||||
<CommandMenuItemComponent action={action} key={action.key} />
|
||||
))}
|
||||
</OptionsDropdownMenu>
|
||||
);
|
||||
};
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
import { PageHeaderCommandMenuButtons } from '@/command-menu-item/components/PageHeaderCommandMenuButtons';
|
||||
import { CommandMenuContextProvider } from '@/command-menu-item/contexts/CommandMenuContextProvider';
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useIsMobile } from 'twenty-ui/utilities';
|
||||
|
||||
export const RecordShowCommandMenu = () => {
|
||||
const contextStoreCurrentObjectMetadataItemId = useAtomComponentStateValue(
|
||||
contextStoreCurrentObjectMetadataItemIdComponentState,
|
||||
MAIN_CONTEXT_STORE_INSTANCE_ID,
|
||||
);
|
||||
|
||||
const contextStoreTargetedRecordsRule = useAtomComponentStateValue(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
MAIN_CONTEXT_STORE_INSTANCE_ID,
|
||||
);
|
||||
|
||||
const hasSelectedRecord =
|
||||
contextStoreTargetedRecordsRule.mode === 'selection' &&
|
||||
contextStoreTargetedRecordsRule.selectedRecordIds.length === 1;
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<>
|
||||
{hasSelectedRecord && contextStoreCurrentObjectMetadataItemId && (
|
||||
<CommandMenuContextProvider
|
||||
isInSidePanel={false}
|
||||
displayType="button"
|
||||
containerType="show-page-header"
|
||||
>
|
||||
{!isMobile && <PageHeaderCommandMenuButtons />}
|
||||
</CommandMenuContextProvider>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext';
|
||||
import { getSidePanelCommandMenuDropdownIdFromCommandMenuId } from '@/command-menu-item/utils/getSidePanelCommandMenuDropdownIdFromCommandMenuId';
|
||||
import { SIDE_PANEL_FOCUS_ID } from '@/side-panel/constants/SidePanelFocusId';
|
||||
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
|
||||
import { sidePanelNavigationStackState } from '@/side-panel/states/sidePanelNavigationStackState';
|
||||
import { SidePanelPageComponentInstanceContext } from '@/side-panel/states/contexts/SidePanelPageComponentInstanceContext';
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { contextStoreRecordShowParentViewComponentState } from '@/context-store/states/contextStoreRecordShowParentViewComponentState';
|
||||
import { CoreObjectNameSingular, AppPath } from 'twenty-shared/types';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useComponentInstanceStateContext } from '@/ui/utilities/state/component-state/hooks/useComponentInstanceStateContext';
|
||||
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useStore } from 'jotai';
|
||||
import { useCallback } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconBrowserMaximize } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { getOsControlSymbol } from 'twenty-ui/utilities';
|
||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||
|
||||
type RecordShowSidePanelOpenRecordButtonProps = {
|
||||
objectNameSingular: string;
|
||||
recordId: string;
|
||||
};
|
||||
|
||||
export const RecordShowSidePanelOpenRecordButton = ({
|
||||
objectNameSingular,
|
||||
recordId,
|
||||
}: RecordShowSidePanelOpenRecordButtonProps) => {
|
||||
const record = useAtomFamilyStateValue(recordStoreFamilyState, recordId) as
|
||||
| ObjectRecord
|
||||
| null
|
||||
| undefined;
|
||||
const { closeSidePanelMenu } = useSidePanelMenu();
|
||||
|
||||
const sidePanelPageComponentInstance = useComponentInstanceStateContext(
|
||||
SidePanelPageComponentInstanceContext,
|
||||
);
|
||||
|
||||
const tabListComponentId = getShowPageTabListComponentId({
|
||||
pageId: sidePanelPageComponentInstance?.instanceId,
|
||||
targetObjectId: recordId,
|
||||
});
|
||||
|
||||
const activeTabId = useAtomComponentStateValue(
|
||||
activeTabIdComponentState,
|
||||
tabListComponentId,
|
||||
);
|
||||
|
||||
const tabListComponentIdInRecordPage = getShowPageTabListComponentId({
|
||||
targetObjectId: recordId,
|
||||
});
|
||||
|
||||
const setActiveTabId = useSetAtomComponentState(
|
||||
activeTabIdComponentState,
|
||||
tabListComponentIdInRecordPage,
|
||||
);
|
||||
|
||||
const parentViewState = useAtomComponentStateCallbackState(
|
||||
contextStoreRecordShowParentViewComponentState,
|
||||
MAIN_CONTEXT_STORE_INSTANCE_ID,
|
||||
);
|
||||
|
||||
const store = useStore();
|
||||
|
||||
const navigate = useNavigateApp();
|
||||
|
||||
const commandMenuId = useAvailableComponentInstanceIdOrThrow(
|
||||
CommandMenuComponentInstanceContext,
|
||||
);
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const handleOpenRecord = useCallback(() => {
|
||||
const tabIdToOpen =
|
||||
activeTabId === 'home'
|
||||
? objectNameSingular === CoreObjectNameSingular.Note ||
|
||||
objectNameSingular === CoreObjectNameSingular.Task
|
||||
? 'richText'
|
||||
: 'timeline'
|
||||
: activeTabId;
|
||||
|
||||
setActiveTabId(tabIdToOpen);
|
||||
|
||||
const parentView = store.get(parentViewState);
|
||||
|
||||
if (
|
||||
isDefined(parentView) &&
|
||||
parentView.parentViewObjectNameSingular !== objectNameSingular
|
||||
) {
|
||||
store.set(parentViewState, undefined);
|
||||
}
|
||||
|
||||
store.set(sidePanelNavigationStackState.atom, []);
|
||||
|
||||
navigate(AppPath.RecordShowPage, {
|
||||
objectNameSingular,
|
||||
objectRecordId: recordId,
|
||||
});
|
||||
|
||||
closeDropdown(
|
||||
getSidePanelCommandMenuDropdownIdFromCommandMenuId(commandMenuId),
|
||||
);
|
||||
|
||||
closeSidePanelMenu();
|
||||
}, [
|
||||
commandMenuId,
|
||||
activeTabId,
|
||||
closeSidePanelMenu,
|
||||
closeDropdown,
|
||||
navigate,
|
||||
objectNameSingular,
|
||||
parentViewState,
|
||||
recordId,
|
||||
setActiveTabId,
|
||||
store,
|
||||
]);
|
||||
|
||||
useHotkeysOnFocusedElement({
|
||||
keys: ['ctrl+Enter,meta+Enter'],
|
||||
callback: handleOpenRecord,
|
||||
focusId: SIDE_PANEL_FOCUS_ID,
|
||||
dependencies: [handleOpenRecord],
|
||||
});
|
||||
|
||||
if (!isDefined(record)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
title={t`Open`}
|
||||
variant="primary"
|
||||
accent="blue"
|
||||
size="small"
|
||||
Icon={IconBrowserMaximize}
|
||||
hotkeys={[getOsControlSymbol(), '⏎']}
|
||||
onClick={handleOpenRecord}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
||||
import { Provider as JotaiProvider } from 'jotai';
|
||||
import * as test from 'storybook/test';
|
||||
import { expect, userEvent, waitFor, within } from 'storybook/test';
|
||||
|
||||
import { RecordIndexCommandMenuDropdown } from '@/command-menu-item/components/RecordIndexCommandMenuDropdown';
|
||||
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
|
||||
import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext';
|
||||
import { recordIndexCommandMenuDropdownPositionComponentState } from '@/command-menu-item/states/recordIndexCommandMenuDropdownPositionComponentState';
|
||||
|
||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { RouterDecorator } from 'twenty-ui/testing';
|
||||
import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator';
|
||||
|
||||
const deleteMock = test.fn();
|
||||
const addToFavoritesMock = test.fn();
|
||||
const exportMock = test.fn();
|
||||
|
||||
const meta: Meta<typeof RecordIndexCommandMenuDropdown> = {
|
||||
title: 'Modules/CommandMenu/RecordIndexCommandMenuDropdown',
|
||||
component: RecordIndexCommandMenuDropdown,
|
||||
decorators: [
|
||||
(Story) => {
|
||||
jotaiStore.set(
|
||||
isDropdownOpenComponentState.atomFamily({
|
||||
instanceId: 'command-menu-dropdown-story-command-menu',
|
||||
}),
|
||||
true,
|
||||
);
|
||||
jotaiStore.set(
|
||||
recordIndexCommandMenuDropdownPositionComponentState.atomFamily({
|
||||
instanceId: 'command-menu-dropdown-story',
|
||||
}),
|
||||
{ x: 10, y: 10 },
|
||||
);
|
||||
|
||||
return (
|
||||
<JotaiProvider store={jotaiStore}>
|
||||
<CommandMenuComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'story-command-menu' }}
|
||||
>
|
||||
<CommandMenuContext.Provider
|
||||
value={{
|
||||
isInSidePanel: true,
|
||||
displayType: 'dropdownItem',
|
||||
containerType: 'index-page-dropdown',
|
||||
commandMenuItems: createMockCommandMenuItems({
|
||||
deleteMock,
|
||||
addToFavoritesMock,
|
||||
exportMock,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
</CommandMenuContext.Provider>
|
||||
</CommandMenuComponentInstanceContext.Provider>
|
||||
</JotaiProvider>
|
||||
);
|
||||
},
|
||||
ContextStoreDecorator,
|
||||
RouterDecorator,
|
||||
],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof RecordIndexCommandMenuDropdown>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
commandMenuId: 'story',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithInteractions: Story = {
|
||||
args: {
|
||||
commandMenuId: 'story',
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement.ownerDocument.body);
|
||||
|
||||
const deleteButton = await canvas.findByText('Delete');
|
||||
await userEvent.click(deleteButton);
|
||||
|
||||
const addToFavoritesButton = await canvas.findByText('Add to favorites');
|
||||
await userEvent.click(addToFavoritesButton);
|
||||
|
||||
const exportButton = await canvas.findByText('Export');
|
||||
await userEvent.click(exportButton);
|
||||
|
||||
const moreActionsButton = await canvas.findByText('More actions');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(deleteMock).toHaveBeenCalled();
|
||||
expect(addToFavoritesMock).toHaveBeenCalled();
|
||||
expect(exportMock).toHaveBeenCalled();
|
||||
expect(moreActionsButton).toBeInTheDocument();
|
||||
});
|
||||
},
|
||||
};
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
||||
import { Provider as JotaiProvider } from 'jotai';
|
||||
import * as test from 'storybook/test';
|
||||
import { expect, userEvent, waitFor, within } from 'storybook/test';
|
||||
|
||||
import { RecordPageSidePanelCommandMenuDropdown } from '@/command-menu-item/components/RecordPageSidePanelCommandMenuDropdown';
|
||||
import { CommandMenuContext } from '@/command-menu-item/contexts/CommandMenuContext';
|
||||
import { createMockCommandMenuItems } from '@/command-menu-item/mock/command-menu-items.mock';
|
||||
import { CommandMenuComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuComponentInstanceContext';
|
||||
import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing';
|
||||
import { jotaiStore } from '@/ui/utilities/state/jotai/jotaiStore';
|
||||
import { ContextStoreDecorator } from '~/testing/decorators/ContextStoreDecorator';
|
||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||
import { JestContextStoreSetter } from '~/testing/jest/JestContextStoreSetter';
|
||||
const deleteMock = test.fn();
|
||||
const addToFavoritesMock = test.fn();
|
||||
const exportMock = test.fn();
|
||||
|
||||
const meta: Meta<typeof RecordPageSidePanelCommandMenuDropdown> = {
|
||||
title: 'Modules/CommandMenu/RecordPageSidePanelCommandMenuDropdown',
|
||||
component: RecordPageSidePanelCommandMenuDropdown,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<JotaiProvider store={jotaiStore}>
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{ instanceId: MAIN_CONTEXT_STORE_INSTANCE_ID }}
|
||||
>
|
||||
<JestContextStoreSetter
|
||||
contextStoreTargetedRecordsRule={{
|
||||
mode: 'selection',
|
||||
selectedRecordIds: ['1'],
|
||||
}}
|
||||
contextStoreNumberOfSelectedRecords={1}
|
||||
>
|
||||
<CommandMenuComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'story-command-menu' }}
|
||||
>
|
||||
<CommandMenuContext.Provider
|
||||
value={{
|
||||
isInSidePanel: true,
|
||||
displayType: 'dropdownItem',
|
||||
containerType: 'command-menu-show-page-dropdown',
|
||||
commandMenuItems: createMockCommandMenuItems({
|
||||
deleteMock,
|
||||
addToFavoritesMock,
|
||||
exportMock,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
</CommandMenuContext.Provider>
|
||||
</CommandMenuComponentInstanceContext.Provider>
|
||||
</JestContextStoreSetter>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
</JotaiProvider>
|
||||
),
|
||||
ComponentDecorator,
|
||||
ContextStoreDecorator,
|
||||
ObjectMetadataItemsDecorator,
|
||||
SnackBarDecorator,
|
||||
RouterDecorator,
|
||||
],
|
||||
args: {
|
||||
commandMenuId: 'story-command-menu',
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof RecordPageSidePanelCommandMenuDropdown>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
commandMenuId: 'story-command-menu',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithButtonClicks: Story = {
|
||||
args: {
|
||||
commandMenuId: 'story-command-menu',
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement.ownerDocument.body);
|
||||
|
||||
let actionButton = await canvas.findByText('Options');
|
||||
await userEvent.click(actionButton);
|
||||
|
||||
const deleteButton = await canvas.findByText('Delete');
|
||||
await userEvent.click(deleteButton);
|
||||
|
||||
actionButton = await canvas.findByText('Options');
|
||||
await userEvent.click(actionButton);
|
||||
|
||||
const addToFavoritesButton = await canvas.findByText('Add to favorites');
|
||||
await userEvent.click(addToFavoritesButton);
|
||||
|
||||
actionButton = await canvas.findByText('Options');
|
||||
await userEvent.click(actionButton);
|
||||
|
||||
const exportButton = await canvas.findByText('Export');
|
||||
await userEvent.click(exportButton);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(deleteMock).toHaveBeenCalled();
|
||||
expect(addToFavoritesMock).toHaveBeenCalled();
|
||||
expect(exportMock).toHaveBeenCalled();
|
||||
});
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user