Refactor table header with divs (#14319)
Fixes https://github.com/twentyhq/core-team-issues/issues/1432 We still have some follow-up issues here, I only took care of making the essential of the table work, but the follow-up issues will be better tackled after everything has been switched to div, otherwise we might fix them multiple times. A first round of refactoring of z-index has been made in a common constant, where it is easier to understand, for which case, what should be the order of the different layers. Though we still need to have another round of refactor because of the stacking contexts, mainly because of the header row that is creating a stacking context that makes it hard to have all scrolling cases work.
This commit is contained in:
+2
-7
@@ -1,7 +1,5 @@
|
||||
import { RecordChip } from '@/object-record/components/RecordChip';
|
||||
import { useChipFieldDisplay } from '@/object-record/record-field/ui/meta-types/hooks/useChipFieldDisplay';
|
||||
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { ChipSize } from 'twenty-ui/components';
|
||||
import { useIsMobile } from 'twenty-ui/utilities';
|
||||
@@ -18,12 +16,9 @@ export const ChipFieldDisplay = () => {
|
||||
} = useChipFieldDisplay();
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
const isRecordTableScrolledLeftComponent = useRecoilComponentValue(
|
||||
isRecordTableScrolledLeftComponentState,
|
||||
);
|
||||
|
||||
const isLabelIdentifierCompact =
|
||||
isMobile && !isRecordTableScrolledLeftComponent;
|
||||
// TODO: reimplement scrolled horizontally here.
|
||||
const isLabelIdentifierCompact = isMobile;
|
||||
|
||||
if (!isDefined(recordValue)) {
|
||||
return null;
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { RecordTableStickyBottomEffect } from '@/object-record/record-table/components/RecordTableStickyBottomEffect';
|
||||
import { RecordTableStickyEffect } from '@/object-record/record-table/components/RecordTableStickyEffect';
|
||||
import { StyledTable } from '@/object-record/record-table/components/RecordTableStyles';
|
||||
import { StyledTableDiv } from '@/object-record/record-table/components/RecordTableStyles';
|
||||
import { RecordTableNoRecordGroupBody } from '@/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBody';
|
||||
import { RecordTableRecordGroupsBody } from '@/object-record/record-table/record-table-body/components/RecordTableRecordGroupsBody';
|
||||
import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader';
|
||||
@@ -12,7 +12,7 @@ import styled from '@emotion/styled';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
const StyledTableWithPointerEvents = styled(StyledTable)<{
|
||||
const StyledTableWithPointerEvents = styled(StyledTableDiv)<{
|
||||
isDragging: boolean;
|
||||
}>`
|
||||
& > * {
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
import { StyledTable } from '@/object-record/record-table/components/RecordTableStyles';
|
||||
import { StyledTableDiv } from '@/object-record/record-table/components/RecordTableStyles';
|
||||
import { RecordTableEmptyState } from '@/object-record/record-table/empty-state/components/RecordTableEmptyState';
|
||||
import { RecordTableHeader } from '@/object-record/record-table/record-table-header/components/RecordTableHeader';
|
||||
import styled from '@emotion/styled';
|
||||
@@ -14,9 +14,9 @@ export interface RecordTableEmptyProps {
|
||||
|
||||
export const RecordTableEmpty = ({ tableBodyRef }: RecordTableEmptyProps) => (
|
||||
<StyledEmptyStateContainer>
|
||||
<StyledTable ref={tableBodyRef}>
|
||||
<StyledTableDiv ref={tableBodyRef}>
|
||||
<RecordTableHeader />
|
||||
</StyledTable>
|
||||
</StyledTableDiv>
|
||||
<RecordTableEmptyState />
|
||||
</StyledEmptyStateContainer>
|
||||
);
|
||||
|
||||
+14
-6
@@ -1,6 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
|
||||
import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState';
|
||||
import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState';
|
||||
import { scrollWrapperScrollLeftComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollLeftComponentState';
|
||||
import { scrollWrapperScrollTopComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollTopComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
@@ -11,7 +12,13 @@ export const RecordTableStickyEffect = () => {
|
||||
scrollWrapperScrollTopComponentState,
|
||||
);
|
||||
|
||||
const setIsRecordTableScrolledVertically = useSetRecoilComponentState(
|
||||
isRecordTableScrolledVerticallyComponentState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setIsRecordTableScrolledVertically(scrollTop > 0);
|
||||
|
||||
if (scrollTop > 0) {
|
||||
document
|
||||
.getElementById('record-table-header')
|
||||
@@ -21,18 +28,19 @@ export const RecordTableStickyEffect = () => {
|
||||
.getElementById('record-table-header')
|
||||
?.classList.remove('header-sticky');
|
||||
}
|
||||
}, [scrollTop]);
|
||||
}, [scrollTop, setIsRecordTableScrolledVertically]);
|
||||
|
||||
const scrollLeft = useRecoilComponentValue(
|
||||
scrollWrapperScrollLeftComponentState,
|
||||
);
|
||||
|
||||
const setIsRecordTableScrolledLeft = useSetRecoilComponentState(
|
||||
isRecordTableScrolledLeftComponentState,
|
||||
const setIsRecordTableScrolledHorizontally = useSetRecoilComponentState(
|
||||
isRecordTableScrolledHorizontallyComponentState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setIsRecordTableScrolledLeft(scrollLeft === 0);
|
||||
setIsRecordTableScrolledHorizontally(scrollLeft > 0);
|
||||
|
||||
if (scrollLeft > 0) {
|
||||
document
|
||||
.getElementById('record-table-body')
|
||||
@@ -54,7 +62,7 @@ export const RecordTableStickyEffect = () => {
|
||||
.getElementById('record-table-footer')
|
||||
?.classList.remove('first-columns-sticky');
|
||||
}
|
||||
}, [scrollLeft, setIsRecordTableScrolledLeft]);
|
||||
}, [scrollLeft, setIsRecordTableScrolledHorizontally]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
+1
-2
@@ -1,9 +1,8 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
export const StyledTable = styled.table`
|
||||
export const StyledTableDiv = styled.div`
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border-spacing: 0;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
|
||||
.footer-sticky tr:nth-last-of-type(2) td {
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ const StyledTableContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
width: fit-content;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
|
||||
+28
-8
@@ -1,17 +1,37 @@
|
||||
export const TABLE_Z_INDEX = {
|
||||
base: 1,
|
||||
cell: {
|
||||
default: 1,
|
||||
sticky: 4,
|
||||
editMode: 6,
|
||||
default: 3,
|
||||
sticky: 10,
|
||||
editMode: 20,
|
||||
},
|
||||
footer: {
|
||||
default: 4,
|
||||
stickyColumn: 5,
|
||||
default: 12,
|
||||
stickyColumn: 20,
|
||||
},
|
||||
header: {
|
||||
default: 6,
|
||||
stickyColumn: 10,
|
||||
noScrollAtAll: {
|
||||
hoverPortalCell: 17,
|
||||
headerRow: 13,
|
||||
headerColumnsSticky: 16,
|
||||
headerColumnsNormal: 15,
|
||||
},
|
||||
scrolledBothVerticallyAndHorizontally: {
|
||||
headerColumnsSticky: 13,
|
||||
headerColumnsNormal: 12,
|
||||
headerRow: 11,
|
||||
hoverPortalCell: 2,
|
||||
},
|
||||
scrolledHorizontallyOnly: {
|
||||
headerColumnsSticky: 13,
|
||||
headerColumnsNormal: 12,
|
||||
headerRow: 11,
|
||||
hoverPortalCell: 9,
|
||||
},
|
||||
scrolledVerticallyOnly: {
|
||||
headerColumnsSticky: 14,
|
||||
headerColumnsNormal: 13,
|
||||
headerRow: 12,
|
||||
hoverPortalCell: 11,
|
||||
},
|
||||
columnGrip: 30,
|
||||
};
|
||||
|
||||
+23
-29
@@ -1,38 +1,32 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
|
||||
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
|
||||
|
||||
const StyledTbody = styled.tbody`
|
||||
&.first-columns-sticky {
|
||||
td:nth-of-type(1) {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
z-index: ${TABLE_Z_INDEX.cell.sticky};
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
td:nth-of-type(2) {
|
||||
position: sticky;
|
||||
left: 11px;
|
||||
z-index: ${TABLE_Z_INDEX.cell.sticky};
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
tr:not(:last-child) td:nth-of-type(3) {
|
||||
// Last row is aggregate footer
|
||||
position: sticky;
|
||||
left: 39px;
|
||||
z-index: ${TABLE_Z_INDEX.cell.sticky};
|
||||
transition: transform 0.3s ease;
|
||||
// TODO: re-implement horizontal scroll here after table have been refactored to divs
|
||||
td:nth-of-type(1) {
|
||||
position: sticky;
|
||||
left: 0px;
|
||||
z-index: ${TABLE_Z_INDEX.cell.sticky};
|
||||
}
|
||||
|
||||
&:not(.disable-shadow)::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
height: calc(100% + 2px);
|
||||
width: 4px;
|
||||
right: 0px;
|
||||
box-shadow: ${({ theme }) => theme.boxShadow.light};
|
||||
clip-path: inset(0px -4px 0px 0px);
|
||||
}
|
||||
td:nth-of-type(2) {
|
||||
position: sticky;
|
||||
left: 16px;
|
||||
z-index: ${TABLE_Z_INDEX.cell.sticky};
|
||||
}
|
||||
|
||||
tr:not(:last-child) td:nth-of-type(3) {
|
||||
position: sticky;
|
||||
left: 49px;
|
||||
z-index: ${TABLE_Z_INDEX.cell.sticky};
|
||||
}
|
||||
|
||||
td:nth-of-type(3) {
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
width: ${38}px;
|
||||
max-width: ${38}px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
+23
-12
@@ -2,10 +2,19 @@ import { RecordTableBody } from '@/object-record/record-table/record-table-body/
|
||||
import { RecordTableBodyDroppableContextProvider } from '@/object-record/record-table/record-table-body/contexts/RecordTableBodyDroppableContext';
|
||||
import { recordTableHoverPositionComponentState } from '@/object-record/record-table/states/recordTableHoverPositionComponentState';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import styled from '@emotion/styled';
|
||||
import { Droppable } from '@hello-pangea/dnd';
|
||||
import { type ReactNode, useState } from 'react';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
const StyledTable = styled.table`
|
||||
table-layout: fixed;
|
||||
|
||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type RecordTableBodyDroppableProps = {
|
||||
children: ReactNode;
|
||||
recordGroupId?: string;
|
||||
@@ -30,19 +39,21 @@ export const RecordTableBodyDroppable = ({
|
||||
isDropDisabled={isDropDisabled}
|
||||
>
|
||||
{(provided) => (
|
||||
<RecordTableBody
|
||||
id={recordTableBodyId}
|
||||
ref={provided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...provided.droppableProps}
|
||||
onMouseLeave={() => setRecordTableHoverPosition(null)}
|
||||
>
|
||||
<RecordTableBodyDroppableContextProvider
|
||||
value={{ droppablePlaceholder: provided.placeholder }}
|
||||
<StyledTable>
|
||||
<RecordTableBody
|
||||
id={recordTableBodyId}
|
||||
ref={provided.innerRef}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...provided.droppableProps}
|
||||
onMouseLeave={() => setRecordTableHoverPosition(null)}
|
||||
>
|
||||
{children}
|
||||
</RecordTableBodyDroppableContextProvider>
|
||||
</RecordTableBody>
|
||||
<RecordTableBodyDroppableContextProvider
|
||||
value={{ droppablePlaceholder: provided.placeholder }}
|
||||
>
|
||||
{children}
|
||||
</RecordTableBodyDroppableContextProvider>
|
||||
</RecordTableBody>
|
||||
</StyledTable>
|
||||
)}
|
||||
</Droppable>
|
||||
);
|
||||
|
||||
+2
-1
@@ -5,7 +5,7 @@ import { useRecordTableRowDraggableContextOrThrow } from '@/object-record/record
|
||||
import { RecordTableTd } from '@/object-record/record-table/record-table-cell/components/RecordTableTd';
|
||||
import { IconListViewGrip } from 'twenty-ui/input';
|
||||
|
||||
export const TABLE_CELL_GRIP_WIDTH = '16px';
|
||||
export const TABLE_CELL_GRIP_WIDTH = 16;
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
height: 32px;
|
||||
@@ -40,6 +40,7 @@ export const RecordTableCellGrip = () => {
|
||||
data-select-disable
|
||||
hasRightBorder={false}
|
||||
hasBottomBorder={false}
|
||||
width={TABLE_CELL_GRIP_WIDTH}
|
||||
>
|
||||
<StyledContainer>
|
||||
<StyledIconWrapper className="icon" isDragging={isDragging}>
|
||||
|
||||
+24
@@ -6,12 +6,15 @@ import styled from '@emotion/styled';
|
||||
import { FieldDisplay } from '@/object-record/record-field/ui/components/FieldDisplay';
|
||||
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
|
||||
import { useIsFieldInputOnly } from '@/object-record/record-field/ui/hooks/useIsFieldInputOnly';
|
||||
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
|
||||
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
|
||||
import { RecordTableCellDisplayMode } from '@/object-record/record-table/record-table-cell/components/RecordTableCellDisplayMode';
|
||||
import { RecordTableCellEditButton } from '@/object-record/record-table/record-table-cell/components/RecordTableCellEditButton';
|
||||
import { RecordTableCellEditMode } from '@/object-record/record-table/record-table-cell/components/RecordTableCellEditMode';
|
||||
import { RecordTableCellFieldInput } from '@/object-record/record-table/record-table-cell/components/RecordTableCellFieldInput';
|
||||
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||
import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState';
|
||||
import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState';
|
||||
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
||||
import { useContext } from 'react';
|
||||
import { BORDER_COMMON } from 'twenty-ui/theme';
|
||||
@@ -20,6 +23,7 @@ import { useIsMobile } from 'twenty-ui/utilities';
|
||||
const StyledRecordTableCellHoveredPortalContent = styled.div<{
|
||||
isReadOnly: boolean;
|
||||
isRowActive: boolean;
|
||||
zIndex: number;
|
||||
}>`
|
||||
align-items: center;
|
||||
background: ${({ theme }) => theme.background.transparent.secondary};
|
||||
@@ -42,6 +46,8 @@ const StyledRecordTableCellHoveredPortalContent = styled.div<{
|
||||
|
||||
position: relative;
|
||||
user-select: none;
|
||||
|
||||
z-index: ${({ zIndex }) => zIndex};
|
||||
`;
|
||||
|
||||
const RecordTableCellHoveredPortalContent = () => {
|
||||
@@ -67,10 +73,28 @@ const RecordTableCellHoveredPortalContent = () => {
|
||||
rowIndex,
|
||||
);
|
||||
|
||||
const isRecordTableScrolledHorizontally = useRecoilComponentValue(
|
||||
isRecordTableScrolledHorizontallyComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableScrolledVertically = useRecoilComponentValue(
|
||||
isRecordTableScrolledVerticallyComponentState,
|
||||
);
|
||||
|
||||
const computedZIndex =
|
||||
isRecordTableScrolledHorizontally && isRecordTableScrolledVertically
|
||||
? TABLE_Z_INDEX.scrolledBothVerticallyAndHorizontally.hoverPortalCell
|
||||
: isRecordTableScrolledHorizontally
|
||||
? TABLE_Z_INDEX.scrolledHorizontallyOnly.hoverPortalCell
|
||||
: isRecordTableScrolledVertically
|
||||
? TABLE_Z_INDEX.scrolledVerticallyOnly.hoverPortalCell
|
||||
: TABLE_Z_INDEX.noScrollAtAll.hoverPortalCell;
|
||||
|
||||
return (
|
||||
<StyledRecordTableCellHoveredPortalContent
|
||||
isReadOnly={isReadOnly}
|
||||
isRowActive={isRowActive}
|
||||
zIndex={computedZIndex}
|
||||
>
|
||||
{isFieldInputOnly ? (
|
||||
<RecordTableCellEditMode>
|
||||
|
||||
+9
-27
@@ -1,22 +1,23 @@
|
||||
import { type DraggableProvidedDragHandleProps } from '@hello-pangea/dnd';
|
||||
import { styled } from '@linaria/react';
|
||||
import { type ReactNode, useContext } from 'react';
|
||||
import { MOBILE_VIEWPORT, ThemeContext } from 'twenty-ui/theme';
|
||||
import { ThemeContext } from 'twenty-ui/theme';
|
||||
|
||||
export const RECORD_TABLE_TD_WIDTH = '32px';
|
||||
export const DEFAULT_RECORD_TABLE_TD_WIDTH = 32;
|
||||
|
||||
const StyledTd = styled.td<{
|
||||
backgroundColor: string;
|
||||
borderColor: string;
|
||||
isDragging?: boolean;
|
||||
fontColor: string;
|
||||
sticky?: boolean;
|
||||
freezeFirstColumns?: boolean;
|
||||
left?: number;
|
||||
hasRightBorder?: boolean;
|
||||
hasBottomBorder?: boolean;
|
||||
width?: number;
|
||||
width: number;
|
||||
}>`
|
||||
min-width: ${({ width }) => width}px;
|
||||
width: ${({ width }) => width}px;
|
||||
max-width: ${({ width }) => width}px;
|
||||
|
||||
border-bottom: 1px solid
|
||||
${({ borderColor, hasBottomBorder, isDragging }) =>
|
||||
hasBottomBorder && !isDragging ? borderColor : 'transparent'};
|
||||
@@ -26,46 +27,31 @@ const StyledTd = styled.td<{
|
||||
hasRightBorder && !isDragging ? `1px solid ${borderColor}` : 'none'};
|
||||
|
||||
padding: 0;
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
text-align: left;
|
||||
|
||||
background: ${({ backgroundColor, isDragging }) =>
|
||||
isDragging ? 'transparent' : backgroundColor};
|
||||
|
||||
${({ freezeFirstColumns }) =>
|
||||
freezeFirstColumns
|
||||
? `@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
width: ${RECORD_TABLE_TD_WIDTH};
|
||||
max-width: ${RECORD_TABLE_TD_WIDTH};
|
||||
}`
|
||||
: ''}
|
||||
// TODO: reimplement horizontal scroll here once we have refactored body with divs
|
||||
`;
|
||||
|
||||
export const RecordTableTd = ({
|
||||
children,
|
||||
isSelected,
|
||||
isDragging,
|
||||
sticky,
|
||||
freezeFirstColumns,
|
||||
left,
|
||||
hasRightBorder = true,
|
||||
hasBottomBorder = true,
|
||||
width,
|
||||
colSpan,
|
||||
width = DEFAULT_RECORD_TABLE_TD_WIDTH,
|
||||
...dragHandleProps
|
||||
}: {
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
isSelected?: boolean;
|
||||
isDragging?: boolean;
|
||||
sticky?: boolean;
|
||||
freezeFirstColumns?: boolean;
|
||||
hasRightBorder?: boolean;
|
||||
hasBottomBorder?: boolean;
|
||||
left?: number;
|
||||
width?: number;
|
||||
colSpan?: number;
|
||||
} & (Partial<DraggableProvidedDragHandleProps> | null)) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
@@ -83,13 +69,9 @@ export const RecordTableTd = ({
|
||||
backgroundColor={tdBackgroundColor}
|
||||
borderColor={borderColor}
|
||||
fontColor={fontColor}
|
||||
sticky={sticky}
|
||||
freezeFirstColumns={freezeFirstColumns}
|
||||
left={left}
|
||||
hasRightBorder={hasRightBorder}
|
||||
hasBottomBorder={hasBottomBorder}
|
||||
width={width}
|
||||
colSpan={colSpan}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...dragHandleProps}
|
||||
>
|
||||
|
||||
+29
-28
@@ -20,44 +20,45 @@ const StyledTableRow = styled.tr<{
|
||||
position: sticky;
|
||||
border: none;
|
||||
|
||||
&.footer-sticky {
|
||||
td {
|
||||
border-top: ${({ theme }) => `1px solid ${theme.border.color.light}`};
|
||||
z-index: ${TABLE_Z_INDEX.footer.default};
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
}
|
||||
// TODO: see how we reimplement horizontal scrolling after all table has been refactored
|
||||
td {
|
||||
border-top: ${({ theme }) => `1px solid ${theme.border.color.light}`};
|
||||
z-index: ${TABLE_Z_INDEX.footer.default};
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
cursor: pointer;
|
||||
td:nth-of-type(1) {
|
||||
width: ${FIRST_TH_WIDTH};
|
||||
left: 0;
|
||||
border-top: none;
|
||||
}
|
||||
&.first-columns-sticky {
|
||||
td:nth-of-type(1) {
|
||||
z-index: ${TABLE_Z_INDEX.footer.stickyColumn};
|
||||
|
||||
td:nth-of-type(1) {
|
||||
position: sticky;
|
||||
z-index: ${TABLE_Z_INDEX.footer.stickyColumn};
|
||||
}
|
||||
td:nth-of-type(2) {
|
||||
position: sticky;
|
||||
z-index: ${TABLE_Z_INDEX.footer.stickyColumn};
|
||||
transition: 0.3s ease;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
height: calc(100% + 2px);
|
||||
width: 4px;
|
||||
right: 0px;
|
||||
box-shadow: ${({ theme }) => theme.boxShadow.light};
|
||||
clip-path: inset(0px -4px 0px 0px);
|
||||
}
|
||||
td:nth-of-type(2) {
|
||||
position: sticky;
|
||||
z-index: ${TABLE_Z_INDEX.footer.stickyColumn};
|
||||
transition: 0.3s ease;
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
height: calc(100% + 2px);
|
||||
width: 4px;
|
||||
right: 0px;
|
||||
box-shadow: ${({ theme }) => theme.boxShadow.light};
|
||||
clip-path: inset(0px -4px 0px 0px);
|
||||
}
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
width: 34px;
|
||||
max-width: 34px;
|
||||
}
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
width: 38px;
|
||||
max-width: 38px;
|
||||
}
|
||||
}
|
||||
|
||||
background: ${({ theme }) => theme.background.primary};
|
||||
${({ hasHorizontalOverflow }) =>
|
||||
`.footer-sticky {
|
||||
|
||||
+6
-4
@@ -1,7 +1,7 @@
|
||||
import { css, useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
|
||||
import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState';
|
||||
|
||||
import { fieldMetadataItemByIdSelector } from '@/object-metadata/states/fieldMetadataItemByIdSelector';
|
||||
import { isFieldMetadataItemLabelIdentifierSelector } from '@/object-metadata/states/isFieldMetadataItemLabelIdentifierSelector';
|
||||
@@ -61,8 +61,8 @@ export const RecordTableColumnHead = ({
|
||||
}),
|
||||
);
|
||||
|
||||
const isRecordTableScrolledLeft = useRecoilComponentValue(
|
||||
isRecordTableScrolledLeftComponentState,
|
||||
const isRecordTableScrolledHorizontally = useRecoilComponentValue(
|
||||
isRecordTableScrolledHorizontallyComponentState,
|
||||
);
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
@@ -77,7 +77,9 @@ export const RecordTableColumnHead = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledTitle hideTitle={isLabelIdentifier && !isRecordTableScrolledLeft}>
|
||||
<StyledTitle
|
||||
hideTitle={isLabelIdentifier && isRecordTableScrolledHorizontally}
|
||||
>
|
||||
<StyledIcon>
|
||||
<Icon size={theme.icon.size.md} />
|
||||
</StyledIcon>
|
||||
|
||||
+108
-67
@@ -2,99 +2,140 @@ import styled from '@emotion/styled';
|
||||
|
||||
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
import { RecordTableHeaderAddColumnButton } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderAddColumnButton';
|
||||
import { RecordTableHeaderCell } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderCell';
|
||||
import { RecordTableHeaderCheckboxColumn } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderCheckboxColumn';
|
||||
import { RecordTableHeaderDragDropColumn } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderDragDropColumn';
|
||||
import { RecordTableHeaderLastColumn } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderLastColumn';
|
||||
import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState';
|
||||
import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
|
||||
|
||||
export const FIRST_TH_WIDTH = '10px';
|
||||
|
||||
const StyledTableHead = styled.thead`
|
||||
const StyledTableHead = styled.div<{
|
||||
stickyColumnZIndex: number;
|
||||
normalColumnZIndex: number;
|
||||
entireRowZIndex: number;
|
||||
}>`
|
||||
cursor: pointer;
|
||||
|
||||
th:nth-of-type(1) {
|
||||
width: ${FIRST_TH_WIDTH};
|
||||
left: 0;
|
||||
border-right-color: ${({ theme }) => theme.background.primary};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
height: 32px;
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
|
||||
div.header-cell:nth-of-type(n + 3) {
|
||||
z-index: ${({ normalColumnZIndex }) => normalColumnZIndex};
|
||||
}
|
||||
|
||||
th:nth-of-type(2) {
|
||||
border-right-color: ${({ theme }) => theme.background.primary};
|
||||
div.header-cell:nth-of-type(1) {
|
||||
position: sticky;
|
||||
left: 0px;
|
||||
z-index: ${({ stickyColumnZIndex }) => stickyColumnZIndex};
|
||||
transition: 0.3s ease;
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
}
|
||||
|
||||
&.first-columns-sticky {
|
||||
th:nth-of-type(1) {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
z-index: ${TABLE_Z_INDEX.header.default};
|
||||
transition: 0.3s ease;
|
||||
}
|
||||
div.header-cell:nth-of-type(2) {
|
||||
position: sticky;
|
||||
left: 17px;
|
||||
top: 0;
|
||||
z-index: ${({ stickyColumnZIndex }) => stickyColumnZIndex};
|
||||
transition: 0.3s ease;
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
}
|
||||
|
||||
th:nth-of-type(2) {
|
||||
position: sticky;
|
||||
left: 11px;
|
||||
z-index: ${TABLE_Z_INDEX.header.default};
|
||||
transition: 0.3s ease;
|
||||
}
|
||||
div.header-cell:nth-of-type(3) {
|
||||
position: sticky;
|
||||
left: 49px;
|
||||
right: 0;
|
||||
z-index: ${({ stickyColumnZIndex }) => stickyColumnZIndex};
|
||||
transition: 0.3s ease;
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
|
||||
th:nth-of-type(3) {
|
||||
position: sticky;
|
||||
left: 39px;
|
||||
z-index: ${TABLE_Z_INDEX.header.default};
|
||||
transition: 0.3s ease;
|
||||
// &::after {
|
||||
// content: '';
|
||||
// position: absolute;
|
||||
// top: -1px;
|
||||
// height: calc(100% + 2px);
|
||||
// width: 4px;
|
||||
// right: 0px;
|
||||
// box-shadow: ${({ theme }) => theme.boxShadow.light};
|
||||
// clip-path: inset(0px -4px 0px 0px);
|
||||
// }
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
height: calc(100% + 2px);
|
||||
width: 4px;
|
||||
right: 0px;
|
||||
box-shadow: ${({ theme }) => theme.boxShadow.light};
|
||||
clip-path: inset(0px -4px 0px 0px);
|
||||
}
|
||||
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
width: 34px;
|
||||
max-width: 34px;
|
||||
}
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
width: 38px;
|
||||
max-width: 38px;
|
||||
min-width: 38px;
|
||||
}
|
||||
}
|
||||
|
||||
&.header-sticky {
|
||||
th {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: ${TABLE_Z_INDEX.header.default};
|
||||
}
|
||||
}
|
||||
|
||||
&.header-sticky.first-columns-sticky {
|
||||
th:nth-of-type(1),
|
||||
th:nth-of-type(2),
|
||||
th:nth-of-type(3) {
|
||||
z-index: ${TABLE_Z_INDEX.header.stickyColumn};
|
||||
}
|
||||
}
|
||||
position: sticky;
|
||||
top: 0px;
|
||||
z-index: ${({ entireRowZIndex }) => entireRowZIndex};
|
||||
`;
|
||||
|
||||
export const RecordTableHeader = () => {
|
||||
const { visibleRecordFields } = useRecordTableContextOrThrow();
|
||||
|
||||
const isRecordTableScrolledHorizontally = useRecoilComponentValue(
|
||||
isRecordTableScrolledHorizontallyComponentState,
|
||||
);
|
||||
|
||||
const isRecordTableScrolledVertically = useRecoilComponentValue(
|
||||
isRecordTableScrolledVerticallyComponentState,
|
||||
);
|
||||
|
||||
const computedStickyColumnZIndex =
|
||||
isRecordTableScrolledHorizontally && isRecordTableScrolledVertically
|
||||
? TABLE_Z_INDEX.scrolledBothVerticallyAndHorizontally.headerColumnsSticky
|
||||
: isRecordTableScrolledHorizontally
|
||||
? TABLE_Z_INDEX.scrolledHorizontallyOnly.headerColumnsSticky
|
||||
: isRecordTableScrolledVertically
|
||||
? TABLE_Z_INDEX.scrolledVerticallyOnly.headerColumnsSticky
|
||||
: TABLE_Z_INDEX.noScrollAtAll.headerColumnsSticky;
|
||||
|
||||
const computedNormalColumnZIndex =
|
||||
isRecordTableScrolledHorizontally && isRecordTableScrolledVertically
|
||||
? TABLE_Z_INDEX.scrolledBothVerticallyAndHorizontally.headerColumnsNormal
|
||||
: isRecordTableScrolledHorizontally
|
||||
? TABLE_Z_INDEX.scrolledHorizontallyOnly.headerColumnsNormal
|
||||
: isRecordTableScrolledVertically
|
||||
? TABLE_Z_INDEX.scrolledVerticallyOnly.headerColumnsNormal
|
||||
: TABLE_Z_INDEX.noScrollAtAll.headerColumnsNormal;
|
||||
|
||||
const computedHeaderRowZIndex =
|
||||
isRecordTableScrolledHorizontally && isRecordTableScrolledVertically
|
||||
? TABLE_Z_INDEX.scrolledBothVerticallyAndHorizontally.headerRow
|
||||
: isRecordTableScrolledHorizontally
|
||||
? TABLE_Z_INDEX.scrolledHorizontallyOnly.headerRow
|
||||
: isRecordTableScrolledVertically
|
||||
? TABLE_Z_INDEX.scrolledVerticallyOnly.headerRow
|
||||
: TABLE_Z_INDEX.noScrollAtAll.headerRow;
|
||||
|
||||
return (
|
||||
<StyledTableHead id="record-table-header" data-select-disable>
|
||||
<tr>
|
||||
<RecordTableHeaderDragDropColumn />
|
||||
<RecordTableHeaderCheckboxColumn />
|
||||
{visibleRecordFields.map((recordField) => (
|
||||
<RecordTableHeaderCell
|
||||
key={recordField.fieldMetadataItemId}
|
||||
recordField={recordField}
|
||||
/>
|
||||
))}
|
||||
<RecordTableHeaderLastColumn />
|
||||
</tr>
|
||||
<StyledTableHead
|
||||
id="record-table-header"
|
||||
data-select-disable
|
||||
stickyColumnZIndex={computedStickyColumnZIndex}
|
||||
normalColumnZIndex={computedNormalColumnZIndex}
|
||||
entireRowZIndex={computedHeaderRowZIndex}
|
||||
>
|
||||
<RecordTableHeaderDragDropColumn />
|
||||
<RecordTableHeaderCheckboxColumn />
|
||||
{visibleRecordFields.map((recordField) => (
|
||||
<RecordTableHeaderCell
|
||||
key={recordField.fieldMetadataItemId}
|
||||
recordField={recordField}
|
||||
/>
|
||||
))}
|
||||
<RecordTableHeaderAddColumnButton />
|
||||
<RecordTableHeaderLastColumn />
|
||||
</StyledTableHead>
|
||||
);
|
||||
};
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { HIDDEN_TABLE_COLUMN_DROPDOWN_ID } from '@/object-record/record-table/constants/HiddenTableColumnDropdownId';
|
||||
import { RecordTableHeaderPlusButtonContent } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderPlusButtonContent';
|
||||
import { 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 }) =>
|
||||
isFirstRowActiveOrFocused
|
||||
? 'none'
|
||||
: `1px solid ${theme.border.color.light}`};
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
border-right: ${({ theme }) => theme.border.color.light} !important;
|
||||
|
||||
cursor: default;
|
||||
|
||||
width: 32px;
|
||||
|
||||
z-index: 1;
|
||||
|
||||
&:hover {
|
||||
background: ${({ theme }) => theme.background.transparent.secondary};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledPlusIconContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 32px;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const StyledDropdownContainer = styled.div`
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const RecordTableHeaderAddColumnButton = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
const { scrollWrapperHTMLElement } = useScrollWrapperElement();
|
||||
|
||||
const isTableWiderThanScreen =
|
||||
(scrollWrapperHTMLElement?.clientWidth ?? 0) <
|
||||
(scrollWrapperHTMLElement?.scrollWidth ?? 0);
|
||||
|
||||
const isFirstRowActive = useRecoilComponentFamilyValue(
|
||||
isRecordTableRowActiveComponentFamilyState,
|
||||
0,
|
||||
);
|
||||
|
||||
const isFirstRowFocused = useRecoilComponentFamilyValue(
|
||||
isRecordTableRowFocusedComponentFamilyState,
|
||||
0,
|
||||
);
|
||||
|
||||
const isFirstRowActiveOrFocused = isFirstRowActive || isFirstRowFocused;
|
||||
|
||||
return (
|
||||
<StyledPlusIconHeaderCell
|
||||
isTableWiderThanScreen={isTableWiderThanScreen}
|
||||
isFirstRowActiveOrFocused={isFirstRowActiveOrFocused}
|
||||
>
|
||||
<StyledDropdownContainer>
|
||||
<Dropdown
|
||||
dropdownId={HIDDEN_TABLE_COLUMN_DROPDOWN_ID}
|
||||
clickableComponent={
|
||||
<StyledPlusIconContainer>
|
||||
<IconPlus size={theme.icon.size.md} />
|
||||
</StyledPlusIconContainer>
|
||||
}
|
||||
dropdownComponents={<RecordTableHeaderPlusButtonContent />}
|
||||
dropdownPlacement="bottom-start"
|
||||
/>
|
||||
</StyledDropdownContainer>
|
||||
</StyledPlusIconHeaderCell>
|
||||
);
|
||||
};
|
||||
+18
-24
@@ -12,7 +12,6 @@ import { useCreateNewIndexRecord } from '@/object-record/record-table/hooks/useC
|
||||
import { RecordTableColumnHeadWithDropdown } from '@/object-record/record-table/record-table-header/components/RecordTableColumnHeadWithDropdown';
|
||||
import { isRecordTableRowActiveComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowActiveComponentFamilyState';
|
||||
import { isRecordTableRowFocusedComponentFamilyState } from '@/object-record/record-table/states/isRecordTableRowFocusedComponentFamilyState';
|
||||
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
|
||||
import { resizeFieldOffsetComponentState } from '@/object-record/record-table/states/resizeFieldOffsetComponentState';
|
||||
import { useTrackPointer } from '@/ui/utilities/pointer-event/hooks/useTrackPointer';
|
||||
import { type PointerEventListener } from '@/ui/utilities/pointer-event/types/PointerEventListener';
|
||||
@@ -20,7 +19,6 @@ import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { useRecoilComponentCallbackState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackState';
|
||||
import { useRecoilComponentFamilyValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValue';
|
||||
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||
import { useSaveRecordFields } from '@/views/hooks/useSaveRecordFields';
|
||||
import { throwIfNotDefined } from 'twenty-shared/utils';
|
||||
@@ -29,26 +27,24 @@ import { LightIconButton } from 'twenty-ui/input';
|
||||
|
||||
const COLUMN_MIN_WIDTH = 104;
|
||||
|
||||
const StyledColumnHeaderCell = styled.th<{
|
||||
const StyledColumnHeaderCell = styled.div<{
|
||||
columnWidth: number;
|
||||
isResizing?: boolean;
|
||||
isFirstRowActiveOrFocused: boolean;
|
||||
}>`
|
||||
border-bottom: ${({ isFirstRowActiveOrFocused, theme }) =>
|
||||
isFirstRowActiveOrFocused
|
||||
? 'none'
|
||||
: `1px solid ${theme.border.color.light}`};
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
|
||||
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;
|
||||
`}
|
||||
position: relative;
|
||||
user-select: none;
|
||||
${({ theme }) => {
|
||||
return `
|
||||
@@ -96,8 +92,6 @@ const StyledColumnHeadContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
& > :first-of-type {
|
||||
flex: 1;
|
||||
@@ -202,10 +196,6 @@ export const RecordTableHeaderCell = ({
|
||||
onMouseUp: handleResizeHandlerEnd,
|
||||
});
|
||||
|
||||
const isRecordTableScrolledLeft = useRecoilComponentValue(
|
||||
isRecordTableScrolledLeftComponentState,
|
||||
);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const { labelIdentifierFieldMetadataItem } = useRecordIndexContextOrThrow();
|
||||
@@ -213,8 +203,7 @@ export const RecordTableHeaderCell = ({
|
||||
const isLabelIdentifier =
|
||||
recordField.fieldMetadataItemId === labelIdentifierFieldMetadataItem?.id;
|
||||
|
||||
const disableColumnResize =
|
||||
isLabelIdentifier && isMobile && !isRecordTableScrolledLeft;
|
||||
const disableColumnResize = isLabelIdentifier || isMobile;
|
||||
|
||||
const { createNewIndexRecord } = useCreateNewIndexRecord({
|
||||
objectMetadataItem,
|
||||
@@ -241,22 +230,27 @@ export const RecordTableHeaderCell = ({
|
||||
0,
|
||||
);
|
||||
|
||||
const widthOffsetWhileResizing =
|
||||
resizedFieldMetadataItemId === recordField.fieldMetadataItemId
|
||||
? resizeFieldOffset
|
||||
: 0;
|
||||
|
||||
const baseWidth = recordField?.size ?? 0;
|
||||
|
||||
const computedDynamicWidth = baseWidth + widthOffsetWhileResizing;
|
||||
|
||||
const columnWidth = Math.max(computedDynamicWidth, COLUMN_MIN_WIDTH);
|
||||
|
||||
const isFirstRowActiveOrFocused = isFirstRowActive || isFirstRowFocused;
|
||||
|
||||
return (
|
||||
<StyledColumnHeaderCell
|
||||
className="header-cell"
|
||||
key={recordField.fieldMetadataItemId}
|
||||
isResizing={
|
||||
resizedFieldMetadataItemId === recordField.fieldMetadataItemId
|
||||
}
|
||||
columnWidth={Math.max(
|
||||
(recordField?.size ?? 0) +
|
||||
(resizedFieldMetadataItemId === recordField.fieldMetadataItemId
|
||||
? resizeFieldOffset
|
||||
: 0) +
|
||||
24,
|
||||
COLUMN_MIN_WIDTH,
|
||||
)}
|
||||
columnWidth={columnWidth}
|
||||
onMouseEnter={() => setIconVisibility(true)}
|
||||
onMouseLeave={() => setIconVisibility(false)}
|
||||
isFirstRowActiveOrFocused={isFirstRowActiveOrFocused}
|
||||
|
||||
+7
-7
@@ -17,21 +17,20 @@ const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
height: 32px;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
min-width: 24px;
|
||||
padding-right: ${({ theme }) => theme.spacing(1)};
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
`;
|
||||
|
||||
const StyledColumnHeaderCell = styled.th<{
|
||||
const StyledColumnHeaderCell = styled.div<{
|
||||
isFirstRowActiveOrFocused: boolean;
|
||||
}>`
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
border-bottom: ${({ isFirstRowActiveOrFocused, theme }) =>
|
||||
isFirstRowActiveOrFocused
|
||||
? 'none'
|
||||
: `1px solid ${theme.border.color.light}`};
|
||||
width: 28px;
|
||||
|
||||
min-width: 32px;
|
||||
box-sizing: border-box;
|
||||
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
`;
|
||||
|
||||
export const RecordTableHeaderCheckboxColumn = () => {
|
||||
@@ -85,6 +84,7 @@ export const RecordTableHeaderCheckboxColumn = () => {
|
||||
return (
|
||||
<StyledColumnHeaderCell
|
||||
isFirstRowActiveOrFocused={isFirstRowActiveOrFocused}
|
||||
className="header-cell"
|
||||
>
|
||||
<StyledContainer data-select-disable>
|
||||
<Checkbox
|
||||
|
||||
+12
-5
@@ -2,14 +2,21 @@ import { styled } from '@linaria/react';
|
||||
import { useContext } from 'react';
|
||||
import { ThemeContext } from 'twenty-ui/theme';
|
||||
|
||||
const StyledTh = styled.th<{ backgroundColor: string }>`
|
||||
background: ${({ backgroundColor }) => backgroundColor};
|
||||
border-bottom: none;
|
||||
border-top: none;
|
||||
const StyledTh = styled.div<{ backgroundColor: string }>`
|
||||
background-color: ${({ backgroundColor }) => backgroundColor};
|
||||
min-width: 17px;
|
||||
min-height: 100%;
|
||||
|
||||
border-bottom: 1px solid ${({ backgroundColor }) => backgroundColor};
|
||||
`;
|
||||
|
||||
export const RecordTableHeaderDragDropColumn = () => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
return <StyledTh backgroundColor={theme.background.primary}></StyledTh>;
|
||||
return (
|
||||
<StyledTh
|
||||
className="header-cell"
|
||||
backgroundColor={theme.background.primary}
|
||||
></StyledTh>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-81
@@ -1,93 +1,16 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { HIDDEN_TABLE_COLUMN_DROPDOWN_ID } from '@/object-record/record-table/constants/HiddenTableColumnDropdownId';
|
||||
import { RecordTableHeaderPlusButtonContent } from '@/object-record/record-table/record-table-header/components/RecordTableHeaderPlusButtonContent';
|
||||
import { 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 StyledLastColumnHeader = styled.div`
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
|
||||
const StyledPlusIconHeaderCell = styled.th<{
|
||||
isTableWiderThanScreen: boolean;
|
||||
isFirstRowActiveOrFocused: boolean;
|
||||
}>`
|
||||
border-bottom: ${({ isFirstRowActiveOrFocused, theme }) =>
|
||||
isFirstRowActiveOrFocused
|
||||
? 'none'
|
||||
: `1px solid ${theme.border.color.light}`};
|
||||
background-color: ${({ theme }) => theme.background.primary};
|
||||
border-left: none !important;
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
border-right: none !important;
|
||||
cursor: default;
|
||||
|
||||
${({ isTableWiderThanScreen, theme }) =>
|
||||
isTableWiderThanScreen
|
||||
? `
|
||||
background-color: ${theme.background.primary};
|
||||
width: 32px;
|
||||
`
|
||||
: 'width: 100%'};
|
||||
z-index: 1;
|
||||
|
||||
&:hover {
|
||||
background: ${({ theme }) => theme.background.transparent.secondary};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledPlusIconContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
justify-content: flex-start;
|
||||
margin-left: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledDropdownContainer = styled.div`
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
export const RecordTableHeaderLastColumn = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
const { scrollWrapperHTMLElement } = useScrollWrapperElement();
|
||||
|
||||
const isTableWiderThanScreen =
|
||||
(scrollWrapperHTMLElement?.clientWidth ?? 0) <
|
||||
(scrollWrapperHTMLElement?.scrollWidth ?? 0);
|
||||
|
||||
const isFirstRowActive = useRecoilComponentFamilyValue(
|
||||
isRecordTableRowActiveComponentFamilyState,
|
||||
0,
|
||||
);
|
||||
|
||||
const isFirstRowFocused = useRecoilComponentFamilyValue(
|
||||
isRecordTableRowFocusedComponentFamilyState,
|
||||
0,
|
||||
);
|
||||
|
||||
const isFirstRowActiveOrFocused = isFirstRowActive || isFirstRowFocused;
|
||||
|
||||
return (
|
||||
<StyledPlusIconHeaderCell
|
||||
isTableWiderThanScreen={isTableWiderThanScreen}
|
||||
isFirstRowActiveOrFocused={isFirstRowActiveOrFocused}
|
||||
>
|
||||
<StyledDropdownContainer>
|
||||
<Dropdown
|
||||
dropdownId={HIDDEN_TABLE_COLUMN_DROPDOWN_ID}
|
||||
clickableComponent={
|
||||
<StyledPlusIconContainer>
|
||||
<IconPlus size={theme.icon.size.md} />
|
||||
</StyledPlusIconContainer>
|
||||
}
|
||||
dropdownComponents={<RecordTableHeaderPlusButtonContent />}
|
||||
dropdownPlacement="bottom-start"
|
||||
/>
|
||||
</StyledDropdownContainer>
|
||||
</StyledPlusIconHeaderCell>
|
||||
);
|
||||
return <StyledLastColumnHeader></StyledLastColumnHeader>;
|
||||
};
|
||||
|
||||
+1
@@ -11,6 +11,7 @@ export const RecordTableCellsEmpty = () => {
|
||||
<RecordTableTd
|
||||
isSelected={isSelected}
|
||||
key={recordField.fieldMetadataItemId}
|
||||
width={recordField.size}
|
||||
/>
|
||||
));
|
||||
};
|
||||
|
||||
+2
-1
@@ -19,8 +19,9 @@ const StyledTr = styled.tr<{
|
||||
isDragging
|
||||
? `1px solid ${theme.border.color.medium}`
|
||||
: '1px solid transparent'};
|
||||
|
||||
border-left: none;
|
||||
position: relative;
|
||||
transition: border-left-color 0.2s ease-in-out;
|
||||
|
||||
&[data-next-row-active-or-focused='true'] {
|
||||
td {
|
||||
|
||||
+6
-28
@@ -1,7 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useCurrentRecordGroupId } from '@/object-record/record-group/hooks/useCurrentRecordGroupId';
|
||||
import { isRecordTableScrolledLeftComponentState } from '@/object-record/record-table/states/isRecordTableScrolledLeftComponentState';
|
||||
import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState';
|
||||
import { scrollWrapperScrollLeftComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollLeftComponentState';
|
||||
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
@@ -11,38 +11,16 @@ export const RecordTableRecordGroupStickyEffect = () => {
|
||||
scrollWrapperScrollLeftComponentState,
|
||||
);
|
||||
|
||||
const setIsRecordTableScrolledLeft = useSetRecoilComponentState(
|
||||
isRecordTableScrolledLeftComponentState,
|
||||
const setIsRecordTableScrolledHorizontally = useSetRecoilComponentState(
|
||||
isRecordTableScrolledHorizontallyComponentState,
|
||||
);
|
||||
|
||||
const currentRecordGroupId = useCurrentRecordGroupId();
|
||||
|
||||
useEffect(() => {
|
||||
setIsRecordTableScrolledLeft(scrollLeft === 0);
|
||||
if (scrollLeft > 0) {
|
||||
document
|
||||
.getElementById(
|
||||
`record-table-footer${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`,
|
||||
)
|
||||
?.classList.add('first-columns-sticky');
|
||||
document
|
||||
.getElementById(
|
||||
`record-table-body${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`,
|
||||
)
|
||||
?.classList.add('first-columns-sticky');
|
||||
} else {
|
||||
document
|
||||
.getElementById(
|
||||
`record-table-footer${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`,
|
||||
)
|
||||
?.classList.remove('first-columns-sticky');
|
||||
document
|
||||
.getElementById(
|
||||
`record-table-body${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`,
|
||||
)
|
||||
?.classList.remove('first-columns-sticky');
|
||||
}
|
||||
}, [currentRecordGroupId, scrollLeft, setIsRecordTableScrolledLeft]);
|
||||
setIsRecordTableScrolledHorizontally(scrollLeft > 0);
|
||||
// TODO: see if we need to reimplement setting classes here.
|
||||
}, [currentRecordGroupId, scrollLeft, setIsRecordTableScrolledHorizontally]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
|
||||
export const isRecordTableScrolledHorizontallyComponentState =
|
||||
createComponentState<boolean>({
|
||||
key: 'isRecordTableScrolledHorizontallyComponentState',
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
defaultValue: true,
|
||||
});
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext';
|
||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
||||
|
||||
export const isRecordTableScrolledLeftComponentState =
|
||||
export const isRecordTableScrolledVerticallyComponentState =
|
||||
createComponentState<boolean>({
|
||||
key: 'isRecordTableScrolledLeftComponentState',
|
||||
key: 'isRecordTableScrolledVerticallyComponentState',
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
defaultValue: true,
|
||||
});
|
||||
Reference in New Issue
Block a user