Record Table Row Navigation (#11879)
# Record Table Row Navigation This PR improves the table accessibility by adding a row navigation and new shortcuts to the table. Closes #896. # Introduce focused active row states on the table This PR implements the focused and active row states feature for the record table, allowing users to navigate through the table with keyboard arrows and providing visual feedback for selection. ## Implementation details: - Added new component states to track focused and active row positions and states. - Implemented dedicated hooks for row state management - Updated UI styling for active and focused rows: - Applied blue border (Adaptive Colors Blue 3) - Added highlight background (Accent Quaternary) - Added styling for focused rows to clearly indicate selection state - Added row state cleanup: - `RecordTableDeactivateRecordTableRowEffect` component to reset states - Added row state reset logic upon navigation ## Bug fixes - Fixed record table unselection in the page change effect - Fixed a hack introduced by https://github.com/twentyhq/twenty/pull/8489 which duplicated the last table column # Shortcuts ## Arrow keys and J+K navigation https://github.com/user-attachments/assets/6b46f6b5-cd98-4053-aaef-f8bf2b9584b5 ## Record selection with X https://github.com/user-attachments/assets/44ab7397-e00c-4dfe-8dd1-b3ffc53b3e5f ## Enter allows for cell navigation, Escape goes back to row navigation https://github.com/user-attachments/assets/890d7e25-2d81-47e3-972f-043a1879b8cc ## Command + Enter opens the record https://github.com/user-attachments/assets/cf8cdbd5-7cf0-4d78-909f-dc6be88b9e25
This commit is contained in:
@ -21,11 +21,10 @@ import { useCallback } from 'react';
|
|||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
|
||||||
import { Button } from 'twenty-ui/input';
|
|
||||||
import { IconBrowserMaximize } from 'twenty-ui/display';
|
import { IconBrowserMaximize } from 'twenty-ui/display';
|
||||||
|
import { Button } from 'twenty-ui/input';
|
||||||
import { getOsControlSymbol } from 'twenty-ui/utilities';
|
import { getOsControlSymbol } from 'twenty-ui/utilities';
|
||||||
|
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||||
const StyledLink = styled(Link)`
|
const StyledLink = styled(Link)`
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
`;
|
`;
|
||||||
|
|||||||
@ -15,21 +15,26 @@ import { useExecuteTasksOnAnyLocationChange } from '@/app/hooks/useExecuteTasksO
|
|||||||
import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken';
|
import { useRequestFreshCaptchaToken } from '@/captcha/hooks/useRequestFreshCaptchaToken';
|
||||||
import { isCaptchaScriptLoadedState } from '@/captcha/states/isCaptchaScriptLoadedState';
|
import { isCaptchaScriptLoadedState } from '@/captcha/states/isCaptchaScriptLoadedState';
|
||||||
import { isCaptchaRequiredForPath } from '@/captcha/utils/isCaptchaRequiredForPath';
|
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 { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
import { CoreObjectNamePlural } from '@/object-metadata/types/CoreObjectNamePlural';
|
||||||
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
|
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 { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||||
|
import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/utils/getRecordIndexIdFromObjectNamePluralAndViewId';
|
||||||
import { AppBasePath } from '@/types/AppBasePath';
|
import { AppBasePath } from '@/types/AppBasePath';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
import { PageHotkeyScope } from '@/types/PageHotkeyScope';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { AnalyticsType } from '~/generated/graphql';
|
||||||
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
import { useIsMatchingLocation } from '~/hooks/useIsMatchingLocation';
|
||||||
import { usePageChangeEffectNavigateLocation } from '~/hooks/usePageChangeEffectNavigateLocation';
|
import { usePageChangeEffectNavigateLocation } from '~/hooks/usePageChangeEffectNavigateLocation';
|
||||||
import { useInitializeQueryParamState } from '~/modules/app/hooks/useInitializeQueryParamState';
|
import { useInitializeQueryParamState } from '~/modules/app/hooks/useInitializeQueryParamState';
|
||||||
import { AnalyticsType } from '~/generated/graphql';
|
|
||||||
import { getPageTitleFromPath } from '~/utils/title-utils';
|
import { getPageTitleFromPath } from '~/utils/title-utils';
|
||||||
|
|
||||||
// TODO: break down into smaller functions and / or hooks
|
// TODO: break down into smaller functions and / or hooks
|
||||||
// - moved usePageChangeEffectNavigateLocation into dedicated hook
|
// - moved usePageChangeEffectNavigateLocation into dedicated hook
|
||||||
export const PageChangeEffect = () => {
|
export const PageChangeEffect = () => {
|
||||||
@ -54,7 +59,19 @@ export const PageChangeEffect = () => {
|
|||||||
const objectNamePlural =
|
const objectNamePlural =
|
||||||
useParams().objectNamePlural ?? CoreObjectNamePlural.Person;
|
useParams().objectNamePlural ?? CoreObjectNamePlural.Person;
|
||||||
|
|
||||||
const resetTableSelections = useResetTableRowSelection(objectNamePlural);
|
const contextStoreCurrentViewId = useRecoilComponentValueV2(
|
||||||
|
contextStoreCurrentViewIdComponentState,
|
||||||
|
MAIN_CONTEXT_STORE_INSTANCE_ID,
|
||||||
|
);
|
||||||
|
|
||||||
|
const recordIndexId = getRecordIndexIdFromObjectNamePluralAndViewId(
|
||||||
|
objectNamePlural,
|
||||||
|
contextStoreCurrentViewId || '',
|
||||||
|
);
|
||||||
|
|
||||||
|
const resetTableSelections = useResetTableRowSelection(recordIndexId);
|
||||||
|
const { unfocusRecordTableRow } = useFocusedRecordTableRow(recordIndexId);
|
||||||
|
const { deactivateRecordTableRow } = useActiveRecordTableRow(recordIndexId);
|
||||||
|
|
||||||
const { executeTasksOnAnyLocationChange } =
|
const { executeTasksOnAnyLocationChange } =
|
||||||
useExecuteTasksOnAnyLocationChange();
|
useExecuteTasksOnAnyLocationChange();
|
||||||
@ -84,8 +101,16 @@ export const PageChangeEffect = () => {
|
|||||||
|
|
||||||
if (isLeavingRecordIndexPage) {
|
if (isLeavingRecordIndexPage) {
|
||||||
resetTableSelections();
|
resetTableSelections();
|
||||||
|
unfocusRecordTableRow();
|
||||||
|
deactivateRecordTableRow();
|
||||||
}
|
}
|
||||||
}, [isMatchingLocation, previousLocation, resetTableSelections]);
|
}, [
|
||||||
|
isMatchingLocation,
|
||||||
|
previousLocation,
|
||||||
|
resetTableSelections,
|
||||||
|
unfocusRecordTableRow,
|
||||||
|
deactivateRecordTableRow,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
|
|||||||
@ -68,6 +68,7 @@ export const RecordChip = ({
|
|||||||
|
|
||||||
const isSidePanelViewOpenRecordInType =
|
const isSidePanelViewOpenRecordInType =
|
||||||
recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL;
|
recordIndexOpenRecordIn === ViewOpenRecordInType.SIDE_PANEL;
|
||||||
|
|
||||||
const onClick = isSidePanelViewOpenRecordInType
|
const onClick = isSidePanelViewOpenRecordInType
|
||||||
? () =>
|
? () =>
|
||||||
openRecordInCommandMenu({
|
openRecordInCommandMenu({
|
||||||
|
|||||||
@ -6,12 +6,14 @@ import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record
|
|||||||
import { RecordTableBodyEffectsWrapper } from '@/object-record/record-table/components/RecordTableBodyEffectsWrapper';
|
import { RecordTableBodyEffectsWrapper } from '@/object-record/record-table/components/RecordTableBodyEffectsWrapper';
|
||||||
import { RecordTableContent } from '@/object-record/record-table/components/RecordTableContent';
|
import { RecordTableContent } from '@/object-record/record-table/components/RecordTableContent';
|
||||||
import { RecordTableEmpty } from '@/object-record/record-table/components/RecordTableEmpty';
|
import { RecordTableEmpty } from '@/object-record/record-table/components/RecordTableEmpty';
|
||||||
import { RecordTableScrollToFocusedElementEffect } from '@/object-record/record-table/components/RecordTableScrollToFocusedElementEffect';
|
import { RecordTableScrollToFocusedCellEffect } from '@/object-record/record-table/components/RecordTableScrollToFocusedCellEffect';
|
||||||
|
import { RecordTableScrollToFocusedRowEffect } from '@/object-record/record-table/components/RecordTableScrollToFocusedRowEffect';
|
||||||
import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
|
import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||||
import { isRecordTableFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableFocusActiveComponentState';
|
import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState';
|
||||||
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
||||||
|
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
|
||||||
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
@ -46,8 +48,13 @@ export const RecordTable = () => {
|
|||||||
const recordTableIsEmpty =
|
const recordTableIsEmpty =
|
||||||
!isRecordTableInitialLoading && allRecordIds.length === 0;
|
!isRecordTableInitialLoading && allRecordIds.length === 0;
|
||||||
|
|
||||||
const isRecordTableFocusActive = useRecoilComponentValueV2(
|
const isRecordTableCellFocusActive = useRecoilComponentValueV2(
|
||||||
isRecordTableFocusActiveComponentState,
|
isRecordTableCellFocusActiveComponentState,
|
||||||
|
recordTableId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isRecordTableRowFocusActive = useRecoilComponentValueV2(
|
||||||
|
isRecordTableRowFocusActiveComponentState,
|
||||||
recordTableId,
|
recordTableId,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -71,7 +78,9 @@ export const RecordTable = () => {
|
|||||||
tableBodyRef={tableBodyRef}
|
tableBodyRef={tableBodyRef}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{isRecordTableFocusActive && <RecordTableScrollToFocusedElementEffect />}
|
{isRecordTableCellFocusActive && <RecordTableScrollToFocusedCellEffect />}
|
||||||
|
|
||||||
|
{isRecordTableRowFocusActive && <RecordTableScrollToFocusedRowEffect />}
|
||||||
|
|
||||||
{recordTableIsEmpty && !hasRecordGroups ? (
|
{recordTableIsEmpty && !hasRecordGroups ? (
|
||||||
<RecordTableEmpty
|
<RecordTableEmpty
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
|
import { RecordTableDeactivateRecordTableRowEffect } from '@/object-record/record-table/components/RecordTableDeactivateRecordTableRowEffect';
|
||||||
import { RecordTableBodyEscapeHotkeyEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyEscapeHotkeyEffect';
|
import { RecordTableBodyEscapeHotkeyEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyEscapeHotkeyEffect';
|
||||||
import { RecordTableBodyFocusClickOutsideEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyFocusClickOutsideEffect';
|
import { RecordTableBodyFocusClickOutsideEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyFocusClickOutsideEffect';
|
||||||
import { RecordTableBodyFocusKeyboardEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyFocusKeyboardEffect';
|
import { RecordTableBodyFocusKeyboardEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyFocusKeyboardEffect';
|
||||||
|
import { RecordTableBodyRowFocusKeyboardEffect } from '@/object-record/record-table/record-table-body/components/RecordTableBodyRowFocusKeyboardEffect';
|
||||||
import { RecordTableNoRecordGroupBodyEffect } from '@/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBodyEffect';
|
import { RecordTableNoRecordGroupBodyEffect } from '@/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBodyEffect';
|
||||||
import { RecordTableRecordGroupBodyEffects } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupBodyEffects';
|
import { RecordTableRecordGroupBodyEffects } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupBodyEffects';
|
||||||
import { isAtLeastOneTableRowSelectedSelector } from '@/object-record/record-table/record-table-row/states/isAtLeastOneTableRowSelectedSelector';
|
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
|
||||||
import { isRecordTableFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableFocusActiveComponentState';
|
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
export interface RecordTableBodyEffectsWrapperProps {
|
export interface RecordTableBodyEffectsWrapperProps {
|
||||||
@ -16,12 +17,8 @@ export const RecordTableBodyEffectsWrapper = ({
|
|||||||
hasRecordGroups,
|
hasRecordGroups,
|
||||||
tableBodyRef,
|
tableBodyRef,
|
||||||
}: RecordTableBodyEffectsWrapperProps) => {
|
}: RecordTableBodyEffectsWrapperProps) => {
|
||||||
const isAtLeastOneRecordSelected = useRecoilComponentValueV2(
|
const isRecordTableRowFocusActive = useRecoilComponentValueV2(
|
||||||
isAtLeastOneTableRowSelectedSelector,
|
isRecordTableRowFocusActiveComponentState,
|
||||||
);
|
|
||||||
|
|
||||||
const isRecordTableFocusActive = useRecoilComponentValueV2(
|
|
||||||
isRecordTableFocusActiveComponentState,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -31,9 +28,11 @@ export const RecordTableBodyEffectsWrapper = ({
|
|||||||
) : (
|
) : (
|
||||||
<RecordTableNoRecordGroupBodyEffect />
|
<RecordTableNoRecordGroupBodyEffect />
|
||||||
)}
|
)}
|
||||||
{isAtLeastOneRecordSelected && <RecordTableBodyEscapeHotkeyEffect />}
|
<RecordTableBodyEscapeHotkeyEffect />
|
||||||
{isRecordTableFocusActive && <RecordTableBodyFocusKeyboardEffect />}
|
<RecordTableBodyFocusKeyboardEffect />
|
||||||
|
{isRecordTableRowFocusActive && <RecordTableBodyRowFocusKeyboardEffect />}
|
||||||
<RecordTableBodyFocusClickOutsideEffect tableBodyRef={tableBodyRef} />
|
<RecordTableBodyFocusClickOutsideEffect tableBodyRef={tableBodyRef} />
|
||||||
|
<RecordTableDeactivateRecordTableRowEffect />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow';
|
||||||
|
import { useListenRightDrawerClose } from '@/ui/layout/right-drawer/hooks/useListenRightDrawerClose';
|
||||||
|
|
||||||
|
export const RecordTableDeactivateRecordTableRowEffect = () => {
|
||||||
|
const { deactivateRecordTableRow } = useActiveRecordTableRow();
|
||||||
|
|
||||||
|
useListenRightDrawerClose(() => {
|
||||||
|
deactivateRecordTableRow();
|
||||||
|
});
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { useHandleContainerMouseEnter } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter';
|
import { useHandleContainerMouseEnter } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter';
|
||||||
import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus';
|
import { useRecordTableMoveFocusedCell } from '@/object-record/record-table/hooks/useRecordTableMoveFocusedCell';
|
||||||
import { useCloseRecordTableCellNoGroup } from '@/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellNoGroup';
|
import { useCloseRecordTableCellNoGroup } from '@/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellNoGroup';
|
||||||
import { useMoveHoverToCurrentCell } from '@/object-record/record-table/record-table-cell/hooks/useMoveHoverToCurrentCell';
|
import { useMoveHoverToCurrentCell } from '@/object-record/record-table/record-table-cell/hooks/useMoveHoverToCurrentCell';
|
||||||
import {
|
import {
|
||||||
@ -28,7 +28,7 @@ export const RecordTableNoRecordGroupBodyContextProvider = ({
|
|||||||
openTableCell(args);
|
openTableCell(args);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { moveFocus } = useRecordTableMoveFocus(recordTableId);
|
const { moveFocus } = useRecordTableMoveFocusedCell(recordTableId);
|
||||||
|
|
||||||
const handleMoveFocus = (direction: MoveFocusDirection) => {
|
const handleMoveFocus = (direction: MoveFocusDirection) => {
|
||||||
moveFocus(direction);
|
moveFocus(direction);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
import { RecordTableBodyContextProvider } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { useHandleContainerMouseEnter } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter';
|
import { useHandleContainerMouseEnter } from '@/object-record/record-table/hooks/internal/useHandleContainerMouseEnter';
|
||||||
import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus';
|
import { useRecordTableMove } from '@/object-record/record-table/hooks/useRecordTableMove';
|
||||||
import { useCloseRecordTableCellInGroup } from '@/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellInGroup';
|
import { useCloseRecordTableCellInGroup } from '@/object-record/record-table/record-table-cell/hooks/internal/useCloseRecordTableCellInGroup';
|
||||||
import { useMoveHoverToCurrentCell } from '@/object-record/record-table/record-table-cell/hooks/useMoveHoverToCurrentCell';
|
import { useMoveHoverToCurrentCell } from '@/object-record/record-table/record-table-cell/hooks/useMoveHoverToCurrentCell';
|
||||||
import {
|
import {
|
||||||
@ -29,10 +29,10 @@ export const RecordTableRecordGroupBodyContextProvider = ({
|
|||||||
openTableCell(args);
|
openTableCell(args);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { moveFocus } = useRecordTableMoveFocus(recordTableId);
|
const { move } = useRecordTableMove(recordTableId);
|
||||||
|
|
||||||
const handleMoveFocus = (direction: MoveFocusDirection) => {
|
const handleMoveFocus = (direction: MoveFocusDirection) => {
|
||||||
moveFocus(direction);
|
move(direction);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { closeTableCellInGroup } = useCloseRecordTableCellInGroup();
|
const { closeTableCellInGroup } = useCloseRecordTableCellInGroup();
|
||||||
|
|||||||
@ -1,13 +1,18 @@
|
|||||||
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState';
|
import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
export const RecordTableScrollToFocusedElementEffect = () => {
|
export const RecordTableScrollToFocusedCellEffect = () => {
|
||||||
|
const { recordTableId } = useRecordTableContextOrThrow();
|
||||||
|
|
||||||
const focusPosition = useRecoilComponentValueV2(
|
const focusPosition = useRecoilComponentValueV2(
|
||||||
recordTableFocusPositionComponentState,
|
recordTableFocusPositionComponentState,
|
||||||
|
recordTableId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Handle cell focus
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!focusPosition) {
|
if (!focusPosition) {
|
||||||
return;
|
return;
|
||||||
@ -36,11 +41,15 @@ export const RecordTableScrollToFocusedElementEffect = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
focusElement.style.scrollMarginTop = '32px';
|
||||||
|
focusElement.style.scrollMarginBottom = '32px';
|
||||||
|
|
||||||
focusElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
focusElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
if (isDefined(focusElement)) {
|
if (isDefined(focusElement)) {
|
||||||
focusElement.style.scrollMarginLeft = '';
|
focusElement.style.scrollMarginLeft = '';
|
||||||
|
focusElement.style.scrollMarginBottom = '';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [focusPosition]);
|
}, [focusPosition]);
|
||||||
@ -0,0 +1,63 @@
|
|||||||
|
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||||
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
|
import { focusedRecordTableRowIndexComponentState } from '@/object-record/record-table/states/focusedRecordTableRowIndexComponentState';
|
||||||
|
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
|
export const RecordTableScrollToFocusedRowEffect = () => {
|
||||||
|
const { recordTableId } = useRecordTableContextOrThrow();
|
||||||
|
|
||||||
|
const focusedRowIndex = useRecoilComponentValueV2(
|
||||||
|
focusedRecordTableRowIndexComponentState,
|
||||||
|
recordTableId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isRowFocusActive = useRecoilComponentValueV2(
|
||||||
|
isRecordTableRowFocusActiveComponentState,
|
||||||
|
recordTableId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const allRecordIds = useRecoilComponentValueV2(
|
||||||
|
recordIndexAllRecordIdsComponentSelector,
|
||||||
|
recordTableId,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
!isRowFocusActive ||
|
||||||
|
!isDefined(focusedRowIndex) ||
|
||||||
|
!allRecordIds?.length
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const recordId = allRecordIds[focusedRowIndex];
|
||||||
|
|
||||||
|
if (!recordId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const focusElement = document.getElementById(
|
||||||
|
`record-table-cell-0-${focusedRowIndex}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!focusElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
focusElement.style.scrollMarginBottom = '32px';
|
||||||
|
focusElement.style.scrollMarginTop = '32px';
|
||||||
|
|
||||||
|
focusElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (isDefined(focusElement)) {
|
||||||
|
focusElement.style.scrollMarginBottom = '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [focusedRowIndex, isRowFocusActive, allRecordIds]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@ -1,7 +1,11 @@
|
|||||||
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
|
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 { useSetIsRecordTableFocusActive } from '@/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableFocusActive';
|
||||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||||
import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState';
|
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 { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
|
|
||||||
@ -24,11 +28,27 @@ export const useLeaveTableFocus = (recordTableId?: string) => {
|
|||||||
recordTableIdFromContext,
|
recordTableIdFromContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { unfocusRecordTableRow } = useFocusedRecordTableRow(
|
||||||
|
recordTableIdFromContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { deactivateRecordTableRow } = useActiveRecordTableRow(
|
||||||
|
recordTableIdFromContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
resetTableRowSelection();
|
resetTableRowSelection();
|
||||||
|
|
||||||
setIsFocusActiveForCurrentPosition(false);
|
setIsFocusActiveForCurrentPosition(false);
|
||||||
|
|
||||||
|
unfocusRecordTableRow();
|
||||||
|
|
||||||
|
deactivateRecordTableRow();
|
||||||
|
|
||||||
setRecordTableHoverPosition(null);
|
setRecordTableHoverPosition(null);
|
||||||
|
|
||||||
|
setHotkeyScope(TableHotkeyScope.Table);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,62 @@
|
|||||||
|
import { activeRecordTableRowIndexComponentState } from '@/object-record/record-table/states/activeRecordTableRowIndexComponentState';
|
||||||
|
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||||
|
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||||
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
|
export const useActiveRecordTableRow = (recordTableId?: string) => {
|
||||||
|
const isRowActiveState = useRecoilComponentCallbackStateV2(
|
||||||
|
isRecordTableRowActiveComponentFamilyState,
|
||||||
|
recordTableId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const activeRowIndexState = useRecoilComponentCallbackStateV2(
|
||||||
|
activeRecordTableRowIndexComponentState,
|
||||||
|
recordTableId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const deactivateRecordTableRow = useRecoilCallback(
|
||||||
|
({ set, snapshot }) =>
|
||||||
|
() => {
|
||||||
|
const activeRowIndex = snapshot
|
||||||
|
.getLoadable(activeRowIndexState)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
if (!isDefined(activeRowIndex)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
set(activeRowIndexState, null);
|
||||||
|
|
||||||
|
set(isRowActiveState(activeRowIndex), false);
|
||||||
|
},
|
||||||
|
[activeRowIndexState, isRowActiveState],
|
||||||
|
);
|
||||||
|
|
||||||
|
const activateRecordTableRow = useRecoilCallback(
|
||||||
|
({ set, snapshot }) =>
|
||||||
|
(rowIndex: number) => {
|
||||||
|
const activeRowIndex = snapshot
|
||||||
|
.getLoadable(activeRowIndexState)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
if (activeRowIndex === rowIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDefined(activeRowIndex)) {
|
||||||
|
set(isRowActiveState(activeRowIndex), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
set(activeRowIndexState, rowIndex);
|
||||||
|
|
||||||
|
set(isRowActiveState(rowIndex), true);
|
||||||
|
},
|
||||||
|
[activeRowIndexState, isRowActiveState],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
activateRecordTableRow,
|
||||||
|
deactivateRecordTableRow,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,110 @@
|
|||||||
|
import { focusedRecordTableRowIndexComponentState } from '@/object-record/record-table/states/focusedRecordTableRowIndexComponentState';
|
||||||
|
import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState';
|
||||||
|
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
|
||||||
|
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||||
|
import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState';
|
||||||
|
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||||
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
|
export const useFocusedRecordTableRow = (recordTableId?: string) => {
|
||||||
|
const isRowFocusedState = useRecoilComponentCallbackStateV2(
|
||||||
|
isRecordTableRowFocusedComponentFamilyState,
|
||||||
|
recordTableId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const focusedRowIndexState = useRecoilComponentCallbackStateV2(
|
||||||
|
focusedRecordTableRowIndexComponentState,
|
||||||
|
recordTableId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isRowFocusActiveState = useRecoilComponentCallbackStateV2(
|
||||||
|
isRecordTableRowFocusActiveComponentState,
|
||||||
|
recordTableId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const focusedCellPositionState = useRecoilComponentCallbackStateV2(
|
||||||
|
recordTableFocusPositionComponentState,
|
||||||
|
recordTableId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isRecordTableCellFocusActiveState = useRecoilComponentCallbackStateV2(
|
||||||
|
isRecordTableCellFocusActiveComponentState,
|
||||||
|
recordTableId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const unfocusRecordTableRow = useRecoilCallback(
|
||||||
|
({ set, snapshot }) =>
|
||||||
|
() => {
|
||||||
|
const focusedRowIndex = snapshot
|
||||||
|
.getLoadable(focusedRowIndexState)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
if (!isDefined(focusedRowIndex)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
set(focusedRowIndexState, null);
|
||||||
|
set(isRowFocusedState(focusedRowIndex), false);
|
||||||
|
set(isRowFocusActiveState, false);
|
||||||
|
},
|
||||||
|
[focusedRowIndexState, isRowFocusedState, isRowFocusActiveState],
|
||||||
|
);
|
||||||
|
|
||||||
|
const focusRecordTableRow = useRecoilCallback(
|
||||||
|
({ set, snapshot }) =>
|
||||||
|
(rowIndex: number) => {
|
||||||
|
const focusedRowIndex = snapshot
|
||||||
|
.getLoadable(focusedRowIndexState)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
if (isDefined(focusedRowIndex) && focusedRowIndex !== rowIndex) {
|
||||||
|
set(isRowFocusedState(focusedRowIndex), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
set(focusedRowIndexState, rowIndex);
|
||||||
|
set(isRowFocusedState(rowIndex), true);
|
||||||
|
set(isRowFocusActiveState, true);
|
||||||
|
},
|
||||||
|
[focusedRowIndexState, isRowFocusedState, isRowFocusActiveState],
|
||||||
|
);
|
||||||
|
|
||||||
|
const restoreRecordTableRowFocusFromCellPosition = useRecoilCallback(
|
||||||
|
({ snapshot }) =>
|
||||||
|
() => {
|
||||||
|
const focusedRowIndex = snapshot
|
||||||
|
.getLoadable(focusedRowIndexState)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
const focusedCellPosition = snapshot
|
||||||
|
.getLoadable(focusedCellPositionState)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
const isRecordTableCellFocusActive = snapshot
|
||||||
|
.getLoadable(isRecordTableCellFocusActiveState)
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
if (
|
||||||
|
!isDefined(focusedRowIndex) ||
|
||||||
|
!isDefined(focusedCellPosition) ||
|
||||||
|
!isRecordTableCellFocusActive
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
focusRecordTableRow(focusedCellPosition.row);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
focusedRowIndexState,
|
||||||
|
focusedCellPositionState,
|
||||||
|
isRecordTableCellFocusActiveState,
|
||||||
|
focusRecordTableRow,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
focusRecordTableRow,
|
||||||
|
unfocusRecordTableRow,
|
||||||
|
restoreRecordTableRowFocusFromCellPosition,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -3,9 +3,7 @@ import { Key } from 'ts-key-enum';
|
|||||||
|
|
||||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
import { useSetHasUserSelectedAllRows } from '@/object-record/record-table/hooks/internal/useSetAllRowSelectedState';
|
import { useSetHasUserSelectedAllRows } from '@/object-record/record-table/hooks/internal/useSetAllRowSelectedState';
|
||||||
import { useRecordTableMoveFocus } from '@/object-record/record-table/hooks/useRecordTableMoveFocus';
|
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
|
||||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||||
|
|
||||||
@ -13,15 +11,16 @@ import { useUpsertRecordFromState } from '../../hooks/useUpsertRecordFromState';
|
|||||||
import { ColumnDefinition } from '../types/ColumnDefinition';
|
import { ColumnDefinition } from '../types/ColumnDefinition';
|
||||||
import { TableHotkeyScope } from '../types/TableHotkeyScope';
|
import { TableHotkeyScope } from '../types/TableHotkeyScope';
|
||||||
|
|
||||||
import { useSetIsRecordTableFocusActive } from '@/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableFocusActive';
|
|
||||||
import { availableTableColumnsComponentState } from '@/object-record/record-table/states/availableTableColumnsComponentState';
|
import { availableTableColumnsComponentState } from '@/object-record/record-table/states/availableTableColumnsComponentState';
|
||||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||||
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
||||||
import { onColumnsChangeComponentState } from '@/object-record/record-table/states/onColumnsChangeComponentState';
|
import { onColumnsChangeComponentState } from '@/object-record/record-table/states/onColumnsChangeComponentState';
|
||||||
import { onEntityCountChangeComponentState } from '@/object-record/record-table/states/onEntityCountChangeComponentState';
|
import { onEntityCountChangeComponentState } from '@/object-record/record-table/states/onEntityCountChangeComponentState';
|
||||||
|
|
||||||
|
import { useRecordTableMove } from '@/object-record/record-table/hooks/useRecordTableMove';
|
||||||
import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState';
|
import { onToggleColumnSortComponentState } from '@/object-record/record-table/states/onToggleColumnSortComponentState';
|
||||||
import { tableLastRowVisibleComponentState } from '@/object-record/record-table/states/tableLastRowVisibleComponentState';
|
import { tableLastRowVisibleComponentState } from '@/object-record/record-table/states/tableLastRowVisibleComponentState';
|
||||||
|
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
@ -31,7 +30,6 @@ import { useSelectAllRows } from './internal/useSelectAllRows';
|
|||||||
import { useSetRecordTableData } from './internal/useSetRecordTableData';
|
import { useSetRecordTableData } from './internal/useSetRecordTableData';
|
||||||
import { useSetRecordTableFocusPosition } from './internal/useSetRecordTableFocusPosition';
|
import { useSetRecordTableFocusPosition } from './internal/useSetRecordTableFocusPosition';
|
||||||
import { useSetRowSelectedState } from './internal/useSetRowSelectedState';
|
import { useSetRowSelectedState } from './internal/useSetRowSelectedState';
|
||||||
|
|
||||||
type useRecordTableProps = {
|
type useRecordTableProps = {
|
||||||
recordTableId?: string;
|
recordTableId?: string;
|
||||||
};
|
};
|
||||||
@ -141,62 +139,65 @@ export const useRecordTable = (props?: useRecordTableProps) => {
|
|||||||
|
|
||||||
const setFocusPosition = useSetRecordTableFocusPosition(recordTableId);
|
const setFocusPosition = useSetRecordTableFocusPosition(recordTableId);
|
||||||
|
|
||||||
const { setIsFocusActiveForCurrentPosition } =
|
const { setHotkeyScopeAndMemorizePreviousScope } = usePreviousHotkeyScope();
|
||||||
useSetIsRecordTableFocusActive(recordTableId);
|
|
||||||
|
|
||||||
const { moveDown, moveLeft, moveRight, moveUp } =
|
const { move } = useRecordTableMove(recordTableId);
|
||||||
useRecordTableMoveFocus(recordTableId);
|
|
||||||
|
|
||||||
const useMapKeyboardToFocus = () => {
|
const useMapKeyboardToFocus = () => {
|
||||||
const setHotkeyScope = useSetHotkeyScope();
|
|
||||||
|
|
||||||
useScopedHotkeys(
|
useScopedHotkeys(
|
||||||
[Key.ArrowUp, `${Key.Shift}+${Key.Enter}`],
|
[Key.ArrowUp, `${Key.Shift}+${Key.Enter}`],
|
||||||
() => {
|
() => {
|
||||||
moveUp();
|
move('up');
|
||||||
},
|
},
|
||||||
TableHotkeyScope.TableFocus,
|
TableHotkeyScope.TableFocus,
|
||||||
[moveUp],
|
[move],
|
||||||
);
|
);
|
||||||
|
|
||||||
useScopedHotkeys(
|
useScopedHotkeys(
|
||||||
Key.ArrowDown,
|
Key.ArrowDown,
|
||||||
() => {
|
() => {
|
||||||
moveDown();
|
move('down');
|
||||||
},
|
},
|
||||||
TableHotkeyScope.TableFocus,
|
TableHotkeyScope.TableFocus,
|
||||||
[moveDown],
|
[move],
|
||||||
|
);
|
||||||
|
|
||||||
|
useScopedHotkeys(
|
||||||
|
[Key.ArrowUp, 'k'],
|
||||||
|
() => {
|
||||||
|
setHotkeyScopeAndMemorizePreviousScope(TableHotkeyScope.TableFocus);
|
||||||
|
move('up');
|
||||||
|
},
|
||||||
|
TableHotkeyScope.Table,
|
||||||
|
[move],
|
||||||
|
);
|
||||||
|
|
||||||
|
useScopedHotkeys(
|
||||||
|
[Key.ArrowDown, 'j'],
|
||||||
|
() => {
|
||||||
|
setHotkeyScopeAndMemorizePreviousScope(TableHotkeyScope.TableFocus);
|
||||||
|
move('down');
|
||||||
|
},
|
||||||
|
TableHotkeyScope.Table,
|
||||||
|
[move],
|
||||||
);
|
);
|
||||||
|
|
||||||
useScopedHotkeys(
|
useScopedHotkeys(
|
||||||
[Key.ArrowLeft, `${Key.Shift}+${Key.Tab}`],
|
[Key.ArrowLeft, `${Key.Shift}+${Key.Tab}`],
|
||||||
() => {
|
() => {
|
||||||
moveLeft();
|
move('left');
|
||||||
},
|
},
|
||||||
TableHotkeyScope.TableFocus,
|
TableHotkeyScope.TableFocus,
|
||||||
[moveLeft],
|
[move],
|
||||||
);
|
);
|
||||||
|
|
||||||
useScopedHotkeys(
|
useScopedHotkeys(
|
||||||
[Key.ArrowRight, Key.Tab],
|
[Key.ArrowRight, Key.Tab],
|
||||||
() => {
|
() => {
|
||||||
moveRight();
|
move('right');
|
||||||
},
|
},
|
||||||
TableHotkeyScope.TableFocus,
|
TableHotkeyScope.TableFocus,
|
||||||
[moveRight],
|
[move],
|
||||||
);
|
|
||||||
|
|
||||||
useScopedHotkeys(
|
|
||||||
[Key.Escape],
|
|
||||||
() => {
|
|
||||||
setHotkeyScope(TableHotkeyScope.Table, {
|
|
||||||
goto: true,
|
|
||||||
keyboardShortcutMenu: true,
|
|
||||||
});
|
|
||||||
setIsFocusActiveForCurrentPosition(false);
|
|
||||||
},
|
|
||||||
TableHotkeyScope.TableFocus,
|
|
||||||
[setIsFocusActiveForCurrentPosition],
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -211,10 +212,7 @@ export const useRecordTable = (props?: useRecordTableProps) => {
|
|||||||
setRowSelected,
|
setRowSelected,
|
||||||
resetTableRowSelection,
|
resetTableRowSelection,
|
||||||
upsertRecordTableItem,
|
upsertRecordTableItem,
|
||||||
moveDown,
|
move,
|
||||||
moveLeft,
|
|
||||||
moveRight,
|
|
||||||
moveUp,
|
|
||||||
useMapKeyboardToFocus,
|
useMapKeyboardToFocus,
|
||||||
selectAllRows,
|
selectAllRows,
|
||||||
setOnColumnsChange,
|
setOnColumnsChange,
|
||||||
|
|||||||
@ -0,0 +1,39 @@
|
|||||||
|
import { useRecordTableMoveFocusedCell } from '@/object-record/record-table/hooks/useRecordTableMoveFocusedCell';
|
||||||
|
import { useRecordTableMoveFocusedRow } from '@/object-record/record-table/hooks/useRecordTableMoveFocusedRow';
|
||||||
|
import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState';
|
||||||
|
import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection';
|
||||||
|
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 useRecordTableMove = (recordTableId?: string) => {
|
||||||
|
const { moveFocusedRow } = useRecordTableMoveFocusedRow(recordTableId);
|
||||||
|
|
||||||
|
const { moveFocus } = useRecordTableMoveFocusedCell(recordTableId);
|
||||||
|
|
||||||
|
const isRecordTableFocusActiveState = useRecoilComponentCallbackStateV2(
|
||||||
|
isRecordTableCellFocusActiveComponentState,
|
||||||
|
recordTableId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const move = useRecoilCallback(
|
||||||
|
({ snapshot }) =>
|
||||||
|
(direction: MoveFocusDirection) => {
|
||||||
|
const isRecordTableFocusActive = getSnapshotValue(
|
||||||
|
snapshot,
|
||||||
|
isRecordTableFocusActiveState,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isRecordTableFocusActive) {
|
||||||
|
moveFocus(direction);
|
||||||
|
} else {
|
||||||
|
moveFocusedRow(direction);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isRecordTableFocusActiveState, moveFocusedRow, moveFocus],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
move,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -9,7 +9,7 @@ import { recordTableFocusPositionComponentState } from '@/object-record/record-t
|
|||||||
import { numberOfTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/numberOfTableColumnsComponentSelector';
|
import { numberOfTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/numberOfTableColumnsComponentSelector';
|
||||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||||
|
|
||||||
export const useRecordTableMoveFocus = (recordTableId?: string) => {
|
export const useRecordTableMoveFocusedCell = (recordTableId?: string) => {
|
||||||
const setFocusPosition = useSetRecordTableFocusPosition(recordTableId);
|
const setFocusPosition = useSetRecordTableFocusPosition(recordTableId);
|
||||||
|
|
||||||
const focusPositionState = useRecoilComponentCallbackStateV2(
|
const focusPositionState = useRecoilComponentCallbackStateV2(
|
||||||
@ -0,0 +1,93 @@
|
|||||||
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
|
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||||
|
import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow';
|
||||||
|
import { focusedRecordTableRowIndexComponentState } from '@/object-record/record-table/states/focusedRecordTableRowIndexComponentState';
|
||||||
|
import { MoveFocusDirection } from '@/object-record/record-table/types/MoveFocusDirection';
|
||||||
|
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||||
|
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
|
export const useRecordTableMoveFocusedRow = (recordTableId?: string) => {
|
||||||
|
const { focusRecordTableRow } = useFocusedRecordTableRow(recordTableId);
|
||||||
|
|
||||||
|
const focusedRowIndexState = useRecoilComponentCallbackStateV2(
|
||||||
|
focusedRecordTableRowIndexComponentState,
|
||||||
|
recordTableId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const recordIndexAllRecordIdsSelector = useRecoilComponentCallbackStateV2(
|
||||||
|
recordIndexAllRecordIdsComponentSelector,
|
||||||
|
recordTableId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const moveFocusedRowUp = useRecoilCallback(
|
||||||
|
({ snapshot }) =>
|
||||||
|
() => {
|
||||||
|
const focusedRowIndex = getSnapshotValue(
|
||||||
|
snapshot,
|
||||||
|
focusedRowIndexState,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isDefined(focusedRowIndex)) {
|
||||||
|
focusRecordTableRow(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newRowIndex = focusedRowIndex - 1;
|
||||||
|
|
||||||
|
if (newRowIndex < 0) {
|
||||||
|
newRowIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
focusRecordTableRow(newRowIndex);
|
||||||
|
},
|
||||||
|
[focusedRowIndexState, focusRecordTableRow],
|
||||||
|
);
|
||||||
|
|
||||||
|
const moveFocusedRowDown = useRecoilCallback(
|
||||||
|
({ snapshot }) =>
|
||||||
|
() => {
|
||||||
|
const allRecordIds = getSnapshotValue(
|
||||||
|
snapshot,
|
||||||
|
recordIndexAllRecordIdsSelector,
|
||||||
|
);
|
||||||
|
const focusedRowIndex = getSnapshotValue(
|
||||||
|
snapshot,
|
||||||
|
focusedRowIndexState,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isDefined(focusedRowIndex)) {
|
||||||
|
focusRecordTableRow(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newRowIndex = focusedRowIndex + 1;
|
||||||
|
|
||||||
|
if (newRowIndex >= allRecordIds.length) {
|
||||||
|
newRowIndex = allRecordIds.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
focusRecordTableRow(newRowIndex);
|
||||||
|
},
|
||||||
|
[
|
||||||
|
recordIndexAllRecordIdsSelector,
|
||||||
|
focusedRowIndexState,
|
||||||
|
focusRecordTableRow,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const moveFocusedRow = (direction: MoveFocusDirection) => {
|
||||||
|
if (direction === 'up') {
|
||||||
|
moveFocusedRowUp();
|
||||||
|
} else if (direction === 'down') {
|
||||||
|
moveFocusedRowDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
moveFocusedRowUp,
|
||||||
|
moveFocusedRowDown,
|
||||||
|
moveFocusedRow,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -6,20 +6,20 @@ const StyledTbody = styled.tbody`
|
|||||||
position: sticky;
|
position: sticky;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 6;
|
z-index: 6;
|
||||||
transition: 0.3s ease;
|
transition: transform 0.3s ease;
|
||||||
}
|
}
|
||||||
td:nth-of-type(2) {
|
td:nth-of-type(2) {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
left: 11px;
|
left: 11px;
|
||||||
z-index: 6;
|
z-index: 6;
|
||||||
transition: 0.3s ease;
|
transition: transform 0.3s ease;
|
||||||
}
|
}
|
||||||
tr:not(:last-child) td:nth-of-type(3) {
|
tr:not(:last-child) td:nth-of-type(3) {
|
||||||
// Last row is aggregate footer
|
// Last row is aggregate footer
|
||||||
position: sticky;
|
position: sticky;
|
||||||
left: 43px;
|
left: 43px;
|
||||||
z-index: 6;
|
z-index: 6;
|
||||||
transition: 0.3s ease;
|
transition: transform 0.3s ease;
|
||||||
|
|
||||||
&:not(.disable-shadow)::after {
|
&:not(.disable-shadow)::after {
|
||||||
content: '';
|
content: '';
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
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 { 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 { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
export const RecordTableBodyEscapeHotkeyEffect = () => {
|
export const RecordTableBodyEscapeHotkeyEffect = () => {
|
||||||
const { recordTableId } = useRecordTableContextOrThrow();
|
const { recordTableId } = useRecordTableContextOrThrow();
|
||||||
|
|
||||||
@ -12,12 +14,22 @@ export const RecordTableBodyEscapeHotkeyEffect = () => {
|
|||||||
recordTableId,
|
recordTableId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { unfocusRecordTableRow } = useFocusedRecordTableRow(recordTableId);
|
||||||
|
|
||||||
|
const isAtLeastOneRecordSelected = useRecoilComponentValueV2(
|
||||||
|
isAtLeastOneTableRowSelectedSelector,
|
||||||
|
);
|
||||||
|
|
||||||
useScopedHotkeys(
|
useScopedHotkeys(
|
||||||
[Key.Escape],
|
[Key.Escape],
|
||||||
() => {
|
() => {
|
||||||
resetTableRowSelection();
|
unfocusRecordTableRow();
|
||||||
|
if (isAtLeastOneRecordSelected) {
|
||||||
|
resetTableRowSelection();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
TableHotkeyScope.Table,
|
TableHotkeyScope.Table,
|
||||||
|
[isAtLeastOneRecordSelected, resetTableRowSelection, unfocusRecordTableRow],
|
||||||
);
|
);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
|
|||||||
@ -1,5 +1,13 @@
|
|||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
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 { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||||
|
import { useSetIsRecordTableFocusActive } from '@/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableFocusActive';
|
||||||
|
import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState';
|
||||||
|
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||||
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
|
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
export const RecordTableBodyFocusKeyboardEffect = () => {
|
export const RecordTableBodyFocusKeyboardEffect = () => {
|
||||||
const { recordTableId } = useRecordTableContextOrThrow();
|
const { recordTableId } = useRecordTableContextOrThrow();
|
||||||
@ -8,7 +16,41 @@ export const RecordTableBodyFocusKeyboardEffect = () => {
|
|||||||
recordTableId,
|
recordTableId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const setHotkeyScope = useSetHotkeyScope();
|
||||||
|
|
||||||
|
const { restoreRecordTableRowFocusFromCellPosition } =
|
||||||
|
useFocusedRecordTableRow(recordTableId);
|
||||||
|
|
||||||
|
const { setIsFocusActiveForCurrentPosition } =
|
||||||
|
useSetIsRecordTableFocusActive(recordTableId);
|
||||||
|
|
||||||
|
const isRecordTableFocusActive = useRecoilComponentValueV2(
|
||||||
|
isRecordTableCellFocusActiveComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
useMapKeyboardToFocus();
|
useMapKeyboardToFocus();
|
||||||
|
|
||||||
|
useScopedHotkeys(
|
||||||
|
[Key.Escape],
|
||||||
|
() => {
|
||||||
|
if (isRecordTableFocusActive) {
|
||||||
|
restoreRecordTableRowFocusFromCellPosition();
|
||||||
|
setIsFocusActiveForCurrentPosition(false);
|
||||||
|
} else {
|
||||||
|
setHotkeyScope(TableHotkeyScope.Table, {
|
||||||
|
goto: true,
|
||||||
|
keyboardShortcutMenu: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TableHotkeyScope.TableFocus,
|
||||||
|
[
|
||||||
|
setIsFocusActiveForCurrentPosition,
|
||||||
|
restoreRecordTableRowFocusFromCellPosition,
|
||||||
|
setHotkeyScope,
|
||||||
|
isRecordTableFocusActive,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
|
import { useRecordTableMoveFocusedRow } from '@/object-record/record-table/hooks/useRecordTableMoveFocusedRow';
|
||||||
|
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||||
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
|
export const RecordTableBodyRowFocusKeyboardEffect = () => {
|
||||||
|
const { recordTableId } = useRecordTableContextOrThrow();
|
||||||
|
|
||||||
|
const { moveFocusedRow } = useRecordTableMoveFocusedRow(recordTableId);
|
||||||
|
|
||||||
|
useScopedHotkeys(
|
||||||
|
[Key.ArrowUp, 'k'],
|
||||||
|
() => {
|
||||||
|
moveFocusedRow('up');
|
||||||
|
},
|
||||||
|
TableHotkeyScope.TableFocus,
|
||||||
|
[moveFocusedRow],
|
||||||
|
);
|
||||||
|
|
||||||
|
useScopedHotkeys(
|
||||||
|
[Key.ArrowDown, 'j'],
|
||||||
|
() => {
|
||||||
|
moveFocusedRow('down');
|
||||||
|
},
|
||||||
|
TableHotkeyScope.TableFocus,
|
||||||
|
[moveFocusedRow],
|
||||||
|
);
|
||||||
|
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
@ -17,6 +17,10 @@ const StyledContainer = styled.div`
|
|||||||
min-width: ${TABLE_CELL_CHECKBOX_MIN_WIDTH};
|
min-width: ${TABLE_CELL_CHECKBOX_MIN_WIDTH};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const StyledRecordTableTd = styled(RecordTableTd)`
|
||||||
|
border-left: 1px solid transparent;
|
||||||
|
`;
|
||||||
|
|
||||||
export const RecordTableCellCheckbox = () => {
|
export const RecordTableCellCheckbox = () => {
|
||||||
const { isSelected } = useRecordTableRowContextOrThrow();
|
const { isSelected } = useRecordTableRowContextOrThrow();
|
||||||
|
|
||||||
@ -27,10 +31,10 @@ export const RecordTableCellCheckbox = () => {
|
|||||||
}, [isSelected, setCurrentRowSelected]);
|
}, [isSelected, setCurrentRowSelected]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecordTableTd isSelected={isSelected} hasRightBorder={false}>
|
<StyledRecordTableTd isSelected={isSelected} hasRightBorder={false}>
|
||||||
<StyledContainer onClick={handleClick}>
|
<StyledContainer onClick={handleClick}>
|
||||||
<Checkbox hoverable checked={isSelected} />
|
<Checkbox hoverable checked={isSelected} />
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
</RecordTableTd>
|
</StyledRecordTableTd>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,20 +6,25 @@ import styled from '@emotion/styled';
|
|||||||
import { FieldDisplay } from '@/object-record/record-field/components/FieldDisplay';
|
import { FieldDisplay } from '@/object-record/record-field/components/FieldDisplay';
|
||||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||||
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
|
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
|
||||||
|
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||||
import { RecordTableCellDisplayMode } from '@/object-record/record-table/record-table-cell/components/RecordTableCellDisplayMode';
|
import { RecordTableCellDisplayMode } from '@/object-record/record-table/record-table-cell/components/RecordTableCellDisplayMode';
|
||||||
import { RecordTableCellEditButton } from '@/object-record/record-table/record-table-cell/components/RecordTableCellEditButton';
|
import { RecordTableCellEditButton } from '@/object-record/record-table/record-table-cell/components/RecordTableCellEditButton';
|
||||||
import { RecordTableCellEditMode } from '@/object-record/record-table/record-table-cell/components/RecordTableCellEditMode';
|
import { RecordTableCellEditMode } from '@/object-record/record-table/record-table-cell/components/RecordTableCellEditMode';
|
||||||
import { RecordTableCellFieldInput } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldInput';
|
import { RecordTableCellFieldInput } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldInput';
|
||||||
|
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||||
|
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { BORDER_COMMON } from 'twenty-ui/theme';
|
import { BORDER_COMMON } from 'twenty-ui/theme';
|
||||||
import { useIsMobile } from 'twenty-ui/utilities';
|
import { useIsMobile } from 'twenty-ui/utilities';
|
||||||
|
|
||||||
const StyledRecordTableCellHoveredPortalContent = styled.div<{
|
const StyledRecordTableCellHoveredPortalContent = styled.div<{
|
||||||
isReadOnly: boolean;
|
isReadOnly: boolean;
|
||||||
|
isRowActive: boolean;
|
||||||
}>`
|
}>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: ${({ theme }) => theme.background.transparent.secondary};
|
background: ${({ theme }) => theme.background.transparent.secondary};
|
||||||
background-color: ${({ theme }) => theme.background.primary};
|
background-color: ${({ theme, isRowActive }) =>
|
||||||
|
isRowActive ? theme.accent.quaternary : theme.background.primary};
|
||||||
border-radius: ${({ isReadOnly }) =>
|
border-radius: ${({ isReadOnly }) =>
|
||||||
!isReadOnly ? BORDER_COMMON.radius.sm : 'none'};
|
!isReadOnly ? BORDER_COMMON.radius.sm : 'none'};
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -28,10 +33,12 @@ const StyledRecordTableCellHoveredPortalContent = styled.div<{
|
|||||||
|
|
||||||
height: 32px;
|
height: 32px;
|
||||||
|
|
||||||
outline: ${({ theme, isReadOnly }) =>
|
outline: ${({ theme, isReadOnly, isRowActive }) =>
|
||||||
isReadOnly
|
isRowActive
|
||||||
? `1px solid ${theme.border.color.medium}`
|
? 'none'
|
||||||
: `1px solid ${theme.font.color.extraLight}`};
|
: isReadOnly
|
||||||
|
? `1px solid ${theme.border.color.medium}`
|
||||||
|
: `1px solid ${theme.font.color.extraLight}`};
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
@ -53,8 +60,18 @@ const RecordTableCellHoveredPortalContent = () => {
|
|||||||
const showButton =
|
const showButton =
|
||||||
!isFieldInputOnly && !isReadOnly && !(isMobile && isFirstColumn);
|
!isFieldInputOnly && !isReadOnly && !(isMobile && isFirstColumn);
|
||||||
|
|
||||||
|
const { rowIndex } = useRecordTableRowContextOrThrow();
|
||||||
|
|
||||||
|
const isRowActive = useRecoilComponentFamilyValueV2(
|
||||||
|
isRecordTableRowActiveComponentFamilyState,
|
||||||
|
rowIndex,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledRecordTableCellHoveredPortalContent isReadOnly={isReadOnly}>
|
<StyledRecordTableCellHoveredPortalContent
|
||||||
|
isReadOnly={isReadOnly}
|
||||||
|
isRowActive={isRowActive}
|
||||||
|
>
|
||||||
{isFieldInputOnly ? (
|
{isFieldInputOnly ? (
|
||||||
<RecordTableCellEditMode>
|
<RecordTableCellEditMode>
|
||||||
<RecordTableCellFieldInput />
|
<RecordTableCellFieldInput />
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { RecordTableCellEditModePortal } from '@/object-record/record-table/record-table-cell/components/RecordTableCellEditModePortal';
|
import { RecordTableCellEditModePortal } from '@/object-record/record-table/record-table-cell/components/RecordTableCellEditModePortal';
|
||||||
import { RecordTableCellHoveredPortal } from '@/object-record/record-table/record-table-cell/components/RecordTableCellHoveredPortal';
|
import { RecordTableCellHoveredPortal } from '@/object-record/record-table/record-table-cell/components/RecordTableCellHoveredPortal';
|
||||||
import { isRecordTableFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableFocusActiveComponentState';
|
import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
export const RecordTableCellPortals = () => {
|
export const RecordTableCellPortals = () => {
|
||||||
const { recordTableId } = useRecordTableContextOrThrow();
|
const { recordTableId } = useRecordTableContextOrThrow();
|
||||||
|
|
||||||
const isRecordTableFocusActive = useRecoilComponentValueV2(
|
const isRecordTableFocusActive = useRecoilComponentValueV2(
|
||||||
isRecordTableFocusActiveComponentState,
|
isRecordTableCellFocusActiveComponentState,
|
||||||
recordTableId,
|
recordTableId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -4,10 +4,5 @@ import { RecordTableTd } from '@/object-record/record-table/record-table-cell/co
|
|||||||
export const RecordTableLastEmptyCell = () => {
|
export const RecordTableLastEmptyCell = () => {
|
||||||
const { isSelected } = useRecordTableRowContextOrThrow();
|
const { isSelected } = useRecordTableRowContextOrThrow();
|
||||||
|
|
||||||
return (
|
return <RecordTableTd isSelected={isSelected} hasRightBorder={false} />;
|
||||||
<>
|
|
||||||
<RecordTableTd isSelected={isSelected} hasRightBorder={false} />
|
|
||||||
<RecordTableTd isSelected={isSelected} hasRightBorder={false} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -26,7 +26,7 @@ const StyledTd = styled.td<{
|
|||||||
hasRightBorder && !isDragging ? `1px solid ${borderColor}` : 'none'};
|
hasRightBorder && !isDragging ? `1px solid ${borderColor}` : 'none'};
|
||||||
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
transition: 0.3s ease;
|
transition: transform 0.3s ease;
|
||||||
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { RecoilRoot, useRecoilValue } from 'recoil';
|
|||||||
|
|
||||||
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
|
import { RecordTableComponentInstance } from '@/object-record/record-table/components/RecordTableComponentInstance';
|
||||||
import { useSetIsRecordTableFocusActive } from '@/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableFocusActive';
|
import { useSetIsRecordTableFocusActive } from '@/object-record/record-table/record-table-cell/hooks/useSetIsRecordTableFocusActive';
|
||||||
import { isRecordTableFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableFocusActiveComponentState';
|
import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState';
|
||||||
import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState';
|
import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState';
|
||||||
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
|
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
|||||||
<RecoilRoot
|
<RecoilRoot
|
||||||
initializeState={({ set }) => {
|
initializeState={({ set }) => {
|
||||||
set(
|
set(
|
||||||
isRecordTableFocusActiveComponentState.atomFamily({
|
isRecordTableCellFocusActiveComponentState.atomFamily({
|
||||||
instanceId: 'test-table-id',
|
instanceId: 'test-table-id',
|
||||||
}),
|
}),
|
||||||
false,
|
false,
|
||||||
@ -50,7 +50,7 @@ const renderHooks = () => {
|
|||||||
const { setIsFocusActive, setIsFocusActiveForCurrentPosition } =
|
const { setIsFocusActive, setIsFocusActiveForCurrentPosition } =
|
||||||
useSetIsRecordTableFocusActive('test-table-id');
|
useSetIsRecordTableFocusActive('test-table-id');
|
||||||
const isRecordTableFocusActive = useRecoilValue(
|
const isRecordTableFocusActive = useRecoilValue(
|
||||||
isRecordTableFocusActiveComponentState.atomFamily({
|
isRecordTableCellFocusActiveComponentState.atomFamily({
|
||||||
instanceId: 'test-table-id',
|
instanceId: 'test-table-id',
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -99,7 +99,7 @@ describe('useSetIsRecordTableFocusActive', () => {
|
|||||||
expect(result.current.focusPosition).toEqual(cellPosition);
|
expect(result.current.focusPosition).toEqual(cellPosition);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove focus-active class when focus is deactivated and update isRecordTableFocusActiveComponentState', () => {
|
it('should remove focus-active class when focus is deactivated and update isRecordTableCellFocusActiveComponentState', () => {
|
||||||
const { result } = renderHooks();
|
const { result } = renderHooks();
|
||||||
|
|
||||||
const cellPosition: TableCellPosition = { column: 1, row: 0 };
|
const cellPosition: TableCellPosition = { column: 1, row: 0 };
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
|||||||
|
|
||||||
import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext';
|
||||||
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
||||||
import { useSetRecordTableFocusPosition } from '@/object-record/record-table/hooks/internal/useSetRecordTableFocusPosition';
|
|
||||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||||
|
|
||||||
export const DEFAULT_CELL_SCOPE: HotkeyScope = {
|
export const DEFAULT_CELL_SCOPE: HotkeyScope = {
|
||||||
@ -36,8 +35,6 @@ export const useOpenRecordTableCellFromCell = () => {
|
|||||||
|
|
||||||
const { cellPosition } = useContext(RecordTableCellContext);
|
const { cellPosition } = useContext(RecordTableCellContext);
|
||||||
|
|
||||||
const setFocusPosition = useSetRecordTableFocusPosition();
|
|
||||||
|
|
||||||
const openTableCell = (
|
const openTableCell = (
|
||||||
initialValue?: string,
|
initialValue?: string,
|
||||||
isActionButtonClick = false,
|
isActionButtonClick = false,
|
||||||
@ -54,8 +51,6 @@ export const useOpenRecordTableCellFromCell = () => {
|
|||||||
isActionButtonClick,
|
isActionButtonClick,
|
||||||
isNavigating,
|
isNavigating,
|
||||||
});
|
});
|
||||||
|
|
||||||
setFocusPosition(cellPosition);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -25,6 +25,10 @@ import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropd
|
|||||||
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
|
import { getRecordFieldInputId } from '@/object-record/utils/getRecordFieldInputId';
|
||||||
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
|
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
|
||||||
|
|
||||||
|
import { useSetRecordTableFocusPosition } from '@/object-record/record-table/hooks/internal/useSetRecordTableFocusPosition';
|
||||||
|
import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow';
|
||||||
|
import { useFocusedRecordTableRow } from '@/object-record/record-table/hooks/useFocusedRecordTableRow';
|
||||||
|
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
|
||||||
import { clickOutsideListenerIsActivatedComponentState } from '@/ui/utilities/pointer-event/states/clickOutsideListenerIsActivatedComponentState';
|
import { clickOutsideListenerIsActivatedComponentState } from '@/ui/utilities/pointer-event/states/clickOutsideListenerIsActivatedComponentState';
|
||||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
@ -82,6 +86,18 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
|||||||
|
|
||||||
const { openFieldInput } = useOpenFieldInputEditMode();
|
const { openFieldInput } = useOpenFieldInputEditMode();
|
||||||
|
|
||||||
|
const { activateRecordTableRow, deactivateRecordTableRow } =
|
||||||
|
useActiveRecordTableRow(tableScopeId);
|
||||||
|
|
||||||
|
const { unfocusRecordTableRow } = useFocusedRecordTableRow(tableScopeId);
|
||||||
|
|
||||||
|
const setIsRowFocusActive = useSetRecoilComponentStateV2(
|
||||||
|
isRecordTableRowFocusActiveComponentState,
|
||||||
|
tableScopeId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const setFocusPosition = useSetRecordTableFocusPosition();
|
||||||
|
|
||||||
const openTableCell = useRecoilCallback(
|
const openTableCell = useRecoilCallback(
|
||||||
({ snapshot, set }) =>
|
({ snapshot, set }) =>
|
||||||
({
|
({
|
||||||
@ -134,6 +150,9 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
|||||||
recordId,
|
recordId,
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
activateRecordTableRow(cellPosition.row);
|
||||||
|
unfocusRecordTableRow();
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -147,6 +166,12 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deactivateRecordTableRow();
|
||||||
|
|
||||||
|
setFocusPosition(cellPosition);
|
||||||
|
|
||||||
|
setIsRowFocusActive(false);
|
||||||
|
|
||||||
setDragSelectionStartEnabled(false);
|
setDragSelectionStartEnabled(false);
|
||||||
|
|
||||||
openFieldInput({
|
openFieldInput({
|
||||||
@ -179,6 +204,9 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
|||||||
},
|
},
|
||||||
[
|
[
|
||||||
clickOutsideListenerIsActivatedState,
|
clickOutsideListenerIsActivatedState,
|
||||||
|
deactivateRecordTableRow,
|
||||||
|
setFocusPosition,
|
||||||
|
setIsRowFocusActive,
|
||||||
setDragSelectionStartEnabled,
|
setDragSelectionStartEnabled,
|
||||||
openFieldInput,
|
openFieldInput,
|
||||||
setCurrentTableCellInEditModePosition,
|
setCurrentTableCellInEditModePosition,
|
||||||
@ -189,6 +217,8 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
|||||||
navigate,
|
navigate,
|
||||||
indexIdentifierUrl,
|
indexIdentifierUrl,
|
||||||
openRecordInCommandMenu,
|
openRecordInCommandMenu,
|
||||||
|
activateRecordTableRow,
|
||||||
|
unfocusRecordTableRow,
|
||||||
setViewableRecordId,
|
setViewableRecordId,
|
||||||
setViewableRecordNameSingular,
|
setViewableRecordNameSingular,
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { isRecordTableFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableFocusActiveComponentState';
|
import { isRecordTableCellFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableCellFocusActiveComponentState';
|
||||||
import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState';
|
import { recordTableFocusPositionComponentState } from '@/object-record/record-table/states/recordTableFocusPositionComponentState';
|
||||||
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
|
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
|
||||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||||
@ -6,7 +6,7 @@ import { useRecoilCallback } from 'recoil';
|
|||||||
|
|
||||||
export const useSetIsRecordTableFocusActive = (recordTableId?: string) => {
|
export const useSetIsRecordTableFocusActive = (recordTableId?: string) => {
|
||||||
const isRecordTableFocusActiveState = useRecoilComponentCallbackStateV2(
|
const isRecordTableFocusActiveState = useRecoilComponentCallbackStateV2(
|
||||||
isRecordTableFocusActiveComponentState,
|
isRecordTableCellFocusActiveComponentState,
|
||||||
recordTableId,
|
recordTableId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import { useRecordTableContextOrThrow } from '@/object-record/record-table/conte
|
|||||||
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||||
import { useTableColumns } from '@/object-record/record-table/hooks/useTableColumns';
|
import { useTableColumns } from '@/object-record/record-table/hooks/useTableColumns';
|
||||||
import { RecordTableColumnHeadWithDropdown } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown';
|
import { RecordTableColumnHeadWithDropdown } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown';
|
||||||
|
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||||
|
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||||
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
|
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
|
||||||
import { resizeFieldOffsetComponentState } from '@/object-record/record-table/states/resizeFieldOffsetComponentState';
|
import { resizeFieldOffsetComponentState } from '@/object-record/record-table/states/resizeFieldOffsetComponentState';
|
||||||
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
|
import { tableColumnsComponentState } from '@/object-record/record-table/states/tableColumnsComponentState';
|
||||||
@ -17,23 +19,27 @@ import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPoin
|
|||||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||||
|
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
|
|
||||||
import { IconPlus } from 'twenty-ui/display';
|
import { IconPlus } from 'twenty-ui/display';
|
||||||
import { LightIconButton } from 'twenty-ui/input';
|
import { LightIconButton } from 'twenty-ui/input';
|
||||||
|
import { mapArrayToObject } from '~/utils/array/mapArrayToObject';
|
||||||
|
|
||||||
const COLUMN_MIN_WIDTH = 104;
|
const COLUMN_MIN_WIDTH = 104;
|
||||||
|
|
||||||
const StyledColumnHeaderCell = styled.th<{
|
const StyledColumnHeaderCell = styled.th<{
|
||||||
columnWidth: number;
|
columnWidth: number;
|
||||||
isResizing?: boolean;
|
isResizing?: boolean;
|
||||||
|
isFirstRowActiveOrFocused: boolean;
|
||||||
}>`
|
}>`
|
||||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
border-bottom: ${({ isFirstRowActiveOrFocused, theme }) =>
|
||||||
|
isFirstRowActiveOrFocused
|
||||||
|
? 'none'
|
||||||
|
: `1px solid ${theme.border.color.light}`};
|
||||||
color: ${({ theme }) => theme.font.color.tertiary};
|
color: ${({ theme }) => theme.font.color.tertiary};
|
||||||
padding: 0;
|
padding: 0;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
transition: 0.3s ease;
|
|
||||||
|
|
||||||
background-color: ${({ theme }) => theme.background.primary};
|
background-color: ${({ theme }) => theme.background.primary};
|
||||||
border-right: 1px solid ${({ theme }) => theme.border.color.light};
|
border-right: 1px solid ${({ theme }) => theme.border.color.light};
|
||||||
@ -215,6 +221,18 @@ export const RecordTableHeaderCell = ({
|
|||||||
|
|
||||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||||
|
|
||||||
|
const isFirstRowActive = useRecoilComponentFamilyValueV2(
|
||||||
|
isRecordTableRowActiveComponentFamilyState,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isFirstRowFocused = useRecoilComponentFamilyValueV2(
|
||||||
|
isRecordTableRowFocusedComponentFamilyState,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isFirstRowActiveOrFocused = isFirstRowActive || isFirstRowFocused;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledColumnHeaderCell
|
<StyledColumnHeaderCell
|
||||||
key={column.fieldMetadataId}
|
key={column.fieldMetadataId}
|
||||||
@ -227,6 +245,7 @@ export const RecordTableHeaderCell = ({
|
|||||||
)}
|
)}
|
||||||
onMouseEnter={() => setIconVisibility(true)}
|
onMouseEnter={() => setIconVisibility(true)}
|
||||||
onMouseLeave={() => setIconVisibility(false)}
|
onMouseLeave={() => setIconVisibility(false)}
|
||||||
|
isFirstRowActiveOrFocused={isFirstRowActiveOrFocused}
|
||||||
>
|
>
|
||||||
<StyledColumnHeadContainer>
|
<StyledColumnHeadContainer>
|
||||||
<RecordTableColumnHeadWithDropdown column={column} />
|
<RecordTableColumnHeadWithDropdown column={column} />
|
||||||
|
|||||||
@ -4,7 +4,10 @@ import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record
|
|||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||||
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState';
|
||||||
|
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||||
|
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||||
import { allRowsSelectedStatusComponentSelector } from '@/object-record/record-table/states/selectors/allRowsSelectedStatusComponentSelector';
|
import { allRowsSelectedStatusComponentSelector } from '@/object-record/record-table/states/selectors/allRowsSelectedStatusComponentSelector';
|
||||||
|
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { Checkbox } from 'twenty-ui/input';
|
import { Checkbox } from 'twenty-ui/input';
|
||||||
|
|
||||||
@ -16,9 +19,14 @@ const StyledContainer = styled.div`
|
|||||||
background-color: ${({ theme }) => theme.background.primary};
|
background-color: ${({ theme }) => theme.background.primary};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledColumnHeaderCell = styled.th`
|
const StyledColumnHeaderCell = styled.th<{
|
||||||
|
isFirstRowActiveOrFocused: boolean;
|
||||||
|
}>`
|
||||||
background-color: ${({ theme }) => theme.background.primary};
|
background-color: ${({ theme }) => theme.background.primary};
|
||||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
border-bottom: ${({ isFirstRowActiveOrFocused, theme }) =>
|
||||||
|
isFirstRowActiveOrFocused
|
||||||
|
? 'none'
|
||||||
|
: `1px solid ${theme.border.color.light}`};
|
||||||
border-right: transparent;
|
border-right: transparent;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
`;
|
`;
|
||||||
@ -58,8 +66,22 @@ export const RecordTableHeaderCheckboxColumn = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isFirstRowActive = useRecoilComponentFamilyValueV2(
|
||||||
|
isRecordTableRowActiveComponentFamilyState,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isFirstRowFocused = useRecoilComponentFamilyValueV2(
|
||||||
|
isRecordTableRowFocusedComponentFamilyState,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isFirstRowActiveOrFocused = isFirstRowActive || isFirstRowFocused;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledColumnHeaderCell>
|
<StyledColumnHeaderCell
|
||||||
|
isFirstRowActiveOrFocused={isFirstRowActiveOrFocused}
|
||||||
|
>
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
hoverable
|
hoverable
|
||||||
|
|||||||
@ -2,49 +2,50 @@ import styled from '@emotion/styled';
|
|||||||
|
|
||||||
import { HIDDEN_TABLE_COLUMN_DROPDOWN_ID } from '@/object-record/record-table/constants/HiddenTableColumnDropdownId';
|
import { HIDDEN_TABLE_COLUMN_DROPDOWN_ID } from '@/object-record/record-table/constants/HiddenTableColumnDropdownId';
|
||||||
import { RecordTableHeaderPlusButtonContent } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderPlusButtonContent';
|
import { RecordTableHeaderPlusButtonContent } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderPlusButtonContent';
|
||||||
|
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||||
|
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement';
|
import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement';
|
||||||
|
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import { IconPlus } from 'twenty-ui/display';
|
import { IconPlus } from 'twenty-ui/display';
|
||||||
|
|
||||||
const StyledPlusIconHeaderCell = styled.th<{
|
const StyledPlusIconHeaderCell = styled.th<{
|
||||||
isTableWiderThanScreen: boolean;
|
isTableWiderThanScreen: boolean;
|
||||||
|
isFirstRowActiveOrFocused: boolean;
|
||||||
}>`
|
}>`
|
||||||
${({ theme }) => {
|
border-bottom: ${({ isFirstRowActiveOrFocused, theme }) =>
|
||||||
return `
|
isFirstRowActiveOrFocused
|
||||||
&:hover {
|
? 'none'
|
||||||
background: ${theme.background.transparent.light};
|
: `1px solid ${theme.border.color.light}`};
|
||||||
};
|
|
||||||
`;
|
|
||||||
}};
|
|
||||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
|
||||||
background-color: ${({ theme }) => theme.background.primary};
|
background-color: ${({ theme }) => theme.background.primary};
|
||||||
border-left: none !important;
|
border-left: none !important;
|
||||||
color: ${({ theme }) => theme.font.color.tertiary};
|
color: ${({ theme }) => theme.font.color.tertiary};
|
||||||
border-right: none !important;
|
border-right: none !important;
|
||||||
width: 32px;
|
cursor: default;
|
||||||
|
|
||||||
${({ isTableWiderThanScreen, theme }) =>
|
${({ isTableWiderThanScreen, theme }) =>
|
||||||
isTableWiderThanScreen
|
isTableWiderThanScreen
|
||||||
? `
|
? `
|
||||||
background-color: ${theme.background.primary};
|
background-color: ${theme.background.primary};
|
||||||
|
width: 32px;
|
||||||
`
|
`
|
||||||
: ''};
|
: 'width: 100%'};
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledEmptyHeaderCell = styled.th`
|
|
||||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
|
||||||
background-color: ${({ theme }) => theme.background.primary};
|
|
||||||
width: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledPlusIconContainer = styled.div`
|
const StyledPlusIconContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 100%;
|
`;
|
||||||
|
|
||||||
|
const StyledDropdownContainer = styled.div`
|
||||||
|
&:hover {
|
||||||
|
background: ${({ theme }) => theme.background.transparent.light};
|
||||||
|
}
|
||||||
|
cursor: pointer;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID =
|
const HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID =
|
||||||
@ -59,9 +60,24 @@ export const RecordTableHeaderLastColumn = () => {
|
|||||||
(scrollWrapperHTMLElement?.clientWidth ?? 0) <
|
(scrollWrapperHTMLElement?.clientWidth ?? 0) <
|
||||||
(scrollWrapperHTMLElement?.scrollWidth ?? 0);
|
(scrollWrapperHTMLElement?.scrollWidth ?? 0);
|
||||||
|
|
||||||
|
const isFirstRowActive = useRecoilComponentFamilyValueV2(
|
||||||
|
isRecordTableRowActiveComponentFamilyState,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isFirstRowFocused = useRecoilComponentFamilyValueV2(
|
||||||
|
isRecordTableRowFocusedComponentFamilyState,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isFirstRowActiveOrFocused = isFirstRowActive || isFirstRowFocused;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<StyledPlusIconHeaderCell
|
||||||
<StyledPlusIconHeaderCell isTableWiderThanScreen={isTableWiderThanScreen}>
|
isTableWiderThanScreen={isTableWiderThanScreen}
|
||||||
|
isFirstRowActiveOrFocused={isFirstRowActiveOrFocused}
|
||||||
|
>
|
||||||
|
<StyledDropdownContainer>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
dropdownId={HIDDEN_TABLE_COLUMN_DROPDOWN_ID}
|
dropdownId={HIDDEN_TABLE_COLUMN_DROPDOWN_ID}
|
||||||
clickableComponent={
|
clickableComponent={
|
||||||
@ -75,8 +91,7 @@ export const RecordTableHeaderLastColumn = () => {
|
|||||||
scope: HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID,
|
scope: HIDDEN_TABLE_COLUMN_DROPDOWN_HOTKEY_SCOPE_ID,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</StyledPlusIconHeaderCell>
|
</StyledDropdownContainer>
|
||||||
<StyledEmptyHeaderCell />
|
</StyledPlusIconHeaderCell>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,8 +5,13 @@ import { RecordTableCellGrip } from '@/object-record/record-table/record-table-c
|
|||||||
import { RecordTableLastEmptyCell } from '@/object-record/record-table/record-table-cell/components/RecordTableLastEmptyCell';
|
import { RecordTableLastEmptyCell } from '@/object-record/record-table/record-table-cell/components/RecordTableLastEmptyCell';
|
||||||
import { RecordTableCells } from '@/object-record/record-table/record-table-row/components/RecordTableCells';
|
import { RecordTableCells } from '@/object-record/record-table/record-table-row/components/RecordTableCells';
|
||||||
import { RecordTableDraggableTr } from '@/object-record/record-table/record-table-row/components/RecordTableDraggableTr';
|
import { RecordTableDraggableTr } from '@/object-record/record-table/record-table-row/components/RecordTableDraggableTr';
|
||||||
|
import { RecordTableRowHotkeyEffect } from '@/object-record/record-table/record-table-row/components/RecordTableRowHotkeyEffect';
|
||||||
|
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
|
||||||
|
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||||
import { ListenRecordUpdatesEffect } from '@/subscription/components/ListenUpdatesEffect';
|
import { ListenRecordUpdatesEffect } from '@/subscription/components/ListenUpdatesEffect';
|
||||||
import { getDefaultRecordFieldsToListen } from '@/subscription/utils/getDefaultRecordFieldsToListen.util';
|
import { getDefaultRecordFieldsToListen } from '@/subscription/utils/getDefaultRecordFieldsToListen.util';
|
||||||
|
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
type RecordTableRowProps = {
|
type RecordTableRowProps = {
|
||||||
recordId: string;
|
recordId: string;
|
||||||
@ -23,6 +28,13 @@ export const RecordTableRow = ({
|
|||||||
const listenedFields = getDefaultRecordFieldsToListen({
|
const listenedFields = getDefaultRecordFieldsToListen({
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
});
|
});
|
||||||
|
const isFocused = useRecoilComponentFamilyValueV2(
|
||||||
|
isRecordTableRowFocusedComponentFamilyState,
|
||||||
|
rowIndexForFocus,
|
||||||
|
);
|
||||||
|
const isRowFocusActive = useRecoilComponentValueV2(
|
||||||
|
isRecordTableRowFocusActiveComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecordTableDraggableTr
|
<RecordTableDraggableTr
|
||||||
@ -30,6 +42,7 @@ export const RecordTableRow = ({
|
|||||||
draggableIndex={rowIndexForDrag}
|
draggableIndex={rowIndexForDrag}
|
||||||
focusIndex={rowIndexForFocus}
|
focusIndex={rowIndexForFocus}
|
||||||
>
|
>
|
||||||
|
{isRowFocusActive && isFocused && <RecordTableRowHotkeyEffect />}
|
||||||
<RecordTableCellGrip />
|
<RecordTableCellGrip />
|
||||||
<RecordTableCellCheckbox />
|
<RecordTableCellCheckbox />
|
||||||
<RecordTableCells />
|
<RecordTableCells />
|
||||||
|
|||||||
@ -0,0 +1,63 @@
|
|||||||
|
import { useOpenRecordInCommandMenu } from '@/command-menu/hooks/useOpenRecordInCommandMenu';
|
||||||
|
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||||
|
import { useSetRecordTableFocusPosition } from '@/object-record/record-table/hooks/internal/useSetRecordTableFocusPosition';
|
||||||
|
import { useActiveRecordTableRow } from '@/object-record/record-table/hooks/useActiveRecordTableRow';
|
||||||
|
import { useSetCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useSetCurrentRowSelected';
|
||||||
|
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
|
||||||
|
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||||
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
|
export const RecordTableRowHotkeyEffect = () => {
|
||||||
|
const { isSelected, recordId, objectNameSingular, rowIndex } =
|
||||||
|
useRecordTableRowContextOrThrow();
|
||||||
|
|
||||||
|
const { setCurrentRowSelected } = useSetCurrentRowSelected();
|
||||||
|
|
||||||
|
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
|
||||||
|
|
||||||
|
const { activateRecordTableRow } = useActiveRecordTableRow();
|
||||||
|
|
||||||
|
const setIsRowFocusActive = useSetRecoilComponentStateV2(
|
||||||
|
isRecordTableRowFocusActiveComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const setFocusPosition = useSetRecordTableFocusPosition();
|
||||||
|
|
||||||
|
useScopedHotkeys(
|
||||||
|
'x',
|
||||||
|
() => {
|
||||||
|
setCurrentRowSelected(!isSelected);
|
||||||
|
},
|
||||||
|
TableHotkeyScope.TableFocus,
|
||||||
|
);
|
||||||
|
|
||||||
|
useScopedHotkeys(
|
||||||
|
[`${Key.Control}+${Key.Enter}`, `${Key.Meta}+${Key.Enter}`],
|
||||||
|
() => {
|
||||||
|
openRecordInCommandMenu({
|
||||||
|
recordId: recordId,
|
||||||
|
objectNameSingular: objectNameSingular,
|
||||||
|
isNewRecord: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
activateRecordTableRow(rowIndex);
|
||||||
|
},
|
||||||
|
TableHotkeyScope.TableFocus,
|
||||||
|
);
|
||||||
|
|
||||||
|
useScopedHotkeys(
|
||||||
|
Key.Enter,
|
||||||
|
() => {
|
||||||
|
setIsRowFocusActive(false);
|
||||||
|
setFocusPosition({
|
||||||
|
row: rowIndex,
|
||||||
|
column: 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
TableHotkeyScope.TableFocus,
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
@ -3,17 +3,70 @@ import { useRecordTableContextOrThrow } from '@/object-record/record-table/conte
|
|||||||
import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
import { RecordTableRowContextProvider } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||||
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
|
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
|
||||||
import { isRowVisibleComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowVisibleComponentFamilyState';
|
import { isRowVisibleComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowVisibleComponentFamilyState';
|
||||||
|
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||||
|
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
|
||||||
|
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||||
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { ReactNode, forwardRef } from 'react';
|
import { ReactNode, forwardRef } from 'react';
|
||||||
|
|
||||||
const StyledTr = styled.tr<{ isDragging: boolean }>`
|
const StyledTr = styled.tr<{
|
||||||
position: relative;
|
isDragging: boolean;
|
||||||
|
}>`
|
||||||
border: ${({ isDragging, theme }) =>
|
border: ${({ isDragging, theme }) =>
|
||||||
isDragging
|
isDragging
|
||||||
? `1px solid ${theme.border.color.medium}`
|
? `1px solid ${theme.border.color.medium}`
|
||||||
: '1px solid transparent'};
|
: '1px solid transparent'};
|
||||||
|
position: relative;
|
||||||
transition: border-left-color 0.2s ease-in-out;
|
transition: border-left-color 0.2s ease-in-out;
|
||||||
|
|
||||||
|
&[data-next-row-active-or-focused='true'] {
|
||||||
|
td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-focused='true'] {
|
||||||
|
td {
|
||||||
|
&:not(:first-of-type) {
|
||||||
|
border-bottom: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||||
|
border-top: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||||
|
border-color: ${({ theme }) => theme.border.color.medium};
|
||||||
|
background-color: ${({ theme }) => theme.background.tertiary};
|
||||||
|
}
|
||||||
|
&:nth-of-type(2) {
|
||||||
|
border-left: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.sm} 0 0
|
||||||
|
${({ theme }) => theme.border.radius.sm};
|
||||||
|
}
|
||||||
|
&:last-of-type {
|
||||||
|
border-right: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||||
|
border-radius: 0 ${({ theme }) => theme.border.radius.sm}
|
||||||
|
${({ theme }) => theme.border.radius.sm} 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-active='true'] {
|
||||||
|
td {
|
||||||
|
&:not(:first-of-type) {
|
||||||
|
border-bottom: 1px solid ${({ theme }) => theme.adaptiveColors.blue3};
|
||||||
|
border-top: 1px solid ${({ theme }) => theme.adaptiveColors.blue3};
|
||||||
|
background-color: ${({ theme }) => theme.accent.quaternary};
|
||||||
|
}
|
||||||
|
&:nth-of-type(2) {
|
||||||
|
border-left: 1px solid ${({ theme }) => theme.adaptiveColors.blue3};
|
||||||
|
border-radius: ${({ theme }) => theme.border.radius.sm} 0 0
|
||||||
|
${({ theme }) => theme.border.radius.sm};
|
||||||
|
}
|
||||||
|
&:last-of-type {
|
||||||
|
border-right: 1px solid ${({ theme }) => theme.adaptiveColors.blue3};
|
||||||
|
border-radius: 0 ${({ theme }) => theme.border.radius.sm}
|
||||||
|
${({ theme }) => theme.border.radius.sm} 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type RecordTableTrProps = {
|
type RecordTableTrProps = {
|
||||||
@ -21,7 +74,10 @@ type RecordTableTrProps = {
|
|||||||
recordId: string;
|
recordId: string;
|
||||||
focusIndex: number;
|
focusIndex: number;
|
||||||
isDragging?: boolean;
|
isDragging?: boolean;
|
||||||
} & React.ComponentProps<typeof StyledTr>;
|
} & Omit<
|
||||||
|
React.ComponentProps<typeof StyledTr>,
|
||||||
|
'isActive' | 'isNextRowActiveOrFocused' | 'isFocused'
|
||||||
|
>;
|
||||||
|
|
||||||
export const RecordTableTr = forwardRef<
|
export const RecordTableTr = forwardRef<
|
||||||
HTMLTableRowElement,
|
HTMLTableRowElement,
|
||||||
@ -38,6 +94,33 @@ export const RecordTableTr = forwardRef<
|
|||||||
recordId,
|
recordId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isActive = useRecoilComponentFamilyValueV2(
|
||||||
|
isRecordTableRowActiveComponentFamilyState,
|
||||||
|
focusIndex,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isNextRowActive = useRecoilComponentFamilyValueV2(
|
||||||
|
isRecordTableRowActiveComponentFamilyState,
|
||||||
|
focusIndex + 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isFocused = useRecoilComponentFamilyValueV2(
|
||||||
|
isRecordTableRowFocusedComponentFamilyState,
|
||||||
|
focusIndex,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isRowFocusActive = useRecoilComponentValueV2(
|
||||||
|
isRecordTableRowFocusActiveComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isNextRowFocused = useRecoilComponentFamilyValueV2(
|
||||||
|
isRecordTableRowFocusedComponentFamilyState,
|
||||||
|
focusIndex + 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isNextRowActiveOrFocused =
|
||||||
|
(isRowFocusActive && isNextRowFocused) || isNextRowActive;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RecordTableRowContextProvider
|
<RecordTableRowContextProvider
|
||||||
value={{
|
value={{
|
||||||
@ -56,6 +139,13 @@ export const RecordTableTr = forwardRef<
|
|||||||
data-virtualized-id={recordId}
|
data-virtualized-id={recordId}
|
||||||
isDragging={isDragging}
|
isDragging={isDragging}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
data-active={isActive ? 'true' : 'false'}
|
||||||
|
data-focused={
|
||||||
|
isRowFocusActive && isFocused && !isActive ? 'true' : 'false'
|
||||||
|
}
|
||||||
|
data-next-row-active-or-focused={
|
||||||
|
isNextRowActiveOrFocused ? 'true' : 'false'
|
||||||
|
}
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||||
|
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||||
|
|
||||||
|
export const activeRecordTableRowIndexComponentState = createComponentStateV2<
|
||||||
|
number | null
|
||||||
|
>({
|
||||||
|
key: 'activeRecordTableRowIndexComponentState',
|
||||||
|
defaultValue: null,
|
||||||
|
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||||
|
});
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||||
|
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||||
|
|
||||||
|
export const focusedRecordTableRowIndexComponentState = createComponentStateV2<
|
||||||
|
number | null
|
||||||
|
>({
|
||||||
|
key: 'focusedRecordTableRowIndexComponentState',
|
||||||
|
defaultValue: null,
|
||||||
|
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||||
|
});
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||||
|
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||||
|
|
||||||
|
export const isRecordTableCellFocusActiveComponentState =
|
||||||
|
createComponentStateV2<boolean>({
|
||||||
|
key: 'isRecordTableCellFocusActiveComponentState',
|
||||||
|
defaultValue: false,
|
||||||
|
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||||
|
});
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||||
|
import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2';
|
||||||
|
|
||||||
|
export const isRecordTableRowActiveComponentFamilyState =
|
||||||
|
createComponentFamilyStateV2<boolean, number>({
|
||||||
|
key: 'isRecordTableRowActiveComponentFamilyState',
|
||||||
|
defaultValue: false,
|
||||||
|
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||||
|
});
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||||
|
|
||||||
export const isRecordTableFocusActiveComponentState =
|
export const isRecordTableRowFocusActiveComponentState =
|
||||||
createComponentStateV2<boolean>({
|
createComponentStateV2<boolean>({
|
||||||
key: 'isRecordTableFocusActiveComponentState',
|
key: 'isRecordTableRowFocusActiveComponentState',
|
||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||||
});
|
});
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||||
|
import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2';
|
||||||
|
|
||||||
|
export const isRecordTableRowFocusedComponentFamilyState =
|
||||||
|
createComponentFamilyStateV2<boolean, number>({
|
||||||
|
key: 'isRecordTableRowFocusedComponentFamilyState',
|
||||||
|
defaultValue: false,
|
||||||
|
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user