Board improvements (#3694)

* New board improvements

* Improve board

* Fix
This commit is contained in:
Charles Bochet 2024-01-30 15:24:03 +01:00 committed by GitHub
parent 511627ccb8
commit f68de1a299
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 205 additions and 54 deletions

View File

@ -126,7 +126,7 @@
"react-error-boundary": "^4.0.11",
"react-helmet-async": "^1.3.0",
"react-hook-form": "^7.45.1",
"react-hotkeys-hook": "^4.4.3",
"react-hotkeys-hook": "^4.4.4",
"react-icons": "^4.12.0",
"react-intersection-observer": "^9.5.2",
"react-loading-skeleton": "^3.3.1",

View File

@ -1,6 +1,7 @@
import { useMemo, useRef, useState } from 'react';
import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum';
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
import { Activity } from '@/activities/types/Activity';
@ -124,7 +125,7 @@ export const CommandMenu = () => {
);
useScopedHotkeys(
'esc',
[Key.Escape],
() => {
closeCommandMenu();
},

View File

@ -1,4 +1,5 @@
import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
@ -22,6 +23,7 @@ export const KeyboardShortcutMenu = () => {
isKeyboardShortcutMenuOpenedState,
);
const { closeCommandMenu } = useCommandMenu();
useScopedHotkeys(
'shift+?,meta+?',
() => {
@ -33,12 +35,12 @@ export const KeyboardShortcutMenu = () => {
);
useScopedHotkeys(
'esc',
[Key.Escape],
() => {
closeKeyboardShortcutMenu();
},
AppHotkeyScope.KeyboardShortcutMenu,
[toggleKeyboardShortcutMenu],
AppHotkeyScope.KeyboardShortcutMenuOpen,
[closeKeyboardShortcutMenu],
);
return (
@ -46,8 +48,8 @@ export const KeyboardShortcutMenu = () => {
{isKeyboardShortcutMenuOpened && (
<KeyboardMenuDialog onClose={toggleKeyboardShortcutMenu}>
<KeyboardMenuGroup heading="Table">
{keyboardShortcutsTable.map((TableShortcut) => (
<KeyboardMenuItem shortcut={TableShortcut} />
{keyboardShortcutsTable.map((TableShortcut, index) => (
<KeyboardMenuItem shortcut={TableShortcut} key={index} />
))}
</KeyboardMenuGroup>
<KeyboardMenuGroup heading="General">

View File

@ -12,9 +12,7 @@ import { useRecordBoardDeprecatedScopedStates } from '@/object-record/record-boa
import { useSetRecordBoardDeprecatedCardSelectedInternal } from '@/object-record/record-board-deprecated/hooks/internal/useSetRecordBoardDeprecatedCardSelectedInternal';
import { RecordBoardDeprecatedScope } from '@/object-record/record-board-deprecated/scopes/RecordBoardDeprecatedScope';
import { Opportunity } from '@/pipeline/types/Opportunity';
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
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 { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { logError } from '~/utils/logError';
@ -129,12 +127,6 @@ export const RecordBoardDeprecated = ({
const boardRef = useRef<HTMLDivElement>(null);
useScopedHotkeys(
'escape',
unselectAllActiveCards,
PageHotkeyScope.OpportunitiesPage,
);
return (
<RecordBoardDeprecatedScope recordBoardScopeId={recordBoardId}>
<RecordBoardDeprecatedContextMenu />

View File

@ -69,7 +69,9 @@ export const RecordBoardDeprecatedColumnDropdownMenu = ({
useScopedHotkeys(
[Key.Escape, Key.Enter],
closeMenu,
() => {
closeMenu();
},
BoardColumnHotkeyScope.BoardColumn,
[],
);

View File

@ -123,7 +123,7 @@ export const RecordBoardDeprecatedOptionsDropdownContent = ({
);
useScopedHotkeys(
Key.Escape,
[Key.Escape],
() => {
setViewEditMode('none');
closeDropdown();

View File

@ -2,15 +2,19 @@ import { useContext, useRef } from 'react';
import styled from '@emotion/styled';
import { DragDropContext, OnDragEndResponder } from '@hello-pangea/dnd'; // Atlassian dnd does not support StrictMode from RN 18, so we use a fork @hello-pangea/dnd https://github.com/atlassian/react-beautiful-dnd/issues/2350
import { useRecoilCallback, useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum';
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection';
import { RecordBoardColumn } from '@/object-record/record-board/record-board-column/components/RecordBoardColumn';
import { RecordBoardScope } from '@/object-record/record-board/scopes/RecordBoardScope';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
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 { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
import { FieldMetadataType } from '~/generated-metadata/graphql';
export type RecordBoardProps = {
recordBoardId: string;
@ -40,17 +44,25 @@ const StyledBoardHeader = styled.div`
`;
export const RecordBoard = ({ recordBoardId }: RecordBoardProps) => {
const { updateOneRecord, objectMetadataItem } =
const { updateOneRecord, selectFieldMetadataItem } =
useContext(RecordBoardContext);
const boardRef = useRef<HTMLDivElement>(null);
const { getColumnIdsState, columnsFamilySelector } =
useRecordBoardStates(recordBoardId);
const columnIds = useRecoilValue(getColumnIdsState());
const selectFieldMetadataItem = objectMetadataItem.fields.find(
(field) => field.type === FieldMetadataType.Select,
);
const { resetRecordSelection, setRecordAsSelected } =
useRecordBoardSelection(recordBoardId);
useListenClickOutsideByClassName({
classNames: ['record-board-card'],
excludeClassNames: ['action-bar', 'context-menu'],
callback: resetRecordSelection,
});
useScopedHotkeys([Key.Escape], resetRecordSelection, TableHotkeyScope.Table);
const onDragEnd: OnDragEndResponder = useRecoilCallback(
({ snapshot }) =>
@ -106,7 +118,8 @@ export const RecordBoard = ({ recordBoardId }: RecordBoardProps) => {
</ScrollWrapper>
<DragSelect
dragSelectable={boardRef}
onDragSelectionChange={() => {}}
onDragSelectionStart={resetRecordSelection}
onDragSelectionChange={setRecordAsSelected}
/>
</StyledWrapper>
</RecordBoardScope>

View File

@ -1,10 +1,12 @@
import { createContext } from 'react';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
type RecordBoardContextProps = {
objectMetadataItem: ObjectMetadataItem;
selectFieldMetadataItem: FieldMetadataItem;
createOneRecord: (recordInput: Partial<ObjectRecord>) => void;
updateOneRecord: ({
idToUpdate,

View File

@ -2,7 +2,7 @@ import { useRecoilCallback } from 'recoil';
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
export const useResetBoardRecordSelection = (recordBoardId?: string) => {
export const useRecordBoardSelection = (recordBoardId?: string) => {
const { getSelectedRecordIdsSelector, isRecordBoardCardSelectedFamilyState } =
useRecordBoardStates(recordBoardId);
@ -20,5 +20,21 @@ export const useResetBoardRecordSelection = (recordBoardId?: string) => {
[getSelectedRecordIdsSelector, isRecordBoardCardSelectedFamilyState],
);
return { resetRecordSelection };
const setRecordAsSelected = useRecoilCallback(
({ snapshot, set }) =>
(recordId: string, isSelected: boolean) => {
const isRecordCurrentlySelected = snapshot
.getLoadable(isRecordBoardCardSelectedFamilyState(recordId))
.getValue();
if (isRecordCurrentlySelected === isSelected) {
return;
}
set(isRecordBoardCardSelectedFamilyState(recordId), isSelected);
},
[isRecordBoardCardSelectedFamilyState],
);
return { resetRecordSelection, setRecordAsSelected };
};

View File

@ -18,7 +18,7 @@ export const RecordBoardCardDraggableContainer = ({
{...draggableProvided?.dragHandleProps}
// eslint-disable-next-line react/jsx-props-no-spreading
{...draggableProvided?.draggableProps}
className="entity-board-card"
className="record-board-card"
data-selectable-id={recordId}
data-select-disable
>

View File

@ -14,6 +14,7 @@ const StyledColumn = styled.div<{ isFirstColumn: boolean }>`
isFirstColumn ? 'none' : theme.border.color.light};
display: flex;
flex-direction: column;
height: fit-content;
max-width: 200px;
min-width: 200px;
@ -60,6 +61,7 @@ export const RecordBoardColumn = ({
columnDefinition: columnDefinition,
isFirstColumn: isFirstColumn,
isLastColumn: isLastColumn,
recordCount: recordIds.length,
}}
>
<Droppable droppableId={recordBoardColumnId}>

View File

@ -1,8 +1,10 @@
import React from 'react';
import React, { useContext } from 'react';
import styled from '@emotion/styled';
import { DroppableProvided } from '@hello-pangea/dnd';
import { Draggable, DroppableProvided } from '@hello-pangea/dnd';
import { RecordBoardColumnCardsMemo } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnCardsMemo';
import { RecordBoardColumnNewButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewButton';
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
const StyledPlaceholder = styled.div`
min-height: 1px;
@ -14,6 +16,10 @@ const StyledColumnCardsContainer = styled.div`
flex-direction: column;
`;
const StyledNewButtonContainer = styled.div`
padding-bottom: ${({ theme }) => theme.spacing(4)};
`;
type RecordBoardColumnCardsContainerProps = {
recordIds: string[];
droppableProvided: DroppableProvided;
@ -23,6 +29,8 @@ export const RecordBoardColumnCardsContainer = ({
recordIds,
droppableProvided,
}: RecordBoardColumnCardsContainerProps) => {
const { columnDefinition } = useContext(RecordBoardColumnContext);
return (
<StyledColumnCardsContainer
ref={droppableProvided?.innerRef}
@ -31,6 +39,23 @@ export const RecordBoardColumnCardsContainer = ({
>
<RecordBoardColumnCardsMemo recordIds={recordIds} />
<StyledPlaceholder>{droppableProvided?.placeholder}</StyledPlaceholder>
<Draggable
draggableId={`new-${columnDefinition.id}`}
index={recordIds.length}
isDragDisabled={true}
>
{(draggableProvided) => (
<div
ref={draggableProvided?.innerRef}
// eslint-disable-next-line react/jsx-props-no-spreading
{...draggableProvided?.draggableProps}
>
<StyledNewButtonContainer>
<RecordBoardColumnNewButton />
</StyledNewButtonContainer>
</div>
)}
</Draggable>
</StyledColumnCardsContainer>
);
};

View File

@ -46,7 +46,9 @@ export const RecordBoardColumnHeader = () => {
const [isBoardColumnMenuOpen, setIsBoardColumnMenuOpen] = useState(false);
const [isHeaderHovered, setIsHeaderHovered] = useState(false);
const { columnDefinition } = useContext(RecordBoardColumnContext);
const { columnDefinition, recordCount } = useContext(
RecordBoardColumnContext,
);
const {
setHotkeyScopeAndMemorizePreviousScope,
@ -66,7 +68,6 @@ export const RecordBoardColumnHeader = () => {
};
const boardColumnTotal = 0;
const cardIds = [];
return (
<>
@ -81,7 +82,7 @@ export const RecordBoardColumnHeader = () => {
/>
{!!boardColumnTotal && <StyledAmount>${boardColumnTotal}</StyledAmount>}
{!isHeaderHovered && (
<StyledNumChildren>{cardIds.length}</StyledNumChildren>
<StyledNumChildren>{recordCount}</StyledNumChildren>
)}
{isHeaderHovered && (
<StyledHeaderActions>

View File

@ -0,0 +1,44 @@
import { useContext } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
import { IconPlus } from '@/ui/display/icon/index';
const StyledButton = styled.button`
align-items: center;
align-self: baseline;
background-color: ${({ theme }) => theme.background.primary};
border: none;
border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${({ theme }) => theme.font.color.tertiary};
cursor: pointer;
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
padding: ${({ theme }) => theme.spacing(1)};
&:hover {
background-color: ${({ theme }) => theme.background.tertiary};
}
`;
export const RecordBoardColumnNewButton = () => {
const theme = useTheme();
const { columnDefinition } = useContext(RecordBoardColumnContext);
const { createOneRecord, selectFieldMetadataItem } =
useContext(RecordBoardContext);
const onNewClick = () => {
createOneRecord({
[selectFieldMetadataItem.name]: columnDefinition.value,
});
};
return (
<StyledButton onClick={onNewClick}>
<IconPlus size={theme.icon.size.md} />
New
</StyledButton>
);
};

View File

@ -6,6 +6,7 @@ type RecordBoardColumnContextProps = {
columnDefinition: RecordBoardColumnDefinition;
isFirstColumn: boolean;
isLastColumn: boolean;
recordCount: number;
};
export const RecordBoardColumnContext =

View File

@ -1,3 +1,5 @@
import { Key } from 'ts-key-enum';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { isDefined } from '~/utils/isDefined';
@ -41,7 +43,7 @@ export const useRegisterInputEvents = <T>({
);
useScopedHotkeys(
'esc',
[Key.Escape],
() => {
onEscape?.(inputValue);
},

View File

@ -6,6 +6,7 @@ import { RecordBoardActionBar } from '@/object-record/record-board/action-bar/co
import { RecordBoard } from '@/object-record/record-board/components/RecordBoard';
import { RecordBoardContextMenu } from '@/object-record/record-board/context-menu/components/RecordBoardContextMenu';
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
import { FieldMetadataType } from '~/generated-metadata/graphql';
type RecordIndexBoardContainerProps = {
recordBoardId: string;
@ -20,14 +21,23 @@ export const RecordIndexBoardContainer = ({
}: RecordIndexBoardContainerProps) => {
const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular });
const selectFieldMetadataItem = objectMetadataItem.fields.find(
(field) => field.type === FieldMetadataType.Select,
);
const { deleteOneRecord } = useDeleteOneRecord({ objectNameSingular });
const { updateOneRecord } = useUpdateOneRecord({ objectNameSingular });
const { createOneRecord } = useCreateOneRecord({ objectNameSingular });
if (!selectFieldMetadataItem) {
return;
}
return (
<RecordBoardContext.Provider
value={{
objectMetadataItem,
selectFieldMetadataItem,
createOneRecord,
updateOneRecord,
deleteOneRecord,

View File

@ -5,8 +5,9 @@ import { useRecoilValue } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useRecordActionBar } from '@/object-record/record-action-bar/hooks/useRecordActionBar';
import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard';
import { useResetBoardRecordSelection } from '@/object-record/record-board/hooks/useResetBoardRecordSelection';
import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection';
import { useLoadRecordIndexBoard } from '@/object-record/record-index/hooks/useLoadRecordIndexBoard';
import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/states/recordIndexFieldDefinitionsState';
import { computeRecordBoardColumnDefinitionsFromObjectMetadata } from '@/object-record/utils/computeRecordBoardColumnDefinitionsFromObjectMetadata';
type RecordIndexBoardContainerEffectProps = {
@ -18,12 +19,13 @@ type RecordIndexBoardContainerEffectProps = {
export const RecordIndexBoardContainerEffect = ({
objectNameSingular,
recordBoardId,
viewBarId,
}: RecordIndexBoardContainerEffectProps) => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
useLoadRecordIndexBoard(objectNameSingular, recordBoardId);
useLoadRecordIndexBoard({ objectNameSingular, recordBoardId, viewBarId });
const navigate = useNavigate();
@ -31,9 +33,13 @@ export const RecordIndexBoardContainerEffect = ({
navigate(`/settings/objects/${objectMetadataItem.namePlural}`);
}, [navigate, objectMetadataItem.namePlural]);
const { setColumns, setObjectSingularName, getSelectedRecordIdsSelector } =
useRecordBoard(recordBoardId);
const { resetRecordSelection } = useResetBoardRecordSelection(recordBoardId);
const {
setColumns,
setObjectSingularName,
getSelectedRecordIdsSelector,
setFieldDefinitions,
} = useRecordBoard(recordBoardId);
const { resetRecordSelection } = useRecordBoardSelection(recordBoardId);
useEffect(() => {
setObjectSingularName(objectNameSingular);
@ -53,6 +59,14 @@ export const RecordIndexBoardContainerEffect = ({
setColumns,
]);
const recordIndexFieldDefinitions = useRecoilValue(
recordIndexFieldDefinitionsState,
);
useEffect(() => {
setFieldDefinitions(recordIndexFieldDefinitions);
}, [objectMetadataItem, setFieldDefinitions, recordIndexFieldDefinitions]);
const selectedRecordIds = useRecoilValue(getSelectedRecordIdsSelector());
const { setActionBarEntries, setContextMenuEntries } = useRecordActionBar({

View File

@ -10,11 +10,19 @@ import { recordIndexFieldDefinitionsState } from '@/object-record/record-index/s
import { recordIndexFiltersState } from '@/object-record/record-index/states/recordIndexFiltersState';
import { recordIndexSortsState } from '@/object-record/record-index/states/recordIndexSortsState';
import { useSetRecordInStore } from '@/object-record/record-store/hooks/useSetRecordInStore';
import { useViewBar } from '@/views/hooks/useViewBar';
export const useLoadRecordIndexBoard = (
objectNameSingular: string,
recordBoardId: string,
) => {
type UseLoadRecordIndexBoardProps = {
objectNameSingular: string;
viewBarId: string;
recordBoardId: string;
};
export const useLoadRecordIndexBoard = ({
objectNameSingular,
viewBarId,
recordBoardId,
}: UseLoadRecordIndexBoardProps) => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
@ -47,6 +55,10 @@ export const useLoadRecordIndexBoard = (
orderBy,
});
const { setEntityCountInCurrentView } = useViewBar({
viewBarId,
});
useEffect(() => {
setRecordIdsInBoard(records);
}, [records, setRecordIdsInBoard]);
@ -55,6 +67,10 @@ export const useLoadRecordIndexBoard = (
setRecordsInStore(records);
}, [records, setRecordsInStore]);
useEffect(() => {
setEntityCountInCurrentView(records.length);
}, [records.length, setEntityCountInCurrentView]);
return {
records,
loading,

View File

@ -1,3 +1,5 @@
import { Key } from 'ts-key-enum';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
@ -28,7 +30,7 @@ export const RecordTableInternalEffect = ({
});
useScopedHotkeys(
'escape',
[Key.Escape],
() => {
resetTableRowSelection();
},

View File

@ -80,7 +80,7 @@ export const TableOptionsDropdownContent = ({
const resetMenu = () => setCurrentMenu(undefined);
useScopedHotkeys(
Key.Escape,
[Key.Escape],
() => {
closeDropdown();
},

View File

@ -59,7 +59,7 @@ export const SingleEntitySelectMenuItems = ({
);
useScopedHotkeys(
Key.Escape,
[Key.Escape],
() => {
onCancel?.();
},

View File

@ -94,7 +94,7 @@ export const DoubleTextInput = ({
);
useScopedHotkeys(
Key.Escape,
[Key.Escape],
() => {
onEscape({
firstValue: firstInternalValue,

View File

@ -2,6 +2,7 @@ import { useState } from 'react';
import { HotkeysEvent } from 'react-hotkeys-hook/dist/types';
import TextareaAutosize from 'react-textarea-autosize';
import styled from '@emotion/styled';
import { Key } from 'ts-key-enum';
import { IconArrowRight } from '@/ui/display/icon/index';
import { Button } from '@/ui/input/button/components/Button';
@ -151,7 +152,7 @@ export const AutosizeTextInput = ({
);
useScopedHotkeys(
'esc',
Key.Escape,
(event: KeyboardEvent) => {
if (!isFocused) {
return;

View File

@ -94,7 +94,7 @@ export const Dropdown = ({
});
useScopedHotkeys(
Key.Escape,
[Key.Escape],
() => {
closeDropdown();
},

View File

@ -66,7 +66,10 @@ export const RightDrawer = () => {
useScopedHotkeys(
[Key.Escape],
() => closeRightDrawer(),
() => {
closeRightDrawer();
},
RightDrawerHotkeyScope.RightDrawer,
[setIsRightDrawerOpen],
);

View File

@ -4,4 +4,5 @@ export enum AppHotkeyScope {
CommandMenu = 'command-menu',
CommandMenuOpen = 'command-menu-open',
KeyboardShortcutMenu = 'keyboard-shortcut-menu',
KeyboardShortcutMenuOpen = 'keyboard-shortcut-menu-open',
}

View File

@ -127,8 +127,9 @@ export const useViewBar = (props?: UseViewProps) => {
if (!isDeeplyEqual(savedViewFields, queriedViewFields)) {
set(currentViewFieldsState, queriedViewFields);
set(savedViewFieldsState, queriedViewFields);
onViewFieldsChange?.(queriedViewFields);
}
onViewFieldsChange?.(queriedViewFields);
},
[scopeId],
);

View File

@ -38381,13 +38381,13 @@ __metadata:
languageName: node
linkType: hard
"react-hotkeys-hook@npm:^4.4.3":
version: 4.4.3
resolution: "react-hotkeys-hook@npm:4.4.3"
"react-hotkeys-hook@npm:^4.4.4":
version: 4.4.4
resolution: "react-hotkeys-hook@npm:4.4.4"
peerDependencies:
react: ">=16.8.1"
react-dom: ">=16.8.1"
checksum: ef79e279129f6e55d81c8762b1da214d9c6ee4617b9597dbc3f93057cba8d166831508967b8e3f763a0c4ce0af3b59e6888c6fc94d152deac9335020b2ba80df
checksum: afe7418c8bd0ecd3a3f315648b84d9978d06a02e55f93df23aaa00e56613fca839f9ff4d10387e8336454702b4aa03cef97f3e67bfe3e121e42f16d84685f55a
languageName: node
linkType: hard
@ -43453,7 +43453,7 @@ __metadata:
react-error-boundary: "npm:^4.0.11"
react-helmet-async: "npm:^1.3.0"
react-hook-form: "npm:^7.45.1"
react-hotkeys-hook: "npm:^4.4.3"
react-hotkeys-hook: "npm:^4.4.4"
react-icons: "npm:^4.12.0"
react-intersection-observer: "npm:^9.5.2"
react-loading-skeleton: "npm:^3.3.1"