Fixes on side panel closing and table rows and board cards activation (#12609)

Fixes https://github.com/twentyhq/core-team-issues/issues/1096

This PR:
- Prevents interaction with elements inside the index page when the side
panel is opened, except for switching between records
- Prevents stacking multiple records in the side panel navigation stack
when navigating from the index
- Adds activation and unfocus logic for board cards when clicked
- Fixes table row activation after clicking on a record chip

Before:


https://github.com/user-attachments/assets/dcfec9fb-392b-4760-9b11-b0f077087b82


After:


https://github.com/user-attachments/assets/93e0dc6a-c693-4484-b23e-f5ae291eb472
This commit is contained in:
Raphaël Bosi
2025-06-16 10:33:57 +02:00
committed by GitHub
parent 46d6e7a8bc
commit 6d6738e7cb
6 changed files with 48 additions and 11 deletions

View File

@ -51,12 +51,14 @@ export const CommandMenuOpenContainer = ({
const handleClickOutside = useRecoilCallback( const handleClickOutside = useRecoilCallback(
({ snapshot }) => ({ snapshot }) =>
() => { (event: MouseEvent | TouchEvent) => {
const hotkeyScope = snapshot const hotkeyScope = snapshot
.getLoadable(currentHotkeyScopeState) .getLoadable(currentHotkeyScopeState)
.getValue(); .getValue();
if (hotkeyScope?.scope === CommandMenuHotkeyScope.CommandMenuFocused) { if (hotkeyScope?.scope === CommandMenuHotkeyScope.CommandMenuFocused) {
event.stopImmediatePropagation();
event.preventDefault();
closeCommandMenu(); closeCommandMenu();
} }
}, },

View File

@ -38,10 +38,12 @@ export const useOpenRecordInCommandMenu = () => {
recordId, recordId,
objectNameSingular, objectNameSingular,
isNewRecord = false, isNewRecord = false,
resetNavigationStack = false,
}: { }: {
recordId: string; recordId: string;
objectNameSingular: string; objectNameSingular: string;
isNewRecord?: boolean; isNewRecord?: boolean;
resetNavigationStack?: boolean;
}) => { }) => {
const navigationStack = getSnapshotValue( const navigationStack = getSnapshotValue(
snapshot, snapshot,
@ -171,7 +173,7 @@ export const useOpenRecordInCommandMenu = () => {
pageIcon: Icon, pageIcon: Icon,
pageIconColor: IconColor, pageIconColor: IconColor,
pageId: pageComponentInstanceId, pageId: pageComponentInstanceId,
resetNavigationStack: false, resetNavigationStack,
}); });
if (objectNameSingular === CoreObjectNameSingular.WorkflowRun) { if (objectNameSingular === CoreObjectNameSingular.WorkflowRun) {

View File

@ -10,6 +10,8 @@ import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/re
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector'; import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
import { ActionMenuDropdownHotkeyScope } from '@/action-menu/types/ActionMenuDropdownHotKeyScope'; import { ActionMenuDropdownHotkeyScope } from '@/action-menu/types/ActionMenuDropdownHotKeyScope';
import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard';
import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard';
import { RecordBoardCardBody } from '@/object-record/record-board/record-board-card/components/RecordBoardCardBody'; import { RecordBoardCardBody } from '@/object-record/record-board/record-board-card/components/RecordBoardCardBody';
import { RecordBoardCardHeader } from '@/object-record/record-board/record-board-card/components/RecordBoardCardHeader'; import { RecordBoardCardHeader } from '@/object-record/record-board/record-board-card/components/RecordBoardCardHeader';
import { RECORD_BOARD_CARD_CLICK_OUTSIDE_ID } from '@/object-record/record-board/record-board-card/constants/RecordBoardCardClickOutsideId'; import { RECORD_BOARD_CARD_CLICK_OUTSIDE_ID } from '@/object-record/record-board/record-board-card/constants/RecordBoardCardClickOutsideId';
@ -142,6 +144,8 @@ export const RecordBoardCard = () => {
const { openDropdown } = useDropdownV2(); const { openDropdown } = useDropdownV2();
const { openRecordFromIndexView } = useOpenRecordFromIndexView(); const { openRecordFromIndexView } = useOpenRecordFromIndexView();
const { activateBoardCard } = useActiveRecordBoardCard(recordBoardId);
const { unfocusBoardCard } = useFocusedRecordBoardCard(recordBoardId);
const handleActionMenuDropdown = (event: React.MouseEvent) => { const handleActionMenuDropdown = (event: React.MouseEvent) => {
event.preventDefault(); event.preventDefault();
@ -156,6 +160,8 @@ export const RecordBoardCard = () => {
}; };
const handleCardClick = () => { const handleCardClick = () => {
activateBoardCard({ rowIndex, columnIndex });
unfocusBoardCard();
openRecordFromIndexView({ recordId }); openRecordFromIndexView({ recordId });
}; };

View File

@ -4,15 +4,17 @@ import { useRecordBoardSelection } from '@/object-record/record-board/hooks/useR
import { RecordBoardCardHeaderContainer } from '@/object-record/record-board/record-board-card/components/RecordBoardCardHeaderContainer'; import { RecordBoardCardHeaderContainer } from '@/object-record/record-board/record-board-card/components/RecordBoardCardHeaderContainer';
import { StopPropagationContainer } from '@/object-record/record-board/record-board-card/components/StopPropagationContainer'; import { StopPropagationContainer } from '@/object-record/record-board/record-board-card/components/StopPropagationContainer';
import { RecordBoardCardContext } from '@/object-record/record-board/record-board-card/contexts/RecordBoardCardContext'; 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 { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardSelectedComponentFamilyState'; import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardSelectedComponentFamilyState';
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState'; import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
import { useActiveRecordBoardCard } from '@/object-record/record-board/hooks/useActiveRecordBoardCard';
import { useFocusedRecordBoardCard } from '@/object-record/record-board/hooks/useFocusedRecordBoardCard';
import { useOpenRecordFromIndexView } from '@/object-record/record-index/hooks/useOpenRecordFromIndexView'; import { useOpenRecordFromIndexView } from '@/object-record/record-index/hooks/useOpenRecordFromIndexView';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
import { useRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyStateV2'; import { useRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyStateV2';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Dispatch, SetStateAction, useContext } from 'react'; import { Dispatch, SetStateAction, useContext } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
@ -45,11 +47,10 @@ export const RecordBoardCardHeader = ({
const record = useRecoilValue(recordStoreFamilyState(recordId)); const record = useRecoilValue(recordStoreFamilyState(recordId));
const { objectMetadataItem } = useContext(RecordBoardContext); const { objectMetadataItem, recordBoardId } = useContext(RecordBoardContext);
const { rowIndex, columnIndex } = useContext(RecordBoardCardContext);
const recordBoardId = useAvailableScopeIdOrThrow( const { activateBoardCard } = useActiveRecordBoardCard(recordBoardId);
RecordBoardScopeInternalContext, const { unfocusBoardCard } = useFocusedRecordBoardCard(recordBoardId);
);
const showCompactView = useRecoilComponentValueV2( const showCompactView = useRecoilComponentValueV2(
isRecordBoardCompactModeActiveComponentState, isRecordBoardCompactModeActiveComponentState,
@ -66,6 +67,12 @@ export const RecordBoardCardHeader = ({
const { openRecordFromIndexView } = useOpenRecordFromIndexView(); const { openRecordFromIndexView } = useOpenRecordFromIndexView();
const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState);
const triggerEvent =
recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL
? 'CLICK'
: 'MOUSE_DOWN';
return ( return (
<RecordBoardCardHeaderContainer showCompactView={showCompactView}> <RecordBoardCardHeaderContainer showCompactView={showCompactView}>
<StopPropagationContainer> <StopPropagationContainer>
@ -76,9 +83,11 @@ export const RecordBoardCardHeader = ({
variant={AvatarChipVariant.Transparent} variant={AvatarChipVariant.Transparent}
maxWidth={150} maxWidth={150}
onClick={() => { onClick={() => {
activateBoardCard({ rowIndex, columnIndex });
unfocusBoardCard();
openRecordFromIndexView({ recordId }); openRecordFromIndexView({ recordId });
}} }}
triggerEvent="CLICK" triggerEvent={triggerEvent}
/> />
)} )}
</StopPropagationContainer> </StopPropagationContainer>

View File

@ -71,6 +71,7 @@ export const useOpenRecordFromIndexView = () => {
openRecordInCommandMenu({ openRecordInCommandMenu({
recordId, recordId,
objectNameSingular, objectNameSingular,
resetNavigationStack: true,
}); });
} else { } else {
navigate(AppPath.RecordShowPage, { navigate(AppPath.RecordShowPage, {

View File

@ -3,13 +3,18 @@ import { FieldContext } from '@/object-record/record-field/contexts/FieldContext
import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly'; import { useIsFieldValueReadOnly } from '@/object-record/record-field/hooks/useIsFieldValueReadOnly';
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
import { useOpenRecordFromIndexView } from '@/object-record/record-index/hooks/useOpenRecordFromIndexView'; import { useOpenRecordFromIndexView } from '@/object-record/record-index/hooks/useOpenRecordFromIndexView';
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
import { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext'; import { RecordUpdateContext } from '@/object-record/record-table/contexts/EntityUpdateMutationHookContext';
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext'; import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext'; import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow';
import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow';
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState'; import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
import { ReactNode, useContext } from 'react'; import { ReactNode, useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { useIsMobile } from 'twenty-ui/utilities'; import { useIsMobile } from 'twenty-ui/utilities';
type RecordTableCellFieldContextLabelIdentifierProps = { type RecordTableCellFieldContextLabelIdentifierProps = {
@ -25,7 +30,10 @@ export const RecordTableCellFieldContextLabelIdentifier = ({
useRecordTableRowContextOrThrow(); useRecordTableRowContextOrThrow();
const { columnDefinition } = useContext(RecordTableCellContext); const { columnDefinition } = useContext(RecordTableCellContext);
const { objectMetadataItem } = useRecordTableContextOrThrow(); const { objectMetadataItem, recordTableId } = useRecordTableContextOrThrow();
const { rowIndex } = useRecordTableRowContextOrThrow();
const { activateRecordTableRow } = useActiveRecordTableRow(recordTableId);
const { unfocusRecordTableRow } = useFocusedRecordTableRow(recordTableId);
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const isRecordTableScrolledLeftComponent = useRecoilComponentValueV2( const isRecordTableScrolledLeftComponent = useRecoilComponentValueV2(
@ -51,6 +59,12 @@ export const RecordTableCellFieldContextLabelIdentifier = ({
const { openRecordFromIndexView } = useOpenRecordFromIndexView(); const { openRecordFromIndexView } = useOpenRecordFromIndexView();
const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState);
const triggerEvent =
recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL
? 'CLICK'
: 'MOUSE_DOWN';
return ( return (
<FieldContext.Provider <FieldContext.Provider
value={{ value={{
@ -64,9 +78,12 @@ export const RecordTableCellFieldContextLabelIdentifier = ({
isReadOnly: isFieldReadOnly, isReadOnly: isFieldReadOnly,
maxWidth: columnDefinition.size, maxWidth: columnDefinition.size,
onRecordChipClick: () => { onRecordChipClick: () => {
activateRecordTableRow(rowIndex);
unfocusRecordTableRow();
openRecordFromIndexView({ recordId }); openRecordFromIndexView({ recordId });
}, },
isForbidden: !hasObjectReadPermissions, isForbidden: !hasObjectReadPermissions,
triggerEvent,
}} }}
> >
{children} {children}