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,
|
objectMetadataItem.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
const tableBodyRef = useRef<HTMLTableElement>(null);
|
const tableBodyRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const { toggleClickOutside } = useClickOutsideListener(
|
const { toggleClickOutside } = useClickOutsideListener(
|
||||||
RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID,
|
RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID,
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ import { RecordTableRecordGroupBodyEffects } from '@/object-record/record-table/
|
|||||||
|
|
||||||
export interface RecordTableBodyEffectsWrapperProps {
|
export interface RecordTableBodyEffectsWrapperProps {
|
||||||
hasRecordGroups: boolean;
|
hasRecordGroups: boolean;
|
||||||
tableBodyRef: React.RefObject<HTMLTableElement>;
|
tableBodyRef: React.RefObject<HTMLDivElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RecordTableBodyEffectsWrapper = ({
|
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 { RecordTableScrollAndZIndexEffect } from '@/object-record/record-table/components/RecordTableScrollAndZIndexEffect';
|
||||||
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
|
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
|
||||||
import { RecordTableNoRecordGroupBody } from '@/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBody';
|
import { RecordTableNoRecordGroupBody } from '@/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBody';
|
||||||
@@ -23,6 +24,7 @@ const StyledTable = styled.div<{
|
|||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
div.header-cell {
|
div.header-cell {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
@@ -36,7 +38,6 @@ const StyledTable = styled.div<{
|
|||||||
div.header-cell:nth-of-type(1) {
|
div.header-cell:nth-of-type(1) {
|
||||||
left: 0px;
|
left: 0px;
|
||||||
|
|
||||||
transition: 0.3s ease;
|
|
||||||
background-color: ${({ theme }) => theme.background.primary};
|
background-color: ${({ theme }) => theme.background.primary};
|
||||||
|
|
||||||
z-index: ${TABLE_Z_INDEX.headerColumnsSticky};
|
z-index: ${TABLE_Z_INDEX.headerColumnsSticky};
|
||||||
@@ -46,7 +47,6 @@ const StyledTable = styled.div<{
|
|||||||
left: 16px;
|
left: 16px;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
||||||
transition: 0.3s ease;
|
|
||||||
background-color: ${({ theme }) => theme.background.primary};
|
background-color: ${({ theme }) => theme.background.primary};
|
||||||
|
|
||||||
z-index: ${TABLE_Z_INDEX.headerColumnsSticky};
|
z-index: ${TABLE_Z_INDEX.headerColumnsSticky};
|
||||||
@@ -56,7 +56,6 @@ const StyledTable = styled.div<{
|
|||||||
left: 48px;
|
left: 48px;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|
||||||
transition: 0.3s ease;
|
|
||||||
background-color: ${({ theme }) => theme.background.primary};
|
background-color: ${({ theme }) => theme.background.primary};
|
||||||
|
|
||||||
z-index: ${TABLE_Z_INDEX.headerColumnsSticky};
|
z-index: ${TABLE_Z_INDEX.headerColumnsSticky};
|
||||||
@@ -143,7 +142,7 @@ const StyledTableContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export interface RecordTableContentProps {
|
export interface RecordTableContentProps {
|
||||||
tableBodyRef: React.RefObject<HTMLTableElement>;
|
tableBodyRef: React.RefObject<HTMLDivElement>;
|
||||||
handleDragSelectionStart: () => void;
|
handleDragSelectionStart: () => void;
|
||||||
handleDragSelectionEnd: () => void;
|
handleDragSelectionEnd: () => void;
|
||||||
hasRecordGroups: boolean;
|
hasRecordGroups: boolean;
|
||||||
@@ -197,6 +196,7 @@ export const RecordTableContent = ({
|
|||||||
<RecordTableNoRecordGroupBody />
|
<RecordTableNoRecordGroupBody />
|
||||||
)}
|
)}
|
||||||
<RecordTableScrollAndZIndexEffect />
|
<RecordTableScrollAndZIndexEffect />
|
||||||
|
<RecordTableResizeEffect />
|
||||||
</StyledTable>
|
</StyledTable>
|
||||||
<DragSelect
|
<DragSelect
|
||||||
selectableItemsContainerRef={containerRef}
|
selectableItemsContainerRef={containerRef}
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ const StyledEmptyStateContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export interface RecordTableEmptyProps {
|
export interface RecordTableEmptyProps {
|
||||||
tableBodyRef: React.RefObject<HTMLTableElement>;
|
tableBodyRef: React.RefObject<HTMLDivElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RecordTableEmpty = ({ tableBodyRef }: RecordTableEmptyProps) => (
|
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,
|
isDropDisabled,
|
||||||
}: RecordTableBodyDroppableProps) => {
|
}: RecordTableBodyDroppableProps) => {
|
||||||
const [v4Persistable] = useState(v4());
|
const [v4Persistable] = useState(v4());
|
||||||
const recordTableBodyId = `record-table-body${recordGroupId ? '-' + recordGroupId : ''}`;
|
|
||||||
|
|
||||||
const setRecordTableHoverPosition = useSetRecoilComponentState(
|
const setRecordTableHoverPosition = useSetRecoilComponentState(
|
||||||
recordTableHoverPositionComponentState,
|
recordTableHoverPositionComponentState,
|
||||||
@@ -42,19 +41,20 @@ export const RecordTableBodyDroppable = ({
|
|||||||
isDropDisabled={isDropDisabled}
|
isDropDisabled={isDropDisabled}
|
||||||
>
|
>
|
||||||
{(provided) => (
|
{(provided) => (
|
||||||
<RecordTableBody
|
<>
|
||||||
id={recordTableBodyId}
|
<RecordTableBody
|
||||||
ref={provided.innerRef}
|
ref={provided.innerRef}
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
{...provided.droppableProps}
|
{...provided.droppableProps}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
>
|
|
||||||
<RecordTableBodyDroppableContextProvider
|
|
||||||
value={{ droppablePlaceholder: provided.placeholder }}
|
|
||||||
>
|
>
|
||||||
{children}
|
<RecordTableBodyDroppableContextProvider
|
||||||
</RecordTableBodyDroppableContextProvider>
|
value={{ droppablePlaceholder: provided.placeholder }}
|
||||||
</RecordTableBody>
|
>
|
||||||
|
{children}
|
||||||
|
</RecordTableBodyDroppableContextProvider>
|
||||||
|
</RecordTableBody>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Droppable>
|
</Droppable>
|
||||||
);
|
);
|
||||||
|
|||||||
+20
-1
@@ -1,8 +1,27 @@
|
|||||||
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
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 { 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 = () => {
|
export const RecordTableLastEmptyCell = () => {
|
||||||
const { isSelected } = useRecordTableRowContextOrThrow();
|
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;
|
width: ${columnWidth}px;
|
||||||
`}
|
`}
|
||||||
text-align: left;
|
text-align: left;
|
||||||
transition: 0.3s ease;
|
|
||||||
${({ theme }) => {
|
${({ theme }) => {
|
||||||
return `
|
return `
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|||||||
-9
@@ -5,13 +5,11 @@ import { RecordTableHeaderPlusButtonContent } from '@/object-record/record-table
|
|||||||
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
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 { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
||||||
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.div<{
|
const StyledPlusIconHeaderCell = styled.div<{
|
||||||
isTableWiderThanScreen: boolean;
|
|
||||||
isFirstRowActiveOrFocused: boolean;
|
isFirstRowActiveOrFocused: boolean;
|
||||||
}>`
|
}>`
|
||||||
border-bottom: ${({ isFirstRowActiveOrFocused, theme }) =>
|
border-bottom: ${({ isFirstRowActiveOrFocused, theme }) =>
|
||||||
@@ -53,12 +51,6 @@ const StyledDropdownContainer = styled.div`
|
|||||||
export const RecordTableHeaderAddColumnButton = () => {
|
export const RecordTableHeaderAddColumnButton = () => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const { scrollWrapperHTMLElement } = useScrollWrapperElement();
|
|
||||||
|
|
||||||
const isTableWiderThanScreen =
|
|
||||||
(scrollWrapperHTMLElement?.clientWidth ?? 0) <
|
|
||||||
(scrollWrapperHTMLElement?.scrollWidth ?? 0);
|
|
||||||
|
|
||||||
const isFirstRowActive = useRecoilComponentFamilyValue(
|
const isFirstRowActive = useRecoilComponentFamilyValue(
|
||||||
isRecordTableRowActiveComponentFamilyState,
|
isRecordTableRowActiveComponentFamilyState,
|
||||||
0,
|
0,
|
||||||
@@ -73,7 +65,6 @@ export const RecordTableHeaderAddColumnButton = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledPlusIconHeaderCell
|
<StyledPlusIconHeaderCell
|
||||||
isTableWiderThanScreen={isTableWiderThanScreen}
|
|
||||||
isFirstRowActiveOrFocused={isFirstRowActiveOrFocused}
|
isFirstRowActiveOrFocused={isFirstRowActiveOrFocused}
|
||||||
className="header-cell"
|
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 { 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 { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
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 { RecordTableHeaderCellContainer } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderCellContainer';
|
||||||
import { RecordTableHeaderResizeHandler } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderResizeHandler';
|
import { RecordTableHeaderResizeHandler } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderResizeHandler';
|
||||||
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
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 { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
||||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
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 = {
|
type RecordTableHeaderCellProps = {
|
||||||
recordField: RecordField;
|
recordField: RecordField;
|
||||||
};
|
};
|
||||||
@@ -117,7 +52,7 @@ export const RecordTableHeaderCell = ({
|
|||||||
const isFirstRowActiveOrFocused = isFirstRowActive || isFirstRowFocused;
|
const isFirstRowActiveOrFocused = isFirstRowActive || isFirstRowFocused;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledColumnHeaderCell
|
<RecordTableHeaderCellContainer
|
||||||
className="header-cell"
|
className="header-cell"
|
||||||
key={recordField.fieldMetadataItemId}
|
key={recordField.fieldMetadataItemId}
|
||||||
isResizing={
|
isResizing={
|
||||||
@@ -126,13 +61,11 @@ export const RecordTableHeaderCell = ({
|
|||||||
columnWidth={columnWidth}
|
columnWidth={columnWidth}
|
||||||
isFirstRowActiveOrFocused={isFirstRowActiveOrFocused}
|
isFirstRowActiveOrFocused={isFirstRowActiveOrFocused}
|
||||||
>
|
>
|
||||||
<StyledColumnHeadContainer>
|
<RecordTableColumnHeadWithDropdown
|
||||||
<RecordTableColumnHeadWithDropdown
|
recordField={recordField}
|
||||||
recordField={recordField}
|
objectMetadataId={objectMetadataItem.id}
|
||||||
objectMetadataId={objectMetadataItem.id}
|
/>
|
||||||
/>
|
|
||||||
</StyledColumnHeadContainer>
|
|
||||||
<RecordTableHeaderResizeHandler recordField={recordField} />
|
<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 { type RecordField } from '@/object-record/record-field/types/RecordField';
|
||||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||||
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
|
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
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 { RecordTableHeaderResizeHandler } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderResizeHandler';
|
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 { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||||
import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState';
|
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 { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||||
import { filterOutByProperty } from 'twenty-shared/utils';
|
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 = () => {
|
export const RecordTableHeaderFirstScrollableCell = () => {
|
||||||
const { objectMetadataItem, visibleRecordFields } =
|
const { objectMetadataItem, visibleRecordFields } =
|
||||||
useRecordTableContextOrThrow();
|
useRecordTableContextOrThrow();
|
||||||
@@ -124,7 +58,7 @@ export const RecordTableHeaderFirstScrollableCell = () => {
|
|||||||
|
|
||||||
const computedDynamicWidth = baseWidth + widthOffsetWhileResizing;
|
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;
|
const isFirstRowActiveOrFocused = isFirstRowActive || isFirstRowFocused;
|
||||||
|
|
||||||
@@ -151,7 +85,7 @@ export const RecordTableHeaderFirstScrollableCell = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledColumnHeaderCell
|
<RecordTableHeaderCellContainer
|
||||||
className="header-cell"
|
className="header-cell"
|
||||||
key={recordField.fieldMetadataItemId}
|
key={recordField.fieldMetadataItemId}
|
||||||
isResizing={
|
isResizing={
|
||||||
@@ -161,13 +95,11 @@ export const RecordTableHeaderFirstScrollableCell = () => {
|
|||||||
isFirstRowActiveOrFocused={isFirstRowActiveOrFocused}
|
isFirstRowActiveOrFocused={isFirstRowActiveOrFocused}
|
||||||
zIndex={zIndex}
|
zIndex={zIndex}
|
||||||
>
|
>
|
||||||
<StyledColumnHeadContainer>
|
<RecordTableColumnHeadWithDropdown
|
||||||
<RecordTableColumnHeadWithDropdown
|
recordField={recordField}
|
||||||
recordField={recordField}
|
objectMetadataId={objectMetadataItem.id}
|
||||||
objectMetadataId={objectMetadataItem.id}
|
/>
|
||||||
/>
|
|
||||||
</StyledColumnHeadContainer>
|
|
||||||
<RecordTableHeaderResizeHandler recordField={recordField} />
|
<RecordTableHeaderResizeHandler recordField={recordField} />
|
||||||
</StyledColumnHeaderCell>
|
</RecordTableHeaderCellContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
+12
-105
@@ -1,102 +1,40 @@
|
|||||||
import styled from '@emotion/styled';
|
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 { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
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 { RecordTableColumnHeadWithDropdown } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown';
|
||||||
import { RecordTableHeaderResizeHandler } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderResizeHandler';
|
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 { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||||
import { resizedFieldMetadataIdComponentState } from '@/object-record/record-table/states/resizedFieldMetadataIdComponentState';
|
import { resizedFieldMetadataIdComponentState } from '@/object-record/record-table/states/resizedFieldMetadataIdComponentState';
|
||||||
import { resizeFieldOffsetComponentState } from '@/object-record/record-table/states/resizeFieldOffsetComponentState';
|
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 { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
||||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||||
|
import { useState } from 'react';
|
||||||
import { findByProperty } from 'twenty-shared/utils';
|
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`
|
const StyledColumnHeadContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
& > :first-of-type {
|
& > :first-of-type {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StyledHeaderIcon = styled.div`
|
|
||||||
margin: ${({ theme }) => theme.spacing(1, 1, 1, 1.5)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const RecordTableHeaderLabelIdentifierCell = () => {
|
export const RecordTableHeaderLabelIdentifierCell = () => {
|
||||||
const { objectMetadataItem, objectPermissions, visibleRecordFields } =
|
const { objectMetadataItem, visibleRecordFields } =
|
||||||
useRecordTableContextOrThrow();
|
useRecordTableContextOrThrow();
|
||||||
|
|
||||||
const [iconIsVisible, setIconIsVisible] = useState(false);
|
const [iconIsVisible, setIconIsVisible] = useState(false);
|
||||||
|
|
||||||
const isMobile = useIsMobile();
|
|
||||||
|
|
||||||
const isFirstRowActive = useRecoilComponentFamilyValue(
|
const isFirstRowActive = useRecoilComponentFamilyValue(
|
||||||
isRecordTableRowActiveComponentFamilyState,
|
isRecordTableRowActiveComponentFamilyState,
|
||||||
0,
|
0,
|
||||||
@@ -113,10 +51,6 @@ export const RecordTableHeaderLabelIdentifierCell = () => {
|
|||||||
findByProperty('fieldMetadataItemId', labelIdentifierFieldMetadataItem?.id),
|
findByProperty('fieldMetadataItemId', labelIdentifierFieldMetadataItem?.id),
|
||||||
);
|
);
|
||||||
|
|
||||||
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
|
||||||
objectMetadataItem,
|
|
||||||
});
|
|
||||||
|
|
||||||
const resizeFieldOffset = useRecoilComponentValue(
|
const resizeFieldOffset = useRecoilComponentValue(
|
||||||
resizeFieldOffsetComponentState,
|
resizeFieldOffsetComponentState,
|
||||||
);
|
);
|
||||||
@@ -125,25 +59,10 @@ export const RecordTableHeaderLabelIdentifierCell = () => {
|
|||||||
resizedFieldMetadataIdComponentState,
|
resizedFieldMetadataIdComponentState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasAnySoftDeleteFilterOnView = useRecoilComponentValue(
|
|
||||||
hasAnySoftDeleteFilterOnViewComponentSelector,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!recordField) {
|
if (!recordField) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePlusButtonClick = () => {
|
|
||||||
createNewIndexRecord();
|
|
||||||
};
|
|
||||||
|
|
||||||
const isReadOnly = isObjectMetadataReadOnly({
|
|
||||||
objectPermissions,
|
|
||||||
objectMetadataItem,
|
|
||||||
});
|
|
||||||
|
|
||||||
const hasObjectUpdatePermissions = objectPermissions.canUpdateObjectRecords;
|
|
||||||
|
|
||||||
const widthOffsetWhileResizing =
|
const widthOffsetWhileResizing =
|
||||||
resizedFieldMetadataItemId === recordField.fieldMetadataItemId
|
resizedFieldMetadataItemId === recordField.fieldMetadataItemId
|
||||||
? resizeFieldOffset
|
? resizeFieldOffset
|
||||||
@@ -153,12 +72,12 @@ export const RecordTableHeaderLabelIdentifierCell = () => {
|
|||||||
|
|
||||||
const computedDynamicWidth = baseWidth + widthOffsetWhileResizing;
|
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;
|
const isFirstRowActiveOrFocused = isFirstRowActive || isFirstRowFocused;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledColumnHeaderCell
|
<RecordTableHeaderCellContainer
|
||||||
className="header-cell"
|
className="header-cell"
|
||||||
key={recordField.fieldMetadataItemId}
|
key={recordField.fieldMetadataItemId}
|
||||||
isResizing={
|
isResizing={
|
||||||
@@ -174,21 +93,9 @@ export const RecordTableHeaderLabelIdentifierCell = () => {
|
|||||||
recordField={recordField}
|
recordField={recordField}
|
||||||
objectMetadataId={objectMetadataItem.id}
|
objectMetadataId={objectMetadataItem.id}
|
||||||
/>
|
/>
|
||||||
{(isMobile || iconIsVisible) &&
|
{iconIsVisible && <RecordTableHeaderLabelIdentifierCellPlusButton />}
|
||||||
!isReadOnly &&
|
|
||||||
hasObjectUpdatePermissions &&
|
|
||||||
!hasAnySoftDeleteFilterOnView && (
|
|
||||||
<StyledHeaderIcon>
|
|
||||||
<LightIconButton
|
|
||||||
Icon={IconPlus}
|
|
||||||
size="small"
|
|
||||||
accent="tertiary"
|
|
||||||
onClick={handlePlusButtonClick}
|
|
||||||
/>
|
|
||||||
</StyledHeaderIcon>
|
|
||||||
)}
|
|
||||||
</StyledColumnHeadContainer>
|
</StyledColumnHeadContainer>
|
||||||
<RecordTableHeaderResizeHandler recordField={recordField} />
|
<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';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
const StyledLastColumnHeader = styled.div`
|
const StyledLastColumnHeader = styled.div<{ width: number }>`
|
||||||
border-bottom: 1px solid ${({ theme }) => 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};
|
||||||
|
|
||||||
width: fit-content;
|
|
||||||
height: 32px;
|
height: 32px;
|
||||||
max-height: 32px;
|
max-height: 32px;
|
||||||
|
|
||||||
|
width: ${({ width }) => width}px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const RecordTableHeaderLastColumn = () => {
|
export const RecordTableHeaderLastColumn = () => {
|
||||||
return (
|
const { lastColumnWidth } = useRecordTableLastColumnWidthToFill();
|
||||||
<StyledLastColumnHeader className="header-cell"></StyledLastColumnHeader>
|
|
||||||
|
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 { type RecordField } from '@/object-record/record-field/types/RecordField';
|
||||||
import { resizedFieldMetadataIdComponentState } from '@/object-record/record-table/states/resizedFieldMetadataIdComponentState';
|
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 styled from '@emotion/styled';
|
||||||
import { useIsMobile } from 'twenty-ui/utilities';
|
import { useIsMobile } from 'twenty-ui/utilities';
|
||||||
|
|
||||||
const StyledResizeHandler = styled.div`
|
const StyledResizeHandler = styled.div<{ isResizing: boolean }>`
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
cursor: col-resize;
|
cursor: col-resize;
|
||||||
padding: 0 ${({ theme }) => theme.spacing(2)};
|
padding: 0 ${({ theme }) => theme.spacing(2)};
|
||||||
@@ -13,6 +14,21 @@ const StyledResizeHandler = styled.div`
|
|||||||
top: 0;
|
top: 0;
|
||||||
width: 3px;
|
width: 3px;
|
||||||
z-index: 1;
|
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 = ({
|
export const RecordTableHeaderResizeHandler = ({
|
||||||
@@ -24,18 +40,26 @@ export const RecordTableHeaderResizeHandler = ({
|
|||||||
|
|
||||||
const columnResizeDisabled = isMobile;
|
const columnResizeDisabled = isMobile;
|
||||||
|
|
||||||
const setResizedFieldMetadataItemId = useSetRecoilComponentState(
|
const [resizedFieldMetadataItemId, setResizedFieldMetadataItemId] =
|
||||||
resizedFieldMetadataIdComponentState,
|
useRecoilComponentState(resizedFieldMetadataIdComponentState);
|
||||||
);
|
|
||||||
|
const isResizing =
|
||||||
|
recordField.fieldMetadataItemId === resizedFieldMetadataItemId;
|
||||||
|
|
||||||
|
const { setDragSelectionStartEnabled } = useDragSelect();
|
||||||
|
|
||||||
|
const handlePointerDown = () => {
|
||||||
|
setDragSelectionStartEnabled(false);
|
||||||
|
setResizedFieldMetadataItemId(recordField.fieldMetadataItemId);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
!columnResizeDisabled && (
|
!columnResizeDisabled && (
|
||||||
<StyledResizeHandler
|
<StyledResizeHandler
|
||||||
className="cursor-col-resize"
|
className="cursor-col-resize"
|
||||||
role="separator"
|
role="separator"
|
||||||
onPointerDown={() => {
|
onPointerDown={handlePointerDown}
|
||||||
setResizedFieldMetadataItemId(recordField.fieldMetadataItemId);
|
isResizing={isResizing}
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
+30
-13
@@ -1,7 +1,11 @@
|
|||||||
import { useUpdateRecordField } from '@/object-record/record-field/hooks/useUpdateRecordField';
|
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 { 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 { resizedFieldMetadataIdComponentState } from '@/object-record/record-table/states/resizedFieldMetadataIdComponentState';
|
||||||
import { resizeFieldOffsetComponentState } from '@/object-record/record-table/states/resizeFieldOffsetComponentState';
|
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 { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
|
||||||
import { type PointerEventListener } from '@/ui/utilities/pointer-event/types/PointerEventListener';
|
import { type PointerEventListener } from '@/ui/utilities/pointer-event/types/PointerEventListener';
|
||||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||||
@@ -13,8 +17,6 @@ import { useCallback, useState } from 'react';
|
|||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
import { findByProperty, throwIfNotDefined } from 'twenty-shared/utils';
|
import { findByProperty, throwIfNotDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
export const COLUMN_RESIZE_MIN_WIDTH = 48;
|
|
||||||
|
|
||||||
export const useResizeTableHeader = () => {
|
export const useResizeTableHeader = () => {
|
||||||
const { visibleRecordFields } = useRecordTableContextOrThrow();
|
const { visibleRecordFields } = useRecordTableContextOrThrow();
|
||||||
|
|
||||||
@@ -33,33 +35,48 @@ export const useResizeTableHeader = () => {
|
|||||||
const [resizedFieldMetadataItemId, setResizedFieldMetadataItemId] =
|
const [resizedFieldMetadataItemId, setResizedFieldMetadataItemId] =
|
||||||
useRecoilComponentState(resizedFieldMetadataIdComponentState);
|
useRecoilComponentState(resizedFieldMetadataIdComponentState);
|
||||||
|
|
||||||
|
const recordField = visibleRecordFields.find(
|
||||||
|
findByProperty('fieldMetadataItemId', resizedFieldMetadataItemId),
|
||||||
|
);
|
||||||
|
|
||||||
|
const { resetTableRowSelection } = useResetTableRowSelection();
|
||||||
|
|
||||||
const { saveRecordFields } = useSaveRecordFields();
|
const { saveRecordFields } = useSaveRecordFields();
|
||||||
|
|
||||||
const { updateRecordField } = useUpdateRecordField();
|
const { updateRecordField } = useUpdateRecordField();
|
||||||
|
|
||||||
const handleResizeHandlerStart = useCallback<PointerEventListener>(
|
const handleResizeHandlerStart = useCallback<PointerEventListener>(
|
||||||
({ x }) => {
|
({ x }) => {
|
||||||
|
resetTableRowSelection();
|
||||||
setInitialPointerPositionX(x);
|
setInitialPointerPositionX(x);
|
||||||
},
|
},
|
||||||
[],
|
[resetTableRowSelection],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleResizeHandlerMove = useCallback<PointerEventListener>(
|
const handleResizeHandlerMove = useCallback<PointerEventListener>(
|
||||||
({ x }) => {
|
({ x }) => {
|
||||||
if (!initialPointerPositionX) return;
|
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(x - initialPointerPositionX);
|
||||||
},
|
},
|
||||||
[setResizeFieldOffset, initialPointerPositionX],
|
[setResizeFieldOffset, initialPointerPositionX, recordField],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { setDragSelectionStartEnabled } = useDragSelect();
|
||||||
|
|
||||||
const handleResizeHandlerEnd = useRecoilCallback(
|
const handleResizeHandlerEnd = useRecoilCallback(
|
||||||
({ snapshot, set }) =>
|
({ snapshot, set }) =>
|
||||||
async () => {
|
async () => {
|
||||||
const recordField = visibleRecordFields.find(
|
|
||||||
findByProperty('fieldMetadataItemId', resizedFieldMetadataItemId),
|
|
||||||
);
|
|
||||||
|
|
||||||
throwIfNotDefined(recordField, 'recordField');
|
throwIfNotDefined(recordField, 'recordField');
|
||||||
|
|
||||||
if (!resizedFieldMetadataItemId) return;
|
if (!resizedFieldMetadataItemId) return;
|
||||||
@@ -70,10 +87,7 @@ export const useResizeTableHeader = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const nextWidth = Math.round(
|
const nextWidth = Math.round(
|
||||||
Math.max(
|
Math.max(recordField.size + resizeFieldOffset, COLUMN_MIN_WIDTH),
|
||||||
recordField.size + resizeFieldOffset,
|
|
||||||
COLUMN_RESIZE_MIN_WIDTH,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
set(resizeFieldOffsetCallbackState, 0);
|
set(resizeFieldOffsetCallbackState, 0);
|
||||||
@@ -90,6 +104,8 @@ export const useResizeTableHeader = () => {
|
|||||||
|
|
||||||
saveRecordFields([updatedRecordField]);
|
saveRecordFields([updatedRecordField]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setDragSelectionStartEnabled(true);
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
saveRecordFields,
|
saveRecordFields,
|
||||||
@@ -97,7 +113,8 @@ export const useResizeTableHeader = () => {
|
|||||||
resizeFieldOffsetCallbackState,
|
resizeFieldOffsetCallbackState,
|
||||||
setResizedFieldMetadataItemId,
|
setResizedFieldMetadataItemId,
|
||||||
updateRecordField,
|
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 { RecordTableCellCheckbox } from '@/object-record/record-table/record-table-cell/components/RecordTableCellCheckbox';
|
||||||
import { RecordTableCellGrip } from '@/object-record/record-table/record-table-cell/components/RecordTableCellGrip';
|
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 { 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 { 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 { RecordTableRowArrowKeysEffect } from '@/object-record/record-table/record-table-row/components/RecordTableRowArrowKeysEffect';
|
import { RecordTableRowArrowKeysEffect } from '@/object-record/record-table/record-table-row/components/RecordTableRowArrowKeysEffect';
|
||||||
@@ -51,6 +52,7 @@ export const RecordTableRow = ({
|
|||||||
<RecordTableCellGrip />
|
<RecordTableCellGrip />
|
||||||
<RecordTableCellCheckbox />
|
<RecordTableCellCheckbox />
|
||||||
<RecordTableCells />
|
<RecordTableCells />
|
||||||
|
<RecordTablePlusButtonCellPlaceholder />
|
||||||
<RecordTableLastEmptyCell />
|
<RecordTableLastEmptyCell />
|
||||||
<ListenRecordUpdatesEffect
|
<ListenRecordUpdatesEffect
|
||||||
objectNameSingular={objectNameSingular}
|
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 styled from '@emotion/styled';
|
||||||
import { type RefObject, useCallback, useState } from 'react';
|
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 { useDragSelectWithAutoScroll } from '@/ui/utilities/drag-select/hooks/useDragSelectWithAutoScroll';
|
||||||
import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
|
import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||||
import { useDragSelect } from '../hooks/useDragSelect';
|
|
||||||
import { type SelectionBox } from '../types/SelectionBox';
|
import { type SelectionBox } from '../types/SelectionBox';
|
||||||
import { isValidSelectionStart } from '../utils/selectionBoxValidation';
|
import { isValidSelectionStart } from '../utils/selectionBoxValidation';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user