Skip to content

Commit

Permalink
unselect all cards using esc key or click (twentyhq#1393)
Browse files Browse the repository at this point in the history
* unselect all cards using esc key or click

* useScopedHotKeys

* useListenClickByClassName

* rules are rules

* smoothing out || cursor-boxing-selection compliant

* replenished activeCardIds

* setRecoilState
  • Loading branch information
friendlymatthew authored Sep 1, 2023
1 parent f067476 commit c3c5cb4
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 7 deletions.
17 changes: 17 additions & 0 deletions front/src/modules/ui/board/components/EntityBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ import { useRecoilState } from 'recoil';

import { CompanyBoardRecoilScopeContext } from '@/companies/states/recoil-scope-contexts/CompanyBoardRecoilScopeContext';
import { GET_PIPELINE_PROGRESS } from '@/pipeline/graphql/queries/getPipelineProgress';
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
import { BoardHeader } from '@/ui/board/components/BoardHeader';
import { StyledBoard } from '@/ui/board/components/StyledBoard';
import { BoardColumnIdContext } from '@/ui/board/contexts/BoardColumnIdContext';
import { SelectedSortType } from '@/ui/filter-n-sort/types/interface';
import { IconList } from '@/ui/icon';
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useListenClickOutsideByClassName } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import {
Expand All @@ -22,6 +25,7 @@ import {
useUpdateOnePipelineProgressStageMutation,
} from '~/generated/graphql';

import { useCurrentCardSelected } from '../hooks/useCurrentCardSelected';
import { useSetCardSelected } from '../hooks/useSetCardSelected';
import { useUpdateBoardCardIds } from '../hooks/useUpdateBoardCardIds';
import { boardColumnsState } from '../states/boardColumnsState';
Expand Down Expand Up @@ -54,6 +58,8 @@ export function EntityBoard({
const [updatePipelineProgressStage] =
useUpdateOnePipelineProgressStageMutation();

const { unselectAllActiveCards } = useCurrentCardSelected();

const updatePipelineProgressStageInDB = useCallback(
async (
pipelineProgressId: NonNullable<PipelineProgress['id']>,
Expand All @@ -70,6 +76,11 @@ export function EntityBoard({
[updatePipelineProgressStage],
);

useListenClickOutsideByClassName({
className: 'entity-board-card',
callback: unselectAllActiveCards,
});

const updateBoardCardIds = useUpdateBoardCardIds();

const onDragEnd: OnDragEndResponder = useCallback(
Expand Down Expand Up @@ -106,6 +117,12 @@ export function EntityBoard({

const boardRef = useRef<HTMLDivElement>(null);

useScopedHotkeys(
'escape',
unselectAllActiveCards,
PageHotkeyScope.OpportunitiesPage,
);

return (boardColumns?.length ?? 0) > 0 ? (
<StyledWrapper>
<BoardHeader
Expand Down
1 change: 1 addition & 0 deletions front/src/modules/ui/board/components/EntityBoardCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export function EntityBoardCard({
ref={draggableProvided?.innerRef}
{...draggableProvided?.dragHandleProps}
{...draggableProvided?.draggableProps}
className="entity-board-card"
data-selectable-id={cardId}
data-select-disable
onContextMenu={handleContextMenu}
Expand Down
36 changes: 33 additions & 3 deletions front/src/modules/ui/board/hooks/useCurrentCardSelected.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useContext } from 'react';
import { useRecoilCallback, useRecoilValue } from 'recoil';
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';

import { actionBarOpenState } from '@/ui/action-bar/states/actionBarIsOpenState';

import { BoardCardIdContext } from '../contexts/BoardCardIdContext';
import { activeCardIdsState } from '../states/activeCardIdsState';
import { isCardSelectedFamilyState } from '../states/isCardSelectedFamilyState';

export function useCurrentCardSelected() {
Expand All @@ -13,19 +14,48 @@ export function useCurrentCardSelected() {
isCardSelectedFamilyState(currentCardId ?? ''),
);

const setActiveCardIds = useSetRecoilState(activeCardIdsState);

const setCurrentCardSelected = useRecoilCallback(
({ set }) =>
(selected: boolean) => {
if (!currentCardId) return;

set(isCardSelectedFamilyState(currentCardId), selected);
set(actionBarOpenState, true);
set(actionBarOpenState, selected);

if (selected) {
setActiveCardIds((prevActiveCardIds) => [
...prevActiveCardIds,
currentCardId,
]);
} else {
setActiveCardIds((prevActiveCardIds) =>
prevActiveCardIds.filter((id) => id !== currentCardId),
);
}
},
[currentCardId, setActiveCardIds],
);

const unselectAllActiveCards = useRecoilCallback(
({ set, snapshot }) =>
() => {
const activeCardIds = snapshot.getLoadable(activeCardIdsState).contents;

activeCardIds.forEach((cardId: string) => {
set(isCardSelectedFamilyState(cardId), false);
});

set(activeCardIdsState, []);
set(actionBarOpenState, false);
},
[currentCardId],
[],
);

return {
currentCardSelected: isCardSelected,
setCurrentCardSelected,
unselectAllActiveCards,
};
}
23 changes: 19 additions & 4 deletions front/src/modules/ui/board/hooks/useSetCardSelected.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,28 @@ import { useRecoilCallback, useSetRecoilState } from 'recoil';

import { actionBarOpenState } from '@/ui/action-bar/states/actionBarIsOpenState';

import { activeCardIdsState } from '../states/activeCardIdsState';
import { isCardSelectedFamilyState } from '../states/isCardSelectedFamilyState';

export function useSetCardSelected() {
const setActionBarOpenState = useSetRecoilState(actionBarOpenState);

return useRecoilCallback(({ set }) => (cardId: string, selected: boolean) => {
set(isCardSelectedFamilyState(cardId), selected);
setActionBarOpenState(true);
});
return useRecoilCallback(
({ set, snapshot }) =>
(cardId: string, selected: boolean) => {
const activeCardIds = snapshot.getLoadable(activeCardIdsState).contents;

set(isCardSelectedFamilyState(cardId), selected);
setActionBarOpenState(selected || activeCardIds.length > 0);

if (selected) {
set(activeCardIdsState, [...activeCardIds, cardId]);
} else {
set(
activeCardIdsState,
activeCardIds.filter((id: string) => id !== cardId),
);
}
},
);
}
6 changes: 6 additions & 0 deletions front/src/modules/ui/board/states/activeCardIdsState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { atom } from 'recoil';

export const activeCardIdsState = atom<string[]>({
key: 'activeCardIdsState',
default: [],
});
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,38 @@ export function useListenClickOutside<T extends Element>({
};
}, [refs, callback, mode]);
}

export const useListenClickOutsideByClassName = ({
className,
callback,
}: {
className: string;
callback: () => void;
}) => {
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
const clickedElement = event.target as HTMLElement;
let isClickedInside = false;
let currentElement: HTMLElement | null = clickedElement;

// Check if the clicked element or any of its parent elements have the specified class
while (currentElement) {
if (currentElement.classList.contains(className)) {
isClickedInside = true;
break;
}
currentElement = currentElement.parentElement;
}

if (!isClickedInside) {
callback();
}
};

document.addEventListener('mousedown', handleClickOutside);

return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [callback, className]);
};

0 comments on commit c3c5cb4

Please sign in to comment.