From 520a883c7379d76dce9ed6620b7db2647dc91b16 Mon Sep 17 00:00:00 2001 From: Pacifique LINJANJA Date: Fri, 7 Jun 2024 17:23:32 +0200 Subject: [PATCH] Fix the "Delete" action on the Kaban view (#5646) # This PR - Fixes #5520 - Created a shared confirmation modal component for the `ContextMenu` and the `ActionBar` components to avoid code repetition - with its storybook file Looking forward to getting feedback @charlesBochet --- .../hooks/useRecordBoardSelection.ts | 11 ++++- .../action-bar/components/ActionBar.tsx | 22 ++++++--- .../context-menu/components/ContextMenu.tsx | 46 +++++++++---------- .../__stories__/NavigationModal.stories.tsx | 35 ++++++++++++++ .../shared/components/NavigationModal.tsx | 19 ++++++++ 5 files changed, 102 insertions(+), 31 deletions(-) create mode 100644 packages/twenty-front/src/modules/ui/navigation/shared/__stories__/NavigationModal.stories.tsx create mode 100644 packages/twenty-front/src/modules/ui/navigation/shared/components/NavigationModal.tsx diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoardSelection.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoardSelection.ts index a2024d4ba..a14c85119 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoardSelection.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoardSelection.ts @@ -1,14 +1,17 @@ -import { useRecoilCallback } from 'recoil'; +import { useRecoilCallback, useSetRecoilState } from 'recoil'; import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; +import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState'; export const useRecordBoardSelection = (recordBoardId?: string) => { + const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState); const { selectedRecordIdsSelector, isRecordBoardCardSelectedFamilyState } = useRecordBoardStates(recordBoardId); const resetRecordSelection = useRecoilCallback( ({ snapshot, set }) => () => { + setContextMenuOpenState(false); const recordIds = snapshot .getLoadable(selectedRecordIdsSelector()) .getValue(); @@ -17,7 +20,11 @@ export const useRecordBoardSelection = (recordBoardId?: string) => { set(isRecordBoardCardSelectedFamilyState(recordId), false); } }, - [selectedRecordIdsSelector, isRecordBoardCardSelectedFamilyState], + [ + selectedRecordIdsSelector, + isRecordBoardCardSelectedFamilyState, + setContextMenuOpenState, + ], ); const setRecordAsSelected = useRecoilCallback( diff --git a/packages/twenty-front/src/modules/ui/navigation/action-bar/components/ActionBar.tsx b/packages/twenty-front/src/modules/ui/navigation/action-bar/components/ActionBar.tsx index f0e2a9d2f..97e565844 100644 --- a/packages/twenty-front/src/modules/ui/navigation/action-bar/components/ActionBar.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/action-bar/components/ActionBar.tsx @@ -1,9 +1,10 @@ -import { useRef } from 'react'; +import { useEffect, useRef } from 'react'; import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; +import { useRecoilValue, useSetRecoilState } from 'recoil'; import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState'; import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState'; +import SharedNavigationModal from '@/ui/navigation/shared/components/NavigationModal'; import { ActionBarItem } from './ActionBarItem'; @@ -39,7 +40,15 @@ const StyledLabel = styled.div` padding-right: ${({ theme }) => theme.spacing(2)}; `; -export const ActionBar = ({ selectedIds }: ActionBarProps) => { +export const ActionBar = ({ selectedIds = [] }: ActionBarProps) => { + const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState); + + useEffect(() => { + if (selectedIds && selectedIds.length > 1) { + setContextMenuOpenState(false); + } + }, [selectedIds, setContextMenuOpenState]); + const contextMenuIsOpen = useRecoilValue(contextMenuIsOpenState); const actionBarEntries = useRecoilValue(actionBarEntriesState); const wrapperRef = useRef(null); @@ -62,9 +71,10 @@ export const ActionBar = ({ selectedIds }: ActionBarProps) => { ))} -
- {actionBarEntries[0]?.ConfirmationModal} -
+ ); }; diff --git a/packages/twenty-front/src/modules/ui/navigation/context-menu/components/ContextMenu.tsx b/packages/twenty-front/src/modules/ui/navigation/context-menu/components/ContextMenu.tsx index 8a3a1cfd0..e27c6096c 100644 --- a/packages/twenty-front/src/modules/ui/navigation/context-menu/components/ContextMenu.tsx +++ b/packages/twenty-front/src/modules/ui/navigation/context-menu/components/ContextMenu.tsx @@ -1,11 +1,12 @@ import React, { useRef } from 'react'; import styled from '@emotion/styled'; -import { useRecoilValue, useSetRecoilState } from 'recoil'; +import { useRecoilValue } from 'recoil'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState'; import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState'; -import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; +import SharedNavigationModal from '@/ui/navigation/shared/components/NavigationModal'; import { contextMenuEntriesState } from '../states/contextMenuEntriesState'; import { contextMenuIsOpenState } from '../states/contextMenuIsOpenState'; @@ -40,15 +41,8 @@ export const ContextMenu = () => { const contextMenuPosition = useRecoilValue(contextMenuPositionState); const contextMenuIsOpen = useRecoilValue(contextMenuIsOpenState); const contextMenuEntries = useRecoilValue(contextMenuEntriesState); - const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState); const wrapperRef = useRef(null); - - useListenClickOutside({ - refs: [wrapperRef], - callback: () => { - setContextMenuOpenState(false); - }, - }); + const actionBarEntries = useRecoilValue(actionBarEntriesState); if (!contextMenuIsOpen) { return null; @@ -61,18 +55,24 @@ export const ContextMenu = () => { : undefined; return ( - - - - {contextMenuEntries.map((item, index) => { - return ; - })} - - - + <> + + + + {contextMenuEntries.map((item, index) => { + return ; + })} + + + + + ); }; diff --git a/packages/twenty-front/src/modules/ui/navigation/shared/__stories__/NavigationModal.stories.tsx b/packages/twenty-front/src/modules/ui/navigation/shared/__stories__/NavigationModal.stories.tsx new file mode 100644 index 000000000..becc1b72a --- /dev/null +++ b/packages/twenty-front/src/modules/ui/navigation/shared/__stories__/NavigationModal.stories.tsx @@ -0,0 +1,35 @@ +import { Meta, StoryObj } from '@storybook/react'; +import { IconTrash } from 'twenty-ui'; + +import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; +import SharedNavigationModal from '@/ui/navigation/shared/components/NavigationModal'; + +const meta: Meta = { + title: 'UI/Navigation/Shared/SharedNavigationModal', + component: SharedNavigationModal, + args: { + actionBarEntries: [ + { + ConfirmationModal: ( + {}} + setIsOpen={() => {}} + isOpen={false} + subtitle="Subtitle" + /> + ), + Icon: IconTrash, + label: 'Label', + onClick: () => {}, + }, + ], + customClassName: 'customClassName', + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/packages/twenty-front/src/modules/ui/navigation/shared/components/NavigationModal.tsx b/packages/twenty-front/src/modules/ui/navigation/shared/components/NavigationModal.tsx new file mode 100644 index 000000000..d81ffe75c --- /dev/null +++ b/packages/twenty-front/src/modules/ui/navigation/shared/components/NavigationModal.tsx @@ -0,0 +1,19 @@ +import { ActionBarEntry } from '@/ui/navigation/action-bar/types/ActionBarEntry'; + +type SharedNavigationModalProps = { + actionBarEntries: ActionBarEntry[]; + customClassName: string; +}; + +const SharedNavigationModal = ({ + actionBarEntries, + customClassName, +}: SharedNavigationModalProps) => { + return ( +
+ {actionBarEntries[0]?.ConfirmationModal ?? null} +
+ ); +}; + +export default SharedNavigationModal;