diff --git a/packages/twenty-front/src/modules/action-menu/actions/components/ActionModal.tsx b/packages/twenty-front/src/modules/action-menu/actions/components/ActionModal.tsx index 14787efc4..c21113dea 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/components/ActionModal.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/components/ActionModal.tsx @@ -33,9 +33,8 @@ export const ActionModal = ({ const { closeActionMenu } = useCloseActionMenu(); const handleConfirmClick = () => { - closeActionMenu(); onConfirmClick(); - setIsOpen(false); + closeActionMenu(); }; const actionConfig = useContext(ActionConfigContext); diff --git a/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx b/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx index d99712590..0749116df 100644 --- a/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx +++ b/packages/twenty-front/src/modules/app/effect-components/PageChangeEffect.tsx @@ -17,11 +17,16 @@ import { isCaptchaScriptLoadedState } from '@/captcha/states/isCaptchaScriptLoad import { isCaptchaRequiredForPath } from '@/captcha/utils/isCaptchaRequiredForPath'; import { MAIN_CONTEXT_STORE_INSTANCE_ID } from '@/context-store/constants/MainContextStoreInstanceId'; import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState'; +import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState'; +import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType'; import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural'; +import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard'; +import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard'; +import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection'; +import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection'; import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow'; import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow'; -import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId'; import { AppBasePath } from '@/types/AppBasePath'; import { AppPath } from '@/types/AppPath'; @@ -64,6 +69,11 @@ export const PageChangeEffect = () => { MAIN_CONTEXT_STORE_INSTANCE_ID, ); + const contextStoreCurrentViewType = useRecoilComponentValueV2( + contextStoreCurrentViewTypeComponentState, + MAIN_CONTEXT_STORE_INSTANCE_ID, + ); + const recordIndexId = getRecordIndexIdFromObjectNamePluralAndViewId( objectNamePlural, contextStoreCurrentViewId || '', @@ -73,6 +83,10 @@ export const PageChangeEffect = () => { const { unfocusRecordTableRow } = useFocusedRecordTableRow(recordIndexId); const { deactivateRecordTableRow } = useActiveRecordTableRow(recordIndexId); + const { resetRecordSelection } = useRecordBoardSelection(recordIndexId); + const { deactivateBoardCard } = useActiveRecordBoardCard(recordIndexId); + const { unfocusBoardCard } = useFocusedRecordBoardCard(recordIndexId); + const { executeTasksOnAnyLocationChange } = useExecuteTasksOnAnyLocationChange(); @@ -100,9 +114,16 @@ export const PageChangeEffect = () => { ); if (isLeavingRecordIndexPage) { - resetTableSelections(); - unfocusRecordTableRow(); - deactivateRecordTableRow(); + if (contextStoreCurrentViewType === ContextStoreViewType.Table) { + resetTableSelections(); + unfocusRecordTableRow(); + deactivateRecordTableRow(); + } + if (contextStoreCurrentViewType === ContextStoreViewType.Kanban) { + resetRecordSelection(); + deactivateBoardCard(); + unfocusBoardCard(); + } } }, [ isMatchingLocation, @@ -110,12 +131,16 @@ export const PageChangeEffect = () => { resetTableSelections, unfocusRecordTableRow, deactivateRecordTableRow, + contextStoreCurrentViewType, + resetRecordSelection, + deactivateBoardCard, + unfocusBoardCard, ]); useEffect(() => { switch (true) { case isMatchingLocation(AppPath.RecordIndexPage): { - setHotkeyScope(TableHotkeyScope.Table, { + setHotkeyScope(RecordIndexHotkeyScope.RecordIndex, { goto: true, keyboardShortcutMenu: true, }); diff --git a/packages/twenty-front/src/modules/command-menu/hooks/useNavigateCommandMenu.ts b/packages/twenty-front/src/modules/command-menu/hooks/useNavigateCommandMenu.ts index 36bb69231..c92fb987d 100644 --- a/packages/twenty-front/src/modules/command-menu/hooks/useNavigateCommandMenu.ts +++ b/packages/twenty-front/src/modules/command-menu/hooks/useNavigateCommandMenu.ts @@ -51,6 +51,10 @@ export const useNavigateCommandMenu = () => { commandMenuCloseAnimationCompleteCleanup(); } + if (isCommandMenuOpened) { + return; + } + setHotkeyScopeAndMemorizePreviousScope( CommandMenuHotkeyScope.CommandMenuFocused, { @@ -58,10 +62,6 @@ export const useNavigateCommandMenu = () => { }, ); - if (isCommandMenuOpened) { - return; - } - copyContextStoreStates({ instanceIdToCopyFrom: MAIN_CONTEXT_STORE_INSTANCE_ID, instanceIdToCopyTo: COMMAND_MENU_COMPONENT_INSTANCE_ID, diff --git a/packages/twenty-front/src/modules/keyboard-shortcut-menu/hooks/__tests__/useKeyboardShortcutMenu.test.tsx b/packages/twenty-front/src/modules/keyboard-shortcut-menu/hooks/__tests__/useKeyboardShortcutMenu.test.tsx index 49d98de15..d38b3b577 100644 --- a/packages/twenty-front/src/modules/keyboard-shortcut-menu/hooks/__tests__/useKeyboardShortcutMenu.test.tsx +++ b/packages/twenty-front/src/modules/keyboard-shortcut-menu/hooks/__tests__/useKeyboardShortcutMenu.test.tsx @@ -1,5 +1,6 @@ import { expect } from '@storybook/test'; -import { act, renderHook } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; +import { act } from 'react'; import { RecoilRoot, useRecoilValue } from 'recoil'; import { isKeyboardShortcutMenuOpenedState } from '@/keyboard-shortcut-menu/states/isKeyboardShortcutMenuOpenedState'; diff --git a/packages/twenty-front/src/modules/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu.ts b/packages/twenty-front/src/modules/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu.ts index 347e40db0..0d568a473 100644 --- a/packages/twenty-front/src/modules/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu.ts +++ b/packages/twenty-front/src/modules/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu.ts @@ -1,4 +1,4 @@ -import { useRecoilState, useRecoilValue } from 'recoil'; +import { useRecoilCallback } from 'recoil'; import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; @@ -6,38 +6,52 @@ import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; import { isKeyboardShortcutMenuOpenedState } from '../states/isKeyboardShortcutMenuOpenedState'; export const useKeyboardShortcutMenu = () => { - const [, setIsKeyboardShortcutMenuOpened] = useRecoilState( - isKeyboardShortcutMenuOpenedState, - ); - const isKeyboardShortcutMenuOpened = useRecoilValue( - isKeyboardShortcutMenuOpenedState, - ); const { setHotkeyScopeAndMemorizePreviousScope, goBackToPreviousHotkeyScope, } = usePreviousHotkeyScope(); - const toggleKeyboardShortcutMenu = () => { - if (isKeyboardShortcutMenuOpened === false) { - setIsKeyboardShortcutMenuOpened(true); - setHotkeyScopeAndMemorizePreviousScope( - AppHotkeyScope.KeyboardShortcutMenu, - ); - } else { - setIsKeyboardShortcutMenuOpened(false); - goBackToPreviousHotkeyScope(); - } - }; + const openKeyboardShortcutMenu = useRecoilCallback( + ({ set }) => + () => { + set(isKeyboardShortcutMenuOpenedState, true); + setHotkeyScopeAndMemorizePreviousScope( + AppHotkeyScope.KeyboardShortcutMenu, + ); + }, + [setHotkeyScopeAndMemorizePreviousScope], + ); - const openKeyboardShortcutMenu = () => { - setIsKeyboardShortcutMenuOpened(true); - setHotkeyScopeAndMemorizePreviousScope(AppHotkeyScope.KeyboardShortcutMenu); - }; + const closeKeyboardShortcutMenu = useRecoilCallback( + ({ set, snapshot }) => + () => { + const isKeyboardShortcutMenuOpened = snapshot + .getLoadable(isKeyboardShortcutMenuOpenedState) + .getValue(); - const closeKeyboardShortcutMenu = () => { - setIsKeyboardShortcutMenuOpened(false); - goBackToPreviousHotkeyScope(); - }; + if (isKeyboardShortcutMenuOpened) { + set(isKeyboardShortcutMenuOpenedState, false); + goBackToPreviousHotkeyScope(); + } + }, + [goBackToPreviousHotkeyScope], + ); + + const toggleKeyboardShortcutMenu = useRecoilCallback( + ({ snapshot }) => + () => { + const isKeyboardShortcutMenuOpened = snapshot + .getLoadable(isKeyboardShortcutMenuOpenedState) + .getValue(); + + if (isKeyboardShortcutMenuOpened === false) { + openKeyboardShortcutMenu(); + } else { + closeKeyboardShortcutMenu(); + } + }, + [closeKeyboardShortcutMenu, openKeyboardShortcutMenu], + ); return { toggleKeyboardShortcutMenu, diff --git a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx index c87033b9b..04b57f57f 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoard.tsx @@ -5,10 +5,14 @@ import { useRecoilCallback, useSetRecoilState } from 'recoil'; import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId'; import { RecordBoardHeader } from '@/object-record/record-board/components/RecordBoardHeader'; +import { RecordBoardScrollToFocusedCardEffect } from '@/object-record/record-board/components/RecordBoardScrollToFocusedCardEffect'; import { RecordBoardStickyHeaderEffect } from '@/object-record/record-board/components/RecordBoardStickyHeaderEffect'; import { RECORD_BOARD_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-board/constants/RecordBoardClickOutsideListenerId'; import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; +import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard'; +import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard'; import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection'; +import { RecordBoardDeactivateBoardCardEffect } from '@/object-record/record-board/record-board-card/components/RecordBoardDeactivateBoardCardEffect'; import { RecordBoardColumn } from '@/object-record/record-board/record-board-column/components/RecordBoardColumn'; import { RecordBoardScope } from '@/object-record/record-board/scopes/RecordBoardScope'; import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext'; @@ -16,14 +20,11 @@ import { getDraggedRecordPosition } from '@/object-record/record-board/utils/get import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState'; import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector'; import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState'; -import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector'; import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { isRemoveSortingModalOpenState } from '@/object-record/record-table/states/isRemoveSortingModalOpenState'; -import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2'; import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect'; -import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener'; import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; @@ -71,6 +72,9 @@ export const RecordBoard = () => { const { closeDropdown } = useDropdownV2(); + const { deactivateBoardCard } = useActiveRecordBoardCard(recordBoardId); + const { unfocusBoardCard } = useFocusedRecordBoardCard(recordBoardId); + const handleDragSelectionStart = () => { closeDropdown(actionMenuId); @@ -91,10 +95,6 @@ export const RecordBoard = () => { recordIndexRecordIdsByGroupComponentFamilyState, ); - const recordIndexAllRecordIdsState = useRecoilComponentCallbackStateV2( - recordIndexAllRecordIdsComponentSelector, - ); - const { resetRecordSelection, setRecordAsSelected } = useRecordBoardSelection(recordBoardId); @@ -115,26 +115,11 @@ export const RecordBoard = () => { refs: [], callback: () => { resetRecordSelection(); + deactivateBoardCard(); + unfocusBoardCard(); }, }); - const selectAll = useRecoilCallback( - ({ snapshot }) => - () => { - const allRecordIds = getSnapshotValue( - snapshot, - recordIndexAllRecordIdsState, - ); - - for (const recordId of allRecordIds) { - setRecordAsSelected(recordId, true); - } - }, - [recordIndexAllRecordIdsState, setRecordAsSelected], - ); - - useScopedHotkeys('ctrl+a,meta+a', selectAll, TableHotkeyScope.Table); - const setIsRemoveSortingModalOpen = useSetRecoilState( isRemoveSortingModalOpenState, ); @@ -228,16 +213,19 @@ export const RecordBoard = () => { componentInstanceId={`scroll-wrapper-record-board-${recordBoardId}`} > + + - {visibleRecordGroupIds.map((recordGroupId) => ( + {visibleRecordGroupIds.map((recordGroupId, index) => ( ))} diff --git a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardBodyEscapeHotkeyEffect.tsx b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardBodyEscapeHotkeyEffect.tsx new file mode 100644 index 000000000..717aaf11f --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardBodyEscapeHotkeyEffect.tsx @@ -0,0 +1,52 @@ +import { useContext } from 'react'; +import { Key } from 'ts-key-enum'; + +import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; +import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard'; +import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection'; +import { recordBoardSelectedRecordIdsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardSelectedRecordIdsComponentSelector'; +import { BoardHotkeyScope } from '@/object-record/record-board/types/BoardHotkeyScope'; +import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; +import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; + +export const RecordBoardBodyEscapeHotkeyEffect = () => { + const { recordBoardId } = useContext(RecordBoardContext); + + const { resetRecordSelection } = useRecordBoardSelection(recordBoardId); + + const { unfocusBoardCard } = useFocusedRecordBoardCard(recordBoardId); + + const selectedRecordIds = useRecoilComponentValueV2( + recordBoardSelectedRecordIdsComponentSelector, + recordBoardId, + ); + + const isAtLeastOneRecordSelected = selectedRecordIds.length > 0; + + useScopedHotkeys( + [Key.Escape], + () => { + unfocusBoardCard(); + if (isAtLeastOneRecordSelected) { + resetRecordSelection(); + } + }, + RecordIndexHotkeyScope.RecordIndex, + [isAtLeastOneRecordSelected, resetRecordSelection, unfocusBoardCard], + ); + + useScopedHotkeys( + [Key.Escape], + () => { + unfocusBoardCard(); + if (isAtLeastOneRecordSelected) { + resetRecordSelection(); + } + }, + BoardHotkeyScope.BoardFocus, + [isAtLeastOneRecordSelected, resetRecordSelection, unfocusBoardCard], + ); + + return null; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardHeader.tsx b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardHeader.tsx index 3ca0b6e68..187606a64 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardHeader.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardHeader.tsx @@ -31,9 +31,10 @@ export const RecordBoardHeader = () => { return ( - {visibleRecordGroupIds.map((recordGroupId) => ( + {visibleRecordGroupIds.map((recordGroupId, index) => ( ))} diff --git a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardHotkeyEffect.tsx b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardHotkeyEffect.tsx new file mode 100644 index 000000000..6ceebcdc1 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardHotkeyEffect.tsx @@ -0,0 +1,121 @@ +import { useContext } from 'react'; +import { Key } from 'ts-key-enum'; + +import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; +import { useRecordBoardCardNavigation } from '@/object-record/record-board/hooks/useRecordBoardCardNavigation'; +import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection'; +import { BoardHotkeyScope } from '@/object-record/record-board/types/BoardHotkeyScope'; +import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector'; +import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; +import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope'; +import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; +import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; +import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; +import { useRecoilCallback } from 'recoil'; + +export const RecordBoardHotkeyEffect = () => { + const { recordBoardId } = useContext(RecordBoardContext); + + const { move } = useRecordBoardCardNavigation(recordBoardId); + + const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope(); + + const recordIndexAllRecordIdsState = useRecoilComponentCallbackStateV2( + recordIndexAllRecordIdsComponentSelector, + ); + + const { setRecordAsSelected } = useRecordBoardSelection(recordBoardId); + + const selectAll = useRecoilCallback( + ({ snapshot }) => + () => { + const allRecordIds = getSnapshotValue( + snapshot, + recordIndexAllRecordIdsState, + ); + + for (const recordId of allRecordIds) { + setRecordAsSelected(recordId, true); + } + }, + [recordIndexAllRecordIdsState, setRecordAsSelected], + ); + + useScopedHotkeys( + 'ctrl+a,meta+a', + selectAll, + RecordIndexHotkeyScope.RecordIndex, + ); + + useScopedHotkeys('ctrl+a,meta+a', selectAll, BoardHotkeyScope.BoardFocus); + + useScopedHotkeys( + Key.ArrowLeft, + () => { + setHotkeyScopeAndMemorizePreviousScope(BoardHotkeyScope.BoardFocus); + move('left'); + }, + RecordIndexHotkeyScope.RecordIndex, + ); + + useScopedHotkeys( + Key.ArrowRight, + () => { + setHotkeyScopeAndMemorizePreviousScope(BoardHotkeyScope.BoardFocus); + move('right'); + }, + RecordIndexHotkeyScope.RecordIndex, + ); + + useScopedHotkeys( + Key.ArrowUp, + () => { + setHotkeyScopeAndMemorizePreviousScope(BoardHotkeyScope.BoardFocus); + move('up'); + }, + RecordIndexHotkeyScope.RecordIndex, + ); + + useScopedHotkeys( + Key.ArrowDown, + () => { + setHotkeyScopeAndMemorizePreviousScope(BoardHotkeyScope.BoardFocus); + move('down'); + }, + RecordIndexHotkeyScope.RecordIndex, + ); + + useScopedHotkeys( + Key.ArrowLeft, + () => { + move('left'); + }, + BoardHotkeyScope.BoardFocus, + ); + + useScopedHotkeys( + Key.ArrowRight, + () => { + move('right'); + }, + BoardHotkeyScope.BoardFocus, + ); + + useScopedHotkeys( + Key.ArrowUp, + () => { + move('up'); + }, + BoardHotkeyScope.BoardFocus, + ); + + useScopedHotkeys( + Key.ArrowDown, + () => { + move('down'); + }, + BoardHotkeyScope.BoardFocus, + ); + + return null; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardScrollToFocusedCardEffect.tsx b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardScrollToFocusedCardEffect.tsx new file mode 100644 index 000000000..1d6cdbf1d --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/components/RecordBoardScrollToFocusedCardEffect.tsx @@ -0,0 +1,45 @@ +import { useEffect } from 'react'; + +import { RecordBoardScopeInternalContext } from '@/object-record/record-board/scopes/scope-internal-context/RecordBoardScopeInternalContext'; +import { focusedRecordBoardCardIndexesComponentState } from '@/object-record/record-board/states/focusedRecordBoardCardIndexesComponentState'; +import { isRecordBoardCardFocusActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCardFocusActiveComponentState'; +import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; +import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; + +export const RecordBoardScrollToFocusedCardEffect = () => { + const recordBoardId = useAvailableScopeIdOrThrow( + RecordBoardScopeInternalContext, + ); + + const focusedCardIndexes = useRecoilComponentValueV2( + focusedRecordBoardCardIndexesComponentState, + recordBoardId, + ); + + const isFocusActive = useRecoilComponentValueV2( + isRecordBoardCardFocusActiveComponentState, + recordBoardId, + ); + + useEffect(() => { + if (!isFocusActive || !focusedCardIndexes) { + return; + } + + const { rowIndex, columnIndex } = focusedCardIndexes; + + const focusElement = document.getElementById( + `record-board-card-${columnIndex}-${rowIndex}`, + ); + + if (!focusElement) { + return; + } + + if (focusElement instanceof HTMLElement) { + focusElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); + } + }, [focusedCardIndexes, isFocusActive]); + + return null; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/useActiveRecordBoardCard.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/useActiveRecordBoardCard.ts new file mode 100644 index 000000000..03ce8f623 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/useActiveRecordBoardCard.ts @@ -0,0 +1,64 @@ +import { activeRecordBoardCardIndexesComponentState } from '@/object-record/record-board/states/activeRecordBoardCardIndexesComponentState'; +import { isRecordBoardCardActiveComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardActiveComponentFamilyState'; +import { BoardCardIndexes } from '@/object-record/record-board/types/BoardCardIndexes'; +import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; +import { useRecoilCallback } from 'recoil'; +import { isDefined } from 'twenty-shared/utils'; + +export const useActiveRecordBoardCard = (recordBoardId?: string) => { + const isCardActiveState = useRecoilComponentCallbackStateV2( + isRecordBoardCardActiveComponentFamilyState, + recordBoardId, + ); + + const activeBoardCardIndexesState = useRecoilComponentCallbackStateV2( + activeRecordBoardCardIndexesComponentState, + recordBoardId, + ); + + const deactivateBoardCard = useRecoilCallback( + ({ set, snapshot }) => + () => { + const activeBoardCardIndexes = snapshot + .getLoadable(activeBoardCardIndexesState) + .getValue(); + + if (!isDefined(activeBoardCardIndexes)) { + return; + } + + set(activeBoardCardIndexesState, null); + set(isCardActiveState(activeBoardCardIndexes), false); + }, + [activeBoardCardIndexesState, isCardActiveState], + ); + + const activateBoardCard = useRecoilCallback( + ({ set, snapshot }) => + (boardCardIndexes: BoardCardIndexes) => { + const activeBoardCardIndexes = snapshot + .getLoadable(activeBoardCardIndexesState) + .getValue(); + + if ( + activeBoardCardIndexes?.rowIndex === boardCardIndexes.rowIndex && + activeBoardCardIndexes?.columnIndex === boardCardIndexes.columnIndex + ) { + return; + } + + if (isDefined(activeBoardCardIndexes)) { + set(isCardActiveState(activeBoardCardIndexes), false); + } + + set(activeBoardCardIndexesState, boardCardIndexes); + set(isCardActiveState(boardCardIndexes), true); + }, + [activeBoardCardIndexesState, isCardActiveState], + ); + + return { + activateBoardCard, + deactivateBoardCard, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/useFocusedRecordBoardCard.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/useFocusedRecordBoardCard.ts new file mode 100644 index 000000000..059d33537 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/useFocusedRecordBoardCard.ts @@ -0,0 +1,70 @@ +import { focusedRecordBoardCardIndexesComponentState } from '@/object-record/record-board/states/focusedRecordBoardCardIndexesComponentState'; +import { isRecordBoardCardFocusActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCardFocusActiveComponentState'; +import { isRecordBoardCardFocusedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardFocusedComponentFamilyState'; +import { BoardCardIndexes } from '@/object-record/record-board/types/BoardCardIndexes'; +import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; +import { useRecoilCallback } from 'recoil'; +import { isDefined } from 'twenty-shared/utils'; + +export const useFocusedRecordBoardCard = (recordBoardId?: string) => { + const isCardFocusedState = useRecoilComponentCallbackStateV2( + isRecordBoardCardFocusedComponentFamilyState, + recordBoardId, + ); + + const focusedBoardCardIndexesState = useRecoilComponentCallbackStateV2( + focusedRecordBoardCardIndexesComponentState, + recordBoardId, + ); + + const isCardFocusActiveState = useRecoilComponentCallbackStateV2( + isRecordBoardCardFocusActiveComponentState, + recordBoardId, + ); + + const unfocusBoardCard = useRecoilCallback( + ({ set, snapshot }) => + () => { + const focusedBoardCardIndexes = snapshot + .getLoadable(focusedBoardCardIndexesState) + .getValue(); + + if (!isDefined(focusedBoardCardIndexes)) { + return; + } + + set(focusedBoardCardIndexesState, null); + set(isCardFocusedState(focusedBoardCardIndexes), false); + set(isCardFocusActiveState, false); + }, + [focusedBoardCardIndexesState, isCardFocusedState, isCardFocusActiveState], + ); + + const focusBoardCard = useRecoilCallback( + ({ set, snapshot }) => + (boardCardIndexes: BoardCardIndexes) => { + const focusedBoardCardIndexes = snapshot + .getLoadable(focusedBoardCardIndexesState) + .getValue(); + + if ( + isDefined(focusedBoardCardIndexes) && + (focusedBoardCardIndexes.rowIndex !== boardCardIndexes.rowIndex || + focusedBoardCardIndexes.columnIndex !== + boardCardIndexes.columnIndex) + ) { + set(isCardFocusedState(focusedBoardCardIndexes), false); + } + + set(focusedBoardCardIndexesState, boardCardIndexes); + set(isCardFocusedState(boardCardIndexes), true); + set(isCardFocusActiveState, true); + }, + [focusedBoardCardIndexesState, isCardFocusedState, isCardFocusActiveState], + ); + + return { + focusBoardCard, + unfocusBoardCard, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoardCardNavigation.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoardCardNavigation.ts new file mode 100644 index 000000000..5dc486d1c --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoardCardNavigation.ts @@ -0,0 +1,216 @@ +import { useRecoilCallback } from 'recoil'; + +import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard'; +import { focusedRecordBoardCardIndexesComponentState } from '@/object-record/record-board/states/focusedRecordBoardCardIndexesComponentState'; +import { visibleRecordGroupIdsComponentFamilySelector } from '@/object-record/record-group/states/selectors/visibleRecordGroupIdsComponentFamilySelector'; +import { recordIndexRecordIdsByGroupComponentFamilyState } from '@/object-record/record-index/states/recordIndexRecordIdsByGroupComponentFamilyState'; +import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; +import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; +import { ViewType } from '@/views/types/ViewType'; +import { isDefined } from 'twenty-shared/utils'; + +type NavigationDirection = 'up' | 'down' | 'left' | 'right'; + +export const useRecordBoardCardNavigation = (recordBoardId?: string) => { + const { focusBoardCard } = useFocusedRecordBoardCard(recordBoardId); + + const focusedBoardCardIndexesState = useRecoilComponentCallbackStateV2( + focusedRecordBoardCardIndexesComponentState, + recordBoardId, + ); + + const visibleRecordGroupIds = useRecoilComponentFamilyValueV2( + visibleRecordGroupIdsComponentFamilySelector, + ViewType.Kanban, + ); + + const recordIdsByGroupState = useRecoilComponentCallbackStateV2( + recordIndexRecordIdsByGroupComponentFamilyState, + ); + + const moveHorizontally = useRecoilCallback( + ({ snapshot }) => + (direction: 'left' | 'right') => { + const focusedBoardCardIndexes = snapshot + .getLoadable(focusedBoardCardIndexesState) + .getValue(); + + if (!isDefined(focusedBoardCardIndexes)) { + if (visibleRecordGroupIds.length === 0) { + return; + } + + const firstGroupId = visibleRecordGroupIds[0]; + const recordIdsInFirstGroup = snapshot + .getLoadable(recordIdsByGroupState(firstGroupId)) + .getValue(); + + if ( + !Array.isArray(recordIdsInFirstGroup) || + recordIdsInFirstGroup.length === 0 + ) { + return; + } + + focusBoardCard({ + columnIndex: 0, + rowIndex: 0, + }); + return; + } + + if (visibleRecordGroupIds.length === 0) { + return; + } + + let newColumnIndex = + direction === 'right' + ? focusedBoardCardIndexes.columnIndex + 1 + : focusedBoardCardIndexes.columnIndex - 1; + + if (newColumnIndex < 0) { + newColumnIndex = 0; + } else if (newColumnIndex >= visibleRecordGroupIds.length) { + newColumnIndex = visibleRecordGroupIds.length - 1; + } + + if (newColumnIndex === focusedBoardCardIndexes.columnIndex) { + return; + } + + let foundColumnWithRecords = false; + const initialColumnIndex = newColumnIndex; + + while (!foundColumnWithRecords) { + const currentGroupId = visibleRecordGroupIds[newColumnIndex]; + const recordIdsInGroup = snapshot + .getLoadable(recordIdsByGroupState(currentGroupId)) + .getValue(); + + if (Array.isArray(recordIdsInGroup) && recordIdsInGroup.length > 0) { + foundColumnWithRecords = true; + } else { + newColumnIndex = + direction === 'right' ? newColumnIndex + 1 : newColumnIndex - 1; + + if ( + newColumnIndex < 0 || + newColumnIndex >= visibleRecordGroupIds.length + ) { + return; + } + + if ( + (direction === 'right' && newColumnIndex <= initialColumnIndex) || + (direction === 'left' && newColumnIndex >= initialColumnIndex) + ) { + return; + } + } + } + + const currentGroupId = visibleRecordGroupIds[newColumnIndex]; + const recordIdsInGroup = snapshot + .getLoadable(recordIdsByGroupState(currentGroupId)) + .getValue(); + + let newRowIndex = focusedBoardCardIndexes.rowIndex; + if (newRowIndex >= recordIdsInGroup.length) { + newRowIndex = recordIdsInGroup.length - 1; + } + + focusBoardCard({ + columnIndex: newColumnIndex, + rowIndex: newRowIndex, + }); + }, + [ + focusedBoardCardIndexesState, + visibleRecordGroupIds, + recordIdsByGroupState, + focusBoardCard, + ], + ); + + const moveVertically = useRecoilCallback( + ({ snapshot }) => + (direction: 'up' | 'down') => { + const focusedBoardCardIndexes = snapshot + .getLoadable(focusedBoardCardIndexesState) + .getValue(); + + if (!isDefined(focusedBoardCardIndexes)) { + if (visibleRecordGroupIds.length === 0) return; + + const firstGroupId = visibleRecordGroupIds[0]; + const recordIdsInFirstGroup = snapshot + .getLoadable(recordIdsByGroupState(firstGroupId)) + .getValue(); + + if ( + !Array.isArray(recordIdsInFirstGroup) || + recordIdsInFirstGroup.length === 0 + ) { + return; + } + + focusBoardCard({ + columnIndex: 0, + rowIndex: 0, + }); + + return; + } + + if (visibleRecordGroupIds.length === 0) return; + + const currentGroupId = + visibleRecordGroupIds[focusedBoardCardIndexes.columnIndex]; + const recordIdsInGroup = snapshot + .getLoadable(recordIdsByGroupState(currentGroupId)) + .getValue(); + + if (!Array.isArray(recordIdsInGroup) || recordIdsInGroup.length === 0) { + return; + } + + let newRowIndex = + direction === 'down' + ? focusedBoardCardIndexes.rowIndex + 1 + : focusedBoardCardIndexes.rowIndex - 1; + + if (newRowIndex < 0) { + newRowIndex = 0; + } else if (newRowIndex >= recordIdsInGroup.length) { + newRowIndex = recordIdsInGroup.length - 1; + } + + if (newRowIndex === focusedBoardCardIndexes.rowIndex) { + return; + } + + focusBoardCard({ + columnIndex: focusedBoardCardIndexes.columnIndex, + rowIndex: newRowIndex, + }); + }, + [ + focusedBoardCardIndexesState, + visibleRecordGroupIds, + recordIdsByGroupState, + focusBoardCard, + ], + ); + + const move = (direction: NavigationDirection) => { + if (direction === 'left' || direction === 'right') { + moveHorizontally(direction); + } else if (direction === 'up' || direction === 'down') { + moveVertically(direction); + } + }; + + return { + move, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoardSelection.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoardSelection.ts index fc7a42347..f3dea2e17 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoardSelection.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/useRecordBoardSelection.ts @@ -2,13 +2,20 @@ import { useRecoilCallback } from 'recoil'; import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId'; import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId'; +import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext'; import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardSelectedComponentFamilyState'; import { recordBoardSelectedRecordIdsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardSelectedRecordIdsComponentSelector'; import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2'; +import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2'; import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue'; -export const useRecordBoardSelection = (recordBoardId: string) => { +export const useRecordBoardSelection = (recordBoardId?: string) => { + const instanceIdFromProps = useAvailableComponentInstanceIdOrThrow( + RecordBoardComponentInstanceContext, + recordBoardId, + ); + const isRecordBoardCardSelectedFamilyState = useRecoilComponentCallbackStateV2( isRecordBoardCardSelectedComponentFamilyState, @@ -24,7 +31,7 @@ export const useRecordBoardSelection = (recordBoardId: string) => { const { closeDropdown } = useDropdownV2(); const dropdownId = getActionMenuDropdownIdFromActionMenuId( - getActionMenuIdFromRecordIndexId(recordBoardId), + getActionMenuIdFromRecordIndexId(instanceIdFromProps), ); const resetRecordSelection = useRecoilCallback( diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx index d066372b3..b77f0a166 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCard.tsx @@ -3,6 +3,8 @@ import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/get import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId'; import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext'; import { RecordBoardScopeInternalContext } from '@/object-record/record-board/scopes/scope-internal-context/RecordBoardScopeInternalContext'; +import { isRecordBoardCardActiveComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardActiveComponentFamilyState'; +import { isRecordBoardCardFocusedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardFocusedComponentFamilyState'; import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardSelectedComponentFamilyState'; import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState'; import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector'; @@ -19,6 +21,7 @@ import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2'; import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId'; import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement'; import { useRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyStateV2'; +import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState'; import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType'; @@ -30,27 +33,44 @@ import { AnimatedEaseInOut } from 'twenty-ui/utilities'; import { useDebouncedCallback } from 'use-debounce'; import { useNavigateApp } from '~/hooks/useNavigateApp'; -const StyledBoardCard = styled.div<{ selected: boolean }>` - background-color: ${({ theme, selected }) => - selected ? theme.accent.quaternary : theme.background.secondary}; - border: 1px solid - ${({ theme, selected }) => - selected ? theme.adaptiveColors.blue3 : theme.border.color.medium}; +const StyledBoardCard = styled.div<{ + isDragging?: boolean; +}>` + background-color: ${({ theme }) => theme.background.secondary}; + border: 1px solid ${({ theme }) => theme.border.color.medium}; border-radius: ${({ theme }) => theme.border.radius.sm}; box-shadow: ${({ theme }) => theme.boxShadow.light}; color: ${({ theme }) => theme.font.color.primary}; - &:hover { - background-color: ${({ theme, selected }) => - selected && theme.accent.tertiary}; - border: 1px solid - ${({ theme, selected }) => - selected ? theme.adaptiveColors.blue3 : theme.border.color.strong}; - } cursor: pointer; + &[data-selected='true'] { + background-color: ${({ theme }) => theme.accent.quaternary}; + } + + &[data-focused='true'] { + background-color: ${({ theme }) => theme.background.tertiary}; + } + + &[data-active='true'] { + background-color: ${({ theme }) => theme.accent.quaternary}; + border: 1px solid ${({ theme }) => theme.adaptiveColors.blue3}; + } + + &:hover { + border: 1px solid ${({ theme }) => theme.border.color.strong}; + + &[data-active='true'] { + border: 1px solid ${({ theme }) => theme.adaptiveColors.blue3}; + } + } + .checkbox-container { transition: all ease-in-out 160ms; - opacity: ${({ selected }) => (selected ? 1 : 0)}; + opacity: 0; + } + + &[data-selected='true'] .checkbox-container { + opacity: 1; } &:hover .checkbox-container { @@ -75,7 +95,9 @@ export const RecordBoardCard = () => { const navigate = useNavigateApp(); const { openRecordInCommandMenu } = useOpenRecordInCommandMenu(); - const { recordId } = useContext(RecordBoardCardContext); + const { recordId, rowIndex, columnIndex } = useContext( + RecordBoardCardContext, + ); const visibleFieldDefinitions = useRecoilComponentValueV2( recordBoardVisibleFieldDefinitionsComponentSelector, @@ -93,6 +115,22 @@ export const RecordBoardCard = () => { recordId, ); + const isCurrentCardFocused = useRecoilComponentFamilyValueV2( + isRecordBoardCardFocusedComponentFamilyState, + { + rowIndex, + columnIndex, + }, + ); + + const isCurrentCardActive = useRecoilComponentFamilyValueV2( + isRecordBoardCardActiveComponentFamilyState, + { + rowIndex, + columnIndex, + }, + ); + const { objectNameSingular } = useRecordIndexContextOrThrow(); const recordBoardId = useAvailableScopeIdOrThrow( @@ -167,7 +205,9 @@ export const RecordBoardCard = () => { diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardDraggableContainer.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardDraggableContainer.tsx index ccced3426..7219f60b1 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardDraggableContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardDraggableContainer.tsx @@ -1,25 +1,50 @@ +import styled from '@emotion/styled'; import { Draggable } from '@hello-pangea/dnd'; +import { useContext } from 'react'; import { RecordBoardCard } from '@/object-record/record-board/record-board-card/components/RecordBoardCard'; +import { RecordBoardCardFocusHotkeyEffect } from '@/object-record/record-board/record-board-card/components/RecordBoardCardFocusHotkeyEffect'; import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext'; +import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext'; +import { isRecordBoardCardFocusedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardFocusedComponentFamilyState'; import { useIsRecordReadOnly } from '@/object-record/record-field/hooks/useIsRecordReadOnly'; +import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; + +const StyledDraggableContainer = styled.div` + scroll-margin-left: 8px; + scroll-margin-right: 8px; + scroll-margin-top: 40px; +`; export const RecordBoardCardDraggableContainer = ({ recordId, - index, + rowIndex, }: { recordId: string; - index: number; + rowIndex: number; }) => { const isRecordReadOnly = useIsRecordReadOnly({ recordId, }); + const { columnIndex } = useContext(RecordBoardColumnContext); + + const isRecordBoardCardFocusActive = useRecoilComponentFamilyValueV2( + isRecordBoardCardFocusedComponentFamilyState, + { + rowIndex, + columnIndex, + }, + ); + return ( - - + + {(draggableProvided) => ( -
+ {isRecordBoardCardFocusActive && ( + + )} -
+ )}
diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardFocusHotkeyEffect.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardFocusHotkeyEffect.tsx new file mode 100644 index 000000000..fffa7eb11 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardCardFocusHotkeyEffect.tsx @@ -0,0 +1,57 @@ +import { useContext } from 'react'; +import { Key } from 'ts-key-enum'; + +import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu'; +import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; +import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard'; +import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useRecordBoardSelection'; +import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext'; +import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardSelectedComponentFamilyState'; +import { BoardHotkeyScope } from '@/object-record/record-board/types/BoardHotkeyScope'; +import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; +import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; +export const RecordBoardCardFocusHotkeyEffect = () => { + const { objectMetadataItem } = useContext(RecordBoardContext); + + const { recordId, rowIndex, columnIndex } = useContext( + RecordBoardCardContext, + ); + + const { openRecordInCommandMenu } = useOpenRecordInCommandMenu(); + + const { activateBoardCard } = useActiveRecordBoardCard(); + + const { setRecordAsSelected } = useRecordBoardSelection(); + + const isRecordBoardCardSelected = useRecoilComponentFamilyValueV2( + isRecordBoardCardSelectedComponentFamilyState, + recordId, + ); + + useScopedHotkeys( + 'x', + () => { + setRecordAsSelected(recordId, !isRecordBoardCardSelected); + }, + BoardHotkeyScope.BoardFocus, + ); + + useScopedHotkeys( + [Key.Enter, `${Key.Control}+${Key.Enter}`, `${Key.Meta}+${Key.Enter}`], + () => { + openRecordInCommandMenu({ + recordId: recordId, + objectNameSingular: objectMetadataItem.nameSingular, + isNewRecord: false, + }); + + activateBoardCard({ + rowIndex, + columnIndex, + }); + }, + BoardHotkeyScope.BoardFocus, + ); + + return null; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardDeactivateBoardCardEffect.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardDeactivateBoardCardEffect.tsx new file mode 100644 index 000000000..52fb10987 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/components/RecordBoardDeactivateBoardCardEffect.tsx @@ -0,0 +1,15 @@ +import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; +import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard'; +import { useListenRightDrawerClose } from '@/ui/layout/right-drawer/hooks/useListenRightDrawerClose'; +import { useContext } from 'react'; + +export const RecordBoardDeactivateBoardCardEffect = () => { + const { recordBoardId } = useContext(RecordBoardContext); + const { deactivateBoardCard } = useActiveRecordBoardCard(recordBoardId); + + useListenRightDrawerClose(() => { + deactivateBoardCard(); + }); + + return null; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/contexts/RecordBoardCardContext.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/contexts/RecordBoardCardContext.ts index db5c0ce77..ac038962f 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-card/contexts/RecordBoardCardContext.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-card/contexts/RecordBoardCardContext.ts @@ -3,6 +3,8 @@ import { createContext } from 'react'; type RecordBoardCardContextProps = { recordId: string; isRecordReadOnly: boolean; + rowIndex: number; + columnIndex: number; }; export const RecordBoardCardContext = diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumn.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumn.tsx index 2bde69425..313a5cc4b 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumn.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumn.tsx @@ -22,10 +22,12 @@ const StyledColumn = styled.div` type RecordBoardColumnProps = { recordBoardColumnId: string; + recordBoardColumnIndex: number; }; export const RecordBoardColumn = ({ recordBoardColumnId, + recordBoardColumnIndex, }: RecordBoardColumnProps) => { const recordGroupDefinition = useRecoilValue( recordGroupDefinitionFamilyState(recordBoardColumnId), @@ -46,6 +48,7 @@ export const RecordBoardColumn = ({ columnDefinition: recordGroupDefinition, columnId: recordBoardColumnId, recordIds: recordIdsByGroup, + columnIndex: recordBoardColumnIndex, }} > diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardsMemo.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardsMemo.tsx index 815b74c64..d0967551d 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardsMemo.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnCardsMemo.tsx @@ -12,7 +12,7 @@ export const RecordBoardColumnCardsMemo = React.memo( )); }, diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderWrapper.tsx b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderWrapper.tsx index 80b263297..e9952130d 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderWrapper.tsx +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeaderWrapper.tsx @@ -8,10 +8,12 @@ import { isDefined } from 'twenty-shared/utils'; type RecordBoardColumnHeaderWrapperProps = { columnId: string; + columnIndex: number; }; export const RecordBoardColumnHeaderWrapper = ({ columnId, + columnIndex, }: RecordBoardColumnHeaderWrapperProps) => { const recordGroupDefinition = useRecoilValue( recordGroupDefinitionFamilyState(columnId), @@ -32,6 +34,7 @@ export const RecordBoardColumnHeaderWrapper = ({ columnId, columnDefinition: recordGroupDefinition, recordIds: recordIdsByGroup, + columnIndex, }} > diff --git a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext.ts b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext.ts index 4140e0cf1..568170bea 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext.ts @@ -6,6 +6,7 @@ type RecordBoardColumnContextProps = { columnDefinition: RecordGroupDefinition; columnId: string; recordIds: string[]; + columnIndex: number; }; export const RecordBoardColumnContext = diff --git a/packages/twenty-front/src/modules/object-record/record-board/states/activeRecordBoardCardIndexesComponentState.ts b/packages/twenty-front/src/modules/object-record/record-board/states/activeRecordBoardCardIndexesComponentState.ts new file mode 100644 index 000000000..be299045f --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/states/activeRecordBoardCardIndexesComponentState.ts @@ -0,0 +1,10 @@ +import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext'; +import { BoardCardIndexes } from '@/object-record/record-board/types/BoardCardIndexes'; +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; + +export const activeRecordBoardCardIndexesComponentState = + createComponentStateV2({ + key: 'activeRecordBoardCardIndexesComponentState', + defaultValue: null, + componentInstanceContext: RecordBoardComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/object-record/record-board/states/focusedRecordBoardCardIndexesComponentState.ts b/packages/twenty-front/src/modules/object-record/record-board/states/focusedRecordBoardCardIndexesComponentState.ts new file mode 100644 index 000000000..3809d3aae --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/states/focusedRecordBoardCardIndexesComponentState.ts @@ -0,0 +1,10 @@ +import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext'; +import { BoardCardIndexes } from '@/object-record/record-board/types/BoardCardIndexes'; +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; + +export const focusedRecordBoardCardIndexesComponentState = + createComponentStateV2({ + key: 'focusedRecordBoardCardIndexesComponentState', + defaultValue: null, + componentInstanceContext: RecordBoardComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardCardActiveComponentFamilyState.ts b/packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardCardActiveComponentFamilyState.ts new file mode 100644 index 000000000..0cdc01036 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardCardActiveComponentFamilyState.ts @@ -0,0 +1,10 @@ +import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext'; +import { BoardCardIndexes } from '@/object-record/record-board/types/BoardCardIndexes'; +import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2'; + +export const isRecordBoardCardActiveComponentFamilyState = + createComponentFamilyStateV2({ + key: 'isRecordBoardCardActiveComponentFamilyState', + defaultValue: false, + componentInstanceContext: RecordBoardComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardCardFocusActiveComponentState.ts b/packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardCardFocusActiveComponentState.ts new file mode 100644 index 000000000..9a943c6e9 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardCardFocusActiveComponentState.ts @@ -0,0 +1,9 @@ +import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext'; +import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2'; + +export const isRecordBoardCardFocusActiveComponentState = + createComponentStateV2({ + key: 'isRecordBoardCardFocusActiveComponentState', + defaultValue: false, + componentInstanceContext: RecordBoardComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardCardFocusedComponentFamilyState.ts b/packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardCardFocusedComponentFamilyState.ts new file mode 100644 index 000000000..ab697d923 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/states/isRecordBoardCardFocusedComponentFamilyState.ts @@ -0,0 +1,10 @@ +import { RecordBoardComponentInstanceContext } from '@/object-record/record-board/states/contexts/RecordBoardComponentInstanceContext'; +import { BoardCardIndexes } from '@/object-record/record-board/types/BoardCardIndexes'; +import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2'; + +export const isRecordBoardCardFocusedComponentFamilyState = + createComponentFamilyStateV2({ + key: 'isRecordBoardCardFocusedComponentFamilyState', + defaultValue: false, + componentInstanceContext: RecordBoardComponentInstanceContext, + }); diff --git a/packages/twenty-front/src/modules/object-record/record-board/types/BoardCardIndexes.ts b/packages/twenty-front/src/modules/object-record/record-board/types/BoardCardIndexes.ts new file mode 100644 index 000000000..b7aee0104 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/types/BoardCardIndexes.ts @@ -0,0 +1,4 @@ +export type BoardCardIndexes = { + rowIndex: number; + columnIndex: number; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-board/types/BoardHotkeyScope.ts b/packages/twenty-front/src/modules/object-record/record-board/types/BoardHotkeyScope.ts new file mode 100644 index 000000000..b58840d74 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-board/types/BoardHotkeyScope.ts @@ -0,0 +1,3 @@ +export enum BoardHotkeyScope { + BoardFocus = 'board-focus', +} diff --git a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainer.tsx b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainer.tsx index edd219383..3a9426497 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-index/components/RecordIndexBoardContainer.tsx @@ -5,10 +5,11 @@ import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { RecordBoard } from '@/object-record/record-board/components/RecordBoard'; +import { RecordBoardBodyEscapeHotkeyEffect } from '@/object-record/record-board/components/RecordBoardBodyEscapeHotkeyEffect'; +import { RecordBoardHotkeyEffect } from '@/object-record/record-board/components/RecordBoardHotkeyEffect'; import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext'; import { RecordIndexRemoveSortingModal } from '@/object-record/record-index/components/RecordIndexRemoveSortingModal'; import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-index/states/recordIndexKanbanFieldMetadataIdState'; - type RecordIndexBoardContainerProps = { recordBoardId: string; viewBarId: string; @@ -55,6 +56,8 @@ export const RecordIndexBoardContainer = ({ > + + ); }; diff --git a/packages/twenty-front/src/modules/object-record/record-index/types/RecordIndexHotkeyScope.ts b/packages/twenty-front/src/modules/object-record/record-index/types/RecordIndexHotkeyScope.ts new file mode 100644 index 000000000..fcdd9d87f --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-index/types/RecordIndexHotkeyScope.ts @@ -0,0 +1,3 @@ +export enum RecordIndexHotkeyScope { + RecordIndex = 'record-index', +} diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx index fd07bc291..16e71e5c7 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableWithWrappers.tsx @@ -10,6 +10,7 @@ import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { useSaveCurrentViewFields } from '@/views/hooks/useSaveCurrentViewFields'; import { mapColumnDefinitionsToViewFields } from '@/views/utils/mapColumnDefinitionToViewField'; +import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance'; import { RecordTableContextProvider } from '@/object-record/record-table/components/RecordTableContextProvider'; import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; @@ -50,7 +51,17 @@ export const RecordTableWithWrappers = ({ useScopedHotkeys( 'ctrl+a,meta+a', handleSelectAllRows, - TableHotkeyScope.Table, + RecordIndexHotkeyScope.RecordIndex, + [], + { + enableOnFormTags: false, + }, + ); + + useScopedHotkeys( + 'ctrl+a,meta+a', + handleSelectAllRows, + TableHotkeyScope.TableFocus, [], { enableOnFormTags: false, diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useLeaveTableFocus.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useLeaveTableFocus.ts index 20283baf3..f59ee32a1 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useLeaveTableFocus.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/internal/useLeaveTableFocus.ts @@ -1,10 +1,10 @@ +import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection'; import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow'; import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow'; import { useSetIsRecordTableFocusActive } from '@/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableFocusActive'; import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState'; -import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2'; @@ -49,6 +49,6 @@ export const useLeaveTableFocus = (recordTableId?: string) => { setRecordTableHoverPosition(null); - setHotkeyScope(TableHotkeyScope.Table); + setHotkeyScope(RecordIndexHotkeyScope.RecordIndex); }; }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTable.ts b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTable.ts index 61970bafd..93a12a9b5 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTable.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/hooks/useRecordTable.ts @@ -7,6 +7,7 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; import { isDeeplyEqual } from '~/utils/isDeeplyEqual'; +import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; import { useUpsertRecordFromState } from '../../hooks/useUpsertRecordFromState'; import { ColumnDefinition } from '../types/ColumnDefinition'; import { TableHotkeyScope } from '../types/TableHotkeyScope'; @@ -168,7 +169,7 @@ export const useRecordTable = (props?: useRecordTableProps) => { setHotkeyScopeAndMemorizePreviousScope(TableHotkeyScope.TableFocus); move('up'); }, - TableHotkeyScope.Table, + RecordIndexHotkeyScope.RecordIndex, [move], ); @@ -178,7 +179,7 @@ export const useRecordTable = (props?: useRecordTableProps) => { setHotkeyScopeAndMemorizePreviousScope(TableHotkeyScope.TableFocus); move('down'); }, - TableHotkeyScope.Table, + RecordIndexHotkeyScope.RecordIndex, [move], ); diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyEscapeHotkeyEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyEscapeHotkeyEffect.tsx index 0fa9d56e3..78c3edf07 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyEscapeHotkeyEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyEscapeHotkeyEffect.tsx @@ -1,12 +1,13 @@ import { Key } from 'ts-key-enum'; +import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { isAtLeastOneTableRowSelectedSelector } from '@/object-record/record-table/record-table-row/states/isAtLeastOneTableRowSelectedSelector'; -import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope'; import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; + export const RecordTableBodyEscapeHotkeyEffect = () => { const { recordTableId } = useRecordTableContextOrThrow(); @@ -28,9 +29,9 @@ export const RecordTableBodyEscapeHotkeyEffect = () => { resetTableRowSelection(); } }, - TableHotkeyScope.Table, + RecordIndexHotkeyScope.RecordIndex, [isAtLeastOneRecordSelected, resetTableRowSelection, unfocusRecordTableRow], ); - return <>; + return null; }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyFocusKeyboardEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyFocusKeyboardEffect.tsx index 08424ac9c..98ae39819 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyFocusKeyboardEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableBodyFocusKeyboardEffect.tsx @@ -1,3 +1,4 @@ +import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; @@ -37,7 +38,7 @@ export const RecordTableBodyFocusKeyboardEffect = () => { restoreRecordTableRowFocusFromCellPosition(); setIsFocusActiveForCurrentPosition(false); } else { - setHotkeyScope(TableHotkeyScope.Table, { + setHotkeyScope(RecordIndexHotkeyScope.RecordIndex, { goto: true, keyboardShortcutMenu: true, }); diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useMoveHoverToCurrentCell.ts b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useMoveHoverToCurrentCell.ts index efb848e8d..a8e4f1e2e 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useMoveHoverToCurrentCell.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-cell/hooks/useMoveHoverToCurrentCell.ts @@ -4,6 +4,7 @@ import { TableCellPosition } from '@/object-record/record-table/types/TableCellP import { currentHotkeyScopeState } from '@/ui/utilities/hotkey/states/internal/currentHotkeyScopeState'; import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue'; +import { RecordIndexHotkeyScope } from '@/object-record/record-index/types/RecordIndexHotkeyScope'; import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState'; import { isSomeCellInEditModeComponentSelector } from '@/object-record/record-table/states/selectors/isSomeCellInEditModeComponentSelector'; import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope'; @@ -38,8 +39,8 @@ export const useMoveHoverToCurrentCell = (recordTableId: string) => { if ( currentHotkeyScope.scope !== TableHotkeyScope.TableFocus && currentHotkeyScope.scope !== TableHotkeyScope.CellEditMode && - currentHotkeyScope.scope !== TableHotkeyScope.Table && - currentHotkeyScope.scope !== AppHotkeyScope.CommandMenuOpen + currentHotkeyScope.scope !== AppHotkeyScope.CommandMenuOpen && + currentHotkeyScope.scope !== RecordIndexHotkeyScope.RecordIndex ) { return; } diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableTr.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableTr.tsx index 301580143..2e5571991 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableTr.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-row/components/RecordTableTr.tsx @@ -139,13 +139,9 @@ export const RecordTableTr = forwardRef< data-virtualized-id={recordId} isDragging={isDragging} ref={ref} - data-active={isActive ? 'true' : 'false'} - data-focused={ - isRowFocusActive && isFocused && !isActive ? 'true' : 'false' - } - data-next-row-active-or-focused={ - isNextRowActiveOrFocused ? 'true' : 'false' - } + data-active={isActive} + data-focused={isRowFocusActive && isFocused && !isActive} + data-next-row-active-or-focused={isNextRowActiveOrFocused} // eslint-disable-next-line react/jsx-props-no-spreading {...props} > diff --git a/packages/twenty-front/src/modules/object-record/record-table/types/TableHotkeyScope.ts b/packages/twenty-front/src/modules/object-record/record-table/types/TableHotkeyScope.ts index b1bae4957..ad2fe85eb 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/types/TableHotkeyScope.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/types/TableHotkeyScope.ts @@ -3,5 +3,4 @@ export enum TableHotkeyScope { CellEditMode = 'cell-edit-mode', CellDateEditMode = 'cell-date-edit-mode', TableFocus = 'table-focus', - Table = 'table', }