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 { 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(

View File

@ -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<HTMLDivElement>(null);
@ -62,9 +71,10 @@ export const ActionBar = ({ selectedIds }: ActionBarProps) => {
<ActionBarItem key={index} item={item} />
))}
</StyledContainerActionBar>
<div data-select-disable className="action-bar">
{actionBarEntries[0]?.ConfirmationModal}
</div>
<SharedNavigationModal
actionBarEntries={actionBarEntries}
customClassName="action-bar"
/>
</>
);
};

View File

@ -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<HTMLDivElement>(null);
useListenClickOutside({
refs: [wrapperRef],
callback: () => {
setContextMenuOpenState(false);
},
});
const actionBarEntries = useRecoilValue(actionBarEntriesState);
if (!contextMenuIsOpen) {
return null;
@ -61,18 +55,24 @@ export const ContextMenu = () => {
: undefined;
return (
<StyledContainerContextMenu
className="context-menu"
ref={wrapperRef}
position={contextMenuPosition}
>
<DropdownMenu data-select-disable width={width}>
<DropdownMenuItemsContainer>
{contextMenuEntries.map((item, index) => {
return <ContextMenuItem key={index} item={item} />;
})}
</DropdownMenuItemsContainer>
</DropdownMenu>
</StyledContainerContextMenu>
<>
<StyledContainerContextMenu
className="context-menu"
ref={wrapperRef}
position={contextMenuPosition}
>
<DropdownMenu data-select-disable width={width}>
<DropdownMenuItemsContainer>
{contextMenuEntries.map((item, index) => {
return <ContextMenuItem key={index} item={item} />;
})}
</DropdownMenuItemsContainer>
</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;