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
This commit is contained in:
Pacifique LINJANJA
2024-06-07 17:23:32 +02:00
committed by GitHub
parent e9cf449706
commit 520a883c73
5 changed files with 102 additions and 31 deletions

View File

@ -1,14 +1,17 @@
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates'; import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState';
export const useRecordBoardSelection = (recordBoardId?: string) => { export const useRecordBoardSelection = (recordBoardId?: string) => {
const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState);
const { selectedRecordIdsSelector, isRecordBoardCardSelectedFamilyState } = const { selectedRecordIdsSelector, isRecordBoardCardSelectedFamilyState } =
useRecordBoardStates(recordBoardId); useRecordBoardStates(recordBoardId);
const resetRecordSelection = useRecoilCallback( const resetRecordSelection = useRecoilCallback(
({ snapshot, set }) => ({ snapshot, set }) =>
() => { () => {
setContextMenuOpenState(false);
const recordIds = snapshot const recordIds = snapshot
.getLoadable(selectedRecordIdsSelector()) .getLoadable(selectedRecordIdsSelector())
.getValue(); .getValue();
@ -17,7 +20,11 @@ export const useRecordBoardSelection = (recordBoardId?: string) => {
set(isRecordBoardCardSelectedFamilyState(recordId), false); set(isRecordBoardCardSelectedFamilyState(recordId), false);
} }
}, },
[selectedRecordIdsSelector, isRecordBoardCardSelectedFamilyState], [
selectedRecordIdsSelector,
isRecordBoardCardSelectedFamilyState,
setContextMenuOpenState,
],
); );
const setRecordAsSelected = useRecoilCallback( const setRecordAsSelected = useRecoilCallback(

View File

@ -1,9 +1,10 @@
import { useRef } from 'react'; import { useEffect, useRef } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil'; import { useRecoilValue, useSetRecoilState } from 'recoil';
import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState'; import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState';
import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState'; import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState';
import SharedNavigationModal from '@/ui/navigation/shared/components/NavigationModal';
import { ActionBarItem } from './ActionBarItem'; import { ActionBarItem } from './ActionBarItem';
@ -39,7 +40,15 @@ const StyledLabel = styled.div`
padding-right: ${({ theme }) => theme.spacing(2)}; 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 contextMenuIsOpen = useRecoilValue(contextMenuIsOpenState);
const actionBarEntries = useRecoilValue(actionBarEntriesState); const actionBarEntries = useRecoilValue(actionBarEntriesState);
const wrapperRef = useRef<HTMLDivElement>(null); const wrapperRef = useRef<HTMLDivElement>(null);
@ -62,9 +71,10 @@ export const ActionBar = ({ selectedIds }: ActionBarProps) => {
<ActionBarItem key={index} item={item} /> <ActionBarItem key={index} item={item} />
))} ))}
</StyledContainerActionBar> </StyledContainerActionBar>
<div data-select-disable className="action-bar"> <SharedNavigationModal
{actionBarEntries[0]?.ConfirmationModal} actionBarEntries={actionBarEntries}
</div> customClassName="action-bar"
/>
</> </>
); );
}; };

View File

@ -1,11 +1,12 @@
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useRecoilValue, useSetRecoilState } from 'recoil'; import { useRecoilValue } from 'recoil';
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu'; import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; 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 { 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 { contextMenuEntriesState } from '../states/contextMenuEntriesState';
import { contextMenuIsOpenState } from '../states/contextMenuIsOpenState'; import { contextMenuIsOpenState } from '../states/contextMenuIsOpenState';
@ -40,15 +41,8 @@ export const ContextMenu = () => {
const contextMenuPosition = useRecoilValue(contextMenuPositionState); const contextMenuPosition = useRecoilValue(contextMenuPositionState);
const contextMenuIsOpen = useRecoilValue(contextMenuIsOpenState); const contextMenuIsOpen = useRecoilValue(contextMenuIsOpenState);
const contextMenuEntries = useRecoilValue(contextMenuEntriesState); const contextMenuEntries = useRecoilValue(contextMenuEntriesState);
const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState);
const wrapperRef = useRef<HTMLDivElement>(null); const wrapperRef = useRef<HTMLDivElement>(null);
const actionBarEntries = useRecoilValue(actionBarEntriesState);
useListenClickOutside({
refs: [wrapperRef],
callback: () => {
setContextMenuOpenState(false);
},
});
if (!contextMenuIsOpen) { if (!contextMenuIsOpen) {
return null; return null;
@ -61,18 +55,24 @@ export const ContextMenu = () => {
: undefined; : undefined;
return ( return (
<StyledContainerContextMenu <>
className="context-menu" <StyledContainerContextMenu
ref={wrapperRef} className="context-menu"
position={contextMenuPosition} ref={wrapperRef}
> position={contextMenuPosition}
<DropdownMenu data-select-disable width={width}> >
<DropdownMenuItemsContainer> <DropdownMenu data-select-disable width={width}>
{contextMenuEntries.map((item, index) => { <DropdownMenuItemsContainer>
return <ContextMenuItem key={index} item={item} />; {contextMenuEntries.map((item, index) => {
})} return <ContextMenuItem key={index} item={item} />;
</DropdownMenuItemsContainer> })}
</DropdownMenu> </DropdownMenuItemsContainer>
</StyledContainerContextMenu> </DropdownMenu>
</StyledContainerContextMenu>
<SharedNavigationModal
actionBarEntries={actionBarEntries}
customClassName="context-menu"
/>
</>
); );
}; };

View File

@ -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<typeof SharedNavigationModal> = {
title: 'UI/Navigation/Shared/SharedNavigationModal',
component: SharedNavigationModal,
args: {
actionBarEntries: [
{
ConfirmationModal: (
<ConfirmationModal
title="Title"
deleteButtonText="Delete"
onConfirmClick={() => {}}
setIsOpen={() => {}}
isOpen={false}
subtitle="Subtitle"
/>
),
Icon: IconTrash,
label: 'Label',
onClick: () => {},
},
],
customClassName: 'customClassName',
},
};
export default meta;
type Story = StoryObj<typeof SharedNavigationModal>;
export const Default: Story = {};

View File

@ -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 (
<div data-select-disable className={customClassName}>
{actionBarEntries[0]?.ConfirmationModal ?? null}
</div>
);
};
export default SharedNavigationModal;