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:
committed by
GitHub
parent
e9cf449706
commit
520a883c73
@ -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(
|
||||||
|
|||||||
@ -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"
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 = {};
|
||||||
@ -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;
|
||||||
Reference in New Issue
Block a user