4162-Sticky-Header (#4627)
* initial commit * functionality added * Suggested changes fixed * Fix broken shadow * Unrelated fix (input stuck under container) * Performance improvement --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
@ -12,8 +12,12 @@ import { RecordTableScope } from '@/object-record/record-table/scopes/RecordTabl
|
||||
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/MobileViewport';
|
||||
import { RGBA } from '@/ui/theme/constants/Rgba';
|
||||
import { scrollLeftState } from '@/ui/utilities/scroll/states/scrollLeftState';
|
||||
import { scrollTopState } from '@/ui/utilities/scroll/states/scrollTopState';
|
||||
|
||||
const StyledTable = styled.table<{ freezeFirstColumns?: boolean }>`
|
||||
const StyledTable = styled.table<{
|
||||
freezeFirstColumns?: boolean;
|
||||
freezeHeaders?: boolean;
|
||||
}>`
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border-spacing: 0;
|
||||
margin-right: ${({ theme }) => theme.table.horizontalCellMargin};
|
||||
@ -65,12 +69,24 @@ const StyledTable = styled.table<{ freezeFirstColumns?: boolean }>`
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
thead th:nth-of-type(1),
|
||||
tbody td:nth-of-type(1) {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
// Label identifier column
|
||||
thead th:nth-of-type(1),
|
||||
thead th:nth-of-type(2) {
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
thead th:nth-child(n + 3) {
|
||||
top: 0;
|
||||
z-index: 5;
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
thead th:nth-of-type(2),
|
||||
tbody td:nth-of-type(2) {
|
||||
left: calc(${({ theme }) => theme.table.checkboxColumnWidth} - 2px);
|
||||
@ -88,9 +104,9 @@ const StyledTable = styled.table<{ freezeFirstColumns?: boolean }>`
|
||||
content: '';
|
||||
height: calc(100% + 1px);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 4px;
|
||||
right: -4px;
|
||||
top: 0;
|
||||
|
||||
${({ freezeFirstColumns, theme }) =>
|
||||
freezeFirstColumns &&
|
||||
@ -123,6 +139,7 @@ export const RecordTable = ({
|
||||
}: RecordTableProps) => {
|
||||
const { scopeId } = useRecordTableStates(recordTableId);
|
||||
const scrollLeft = useRecoilValue(scrollLeftState);
|
||||
const scrollTop = useRecoilValue(scrollTopState);
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItemOnly({
|
||||
objectNameSingular,
|
||||
@ -141,6 +158,7 @@ export const RecordTable = ({
|
||||
>
|
||||
<StyledTable
|
||||
freezeFirstColumns={scrollLeft > 0}
|
||||
freezeHeaders={scrollTop > 0}
|
||||
className="entity-table-cell"
|
||||
>
|
||||
<RecordTableHeader createRecord={createRecord} />
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import { useContext } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { isLabelIdentifierField } from '@/object-metadata/utils/isLabelIdentifierField';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
@ -10,39 +8,14 @@ import { RecordTableCellContext } from '@/object-record/record-table/contexts/Re
|
||||
import { RecordTableContext } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
import { RecordTableCell } from '@/object-record/record-table/record-table-cell/components/RecordTableCell';
|
||||
import { useSetCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useSetCurrentRowSelected';
|
||||
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||
import { RelationPickerHotkeyScope } from '@/object-record/relation-picker/types/RelationPickerHotkeyScope';
|
||||
import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState';
|
||||
import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
const StyledContainer = styled.td<{ isSelected: boolean }>`
|
||||
background: ${({ isSelected, theme }) =>
|
||||
isSelected ? theme.accent.quaternary : theme.background.primary};
|
||||
`;
|
||||
|
||||
export const RecordTableCellContainer = () => {
|
||||
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
|
||||
const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState);
|
||||
|
||||
const { setCurrentRowSelected } = useSetCurrentRowSelected();
|
||||
|
||||
const handleContextMenu = (event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
setCurrentRowSelected(true);
|
||||
setContextMenuPosition({
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
});
|
||||
setContextMenuOpenState(true);
|
||||
};
|
||||
|
||||
const { objectMetadataItem } = useContext(RecordTableContext);
|
||||
const { columnDefinition } = useContext(RecordTableCellContext);
|
||||
const { recordId, pathToShowPage, isSelected } = useContext(
|
||||
RecordTableRowContext,
|
||||
);
|
||||
const { recordId, pathToShowPage } = useContext(RecordTableRowContext);
|
||||
|
||||
const updateRecord = useContext(RecordUpdateContext);
|
||||
|
||||
@ -55,29 +28,24 @@ export const RecordTableCellContainer = () => {
|
||||
: TableHotkeyScope.CellEditMode;
|
||||
|
||||
return (
|
||||
<StyledContainer
|
||||
isSelected={isSelected}
|
||||
onContextMenu={(event) => handleContextMenu(event)}
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
recoilScopeId: recordId + columnDefinition.label,
|
||||
entityId: recordId,
|
||||
fieldDefinition: columnDefinition,
|
||||
useUpdateRecord: () => [updateRecord, {}],
|
||||
hotkeyScope: customHotkeyScope,
|
||||
basePathToShowPage: pathToShowPage,
|
||||
isLabelIdentifier: isLabelIdentifierField({
|
||||
fieldMetadataItem: {
|
||||
id: columnDefinition.fieldMetadataId,
|
||||
name: columnDefinition.metadata.fieldName,
|
||||
},
|
||||
objectMetadataItem,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
recoilScopeId: recordId + columnDefinition.label,
|
||||
entityId: recordId,
|
||||
fieldDefinition: columnDefinition,
|
||||
useUpdateRecord: () => [updateRecord, {}],
|
||||
hotkeyScope: customHotkeyScope,
|
||||
basePathToShowPage: pathToShowPage,
|
||||
isLabelIdentifier: isLabelIdentifierField({
|
||||
fieldMetadataItem: {
|
||||
id: columnDefinition.fieldMetadataId,
|
||||
name: columnDefinition.metadata.fieldName,
|
||||
},
|
||||
objectMetadataItem,
|
||||
}),
|
||||
}}
|
||||
>
|
||||
<RecordTableCell customHotkeyScope={{ scope: customHotkeyScope }} />
|
||||
</FieldContext.Provider>
|
||||
</StyledContainer>
|
||||
<RecordTableCell customHotkeyScope={{ scope: customHotkeyScope }} />
|
||||
</FieldContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
@ -28,8 +28,7 @@ const StyledPlusIconHeaderCell = styled.th<{ isTableWiderThanScreen: boolean }>`
|
||||
min-width: 32px;
|
||||
${({ isTableWiderThanScreen, theme }) =>
|
||||
isTableWiderThanScreen &&
|
||||
`position: relative;
|
||||
right: 0;
|
||||
`
|
||||
width: 32px;
|
||||
border-right: none !important;
|
||||
background-color: ${theme.background.primary};
|
||||
|
||||
@ -1,16 +1,21 @@
|
||||
import { ReactElement, useContext, useState } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useGetButtonIcon } from '@/object-record/record-field/hooks/useGetButtonIcon';
|
||||
import { useIsFieldEmpty } from '@/object-record/record-field/hooks/useIsFieldEmpty';
|
||||
import { useIsFieldInputOnly } from '@/object-record/record-field/hooks/useIsFieldInputOnly';
|
||||
import { RecordTableCellContext } from '@/object-record/record-table/contexts/RecordTableCellContext';
|
||||
import { RecordTableRowContext } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
import { useGetIsSomeCellInEditModeState } from '@/object-record/record-table/hooks/internal/useGetIsSomeCellInEditMode';
|
||||
import { useOpenRecordTableCell } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCell';
|
||||
import { useSetCurrentRowSelected } from '@/object-record/record-table/record-table-row/hooks/useSetCurrentRowSelected';
|
||||
import { isSoftFocusUsingMouseState } from '@/object-record/record-table/states/isSoftFocusUsingMouseState';
|
||||
import { IconArrowUpRight } from '@/ui/display/icon';
|
||||
import { contextMenuIsOpenState } from '@/ui/navigation/context-menu/states/contextMenuIsOpenState';
|
||||
import { contextMenuPositionState } from '@/ui/navigation/context-menu/states/contextMenuPositionState';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||
|
||||
import { CellHotkeyScopeContext } from '../../contexts/CellHotkeyScopeContext';
|
||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||
@ -24,6 +29,12 @@ import { RecordTableCellDisplayMode } from './RecordTableCellDisplayMode';
|
||||
import { RecordTableCellEditMode } from './RecordTableCellEditMode';
|
||||
import { RecordTableCellSoftFocusMode } from './RecordTableCellSoftFocusMode';
|
||||
|
||||
const StyledTd = styled.td<{ isSelected: boolean; isInEditMode: boolean }>`
|
||||
background: ${({ isSelected, theme }) =>
|
||||
isSelected ? theme.accent.quaternary : theme.background.primary};
|
||||
z-index: ${({ isInEditMode }) => (isInEditMode ? '4 !important' : '3')};
|
||||
`;
|
||||
|
||||
const StyledCellBaseContainer = styled.div`
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
@ -61,7 +72,6 @@ export const RecordTableCellContainer = ({
|
||||
|
||||
const { isCurrentTableCellInEditMode } = useCurrentTableCellEditMode();
|
||||
const isSomeCellInEditModeState = useGetIsSomeCellInEditModeState();
|
||||
const isSomeCellInEditMode = useRecoilValue(isSomeCellInEditModeState());
|
||||
|
||||
const setIsSoftFocusUsingMouseState = useSetRecoilState(
|
||||
isSoftFocusUsingMouseState,
|
||||
@ -80,14 +90,44 @@ export const RecordTableCellContainer = ({
|
||||
openTableCell();
|
||||
};
|
||||
|
||||
const handleContainerMouseEnter = () => {
|
||||
if (!isHovered && !isSomeCellInEditMode) {
|
||||
setIsHovered(true);
|
||||
moveSoftFocusToCurrentCellOnHover();
|
||||
setIsSoftFocusUsingMouseState(true);
|
||||
}
|
||||
const { isSelected } = useContext(RecordTableRowContext);
|
||||
|
||||
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
|
||||
const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState);
|
||||
|
||||
const { setCurrentRowSelected } = useSetCurrentRowSelected();
|
||||
|
||||
const handleContextMenu = (event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
setCurrentRowSelected(true);
|
||||
setContextMenuPosition({
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
});
|
||||
setContextMenuOpenState(true);
|
||||
};
|
||||
|
||||
const handleContainerMouseEnter = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
() => {
|
||||
const isSomeCellInEditMode = getSnapshotValue(
|
||||
snapshot,
|
||||
isSomeCellInEditModeState(),
|
||||
);
|
||||
if (!isHovered && !isSomeCellInEditMode) {
|
||||
setIsHovered(true);
|
||||
moveSoftFocusToCurrentCellOnHover();
|
||||
setIsSoftFocusUsingMouseState(true);
|
||||
}
|
||||
},
|
||||
[
|
||||
isHovered,
|
||||
isSomeCellInEditModeState,
|
||||
moveSoftFocusToCurrentCellOnHover,
|
||||
setIsSoftFocusUsingMouseState,
|
||||
],
|
||||
);
|
||||
|
||||
const handleContainerMouseLeave = () => {
|
||||
setIsHovered(false);
|
||||
};
|
||||
@ -109,46 +149,52 @@ export const RecordTableCellContainer = ({
|
||||
(!isFirstColumn || !isEmpty);
|
||||
|
||||
return (
|
||||
<CellHotkeyScopeContext.Provider
|
||||
value={editHotkeyScope ?? DEFAULT_CELL_SCOPE}
|
||||
<StyledTd
|
||||
isSelected={isSelected}
|
||||
onContextMenu={(event) => handleContextMenu(event)}
|
||||
isInEditMode={isCurrentTableCellInEditMode}
|
||||
>
|
||||
<StyledCellBaseContainer
|
||||
onMouseEnter={handleContainerMouseEnter}
|
||||
onMouseLeave={handleContainerMouseLeave}
|
||||
<CellHotkeyScopeContext.Provider
|
||||
value={editHotkeyScope ?? DEFAULT_CELL_SCOPE}
|
||||
>
|
||||
{isCurrentTableCellInEditMode ? (
|
||||
<RecordTableCellEditMode
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeVerticalPosition={editModeVerticalPosition}
|
||||
>
|
||||
{editModeContent}
|
||||
</RecordTableCellEditMode>
|
||||
) : hasSoftFocus ? (
|
||||
<>
|
||||
{showButton && (
|
||||
<RecordTableCellButton
|
||||
onClick={handleButtonClick}
|
||||
Icon={buttonIcon}
|
||||
/>
|
||||
)}
|
||||
<RecordTableCellSoftFocusMode>
|
||||
{editModeContentOnly ? editModeContent : nonEditModeContent}
|
||||
</RecordTableCellSoftFocusMode>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{showButton && (
|
||||
<RecordTableCellButton
|
||||
onClick={handleButtonClick}
|
||||
Icon={buttonIcon}
|
||||
/>
|
||||
)}
|
||||
<RecordTableCellDisplayMode>
|
||||
{editModeContentOnly ? editModeContent : nonEditModeContent}
|
||||
</RecordTableCellDisplayMode>
|
||||
</>
|
||||
)}
|
||||
</StyledCellBaseContainer>
|
||||
</CellHotkeyScopeContext.Provider>
|
||||
<StyledCellBaseContainer
|
||||
onMouseEnter={handleContainerMouseEnter}
|
||||
onMouseLeave={handleContainerMouseLeave}
|
||||
>
|
||||
{isCurrentTableCellInEditMode ? (
|
||||
<RecordTableCellEditMode
|
||||
editModeHorizontalAlign={editModeHorizontalAlign}
|
||||
editModeVerticalPosition={editModeVerticalPosition}
|
||||
>
|
||||
{editModeContent}
|
||||
</RecordTableCellEditMode>
|
||||
) : hasSoftFocus ? (
|
||||
<>
|
||||
{showButton && (
|
||||
<RecordTableCellButton
|
||||
onClick={handleButtonClick}
|
||||
Icon={buttonIcon}
|
||||
/>
|
||||
)}
|
||||
<RecordTableCellSoftFocusMode>
|
||||
{editModeContentOnly ? editModeContent : nonEditModeContent}
|
||||
</RecordTableCellSoftFocusMode>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{showButton && (
|
||||
<RecordTableCellButton
|
||||
onClick={handleButtonClick}
|
||||
Icon={buttonIcon}
|
||||
/>
|
||||
)}
|
||||
<RecordTableCellDisplayMode>
|
||||
{editModeContentOnly ? editModeContent : nonEditModeContent}
|
||||
</RecordTableCellDisplayMode>
|
||||
</>
|
||||
)}
|
||||
</StyledCellBaseContainer>
|
||||
</CellHotkeyScopeContext.Provider>
|
||||
</StyledTd>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user