diff --git a/packages/twenty-front/src/modules/action-menu/actions/display/components/__stories__/ActionButton.stories.tsx b/packages/twenty-front/src/modules/action-menu/actions/display/components/__stories__/ActionButton.stories.tsx new file mode 100644 index 000000000..89a7bf47a --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/display/components/__stories__/ActionButton.stories.tsx @@ -0,0 +1,66 @@ +import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKeys'; +import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey'; +import { createMockActionMenuActions } from '@/action-menu/mock/action-menu-actions.mock'; +import { getActionLabel } from '@/action-menu/utils/getActionLabel'; +import { expect } from '@storybook/jest'; +import { Meta, StoryObj } from '@storybook/react'; +import { fn, userEvent, within } from '@storybook/test'; +import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing'; +import { ActionButton } from '../ActionButton'; + +const meta: Meta = { + title: 'Modules/ActionMenu/Actions/Display/ActionButton', + component: ActionButton, + decorators: [ComponentDecorator, RouterDecorator], +}; + +export default meta; + +type Story = StoryObj; + +const deleteMock = fn(); +const addToFavoritesMock = fn(); + +const mockActions = createMockActionMenuActions({ + deleteMock, + addToFavoritesMock, +}); + +const addToFavoritesAction = mockActions.find( + (action) => action.key === SingleRecordActionKeys.ADD_TO_FAVORITES, +); + +const goToPeopleAction = mockActions.find( + (action) => action.key === NoSelectionRecordActionKeys.GO_TO_PEOPLE, +); + +export const Default: Story = { + args: { + action: addToFavoritesAction, + onClick: addToFavoritesMock, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click( + await canvas.findByText( + getActionLabel(addToFavoritesAction?.shortLabel ?? ''), + ), + ); + expect(addToFavoritesMock).toHaveBeenCalled(); + }, +}; + +export const WithLink: Story = { + args: { + action: goToPeopleAction, + to: '/objects/people', + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const menuItem = await canvas.findByText( + getActionLabel(goToPeopleAction?.shortLabel ?? ''), + ); + expect(menuItem).toBeVisible(); + expect(canvas.getByRole('link')).toHaveAttribute('href', '/objects/people'); + }, +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/display/components/__stories__/ActionComponent.stories.tsx b/packages/twenty-front/src/modules/action-menu/actions/display/components/__stories__/ActionComponent.stories.tsx new file mode 100644 index 000000000..c2ec32648 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/display/components/__stories__/ActionComponent.stories.tsx @@ -0,0 +1,67 @@ +import { Meta, StoryObj } from '@storybook/react'; +import { expect, within } from '@storybook/test'; + +import { ActionComponent } from '@/action-menu/actions/display/components/ActionComponent'; +import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey'; +import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; +import { createMockActionMenuActions } from '@/action-menu/mock/action-menu-actions.mock'; +import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext'; +import { getActionLabel } from '@/action-menu/utils/getActionLabel'; +import { ComponentDecorator } from 'twenty-ui/testing'; + +const mockActions = createMockActionMenuActions({}); + +const addToFavoritesAction = mockActions.find( + (action) => action.key === SingleRecordActionKeys.ADD_TO_FAVORITES, +); + +if (!addToFavoritesAction) { + throw new Error('Add to favorites action not found'); +} + +const meta: Meta = { + title: 'Modules/ActionMenu/Actions/Display/ActionComponent', + component: ActionComponent, + decorators: [ + ComponentDecorator, + (Story) => ( + + + + + + ), + ], + args: { + action: addToFavoritesAction, + }, + parameters: { + container: { + width: 'auto', + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + expect( + await canvas.findByText( + getActionLabel(addToFavoritesAction?.shortLabel ?? ''), + ), + ).toBeVisible(); + }, +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/display/components/__stories__/ActionDisplay.stories.tsx b/packages/twenty-front/src/modules/action-menu/actions/display/components/__stories__/ActionDisplay.stories.tsx new file mode 100644 index 000000000..5e47940aa --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/display/components/__stories__/ActionDisplay.stories.tsx @@ -0,0 +1,139 @@ +import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey'; +import { ActionConfigContext } from '@/action-menu/contexts/ActionConfigContext'; +import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; +import { createMockActionMenuActions } from '@/action-menu/mock/action-menu-actions.mock'; +import { getActionLabel } from '@/action-menu/utils/getActionLabel'; +import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext'; +import { expect } from '@storybook/jest'; +import { Meta, StoryObj } from '@storybook/react'; +import { fn, userEvent, within } from '@storybook/test'; +import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing'; +import { ActionDisplay } from '../ActionDisplay'; + +type Story = StoryObj; + +const deleteMock = fn(); +const addToFavoritesMock = fn(); + +const mockActions = createMockActionMenuActions({ + deleteMock, + addToFavoritesMock, +}); + +const addToFavoritesAction = mockActions.find( + (action) => action.key === SingleRecordActionKeys.ADD_TO_FAVORITES, +); + +if (!addToFavoritesAction) { + throw new Error('addToFavoritesAction not found'); +} + +const meta: Meta = { + title: 'Modules/ActionMenu/Actions/Display/ActionDisplay', + component: ActionDisplay, + decorators: [ + (Story) => ( + + + + ), + ComponentDecorator, + RouterDecorator, + ], +}; + +export default meta; + +export const AsButton: Story = { + args: { + onClick: addToFavoritesMock, + }, + decorators: [ + (Story) => ( + + + + ), + ], + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click( + await canvas.findByText( + getActionLabel(addToFavoritesAction?.shortLabel ?? ''), + ), + ); + expect(addToFavoritesMock).toHaveBeenCalled(); + }, +}; + +export const AsListItem: Story = { + args: { + onClick: addToFavoritesMock, + }, + decorators: [ + (Story) => ( + + + + ), + (Story) => ( + + + + ), + ], + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click( + await canvas.findByText( + getActionLabel(addToFavoritesAction?.label ?? ''), + ), + ); + expect(addToFavoritesMock).toHaveBeenCalled(); + }, +}; + +export const AsDropdownItem: Story = { + args: { + onClick: addToFavoritesMock, + }, + decorators: [ + (Story) => ( + + + + ), + ], + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click( + await canvas.findByText( + getActionLabel(addToFavoritesAction?.label ?? ''), + ), + ); + expect(addToFavoritesMock).toHaveBeenCalled(); + }, +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/display/components/__stories__/ActionDropdownItem.stories.tsx b/packages/twenty-front/src/modules/action-menu/actions/display/components/__stories__/ActionDropdownItem.stories.tsx new file mode 100644 index 000000000..12bdef064 --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/display/components/__stories__/ActionDropdownItem.stories.tsx @@ -0,0 +1,65 @@ +import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKeys'; +import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey'; +import { createMockActionMenuActions } from '@/action-menu/mock/action-menu-actions.mock'; +import { getActionLabel } from '@/action-menu/utils/getActionLabel'; +import { expect } from '@storybook/jest'; +import { Meta, StoryObj } from '@storybook/react'; +import { fn, userEvent, within } from '@storybook/test'; +import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing'; +import { ActionDropdownItem } from '../ActionDropdownItem'; + +const meta: Meta = { + title: 'Modules/ActionMenu/Actions/Display/ActionDropdownItem', + component: ActionDropdownItem, + decorators: [ComponentDecorator, RouterDecorator], +}; + +export default meta; + +type Story = StoryObj; + +const deleteMock = fn(); +const addToFavoritesMock = fn(); + +const mockActions = createMockActionMenuActions({ + deleteMock, + addToFavoritesMock, +}); + +const addToFavoritesAction = mockActions.find( + (action) => action.key === SingleRecordActionKeys.ADD_TO_FAVORITES, +); + +const goToPeopleAction = mockActions.find( + (action) => action.key === NoSelectionRecordActionKeys.GO_TO_PEOPLE, +); + +export const Default: Story = { + args: { + action: addToFavoritesAction, + onClick: addToFavoritesMock, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click( + await canvas.findByText( + getActionLabel(addToFavoritesAction?.label ?? ''), + ), + ); + expect(addToFavoritesMock).toHaveBeenCalled(); + }, +}; + +export const WithLink: Story = { + args: { + action: goToPeopleAction, + to: '/objects/people', + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const dropdownItem = await canvas.findByText( + getActionLabel(goToPeopleAction?.label ?? ''), + ); + expect(dropdownItem).toBeVisible(); + }, +}; diff --git a/packages/twenty-front/src/modules/action-menu/actions/display/components/__stories__/ActionListItem.stories.tsx b/packages/twenty-front/src/modules/action-menu/actions/display/components/__stories__/ActionListItem.stories.tsx new file mode 100644 index 000000000..d4dfaa7fb --- /dev/null +++ b/packages/twenty-front/src/modules/action-menu/actions/display/components/__stories__/ActionListItem.stories.tsx @@ -0,0 +1,76 @@ +import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKeys'; +import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey'; +import { createMockActionMenuActions } from '@/action-menu/mock/action-menu-actions.mock'; +import { getActionLabel } from '@/action-menu/utils/getActionLabel'; +import { SelectableListComponentInstanceContext } from '@/ui/layout/selectable-list/states/contexts/SelectableListComponentInstanceContext'; +import { expect } from '@storybook/jest'; +import { Meta, StoryObj } from '@storybook/react'; +import { fn, userEvent, within } from '@storybook/test'; +import { ComponentDecorator, RouterDecorator } from 'twenty-ui/testing'; +import { ActionListItem } from '../ActionListItem'; + +type Story = StoryObj; + +const deleteMock = fn(); +const addToFavoritesMock = fn(); + +const mockActions = createMockActionMenuActions({ + deleteMock, + addToFavoritesMock, +}); + +const addToFavoritesAction = mockActions.find( + (action) => action.key === SingleRecordActionKeys.ADD_TO_FAVORITES, +); + +const goToPeopleAction = mockActions.find( + (action) => action.key === NoSelectionRecordActionKeys.GO_TO_PEOPLE, +); + +const meta: Meta = { + title: 'Modules/ActionMenu/Actions/Display/ActionListItem', + component: ActionListItem, + decorators: [ + (Story) => ( + + + + ), + ComponentDecorator, + RouterDecorator, + ], +}; + +export default meta; + +export const Default: Story = { + args: { + action: addToFavoritesAction, + onClick: addToFavoritesMock, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click( + await canvas.findByText( + getActionLabel(addToFavoritesAction?.label ?? ''), + ), + ); + expect(addToFavoritesMock).toHaveBeenCalled(); + }, +}; + +export const WithLink: Story = { + args: { + action: goToPeopleAction, + to: '/objects/people', + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const listItem = await canvas.findByText( + getActionLabel(goToPeopleAction?.label ?? ''), + ); + expect(listItem).toBeVisible(); + }, +}; diff --git a/packages/twenty-front/src/modules/action-menu/mock/action-menu-actions.mock.tsx b/packages/twenty-front/src/modules/action-menu/mock/action-menu-actions.mock.tsx index a16f13751..888d0c788 100644 --- a/packages/twenty-front/src/modules/action-menu/mock/action-menu-actions.mock.tsx +++ b/packages/twenty-front/src/modules/action-menu/mock/action-menu-actions.mock.tsx @@ -1,20 +1,30 @@ import { Action } from '@/action-menu/actions/components/Action'; +import { ActionLink } from '@/action-menu/actions/components/ActionLink'; +import { NoSelectionRecordActionKeys } from '@/action-menu/actions/record-actions/no-selection/types/NoSelectionRecordActionsKeys'; import { SingleRecordActionKeys } from '@/action-menu/actions/record-actions/single-record/types/SingleRecordActionsKey'; import { ActionConfig } from '@/action-menu/actions/types/ActionConfig'; import { ActionScope } from '@/action-menu/actions/types/ActionScope'; import { ActionType } from '@/action-menu/actions/types/ActionType'; import { ActionViewType } from '@/action-menu/actions/types/ActionViewType'; +import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; +import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; +import { AppPath } from '@/types/AppPath'; import { msg } from '@lingui/core/macro'; -import { IconFileExport, IconHeart, IconTrash } from 'twenty-ui/display'; +import { + IconFileExport, + IconHeart, + IconTrash, + IconUser, +} from 'twenty-ui/display'; export const createMockActionMenuActions = ({ - deleteMock, - addToFavoritesMock, - exportMock, + deleteMock = () => {}, + addToFavoritesMock = () => {}, + exportMock = () => {}, }: { - deleteMock: () => void; - addToFavoritesMock: () => void; - exportMock: () => void; + deleteMock?: () => void; + addToFavoritesMock?: () => void; + exportMock?: () => void; }): ActionConfig[] => [ { type: ActionType.Standard, @@ -66,4 +76,30 @@ export const createMockActionMenuActions = ({ ], component: , }, + { + type: ActionType.Navigation, + scope: ActionScope.Global, + key: NoSelectionRecordActionKeys.GO_TO_PEOPLE, + label: msg`Go to People`, + shortLabel: msg`People`, + position: 19, + Icon: IconUser, + isPinned: false, + availableOn: [ + ActionViewType.INDEX_PAGE_NO_SELECTION, + ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION, + ActionViewType.INDEX_PAGE_BULK_SELECTION, + ActionViewType.SHOW_PAGE, + ], + shouldBeRegistered: ({ objectMetadataItem, viewType }) => + objectMetadataItem?.nameSingular !== CoreObjectNameSingular.Person || + viewType === ActionViewType.SHOW_PAGE, + component: ( + + ), + hotKeys: ['G', 'P'], + }, ];