Introduce IsScrollable on ScrollWrapper (#9724)
It's beautiful! Using same techniques as for the Header! Was not that easy but looks very nice now!
This commit is contained in:
@ -3,6 +3,7 @@ import { isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
|
||||
import { recordIndexAllRecordIdsComponentSelector } from '@/object-record/record-index/states/selectors/recordIndexAllRecordIdsComponentSelector';
|
||||
import { RecordTableStickyBottomEffect } from '@/object-record/record-table/components/RecordTableStickyBottomEffect';
|
||||
import { RecordTableStickyEffect } from '@/object-record/record-table/components/RecordTableStickyEffect';
|
||||
import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
|
||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||
@ -26,6 +27,10 @@ const StyledTable = styled.table`
|
||||
border-spacing: 0;
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
|
||||
.footer-sticky tr:nth-last-child(2) td {
|
||||
border-bottom-color: ${({ theme }) => theme.background.transparent};
|
||||
}
|
||||
`;
|
||||
|
||||
export const RecordTable = () => {
|
||||
@ -90,6 +95,7 @@ export const RecordTable = () => {
|
||||
<RecordTableRecordGroupsBody />
|
||||
)}
|
||||
<RecordTableStickyEffect />
|
||||
<RecordTableStickyBottomEffect />
|
||||
</StyledTable>
|
||||
<DragSelect
|
||||
dragSelectable={tableBodyRef}
|
||||
|
||||
@ -28,7 +28,7 @@ export const RecordTableNoRecordGroupRows = () => {
|
||||
})}
|
||||
<RecordTableBodyFetchMoreLoader />
|
||||
{!isRecordTableInitialLoading && allRecordIds.length > 0 && (
|
||||
<RecordTableAggregateFooter endOfTableSticky />
|
||||
<RecordTableAggregateFooter />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { scrollWrapperScrollBottomComponentState } from '@/ui/utilities/scroll/states/scrollWrappeScrollBottomComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
export const RecordTableStickyBottomEffect = () => {
|
||||
const scrollBottom = useRecoilComponentValueV2(
|
||||
scrollWrapperScrollBottomComponentState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (scrollBottom > 1) {
|
||||
document
|
||||
.getElementById('record-table-body')
|
||||
?.classList.add('footer-sticky');
|
||||
document
|
||||
.getElementById('record-table-footer')
|
||||
?.classList.add('footer-sticky');
|
||||
} else {
|
||||
document
|
||||
.getElementById('record-table-body')
|
||||
?.classList.remove('footer-sticky');
|
||||
document
|
||||
.getElementById('record-table-footer')
|
||||
?.classList.remove('footer-sticky');
|
||||
}
|
||||
}, [scrollBottom]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
@ -3,12 +3,9 @@ import { styled } from '@linaria/react';
|
||||
import { ReactNode, useContext } from 'react';
|
||||
import { MOBILE_VIEWPORT, ThemeContext } from 'twenty-ui';
|
||||
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const RECORD_TABLE_TD_WIDTH = '32px';
|
||||
|
||||
const StyledTd = styled.td<{
|
||||
zIndex?: number;
|
||||
backgroundColor: string;
|
||||
borderColor: string;
|
||||
isDragging?: boolean;
|
||||
@ -33,7 +30,6 @@ const StyledTd = styled.td<{
|
||||
text-align: left;
|
||||
|
||||
background: ${({ backgroundColor }) => backgroundColor};
|
||||
z-index: ${({ zIndex }) => (isDefined(zIndex) ? zIndex : 'auto')};
|
||||
${({ isDragging }) =>
|
||||
isDragging
|
||||
? `
|
||||
@ -53,7 +49,6 @@ const StyledTd = styled.td<{
|
||||
|
||||
export const RecordTableTd = ({
|
||||
children,
|
||||
zIndex,
|
||||
isSelected,
|
||||
isDragging,
|
||||
sticky,
|
||||
@ -67,7 +62,6 @@ export const RecordTableTd = ({
|
||||
}: {
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
zIndex?: number;
|
||||
isSelected?: boolean;
|
||||
isDragging?: boolean;
|
||||
sticky?: boolean;
|
||||
@ -90,7 +84,6 @@ export const RecordTableTd = ({
|
||||
return (
|
||||
<StyledTd
|
||||
isDragging={isDragging}
|
||||
zIndex={zIndex}
|
||||
backgroundColor={tdBackgroundColor}
|
||||
borderColor={borderColor}
|
||||
fontColor={fontColor}
|
||||
|
||||
@ -6,6 +6,7 @@ import { FIRST_TH_WIDTH } from '@/object-record/record-table/record-table-header
|
||||
import { visibleTableColumnsComponentSelector } from '@/object-record/record-table/states/selectors/visibleTableColumnsComponentSelector';
|
||||
import { scrollWrapperInstanceComponentState } from '@/ui/utilities/scroll/states/scrollWrapperInstanceComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { isUndefined } from '@sniptt/guards';
|
||||
import { MOBILE_VIEWPORT } from 'twenty-ui';
|
||||
|
||||
const StyledTd = styled.td`
|
||||
@ -13,26 +14,30 @@ const StyledTd = styled.td`
|
||||
`;
|
||||
|
||||
const StyledTableRow = styled.tr<{
|
||||
endOfTableSticky?: boolean;
|
||||
hasHorizontalOverflow?: boolean;
|
||||
}>`
|
||||
td {
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
z-index: 5;
|
||||
position: sticky;
|
||||
border: none;
|
||||
|
||||
&.footer-sticky {
|
||||
td {
|
||||
border-top: ${({ theme }) => `1px solid ${theme.border.color.light}`};
|
||||
z-index: 5;
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
cursor: pointer;
|
||||
td:nth-of-type(1) {
|
||||
width: ${FIRST_TH_WIDTH};
|
||||
left: 0;
|
||||
border-right-color: ${({ theme }) => theme.background.primary};
|
||||
border-top: none;
|
||||
}
|
||||
td:nth-of-type(2) {
|
||||
border-right-color: ${({ theme }) => theme.background.primary};
|
||||
}
|
||||
&.first-columns-sticky {
|
||||
td:nth-of-type(2) {
|
||||
position: sticky;
|
||||
z-index: 5;
|
||||
z-index: 10;
|
||||
transition: 0.3s ease;
|
||||
&::after {
|
||||
content: '';
|
||||
@ -50,37 +55,32 @@ const StyledTableRow = styled.tr<{
|
||||
}
|
||||
}
|
||||
}
|
||||
position: sticky;
|
||||
z-index: 5;
|
||||
background: ${({ theme }) => theme.background.primary};
|
||||
${({ endOfTableSticky, hasHorizontalOverflow }) =>
|
||||
endOfTableSticky &&
|
||||
`
|
||||
bottom: ${hasHorizontalOverflow ? '10px' : '0'};
|
||||
${
|
||||
hasHorizontalOverflow &&
|
||||
`
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -10px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 10px;
|
||||
background: inherit;
|
||||
${({ hasHorizontalOverflow }) =>
|
||||
`.footer-sticky {
|
||||
bottom: ${hasHorizontalOverflow ? '10px' : '0'};
|
||||
${
|
||||
hasHorizontalOverflow &&
|
||||
`
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -10px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 10px;
|
||||
background: inherit;
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
export const RecordTableAggregateFooter = ({
|
||||
currentRecordGroupId,
|
||||
endOfTableSticky,
|
||||
}: {
|
||||
currentRecordGroupId?: string;
|
||||
|
||||
endOfTableSticky?: boolean;
|
||||
}) => {
|
||||
const visibleTableColumns = useRecoilComponentValueV2(
|
||||
visibleTableColumnsComponentSelector,
|
||||
@ -99,8 +99,9 @@ export const RecordTableAggregateFooter = ({
|
||||
<StyledTableRow
|
||||
id={`record-table-footer${currentRecordGroupId ? '-' + currentRecordGroupId : ''}`}
|
||||
data-select-disable
|
||||
endOfTableSticky={endOfTableSticky}
|
||||
hasHorizontalOverflow={hasHorizontalOverflow}
|
||||
hasHorizontalOverflow={
|
||||
hasHorizontalOverflow && isUndefined(currentRecordGroupId)
|
||||
}
|
||||
>
|
||||
<StyledTd />
|
||||
{visibleTableColumns.map((column, index) => {
|
||||
|
||||
@ -6,10 +6,6 @@ const StyledTr = styled.tr<{ isDragging: boolean }>`
|
||||
? `1px solid ${theme.border.color.medium}`
|
||||
: '1px solid transparent'};
|
||||
transition: border-left-color 0.2s ease-in-out;
|
||||
|
||||
&:nth-last-child(2) td {
|
||||
border-bottom: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const RecordTableTr = StyledTr;
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
} from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
||||
|
||||
import { ScrollWrapperComponentInstanceContext } from '@/ui/utilities/scroll/states/contexts/ScrollWrapperComponentInstanceContext';
|
||||
import { scrollWrapperScrollBottomComponentState } from '@/ui/utilities/scroll/states/scrollWrappeScrollBottomComponentState';
|
||||
import { scrollWrapperInstanceComponentState } from '@/ui/utilities/scroll/states/scrollWrapperInstanceComponentState';
|
||||
import { scrollWrapperScrollLeftComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollLeftComponentState';
|
||||
import { scrollWrapperScrollTopComponentState } from '@/ui/utilities/scroll/states/scrollWrapperScrollTopComponentState';
|
||||
@ -106,10 +107,18 @@ export const ScrollWrapper = ({
|
||||
componentInstanceId,
|
||||
);
|
||||
|
||||
const setScrollBottom = useSetRecoilComponentStateV2(
|
||||
scrollWrapperScrollBottomComponentState,
|
||||
componentInstanceId,
|
||||
);
|
||||
|
||||
const handleScroll = (overlayScroll: OverlayScrollbars) => {
|
||||
const target = overlayScroll.elements().scrollOffsetElement;
|
||||
setScrollTop(target.scrollTop);
|
||||
setScrollLeft(target.scrollLeft);
|
||||
setScrollBottom(
|
||||
target.scrollHeight - target.clientHeight - target.scrollTop,
|
||||
);
|
||||
};
|
||||
|
||||
const setOverlayScrollbars = useSetRecoilComponentStateV2(
|
||||
@ -129,6 +138,12 @@ export const ScrollWrapper = ({
|
||||
},
|
||||
},
|
||||
events: {
|
||||
updated: (osInstance) => {
|
||||
const { scrollOffsetElement: target } = osInstance.elements();
|
||||
setScrollBottom(
|
||||
target.scrollHeight - target.clientHeight - target.scrollTop,
|
||||
);
|
||||
},
|
||||
scroll: (osInstance) => {
|
||||
const { scrollOffsetElement: target, scrollbarVertical } =
|
||||
osInstance.elements();
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { ScrollWrapperComponentInstanceContext } from '@/ui/utilities/scroll/states/contexts/ScrollWrapperComponentInstanceContext';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
|
||||
export const scrollWrapperScrollBottomComponentState =
|
||||
createComponentStateV2<number>({
|
||||
key: 'scrollWrapperScrollBottomComponentState',
|
||||
defaultValue: 0,
|
||||
componentInstanceContext: ScrollWrapperComponentInstanceContext,
|
||||
});
|
||||
Reference in New Issue
Block a user