Fixed table header resize bugs (#14401)
This PR fixes the main resize bugs in https://github.com/twentyhq/core-team-issues/issues/1453 Changes : - A new `RecordTableResizeEffect` has been created to modify the last column width to compensate for the resize width dynamically, because of flew-wrap on the table elements, we cannot use width: 100% everywhere and we have to set the width of everything. - CSS transitions have been removed because they were degrading both the performance and the UX when resizing - Created a common `RecordTableHeaderCellContainer`, this was needed to factorize the various table header components - Created a `RecordTableHeaderLabelIdentifierCellPlusButton` component to ease the reading of `RecordTableHeaderLabelIdentifierCell` - Put the CSS of the blue line while resizing in the `RecordTableHeaderResizeHandler` component to avoid duplicating it everywhere. - Extracted `COLUMN_MIN_WIDTH` in a constant file Fixes https://github.com/twentyhq/core-team-issues/issues/1453
This commit is contained in:
+1
-1
@@ -24,7 +24,7 @@ export const RecordTable = () => {
|
||||
objectMetadataItem.id,
|
||||
);
|
||||
|
||||
const tableBodyRef = useRef<HTMLTableElement>(null);
|
||||
const tableBodyRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { toggleClickOutside } = useClickOutsideListener(
|
||||
RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID,
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ import { RecordTableRecordGroupBodyEffects } from '@/object-record/record-table/
|
||||
|
||||
export interface RecordTableBodyEffectsWrapperProps {
|
||||
hasRecordGroups: boolean;
|
||||
tableBodyRef: React.RefObject<HTMLTableElement>;
|
||||
tableBodyRef: React.RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export const RecordTableBodyEffectsWrapper = ({
|
||||
|
||||
+4
-4
@@ -1,3 +1,4 @@
|
||||
import { RecordTableResizeEffect } from '@/object-record/record-table/components/RecordTableResizeEffect';
|
||||
import { RecordTableScrollAndZIndexEffect } from '@/object-record/record-table/components/RecordTableScrollAndZIndexEffect';
|
||||
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
|
||||
import { RecordTableNoRecordGroupBody } from '@/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBody';
|
||||
@@ -23,6 +24,7 @@ const StyledTable = styled.div<{
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
|
||||
div.header-cell {
|
||||
position: sticky;
|
||||
@@ -36,7 +38,6 @@ const StyledTable = styled.div<{
|
||||
div.header-cell:nth-of-type(1) {
|
||||
left: 0px;
|
||||
|
||||
transition: 0.3s ease;
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
|
||||
z-index: ${TABLE_Z_INDEX.headerColumnsSticky};
|
||||
@@ -46,7 +47,6 @@ const StyledTable = styled.div<{
|
||||
left: 16px;
|
||||
top: 0;
|
||||
|
||||
transition: 0.3s ease;
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
|
||||
z-index: ${TABLE_Z_INDEX.headerColumnsSticky};
|
||||
@@ -56,7 +56,6 @@ const StyledTable = styled.div<{
|
||||
left: 48px;
|
||||
right: 0;
|
||||
|
||||
transition: 0.3s ease;
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
|
||||
z-index: ${TABLE_Z_INDEX.headerColumnsSticky};
|
||||
@@ -143,7 +142,7 @@ const StyledTableContainer = styled.div`
|
||||
`;
|
||||
|
||||
export interface RecordTableContentProps {
|
||||
tableBodyRef: React.RefObject<HTMLTableElement>;
|
||||
tableBodyRef: React.RefObject<HTMLDivElement>;
|
||||
handleDragSelectionStart: () => void;
|
||||
handleDragSelectionEnd: () => void;
|
||||
hasRecordGroups: boolean;
|
||||
@@ -197,6 +196,7 @@ export const RecordTableContent = ({
|
||||
<RecordTableNoRecordGroupBody />
|
||||
)}
|
||||
<RecordTableScrollAndZIndexEffect />
|
||||
<RecordTableResizeEffect />
|
||||
</StyledTable>
|
||||
<DragSelect
|
||||
selectableItemsContainerRef={containerRef}
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ const StyledEmptyStateContainer = styled.div`
|
||||
`;
|
||||
|
||||
export interface RecordTableEmptyProps {
|
||||
tableBodyRef: React.RefObject<HTMLTableElement>;
|
||||
tableBodyRef: React.RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export const RecordTableEmpty = ({ tableBodyRef }: RecordTableEmptyProps) => (
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import { recordTableWidthComponentState } from '@/object-record/record-table/states/recordTableWidthComponentState';
|
||||
import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const RecordTableResizeEffect = () => {
|
||||
const setRecordTableWidth = useSetRecoilComponentState(
|
||||
recordTableWidthComponentState,
|
||||
);
|
||||
|
||||
const { scrollWrapperHTMLElement } = useScrollWrapperElement();
|
||||
|
||||
useEffect(() => {
|
||||
const tableWidth = scrollWrapperHTMLElement?.clientWidth ?? 0;
|
||||
|
||||
setRecordTableWidth(tableWidth);
|
||||
|
||||
const tableResizeObserver = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
if (entry.target === scrollWrapperHTMLElement) {
|
||||
const newWidth = scrollWrapperHTMLElement.clientWidth;
|
||||
|
||||
setRecordTableWidth(newWidth);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (isDefined(scrollWrapperHTMLElement)) {
|
||||
tableResizeObserver.observe(scrollWrapperHTMLElement);
|
||||
}
|
||||
|
||||
return () => {
|
||||
tableResizeObserver.disconnect();
|
||||
};
|
||||
}, [setRecordTableWidth, scrollWrapperHTMLElement]);
|
||||
|
||||
return null;
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
export const COLUMN_MIN_WIDTH = 48;
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { recordTableWidthComponentState } from '@/object-record/record-table/states/recordTableWidthComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { sumByProperty } from 'twenty-shared/utils';
|
||||
|
||||
// TODO: use this everywhere and extract in files
|
||||
export const RECORD_TABLE_DRAG_DROP_COLUMN_WIDTH = 16;
|
||||
export const RECORD_TABLE_CHECKBOX_COLUMN_WIDTH = 32;
|
||||
export const RECORD_TABLE_PLUS_BUTTON_COLUMN_WIDTH = 32;
|
||||
|
||||
export const useRecordTableLastColumnWidthToFill = () => {
|
||||
const { visibleRecordFields } = useRecordTableContextOrThrow();
|
||||
|
||||
const recordTableWidth = useRecoilComponentValue(
|
||||
recordTableWidthComponentState,
|
||||
);
|
||||
|
||||
const totalColumnsWidth = visibleRecordFields.reduce(
|
||||
sumByProperty('size'),
|
||||
0,
|
||||
);
|
||||
|
||||
const widthOfBorders = visibleRecordFields.length;
|
||||
|
||||
const fixedColumnsWidth =
|
||||
RECORD_TABLE_DRAG_DROP_COLUMN_WIDTH +
|
||||
RECORD_TABLE_CHECKBOX_COLUMN_WIDTH +
|
||||
RECORD_TABLE_PLUS_BUTTON_COLUMN_WIDTH +
|
||||
widthOfBorders;
|
||||
|
||||
const remainingWidthToFill = Math.max(
|
||||
0,
|
||||
recordTableWidth - fixedColumnsWidth - totalColumnsWidth,
|
||||
);
|
||||
|
||||
const width = remainingWidthToFill > 0 ? remainingWidthToFill : 0;
|
||||
|
||||
return {
|
||||
lastColumnWidth: width,
|
||||
};
|
||||
};
|
||||
+13
-13
@@ -20,7 +20,6 @@ export const RecordTableBodyDroppable = ({
|
||||
isDropDisabled,
|
||||
}: RecordTableBodyDroppableProps) => {
|
||||
const [v4Persistable] = useState(v4());
|
||||
const recordTableBodyId = `record-table-body${recordGroupId ? '-' + recordGroupId : ''}`;
|
||||
|
||||
const setRecordTableHoverPosition = useSetRecoilComponentState(
|
||||
recordTableHoverPositionComponentState,
|
||||
@@ -42,19 +41,20 @@ export const RecordTableBodyDroppable = ({
|
||||
isDropDisabled={isDropDisabled}
|
||||
>
|
||||
{(provided) => (
|
||||
<RecordTableBody
|
||||
id={recordTableBodyId}
|
||||
ref={provided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...provided.droppableProps}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<RecordTableBodyDroppableContextProvider
|
||||
value={{ droppablePlaceholder: provided.placeholder }}
|
||||
<>
|
||||
<RecordTableBody
|
||||
ref={provided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...provided.droppableProps}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{children}
|
||||
</RecordTableBodyDroppableContextProvider>
|
||||
</RecordTableBody>
|
||||
<RecordTableBodyDroppableContextProvider
|
||||
value={{ droppablePlaceholder: provided.placeholder }}
|
||||
>
|
||||
{children}
|
||||
</RecordTableBodyDroppableContextProvider>
|
||||
</RecordTableBody>
|
||||
</>
|
||||
)}
|
||||
</Droppable>
|
||||
);
|
||||
|
||||
+20
-1
@@ -1,8 +1,27 @@
|
||||
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
import { useRecordTableLastColumnWidthToFill } from '@/object-record/record-table/hooks/useRecordTableLastColumnWidthToFill';
|
||||
import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
|
||||
import { resizeFieldOffsetComponentState } from '@/object-record/record-table/states/resizeFieldOffsetComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
|
||||
export const RecordTableLastEmptyCell = () => {
|
||||
const { isSelected } = useRecordTableRowContextOrThrow();
|
||||
const { lastColumnWidth } = useRecordTableLastColumnWidthToFill();
|
||||
|
||||
return <RecordTableTd isSelected={isSelected} hasRightBorder={false} />;
|
||||
const resizeFieldOffset = useRecoilComponentValue(
|
||||
resizeFieldOffsetComponentState,
|
||||
);
|
||||
|
||||
const width =
|
||||
resizeFieldOffset > 0
|
||||
? lastColumnWidth + resizeFieldOffset
|
||||
: lastColumnWidth;
|
||||
|
||||
return (
|
||||
<RecordTableTd
|
||||
isSelected={isSelected}
|
||||
hasRightBorder={false}
|
||||
width={width}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
import { RECORD_TABLE_PLUS_BUTTON_COLUMN_WIDTH } from '@/object-record/record-table/hooks/useRecordTableLastColumnWidthToFill';
|
||||
import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
|
||||
|
||||
export const RecordTablePlusButtonCellPlaceholder = () => {
|
||||
const { isSelected } = useRecordTableRowContextOrThrow();
|
||||
|
||||
return (
|
||||
<RecordTableTd
|
||||
isSelected={isSelected}
|
||||
hasRightBorder={false}
|
||||
width={RECORD_TABLE_PLUS_BUTTON_COLUMN_WIDTH}
|
||||
/>
|
||||
);
|
||||
};
|
||||
-1
@@ -21,7 +21,6 @@ const StyledColumnFooterCell = styled.div<{
|
||||
width: ${columnWidth}px;
|
||||
`}
|
||||
text-align: left;
|
||||
transition: 0.3s ease;
|
||||
${({ theme }) => {
|
||||
return `
|
||||
&:hover {
|
||||
|
||||
-9
@@ -5,13 +5,11 @@ import { RecordTableHeaderPlusButtonContent } from '@/object-record/record-table
|
||||
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 { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement';
|
||||
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
|
||||
const StyledPlusIconHeaderCell = styled.div<{
|
||||
isTableWiderThanScreen: boolean;
|
||||
isFirstRowActiveOrFocused: boolean;
|
||||
}>`
|
||||
border-bottom: ${({ isFirstRowActiveOrFocused, theme }) =>
|
||||
@@ -53,12 +51,6 @@ const StyledDropdownContainer = styled.div`
|
||||
export const RecordTableHeaderAddColumnButton = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
const { scrollWrapperHTMLElement } = useScrollWrapperElement();
|
||||
|
||||
const isTableWiderThanScreen =
|
||||
(scrollWrapperHTMLElement?.clientWidth ?? 0) <
|
||||
(scrollWrapperHTMLElement?.scrollWidth ?? 0);
|
||||
|
||||
const isFirstRowActive = useRecoilComponentFamilyValue(
|
||||
isRecordTableRowActiveComponentFamilyState,
|
||||
0,
|
||||
@@ -73,7 +65,6 @@ export const RecordTableHeaderAddColumnButton = () => {
|
||||
|
||||
return (
|
||||
<StyledPlusIconHeaderCell
|
||||
isTableWiderThanScreen={isTableWiderThanScreen}
|
||||
isFirstRowActiveOrFocused={isFirstRowActiveOrFocused}
|
||||
className="header-cell"
|
||||
>
|
||||
|
||||
+8
-75
@@ -1,8 +1,8 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { type RecordField } from '@/object-record/record-field/types/RecordField';
|
||||
import { COLUMN_MIN_WIDTH } from '@/object-record/record-table/constants/ColumnMinWidth';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableColumnHeadWithDropdown } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown';
|
||||
import { RecordTableHeaderCellContainer } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderCellContainer';
|
||||
import { RecordTableHeaderResizeHandler } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderResizeHandler';
|
||||
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||
@@ -11,71 +11,6 @@ import { resizeFieldOffsetComponentState } from '@/object-record/record-table/st
|
||||
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
|
||||
const COLUMN_MIN_WIDTH = 48;
|
||||
|
||||
const StyledColumnHeaderCell = styled.div<{
|
||||
columnWidth: number;
|
||||
isResizing?: boolean;
|
||||
isFirstRowActiveOrFocused: boolean;
|
||||
}>`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
|
||||
height: 32px;
|
||||
max-height: 32px;
|
||||
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
border-right: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
|
||||
${({ columnWidth }) => `
|
||||
min-width: ${columnWidth}px;
|
||||
width: ${columnWidth}px;
|
||||
`}
|
||||
user-select: none;
|
||||
${({ theme }) => {
|
||||
return `
|
||||
&:hover {
|
||||
background: ${theme.background.secondary};
|
||||
};
|
||||
&:active {
|
||||
background: ${theme.background.tertiary};
|
||||
};
|
||||
`;
|
||||
}};
|
||||
${({ isResizing, theme }) => {
|
||||
if (isResizing === true) {
|
||||
return `&:after {
|
||||
background-color: ${theme.color.blue};
|
||||
bottom: 0;
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: -1px;
|
||||
top: 0;
|
||||
width: 2px;
|
||||
}`;
|
||||
}
|
||||
}};
|
||||
|
||||
// TODO: refactor this, each component should own its CSS
|
||||
div {
|
||||
overflow: hidden;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledColumnHeadContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
& > :first-of-type {
|
||||
flex: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
type RecordTableHeaderCellProps = {
|
||||
recordField: RecordField;
|
||||
};
|
||||
@@ -117,7 +52,7 @@ export const RecordTableHeaderCell = ({
|
||||
const isFirstRowActiveOrFocused = isFirstRowActive || isFirstRowFocused;
|
||||
|
||||
return (
|
||||
<StyledColumnHeaderCell
|
||||
<RecordTableHeaderCellContainer
|
||||
className="header-cell"
|
||||
key={recordField.fieldMetadataItemId}
|
||||
isResizing={
|
||||
@@ -126,13 +61,11 @@ export const RecordTableHeaderCell = ({
|
||||
columnWidth={columnWidth}
|
||||
isFirstRowActiveOrFocused={isFirstRowActiveOrFocused}
|
||||
>
|
||||
<StyledColumnHeadContainer>
|
||||
<RecordTableColumnHeadWithDropdown
|
||||
recordField={recordField}
|
||||
objectMetadataId={objectMetadataItem.id}
|
||||
/>
|
||||
</StyledColumnHeadContainer>
|
||||
<RecordTableColumnHeadWithDropdown
|
||||
recordField={recordField}
|
||||
objectMetadataId={objectMetadataItem.id}
|
||||
/>
|
||||
<RecordTableHeaderResizeHandler recordField={recordField} />
|
||||
</StyledColumnHeaderCell>
|
||||
</RecordTableHeaderCellContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledHeaderCell = styled.div<{
|
||||
columnWidth: number;
|
||||
isResizing?: boolean;
|
||||
isFirstRowActiveOrFocused: boolean;
|
||||
zIndex?: number;
|
||||
}>`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
|
||||
height: 32px;
|
||||
max-height: 32px;
|
||||
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
border-right: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
|
||||
${({ columnWidth }) => `
|
||||
min-width: ${columnWidth}px;
|
||||
width: ${columnWidth}px;
|
||||
`}
|
||||
user-select: none;
|
||||
${({ theme }) => {
|
||||
return `
|
||||
&:hover {
|
||||
background: ${theme.background.secondary};
|
||||
};
|
||||
&:active {
|
||||
background: ${theme.background.tertiary};
|
||||
};
|
||||
`;
|
||||
}};
|
||||
${({ isResizing, theme }) => {
|
||||
if (isResizing === true) {
|
||||
return `&:after {
|
||||
background-color: ${theme.color.blue};
|
||||
bottom: 0;
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: -1px;
|
||||
top: 0;
|
||||
width: 2px;
|
||||
}`;
|
||||
}
|
||||
}};
|
||||
|
||||
z-index: ${({ zIndex }) => zIndex ?? 'auto'};
|
||||
`;
|
||||
|
||||
export const RecordTableHeaderCellContainer = StyledHeaderCell;
|
||||
+11
-79
@@ -1,12 +1,13 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { type RecordField } from '@/object-record/record-field/types/RecordField';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableColumnHeadWithDropdown } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown';
|
||||
import { RecordTableHeaderResizeHandler } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderResizeHandler';
|
||||
import { COLUMN_RESIZE_MIN_WIDTH } from '@/object-record/record-table/record-table-header/hooks/useResizeTableHeader';
|
||||
|
||||
import { RecordTableHeaderCellContainer } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderCellContainer';
|
||||
|
||||
import { COLUMN_MIN_WIDTH } from '@/object-record/record-table/constants/ColumnMinWidth';
|
||||
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||
import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState';
|
||||
@@ -17,73 +18,6 @@ import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-st
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { filterOutByProperty } from 'twenty-shared/utils';
|
||||
|
||||
// TODO: factorize duplicated code here
|
||||
const StyledColumnHeaderCell = styled.div<{
|
||||
columnWidth: number;
|
||||
isResizing?: boolean;
|
||||
isFirstRowActiveOrFocused: boolean;
|
||||
zIndex: number;
|
||||
}>`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
|
||||
height: 32px;
|
||||
max-height: 32px;
|
||||
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
border-right: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
|
||||
${({ columnWidth }) => `
|
||||
min-width: ${columnWidth}px;
|
||||
width: ${columnWidth}px;
|
||||
`}
|
||||
user-select: none;
|
||||
${({ theme }) => {
|
||||
return `
|
||||
&:hover {
|
||||
background: ${theme.background.secondary};
|
||||
};
|
||||
&:active {
|
||||
background: ${theme.background.tertiary};
|
||||
};
|
||||
`;
|
||||
}};
|
||||
${({ isResizing, theme }) => {
|
||||
if (isResizing === true) {
|
||||
return `&:after {
|
||||
background-color: ${theme.color.blue};
|
||||
bottom: 0;
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: -1px;
|
||||
top: 0;
|
||||
width: 2px;
|
||||
}`;
|
||||
}
|
||||
}};
|
||||
|
||||
// TODO: refactor this, each component should own its CSS
|
||||
div {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
z-index: ${({ zIndex }) => zIndex};
|
||||
`;
|
||||
|
||||
const StyledColumnHeadContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
& > :first-of-type {
|
||||
flex: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
export const RecordTableHeaderFirstScrollableCell = () => {
|
||||
const { objectMetadataItem, visibleRecordFields } =
|
||||
useRecordTableContextOrThrow();
|
||||
@@ -124,7 +58,7 @@ export const RecordTableHeaderFirstScrollableCell = () => {
|
||||
|
||||
const computedDynamicWidth = baseWidth + widthOffsetWhileResizing;
|
||||
|
||||
const columnWidth = Math.max(computedDynamicWidth, COLUMN_RESIZE_MIN_WIDTH);
|
||||
const columnWidth = Math.max(computedDynamicWidth, COLUMN_MIN_WIDTH);
|
||||
|
||||
const isFirstRowActiveOrFocused = isFirstRowActive || isFirstRowFocused;
|
||||
|
||||
@@ -151,7 +85,7 @@ export const RecordTableHeaderFirstScrollableCell = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledColumnHeaderCell
|
||||
<RecordTableHeaderCellContainer
|
||||
className="header-cell"
|
||||
key={recordField.fieldMetadataItemId}
|
||||
isResizing={
|
||||
@@ -161,13 +95,11 @@ export const RecordTableHeaderFirstScrollableCell = () => {
|
||||
isFirstRowActiveOrFocused={isFirstRowActiveOrFocused}
|
||||
zIndex={zIndex}
|
||||
>
|
||||
<StyledColumnHeadContainer>
|
||||
<RecordTableColumnHeadWithDropdown
|
||||
recordField={recordField}
|
||||
objectMetadataId={objectMetadataItem.id}
|
||||
/>
|
||||
</StyledColumnHeadContainer>
|
||||
<RecordTableColumnHeadWithDropdown
|
||||
recordField={recordField}
|
||||
objectMetadataId={objectMetadataItem.id}
|
||||
/>
|
||||
<RecordTableHeaderResizeHandler recordField={recordField} />
|
||||
</StyledColumnHeaderCell>
|
||||
</RecordTableHeaderCellContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+12
-105
@@ -1,102 +1,40 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
|
||||
|
||||
import { hasAnySoftDeleteFilterOnViewComponentSelector } from '@/object-record/record-filter/states/hasAnySoftDeleteFilterOnView';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||
import { RecordTableColumnHeadWithDropdown } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown';
|
||||
import { RecordTableHeaderResizeHandler } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderResizeHandler';
|
||||
import { COLUMN_RESIZE_MIN_WIDTH } from '@/object-record/record-table/record-table-header/hooks/useResizeTableHeader';
|
||||
|
||||
import { RecordTableHeaderCellContainer } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderCellContainer';
|
||||
|
||||
import { COLUMN_MIN_WIDTH } from '@/object-record/record-table/constants/ColumnMinWidth';
|
||||
import { RecordTableHeaderLabelIdentifierCellPlusButton } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderLabelIdentifierCellPlusButton';
|
||||
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||
import { resizedFieldMetadataIdComponentState } from '@/object-record/record-table/states/resizedFieldMetadataIdComponentState';
|
||||
import { resizeFieldOffsetComponentState } from '@/object-record/record-table/states/resizeFieldOffsetComponentState';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useState } from 'react';
|
||||
import { findByProperty } from 'twenty-shared/utils';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
import { LightIconButton } from 'twenty-ui/input';
|
||||
|
||||
// TODO: factorize duplicated code here
|
||||
const StyledColumnHeaderCell = styled.div<{
|
||||
columnWidth: number;
|
||||
isResizing?: boolean;
|
||||
isFirstRowActiveOrFocused: boolean;
|
||||
}>`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
|
||||
height: 32px;
|
||||
max-height: 32px;
|
||||
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
border-right: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
|
||||
${({ columnWidth }) => `
|
||||
min-width: ${columnWidth}px;
|
||||
width: ${columnWidth}px;
|
||||
`}
|
||||
user-select: none;
|
||||
${({ theme }) => {
|
||||
return `
|
||||
&:hover {
|
||||
background: ${theme.background.secondary};
|
||||
};
|
||||
&:active {
|
||||
background: ${theme.background.tertiary};
|
||||
};
|
||||
`;
|
||||
}};
|
||||
${({ isResizing, theme }) => {
|
||||
if (isResizing === true) {
|
||||
return `&:after {
|
||||
background-color: ${theme.color.blue};
|
||||
bottom: 0;
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: -1px;
|
||||
top: 0;
|
||||
width: 2px;
|
||||
}`;
|
||||
}
|
||||
}};
|
||||
|
||||
// TODO: refactor this, each component should own its CSS
|
||||
div {
|
||||
overflow: hidden;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledColumnHeadContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
|
||||
& > :first-of-type {
|
||||
flex: 1;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledHeaderIcon = styled.div`
|
||||
margin: ${({ theme }) => theme.spacing(1, 1, 1, 1.5)};
|
||||
`;
|
||||
|
||||
export const RecordTableHeaderLabelIdentifierCell = () => {
|
||||
const { objectMetadataItem, objectPermissions, visibleRecordFields } =
|
||||
const { objectMetadataItem, visibleRecordFields } =
|
||||
useRecordTableContextOrThrow();
|
||||
|
||||
const [iconIsVisible, setIconIsVisible] = useState(false);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const isFirstRowActive = useRecoilComponentFamilyValue(
|
||||
isRecordTableRowActiveComponentFamilyState,
|
||||
0,
|
||||
@@ -113,10 +51,6 @@ export const RecordTableHeaderLabelIdentifierCell = () => {
|
||||
findByProperty('fieldMetadataItemId', labelIdentifierFieldMetadataItem?.id),
|
||||
);
|
||||
|
||||
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const resizeFieldOffset = useRecoilComponentValue(
|
||||
resizeFieldOffsetComponentState,
|
||||
);
|
||||
@@ -125,25 +59,10 @@ export const RecordTableHeaderLabelIdentifierCell = () => {
|
||||
resizedFieldMetadataIdComponentState,
|
||||
);
|
||||
|
||||
const hasAnySoftDeleteFilterOnView = useRecoilComponentValue(
|
||||
hasAnySoftDeleteFilterOnViewComponentSelector,
|
||||
);
|
||||
|
||||
if (!recordField) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const handlePlusButtonClick = () => {
|
||||
createNewIndexRecord();
|
||||
};
|
||||
|
||||
const isReadOnly = isObjectMetadataReadOnly({
|
||||
objectPermissions,
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||
|
||||
const widthOffsetWhileResizing =
|
||||
resizedFieldMetadataItemId === recordField.fieldMetadataItemId
|
||||
? resizeFieldOffset
|
||||
@@ -153,12 +72,12 @@ export const RecordTableHeaderLabelIdentifierCell = () => {
|
||||
|
||||
const computedDynamicWidth = baseWidth + widthOffsetWhileResizing;
|
||||
|
||||
const columnWidth = Math.max(computedDynamicWidth, COLUMN_RESIZE_MIN_WIDTH);
|
||||
const columnWidth = Math.max(computedDynamicWidth, COLUMN_MIN_WIDTH);
|
||||
|
||||
const isFirstRowActiveOrFocused = isFirstRowActive || isFirstRowFocused;
|
||||
|
||||
return (
|
||||
<StyledColumnHeaderCell
|
||||
<RecordTableHeaderCellContainer
|
||||
className="header-cell"
|
||||
key={recordField.fieldMetadataItemId}
|
||||
isResizing={
|
||||
@@ -174,21 +93,9 @@ export const RecordTableHeaderLabelIdentifierCell = () => {
|
||||
recordField={recordField}
|
||||
objectMetadataId={objectMetadataItem.id}
|
||||
/>
|
||||
{(isMobile || iconIsVisible) &&
|
||||
!isReadOnly &&
|
||||
hasObjectUpdatePermissions &&
|
||||
!hasAnySoftDeleteFilterOnView && (
|
||||
<StyledHeaderIcon>
|
||||
<LightIconButton
|
||||
Icon={IconPlus}
|
||||
size="small"
|
||||
accent="tertiary"
|
||||
onClick={handlePlusButtonClick}
|
||||
/>
|
||||
</StyledHeaderIcon>
|
||||
)}
|
||||
{iconIsVisible && <RecordTableHeaderLabelIdentifierCellPlusButton />}
|
||||
</StyledColumnHeadContainer>
|
||||
<RecordTableHeaderResizeHandler recordField={recordField} />
|
||||
</StyledColumnHeaderCell>
|
||||
</RecordTableHeaderCellContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
import { isObjectMetadataReadOnly } from '@/object-record/read-only/utils/isObjectMetadataReadOnly';
|
||||
import { hasAnySoftDeleteFilterOnViewComponentSelector } from '@/object-record/record-filter/states/hasAnySoftDeleteFilterOnView';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useCreateNewIndexRecord';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import styled from '@emotion/styled';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
import { LightIconButton } from 'twenty-ui/input';
|
||||
import { useIsMobile } from 'twenty-ui/utilities';
|
||||
|
||||
const StyledHeaderIcon = styled.div`
|
||||
margin: ${({ theme }) => theme.spacing(1, 1, 1, 1.5)};
|
||||
`;
|
||||
|
||||
export const RecordTableHeaderLabelIdentifierCellPlusButton = () => {
|
||||
const { objectMetadataItem, objectPermissions } =
|
||||
useRecordTableContextOrThrow();
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const handlePlusButtonClick = () => {
|
||||
createNewIndexRecord();
|
||||
};
|
||||
|
||||
const isReadOnly = isObjectMetadataReadOnly({
|
||||
objectPermissions,
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
const hasAnySoftDeleteFilterOnView = useRecoilComponentValue(
|
||||
hasAnySoftDeleteFilterOnViewComponentSelector,
|
||||
);
|
||||
|
||||
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
||||
|
||||
return (
|
||||
!isMobile &&
|
||||
!isReadOnly &&
|
||||
hasObjectUpdatePermissions &&
|
||||
!hasAnySoftDeleteFilterOnView && (
|
||||
<StyledHeaderIcon>
|
||||
<LightIconButton
|
||||
Icon={IconPlus}
|
||||
size="small"
|
||||
accent="tertiary"
|
||||
onClick={handlePlusButtonClick}
|
||||
/>
|
||||
</StyledHeaderIcon>
|
||||
)
|
||||
);
|
||||
};
|
||||
+14
-4
@@ -1,19 +1,29 @@
|
||||
import { useRecordTableLastColumnWidthToFill } from '@/object-record/record-table/hooks/useRecordTableLastColumnWidthToFill';
|
||||
import { resizeFieldOffsetComponentState } from '@/object-record/record-table/states/resizeFieldOffsetComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledLastColumnHeader = styled.div`
|
||||
const StyledLastColumnHeader = styled.div<{ width: number }>`
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
border-left: none !important;
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
|
||||
width: fit-content;
|
||||
height: 32px;
|
||||
max-height: 32px;
|
||||
|
||||
width: ${({ width }) => width}px;
|
||||
`;
|
||||
|
||||
export const RecordTableHeaderLastColumn = () => {
|
||||
return (
|
||||
<StyledLastColumnHeader className="header-cell"></StyledLastColumnHeader>
|
||||
const { lastColumnWidth } = useRecordTableLastColumnWidthToFill();
|
||||
|
||||
const resizeFieldOffset = useRecoilComponentValue(
|
||||
resizeFieldOffsetComponentState,
|
||||
);
|
||||
|
||||
const width = lastColumnWidth - resizeFieldOffset;
|
||||
|
||||
return <StyledLastColumnHeader className="header-cell" width={width} />;
|
||||
};
|
||||
|
||||
+32
-8
@@ -1,10 +1,11 @@
|
||||
import { type RecordField } from '@/object-record/record-field/types/RecordField';
|
||||
import { resizedFieldMetadataIdComponentState } from '@/object-record/record-table/states/resizedFieldMetadataIdComponentState';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
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`
|
||||
const StyledResizeHandler = styled.div<{ isResizing: boolean }>`
|
||||
bottom: 0;
|
||||
cursor: col-resize;
|
||||
padding: 0 ${({ theme }) => theme.spacing(2)};
|
||||
@@ -13,6 +14,21 @@ const StyledResizeHandler = styled.div`
|
||||
top: 0;
|
||||
width: 3px;
|
||||
z-index: 1;
|
||||
|
||||
${({ isResizing, theme }) => {
|
||||
if (isResizing === true) {
|
||||
return `&:after {
|
||||
background-color: ${theme.color.blue};
|
||||
bottom: 0;
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 0;
|
||||
width: 2px;
|
||||
}`;
|
||||
}
|
||||
}};
|
||||
`;
|
||||
|
||||
export const RecordTableHeaderResizeHandler = ({
|
||||
@@ -24,18 +40,26 @@ export const RecordTableHeaderResizeHandler = ({
|
||||
|
||||
const columnResizeDisabled = isMobile;
|
||||
|
||||
const setResizedFieldMetadataItemId = useSetRecoilComponentState(
|
||||
resizedFieldMetadataIdComponentState,
|
||||
);
|
||||
const [resizedFieldMetadataItemId, setResizedFieldMetadataItemId] =
|
||||
useRecoilComponentState(resizedFieldMetadataIdComponentState);
|
||||
|
||||
const isResizing =
|
||||
recordField.fieldMetadataItemId === resizedFieldMetadataItemId;
|
||||
|
||||
const { setDragSelectionStartEnabled } = useDragSelect();
|
||||
|
||||
const handlePointerDown = () => {
|
||||
setDragSelectionStartEnabled(false);
|
||||
setResizedFieldMetadataItemId(recordField.fieldMetadataItemId);
|
||||
};
|
||||
|
||||
return (
|
||||
!columnResizeDisabled && (
|
||||
<StyledResizeHandler
|
||||
className="cursor-col-resize"
|
||||
role="separator"
|
||||
onPointerDown={() => {
|
||||
setResizedFieldMetadataItemId(recordField.fieldMetadataItemId);
|
||||
}}
|
||||
onPointerDown={handlePointerDown}
|
||||
isResizing={isResizing}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
+30
-13
@@ -1,7 +1,11 @@
|
||||
import { useUpdateRecordField } from '@/object-record/record-field/hooks/useUpdateRecordField';
|
||||
import { COLUMN_MIN_WIDTH } from '@/object-record/record-table/constants/ColumnMinWidth';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { useResetTableRowSelection } from '@/object-record/record-table/hooks/internal/useResetTableRowSelection';
|
||||
|
||||
import { resizedFieldMetadataIdComponentState } from '@/object-record/record-table/states/resizedFieldMetadataIdComponentState';
|
||||
import { resizeFieldOffsetComponentState } from '@/object-record/record-table/states/resizeFieldOffsetComponentState';
|
||||
import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
|
||||
import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
|
||||
import { type PointerEventListener } from '@/ui/utilities/pointer-event/types/PointerEventListener';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
@@ -13,8 +17,6 @@ import { useCallback, useState } from 'react';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { findByProperty, throwIfNotDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const COLUMN_RESIZE_MIN_WIDTH = 48;
|
||||
|
||||
export const useResizeTableHeader = () => {
|
||||
const { visibleRecordFields } = useRecordTableContextOrThrow();
|
||||
|
||||
@@ -33,33 +35,48 @@ export const useResizeTableHeader = () => {
|
||||
const [resizedFieldMetadataItemId, setResizedFieldMetadataItemId] =
|
||||
useRecoilComponentState(resizedFieldMetadataIdComponentState);
|
||||
|
||||
const recordField = visibleRecordFields.find(
|
||||
findByProperty('fieldMetadataItemId', resizedFieldMetadataItemId),
|
||||
);
|
||||
|
||||
const { resetTableRowSelection } = useResetTableRowSelection();
|
||||
|
||||
const { saveRecordFields } = useSaveRecordFields();
|
||||
|
||||
const { updateRecordField } = useUpdateRecordField();
|
||||
|
||||
const handleResizeHandlerStart = useCallback<PointerEventListener>(
|
||||
({ x }) => {
|
||||
resetTableRowSelection();
|
||||
setInitialPointerPositionX(x);
|
||||
},
|
||||
[],
|
||||
[resetTableRowSelection],
|
||||
);
|
||||
|
||||
const handleResizeHandlerMove = useCallback<PointerEventListener>(
|
||||
({ x }) => {
|
||||
if (!initialPointerPositionX) return;
|
||||
|
||||
throwIfNotDefined(recordField, 'recordField');
|
||||
|
||||
const newResizeOffset = x - initialPointerPositionX;
|
||||
|
||||
const newRecordFieldSizeWithOffset = recordField.size + newResizeOffset;
|
||||
|
||||
if (newRecordFieldSizeWithOffset < COLUMN_MIN_WIDTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
setResizeFieldOffset(x - initialPointerPositionX);
|
||||
},
|
||||
[setResizeFieldOffset, initialPointerPositionX],
|
||||
[setResizeFieldOffset, initialPointerPositionX, recordField],
|
||||
);
|
||||
|
||||
const { setDragSelectionStartEnabled } = useDragSelect();
|
||||
|
||||
const handleResizeHandlerEnd = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
async () => {
|
||||
const recordField = visibleRecordFields.find(
|
||||
findByProperty('fieldMetadataItemId', resizedFieldMetadataItemId),
|
||||
);
|
||||
|
||||
throwIfNotDefined(recordField, 'recordField');
|
||||
|
||||
if (!resizedFieldMetadataItemId) return;
|
||||
@@ -70,10 +87,7 @@ export const useResizeTableHeader = () => {
|
||||
);
|
||||
|
||||
const nextWidth = Math.round(
|
||||
Math.max(
|
||||
recordField.size + resizeFieldOffset,
|
||||
COLUMN_RESIZE_MIN_WIDTH,
|
||||
),
|
||||
Math.max(recordField.size + resizeFieldOffset, COLUMN_MIN_WIDTH),
|
||||
);
|
||||
|
||||
set(resizeFieldOffsetCallbackState, 0);
|
||||
@@ -90,6 +104,8 @@ export const useResizeTableHeader = () => {
|
||||
|
||||
saveRecordFields([updatedRecordField]);
|
||||
}
|
||||
|
||||
setDragSelectionStartEnabled(true);
|
||||
},
|
||||
[
|
||||
saveRecordFields,
|
||||
@@ -97,7 +113,8 @@ export const useResizeTableHeader = () => {
|
||||
resizeFieldOffsetCallbackState,
|
||||
setResizedFieldMetadataItemId,
|
||||
updateRecordField,
|
||||
visibleRecordFields,
|
||||
setDragSelectionStartEnabled,
|
||||
recordField,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
+2
@@ -2,6 +2,7 @@ import { useRecordIndexContextOrThrow } from '@/object-record/record-index/conte
|
||||
import { RecordTableCellCheckbox } from '@/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox';
|
||||
import { RecordTableCellGrip } from '@/object-record/record-table/record-table-cell/components/RecordTableCellGrip';
|
||||
import { RecordTableLastEmptyCell } from '@/object-record/record-table/record-table-cell/components/RecordTableLastEmptyCell';
|
||||
import { RecordTablePlusButtonCellPlaceholder } from '@/object-record/record-table/record-table-cell/components/RecordTablePlusButtonCellPlaceholder';
|
||||
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 { RecordTableRowArrowKeysEffect } from '@/object-record/record-table/record-table-row/components/RecordTableRowArrowKeysEffect';
|
||||
@@ -51,6 +52,7 @@ export const RecordTableRow = ({
|
||||
<RecordTableCellGrip />
|
||||
<RecordTableCellCheckbox />
|
||||
<RecordTableCells />
|
||||
<RecordTablePlusButtonCellPlaceholder />
|
||||
<RecordTableLastEmptyCell />
|
||||
<ListenRecordUpdatesEffect
|
||||
objectNameSingular={objectNameSingular}
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
|
||||
export const recordTableWidthComponentState = createComponentState<number>({
|
||||
key: 'recordTableWidthComponentState',
|
||||
defaultValue: 0,
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
});
|
||||
+1
-1
@@ -1,11 +1,11 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { type RefObject, useCallback, useState } from 'react';
|
||||
|
||||
import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
|
||||
import { useDragSelectWithAutoScroll } from '@/ui/utilities/drag-select/hooks/useDragSelectWithAutoScroll';
|
||||
import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { useDragSelect } from '../hooks/useDragSelect';
|
||||
import { type SelectionBox } from '../types/SelectionBox';
|
||||
import { isValidSelectionStart } from '../utils/selectionBoxValidation';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user