Fix column resize handle (#14559)
This PR fixes the column resize handle issues. Fixes in https://github.com/twentyhq/core-team-issues/issues/1490 : - We see the pointer blink when resizing => Already here before refactor, this is because the mouse enter / leave events shouldn't be triggered while resizing. - Resizing handle sometimes cannot be clicked anymore / does not appear anymore (see if another listener doesn't prevent this) The code will need to be cleaned after all fixes have been done though has it duplicates logic between the different header components.
This commit is contained in:
+8
@@ -5,7 +5,9 @@ import { HIDDEN_TABLE_COLUMN_DROPDOWN_ID } from '@/object-record/record-table/co
|
||||
import { RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH } from '@/object-record/record-table/constants/RecordTableColumnAddColumnButtonWidth';
|
||||
import { RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH_CLASS_NAME } from '@/object-record/record-table/constants/RecordTableColumnAddColumnButtonWidthClassName';
|
||||
import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableHeaderPlusButtonContent } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderPlusButtonContent';
|
||||
import { RecordTableHeaderResizeHandler } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderResizeHandler';
|
||||
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';
|
||||
@@ -87,6 +89,8 @@ export const RecordTableHeaderAddColumnButton = () => {
|
||||
const shouldDisplayBorderBottom =
|
||||
hasRecordGroups || !isFirstRowActiveOrFocused || isScrolledVertically;
|
||||
|
||||
const { visibleRecordFields } = useRecordTableContextOrThrow();
|
||||
|
||||
return (
|
||||
<StyledPlusIconHeaderCell
|
||||
shouldDisplayBorderBottom={shouldDisplayBorderBottom}
|
||||
@@ -95,6 +99,10 @@ export const RecordTableHeaderAddColumnButton = () => {
|
||||
RECORD_TABLE_COLUMN_ADD_COLUMN_BUTTON_WIDTH_CLASS_NAME,
|
||||
)}
|
||||
>
|
||||
<RecordTableHeaderResizeHandler
|
||||
recordFieldIndex={visibleRecordFields.length}
|
||||
position="left"
|
||||
/>
|
||||
<StyledDropdownContainer>
|
||||
<Dropdown
|
||||
dropdownId={HIDDEN_TABLE_COLUMN_DROPDOWN_ID}
|
||||
|
||||
+17
-1
@@ -9,10 +9,12 @@ import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/reco
|
||||
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
|
||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||
import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState';
|
||||
import { resizedFieldMetadataIdComponentState } from '@/object-record/record-table/states/resizedFieldMetadataIdComponentState';
|
||||
import { getRecordTableColumnFieldWidthClassName } from '@/object-record/record-table/utils/getRecordTableColumnFieldWidthClassName';
|
||||
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { cx } from '@linaria/core';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
type RecordTableHeaderCellProps = {
|
||||
recordField: RecordField;
|
||||
@@ -50,6 +52,12 @@ export const RecordTableHeaderCell = ({
|
||||
hasRecordGroupsComponentSelector,
|
||||
);
|
||||
|
||||
const resizedFieldMetadataItemId = useRecoilComponentValue(
|
||||
resizedFieldMetadataIdComponentState,
|
||||
);
|
||||
|
||||
const isResizingAnyColumn = isDefined(resizedFieldMetadataItemId);
|
||||
|
||||
const shouldDisplayBorderBottom =
|
||||
hasRecordGroups || !isFirstRowActiveOrFocused || isScrolledVertically;
|
||||
|
||||
@@ -61,12 +69,20 @@ export const RecordTableHeaderCell = ({
|
||||
)}
|
||||
key={recordField.fieldMetadataItemId}
|
||||
shouldDisplayBorderBottom={shouldDisplayBorderBottom}
|
||||
isResizing={isResizingAnyColumn}
|
||||
>
|
||||
<RecordTableHeaderResizeHandler
|
||||
recordFieldIndex={recordFieldIndex}
|
||||
position="left"
|
||||
/>
|
||||
<RecordTableColumnHeadWithDropdown
|
||||
recordField={recordField}
|
||||
objectMetadataId={objectMetadataItem.id}
|
||||
/>
|
||||
<RecordTableHeaderResizeHandler recordField={recordField} />
|
||||
<RecordTableHeaderResizeHandler
|
||||
recordFieldIndex={recordFieldIndex}
|
||||
position="right"
|
||||
/>
|
||||
</RecordTableHeaderCellContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+7
-2
@@ -4,6 +4,7 @@ import styled from '@emotion/styled';
|
||||
const StyledHeaderCell = styled.div<{
|
||||
zIndex?: number;
|
||||
shouldDisplayBorderBottom: boolean;
|
||||
isResizing: boolean;
|
||||
}>`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
padding: 0;
|
||||
@@ -23,7 +24,11 @@ const StyledHeaderCell = styled.div<{
|
||||
: 'none'};
|
||||
|
||||
user-select: none;
|
||||
${({ theme }) => {
|
||||
${({ theme, isResizing }) => {
|
||||
if (isResizing) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return `
|
||||
&:hover {
|
||||
background: ${theme.background.secondary};
|
||||
@@ -34,7 +39,7 @@ const StyledHeaderCell = styled.div<{
|
||||
`;
|
||||
}};
|
||||
|
||||
cursor: pointer;
|
||||
cursor: ${({ isResizing }) => (isResizing ? 'col-resize' : 'pointer')};
|
||||
|
||||
z-index: ${({ zIndex }) => zIndex ?? 'auto'};
|
||||
`;
|
||||
|
||||
+11
-2
@@ -13,11 +13,12 @@ import { isRecordTableRowFocusActiveComponentState } from '@/object-record/recor
|
||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||
import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState';
|
||||
import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState';
|
||||
import { resizedFieldMetadataIdComponentState } from '@/object-record/record-table/states/resizedFieldMetadataIdComponentState';
|
||||
import { getRecordTableColumnFieldWidthClassName } from '@/object-record/record-table/utils/getRecordTableColumnFieldWidthClassName';
|
||||
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { cx } from '@linaria/core';
|
||||
import { filterOutByProperty } from 'twenty-shared/utils';
|
||||
import { filterOutByProperty, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const RecordTableHeaderFirstScrollableCell = () => {
|
||||
const { objectMetadataItem, visibleRecordFields } =
|
||||
@@ -94,6 +95,12 @@ export const RecordTableHeaderFirstScrollableCell = () => {
|
||||
const shouldDisplayBorderBottom =
|
||||
hasRecordGroups || !isFirstRowActiveOrFocused || isScrolledVertically;
|
||||
|
||||
const resizedFieldMetadataItemId = useRecoilComponentValue(
|
||||
resizedFieldMetadataIdComponentState,
|
||||
);
|
||||
|
||||
const isResizingAnyColumn = isDefined(resizedFieldMetadataItemId);
|
||||
|
||||
if (!recordField) {
|
||||
return <></>;
|
||||
}
|
||||
@@ -104,12 +111,14 @@ export const RecordTableHeaderFirstScrollableCell = () => {
|
||||
key={recordField.fieldMetadataItemId}
|
||||
shouldDisplayBorderBottom={shouldDisplayBorderBottom}
|
||||
zIndex={zIndex}
|
||||
isResizing={isResizingAnyColumn}
|
||||
>
|
||||
<RecordTableHeaderResizeHandler recordFieldIndex={1} position="left" />
|
||||
<RecordTableColumnHeadWithDropdown
|
||||
recordField={recordField}
|
||||
objectMetadataId={objectMetadataItem.id}
|
||||
/>
|
||||
<RecordTableHeaderResizeHandler recordField={recordField} />
|
||||
<RecordTableHeaderResizeHandler recordFieldIndex={1} position="right" />
|
||||
</RecordTableHeaderCellContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+10
-2
@@ -13,12 +13,13 @@ import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/reco
|
||||
import { isRecordTableRowFocusActiveComponentState } from '@/object-record/record-table/states/isRecordTableRowFocusActiveComponentState';
|
||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||
import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState';
|
||||
import { resizedFieldMetadataIdComponentState } from '@/object-record/record-table/states/resizedFieldMetadataIdComponentState';
|
||||
import { getRecordTableColumnFieldWidthClassName } from '@/object-record/record-table/utils/getRecordTableColumnFieldWidthClassName';
|
||||
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { cx } from '@linaria/core';
|
||||
import { useState } from 'react';
|
||||
import { findByProperty } from 'twenty-shared/utils';
|
||||
import { findByProperty, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
const StyledColumnHeadContainer = styled.div`
|
||||
cursor: pointer;
|
||||
@@ -66,6 +67,12 @@ export const RecordTableHeaderLabelIdentifierCell = () => {
|
||||
hasRecordGroupsComponentSelector,
|
||||
);
|
||||
|
||||
const resizedFieldMetadataItemId = useRecoilComponentValue(
|
||||
resizedFieldMetadataIdComponentState,
|
||||
);
|
||||
|
||||
const isResizingAnyColumn = isDefined(resizedFieldMetadataItemId);
|
||||
|
||||
const shouldDisplayBorderBottom =
|
||||
hasRecordGroups || !isFirstRowActiveOrFocused || isScrolledVertically;
|
||||
|
||||
@@ -80,6 +87,7 @@ export const RecordTableHeaderLabelIdentifierCell = () => {
|
||||
onMouseEnter={() => setIconIsVisible(true)}
|
||||
onMouseLeave={() => setIconIsVisible(false)}
|
||||
shouldDisplayBorderBottom={shouldDisplayBorderBottom}
|
||||
isResizing={isResizingAnyColumn}
|
||||
>
|
||||
<StyledColumnHeadContainer>
|
||||
<RecordTableColumnHeadWithDropdown
|
||||
@@ -88,7 +96,7 @@ export const RecordTableHeaderLabelIdentifierCell = () => {
|
||||
/>
|
||||
{iconIsVisible && <RecordTableHeaderLabelIdentifierCellPlusButton />}
|
||||
</StyledColumnHeadContainer>
|
||||
<RecordTableHeaderResizeHandler recordField={recordField} />
|
||||
<RecordTableHeaderResizeHandler recordFieldIndex={0} position="right" />
|
||||
</RecordTableHeaderCellContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+24
-10
@@ -1,21 +1,24 @@
|
||||
import { type RecordField } from '@/object-record/record-field/types/RecordField';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { resizedFieldMetadataIdComponentState } from '@/object-record/record-table/states/resizedFieldMetadataIdComponentState';
|
||||
import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
|
||||
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
|
||||
import styled from '@emotion/styled';
|
||||
import { useIsMobile } from 'twenty-ui/utilities';
|
||||
|
||||
const StyledResizeHandler = styled.div<{ isResizing: boolean }>`
|
||||
const StyledResizeHandler = styled.div<{
|
||||
isResizing: boolean;
|
||||
position: 'left' | 'right';
|
||||
}>`
|
||||
bottom: 0;
|
||||
cursor: col-resize;
|
||||
padding: 0 ${({ theme }) => theme.spacing(2)};
|
||||
position: absolute;
|
||||
right: -9px;
|
||||
${({ position }) => (position === 'left' ? 'left: -1px;' : 'right: -1px;')}
|
||||
top: 0;
|
||||
width: 3px;
|
||||
width: 10px;
|
||||
z-index: 1;
|
||||
|
||||
${({ isResizing, theme }) => {
|
||||
${({ isResizing, theme, position }) => {
|
||||
if (isResizing === true) {
|
||||
return `&:after {
|
||||
background-color: ${theme.color.blue};
|
||||
@@ -23,7 +26,8 @@ const StyledResizeHandler = styled.div<{ isResizing: boolean }>`
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
${position === 'left' ? 'left: -1px;' : 'right: -1px;'}
|
||||
|
||||
top: 0;
|
||||
width: 2px;
|
||||
}`;
|
||||
@@ -32,10 +36,19 @@ const StyledResizeHandler = styled.div<{ isResizing: boolean }>`
|
||||
`;
|
||||
|
||||
export const RecordTableHeaderResizeHandler = ({
|
||||
recordField,
|
||||
recordFieldIndex,
|
||||
position,
|
||||
}: {
|
||||
recordField: RecordField;
|
||||
recordFieldIndex: number;
|
||||
position: 'left' | 'right';
|
||||
}) => {
|
||||
const { visibleRecordFields } = useRecordTableContextOrThrow();
|
||||
|
||||
const recordField: RecordField | undefined =
|
||||
position === 'left'
|
||||
? visibleRecordFields[recordFieldIndex - 1]
|
||||
: visibleRecordFields[recordFieldIndex];
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const columnResizeDisabled = isMobile;
|
||||
@@ -44,13 +57,13 @@ export const RecordTableHeaderResizeHandler = ({
|
||||
useRecoilComponentState(resizedFieldMetadataIdComponentState);
|
||||
|
||||
const isResizing =
|
||||
recordField.fieldMetadataItemId === resizedFieldMetadataItemId;
|
||||
recordField?.fieldMetadataItemId === resizedFieldMetadataItemId;
|
||||
|
||||
const { setDragSelectionStartEnabled } = useDragSelect();
|
||||
|
||||
const handlePointerDown = () => {
|
||||
setDragSelectionStartEnabled(false);
|
||||
setResizedFieldMetadataItemId(recordField.fieldMetadataItemId);
|
||||
setResizedFieldMetadataItemId(recordField?.fieldMetadataItemId);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -60,6 +73,7 @@ export const RecordTableHeaderResizeHandler = ({
|
||||
role="separator"
|
||||
onPointerDown={handlePointerDown}
|
||||
isResizing={isResizing}
|
||||
position={position}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user