Add back shadow on frozen columns and header in table (#14593)
This PR adds back box-shadow on header and first frozen columns of the table, they appear at scroll. With the recent refactor and the removal of a lot of re-render, and the usage of CSS variables, it is now completely fluid. # Before <img width="500" height="234" alt="image" src="https://github.com/user-attachments/assets/9735acb0-5951-4a2d-845c-49e917e99053" /> <img width="576" height="242" alt="image" src="https://github.com/user-attachments/assets/328654bf-97c7-43a2-86b1-0f100ed60daa" /> # After <img width="455" height="250" alt="image" src="https://github.com/user-attachments/assets/3450a63f-4d6f-45ed-bfd2-3c10856b21a7" /> <img width="488" height="244" alt="image" src="https://github.com/user-attachments/assets/1057a391-3a98-4b5e-97f1-890f79e6f8d0" />
This commit is contained in:
+32
-26
@@ -1,25 +1,25 @@
|
||||
import { RECORD_TABLE_HORIZONTAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableHorizontalScrollShadowVisibilityCssVariableName';
|
||||
import { RECORD_TABLE_VERTICAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableVerticalScrollShadowVisibilityCssVariableName';
|
||||
import { isRecordTableScrolledHorizontallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledHorizontallyComponentState';
|
||||
import { isRecordTableScrolledVerticallyComponentState } from '@/object-record/record-table/states/isRecordTableScrolledVerticallyComponentState';
|
||||
import { updateRecordTableCSSVariable } from '@/object-record/record-table/utils/updateRecordTableCSSVariable';
|
||||
|
||||
import { useScrollWrapperHTMLElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperHTMLElement';
|
||||
import { useSetRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentState';
|
||||
import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const RecordTableScrollAndZIndexEffect = () => {
|
||||
const { scrollWrapperHTMLElement } = useScrollWrapperHTMLElement();
|
||||
|
||||
const setIsRecordTableScrolledHorizontally = useSetRecoilComponentState(
|
||||
isRecordTableScrolledHorizontallyComponentState,
|
||||
);
|
||||
const [
|
||||
isRecordTableScrolledHorizontally,
|
||||
setIsRecordTableScrolledHorizontally,
|
||||
] = useRecoilComponentState(isRecordTableScrolledHorizontallyComponentState);
|
||||
|
||||
const setIsRecordTableScrolledVertically = useSetRecoilComponentState(
|
||||
isRecordTableScrolledVerticallyComponentState,
|
||||
);
|
||||
|
||||
const [isScrolledVertically, setIsScrolledVertically] = useState(false);
|
||||
const [isScrolledHorizontally, setIsScrolledHorizontally] = useState(false);
|
||||
const [isRecordTableScrolledVertically, setIsRecordTableScrolledVertically] =
|
||||
useRecoilComponentState(isRecordTableScrolledVerticallyComponentState);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDefined(scrollWrapperHTMLElement)) {
|
||||
@@ -29,29 +29,35 @@ export const RecordTableScrollAndZIndexEffect = () => {
|
||||
const handleScroll = (event: any) => {
|
||||
const target = event.currentTarget;
|
||||
|
||||
let somethingHasChanged = false;
|
||||
|
||||
const newIsScrolledVertically = target?.scrollTop > 0;
|
||||
|
||||
if (newIsScrolledVertically !== isScrolledVertically) {
|
||||
setIsScrolledVertically(newIsScrolledVertically);
|
||||
if (newIsScrolledVertically !== isRecordTableScrolledVertically) {
|
||||
setIsRecordTableScrolledVertically(newIsScrolledVertically);
|
||||
somethingHasChanged = true;
|
||||
|
||||
const newVisibilityOfShadows = newIsScrolledVertically
|
||||
? 'visible'
|
||||
: 'hidden';
|
||||
|
||||
updateRecordTableCSSVariable(
|
||||
RECORD_TABLE_VERTICAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME,
|
||||
newVisibilityOfShadows,
|
||||
);
|
||||
}
|
||||
|
||||
const newIsScrolledHorizontally = target?.scrollLeft > 0;
|
||||
|
||||
if (newIsScrolledHorizontally !== isScrolledHorizontally) {
|
||||
setIsScrolledHorizontally(newIsScrolledHorizontally);
|
||||
if (newIsScrolledHorizontally !== isRecordTableScrolledHorizontally) {
|
||||
setIsRecordTableScrolledHorizontally(newIsScrolledHorizontally);
|
||||
somethingHasChanged = true;
|
||||
}
|
||||
|
||||
if (!somethingHasChanged) {
|
||||
return;
|
||||
}
|
||||
const newVisibilityOfShadows = newIsScrolledHorizontally
|
||||
? 'visible'
|
||||
: 'hidden';
|
||||
|
||||
// TODO: insert imperative CSS update here
|
||||
updateRecordTableCSSVariable(
|
||||
RECORD_TABLE_HORIZONTAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME,
|
||||
newVisibilityOfShadows,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
scrollWrapperHTMLElement?.addEventListener('scroll', handleScroll);
|
||||
@@ -61,8 +67,8 @@ export const RecordTableScrollAndZIndexEffect = () => {
|
||||
};
|
||||
}, [
|
||||
scrollWrapperHTMLElement,
|
||||
isScrolledVertically,
|
||||
isScrolledHorizontally,
|
||||
isRecordTableScrolledVertically,
|
||||
isRecordTableScrolledHorizontally,
|
||||
setIsRecordTableScrolledVertically,
|
||||
setIsRecordTableScrolledHorizontally,
|
||||
]);
|
||||
|
||||
+47
-10
@@ -9,14 +9,54 @@ import { RECORD_TABLE_COLUMN_LAST_EMPTY_COLUMN_WIDTH_CLASS_NAME } from '@/object
|
||||
import { RECORD_TABLE_COLUMN_LAST_EMPTY_COLUMN_WIDTH_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableColumnLastEmptyColumnWidthVariableName';
|
||||
import { RECORD_TABLE_COLUMN_WITH_GROUP_LAST_EMPTY_COLUMN_WIDTH_CLASS_NAME } from '@/object-record/record-table/constants/RecordTableColumnWithGroupLastEmptyColumnWidthClassName';
|
||||
import { RECORD_TABLE_COLUMN_WITH_GROUP_LAST_EMPTY_COLUMN_WIDTH_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableColumnWithGroupLastEmptyColumnWidthVariableName';
|
||||
import { RECORD_TABLE_HORIZONTAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableHorizontalScrollShadowVisibilityCssVariableName';
|
||||
import { RECORD_TABLE_LABEL_IDENTIFIER_COLUMN_WIDTH_ON_MOBILE } from '@/object-record/record-table/constants/RecordTableLabelIdentifierColumnWidthOnMobile';
|
||||
import { RECORD_TABLE_VERTICAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME } from '@/object-record/record-table/constants/RecordTableVerticalScrollShadowVisibilityCssVariableName';
|
||||
|
||||
import { TABLE_Z_INDEX } from '@/object-record/record-table/constants/TableZIndex';
|
||||
import { getRecordTableColumnFieldWidthClassName } from '@/object-record/record-table/utils/getRecordTableColumnFieldWidthClassName';
|
||||
import { getRecordTableColumnFieldWidthCSSVariableName } from '@/object-record/record-table/utils/getRecordTableColumnFieldWidthCSSVariableName';
|
||||
import { css, type Theme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { MOBILE_VIEWPORT } from 'twenty-ui/theme';
|
||||
|
||||
const VerticalScrollBoxShadowCSS = ({ theme }: { theme: Theme }) => css`
|
||||
&::before {
|
||||
bottom: -1px;
|
||||
box-shadow:
|
||||
0px 2px 4px 0px ${theme.boxShadow.color},
|
||||
0px 0px 4px 0px ${theme.boxShadow.color};
|
||||
clip-path: inset(0px 0px -4px 0px);
|
||||
content: '';
|
||||
height: 4px;
|
||||
position: absolute;
|
||||
visibility: var(
|
||||
${RECORD_TABLE_VERTICAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME},
|
||||
hidden
|
||||
);
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
const HorizontalScrollBoxShadowCSS = ({ theme }: { theme: Theme }) => css`
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
height: calc(100% + 2px);
|
||||
width: 4px;
|
||||
right: -1px;
|
||||
box-shadow:
|
||||
2px 0px 4px 0px ${theme.boxShadow.color},
|
||||
0px 0px 4px 0px ${theme.boxShadow.color};
|
||||
clip-path: inset(0px -4px 0px 0px);
|
||||
visibility: var(
|
||||
${RECORD_TABLE_HORIZONTAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME},
|
||||
hidden
|
||||
);
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledTable = styled.div<{
|
||||
isDragging?: boolean;
|
||||
visibleRecordFields: RecordField[];
|
||||
@@ -35,6 +75,8 @@ const StyledTable = styled.div<{
|
||||
div.header-cell {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
|
||||
${VerticalScrollBoxShadowCSS}
|
||||
}
|
||||
|
||||
div.header-cell:nth-of-type(n + 5) {
|
||||
@@ -78,16 +120,7 @@ const StyledTable = styled.div<{
|
||||
? TABLE_Z_INDEX.headerColumns.withGroups.headerColumnsSticky
|
||||
: TABLE_Z_INDEX.headerColumns.withoutGroups.headerColumnsSticky};
|
||||
|
||||
// &::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);
|
||||
// }
|
||||
${HorizontalScrollBoxShadowCSS}
|
||||
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
width: ${RECORD_TABLE_LABEL_IDENTIFIER_COLUMN_WIDTH_ON_MOBILE}px;
|
||||
@@ -117,6 +150,8 @@ const StyledTable = styled.div<{
|
||||
div.table-cell-0-0 {
|
||||
position: sticky;
|
||||
left: 48px;
|
||||
|
||||
${HorizontalScrollBoxShadowCSS}
|
||||
}
|
||||
|
||||
div.table-cell:nth-of-type(3) {
|
||||
@@ -126,6 +161,8 @@ const StyledTable = styled.div<{
|
||||
hasRecordGroups
|
||||
? TABLE_Z_INDEX.cell.withGroups.sticky
|
||||
: TABLE_Z_INDEX.cell.withoutGroups.sticky};
|
||||
|
||||
${HorizontalScrollBoxShadowCSS}
|
||||
}
|
||||
|
||||
div.${RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH_CLASS_NAME} {
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const RECORD_TABLE_HORIZONTAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME =
|
||||
'--record-table-horizontal-scroll-visibility';
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const RECORD_TABLE_VERTICAL_SCROLL_SHADOW_VISIBILITY_CSS_VARIABLE_NAME =
|
||||
'--record-table-vertical-scroll-visibility';
|
||||
@@ -2,6 +2,7 @@ import { GRAY_SCALE } from './GrayScale';
|
||||
import { RGBA } from './Rgba';
|
||||
|
||||
export const BOX_SHADOW_DARK = {
|
||||
color: RGBA(GRAY_SCALE.gray100, 0.6),
|
||||
light: `0px 2px 4px 0px ${RGBA(
|
||||
GRAY_SCALE.gray100,
|
||||
0.04,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { GRAY_SCALE } from './GrayScale';
|
||||
import { RGBA } from './Rgba';
|
||||
|
||||
export const BOX_SHADOW_LIGHT = {
|
||||
color: RGBA(GRAY_SCALE.gray100, 0.04),
|
||||
light: `0px 2px 4px 0px ${RGBA(
|
||||
GRAY_SCALE.gray100,
|
||||
0.04,
|
||||
|
||||
Reference in New Issue
Block a user