Skip to content

Commit

Permalink
Data Skeleton Loading on Indexes (#5828)
Browse files Browse the repository at this point in the history
### Description
Data Skeleton Loading on Indexes

### Refs
#4459

### Demo


https://github.com/twentyhq/twenty/assets/140154534/d9c9b0fa-2d8c-4b0d-8d48-cae09530622a


Fixes #4459

---------

Co-authored-by: gitstart-twenty <[email protected]>
Co-authored-by: v1b3m <[email protected]>
Co-authored-by: Matheus <[email protected]>
Co-authored-by: Lucas Bordeau <[email protected]>
  • Loading branch information
5 people authored Jun 19, 2024
1 parent ff21396 commit 7010590
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const StyledBoardCardWrapper = styled.div`
width: 100%;
`;

const StyledBoardCardHeader = styled.div<{
export const StyledBoardCardHeader = styled.div<{
showCompactView: boolean;
}>`
align-items: center;
Expand All @@ -89,7 +89,7 @@ const StyledBoardCardHeader = styled.div<{
}
`;

const StyledBoardCardBody = styled.div`
export const StyledBoardCardBody = styled.div`
display: flex;
flex-direction: column;
gap: ${({ theme }) => theme.spacing(0.5)};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';

import {
StyledBoardCardBody,
StyledBoardCardHeader,
} from '@/object-record/record-board/record-board-card/components/RecordBoardCard';

const StyledSkeletonIconAndText = styled.div`
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
`;

const StyledSkeletonTitle = styled.div`
padding-left: ${({ theme }) => theme.spacing(2)};
`;

const StyledSeparator = styled.div`
height: ${({ theme }) => theme.spacing(2)};
`;

export const RecordBoardColumnCardContainerSkeletonLoader = ({
numberOfFields,
titleSkeletonWidth,
isCompactModeActive,
}: {
numberOfFields: number;
titleSkeletonWidth: number;
isCompactModeActive: boolean;
}) => {
const theme = useTheme();
const skeletonItems = Array.from({ length: numberOfFields }).map(
(_, index) => ({
id: `skeleton-item-${index}`,
}),
);
return (
<SkeletonTheme
baseColor={theme.background.tertiary}
highlightColor={theme.background.transparent.lighter}
borderRadius={4}
>
<StyledBoardCardHeader showCompactView={isCompactModeActive}>
<StyledSkeletonTitle>
<Skeleton width={titleSkeletonWidth} height={16} />
</StyledSkeletonTitle>
</StyledBoardCardHeader>
<StyledSeparator />
{!isCompactModeActive &&
skeletonItems.map(({ id }) => (
<StyledBoardCardBody key={id}>
<StyledSkeletonIconAndText>
<Skeleton width={16} height={16} />
<Skeleton width={151} height={16} />
</StyledSkeletonIconAndText>
</StyledBoardCardBody>
))}
</SkeletonTheme>
);
};
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import React, { useContext } from 'react';
import styled from '@emotion/styled';
import { Draggable, DroppableProvided } from '@hello-pangea/dnd';
import { useRecoilValue } from 'recoil';

import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
import { RecordBoardColumnCardContainerSkeletonLoader } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnCardContainerSkeletonLoader';
import { RecordBoardColumnCardsMemo } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnCardsMemo';
import { RecordBoardColumnFetchMoreLoader } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnFetchMoreLoader';
import { RecordBoardColumnNewButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewButton';
import { RecordBoardColumnNewOpportunityButton } from '@/object-record/record-board/record-board-column/components/RecordBoardColumnNewOpportunityButton';
import { RecordBoardColumnContext } from '@/object-record/record-board/record-board-column/contexts/RecordBoardColumnContext';
import { getNumberOfCardsPerColumnForSkeletonLoading } from '@/object-record/record-board/record-board-column/utils/getNumberOfCardsPerColumnForSkeletonLoading';
import { isRecordIndexBoardColumnLoadingFamilyState } from '@/object-record/states/isRecordBoardColumnLoadingFamilyState';

const StyledColumnCardsContainer = styled.div`
display: flex;
Expand All @@ -20,6 +25,17 @@ const StyledNewButtonContainer = styled.div`
padding-bottom: ${({ theme }) => theme.spacing(4)};
`;

const StyledSkeletonCardContainer = styled.div`
background-color: ${({ theme }) => theme.background.secondary};
border: 1px solid ${({ theme }) => theme.background.quaternary};
border-radius: ${({ theme }) => theme.border.radius.md};
box-shadow:
0px 4px 8px 0px rgba(0, 0, 0, 0.08),
0px 0px 4px 0px rgba(0, 0, 0, 0.08);
color: ${({ theme }) => theme.font.color.primary};
margin-bottom: ${({ theme }) => theme.spacing(2)};
`;

type RecordBoardColumnCardsContainerProps = {
recordIds: string[];
droppableProvided: DroppableProvided;
Expand All @@ -32,13 +48,51 @@ export const RecordBoardColumnCardsContainer = ({
const { columnDefinition } = useContext(RecordBoardColumnContext);
const { objectMetadataItem } = useContext(RecordBoardContext);

const columnId = columnDefinition.id;

const isRecordIndexBoardColumnLoading = useRecoilValue(
isRecordIndexBoardColumnLoadingFamilyState(columnId),
);

const { isCompactModeActiveState, visibleFieldDefinitionsState } =
useRecordBoardStates();

const visibleFieldDefinitions = useRecoilValue(
visibleFieldDefinitionsState(),
);

const numberOfFields = visibleFieldDefinitions.length;

const isCompactModeActive = useRecoilValue(isCompactModeActiveState);

return (
<StyledColumnCardsContainer
ref={droppableProvided?.innerRef}
// eslint-disable-next-line react/jsx-props-no-spreading
{...droppableProvided?.droppableProps}
>
<RecordBoardColumnCardsMemo recordIds={recordIds} />
{isRecordIndexBoardColumnLoading ? (
Array.from(
{
length: getNumberOfCardsPerColumnForSkeletonLoading(
columnDefinition.position,
),
},
(_, index) => (
<StyledSkeletonCardContainer
key={`${columnDefinition.id}-${index}`}
>
<RecordBoardColumnCardContainerSkeletonLoader
numberOfFields={numberOfFields}
titleSkeletonWidth={isCompactModeActive ? 72 : 54}
isCompactModeActive={isCompactModeActive}
/>
</StyledSkeletonCardContainer>
),
)
) : (
<RecordBoardColumnCardsMemo recordIds={recordIds} />
)}
<RecordBoardColumnFetchMoreLoader />
<Draggable
draggableId={`new-${columnDefinition.id}`}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const getNumberOfCardsPerColumnForSkeletonLoading = (
columnIndex: number,
): number => {
const skeletonCounts: Record<number, number> = {
0: 2,
1: 1,
2: 3,
3: 0,
4: 1,
};

return skeletonCounts[columnIndex] || 0;
};
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { useEffect } from 'react';
import { useRecoilState } from 'recoil';
import { useRecoilState, useSetRecoilState } from 'recoil';

import { isRecordBoardFetchingRecordsByColumnFamilyState } from '@/object-record/record-board/states/isRecordBoardFetchingRecordsByColumnFamilyState';
import { recordBoardShouldFetchMoreInColumnComponentFamilyState } from '@/object-record/record-board/states/recordBoardShouldFetchMoreInColumnComponentFamilyState';
import { useLoadRecordIndexBoardColumn } from '@/object-record/record-index/hooks/useLoadRecordIndexBoardColumn';
import { isRecordIndexBoardColumnLoadingFamilyState } from '@/object-record/states/isRecordBoardColumnLoadingFamilyState';
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';

export const RecordIndexBoardColumnLoaderEffect = ({
Expand Down Expand Up @@ -34,7 +35,7 @@ export const RecordIndexBoardColumnLoaderEffect = ({
}),
);

const { fetchMoreRecords, loading, hasNextPage } =
const { fetchMoreRecords, loading, records, hasNextPage } =
useLoadRecordIndexBoardColumn({
objectNameSingular,
recordBoardId,
Expand All @@ -43,6 +44,14 @@ export const RecordIndexBoardColumnLoaderEffect = ({
columnId,
});

const setIsRecordIndexLoading = useSetRecoilState(
isRecordIndexBoardColumnLoadingFamilyState(columnId),
);

useEffect(() => {
setIsRecordIndexLoading(loading && records.length === 0);
}, [records, loading, setIsRecordIndexLoading]);

useEffect(() => {
const run = async () => {
if (!loading && shouldFetchMore && hasNextPage) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useRecoilValue } from 'recoil';

import { RecordTableBodyFetchMoreLoader } from '@/object-record/record-table/components/RecordTableBodyFetchMoreLoader';
import { RecordTableBodyLoading } from '@/object-record/record-table/components/RecordTableBodyLoading';
import { RecordTableRow } from '@/object-record/record-table/components/RecordTableRow';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { DraggableTableBody } from '@/ui/layout/draggable-list/components/DraggableTableBody';
Expand All @@ -14,10 +15,19 @@ export const RecordTableBody = ({
objectNameSingular,
recordTableId,
}: RecordTableBodyProps) => {
const { tableRowIdsState } = useRecordTableStates();
const { tableRowIdsState, isRecordTableInitialLoadingState } =
useRecordTableStates();

const tableRowIds = useRecoilValue(tableRowIdsState);

const isRecordTableInitialLoading = useRecoilValue(
isRecordTableInitialLoadingState,
);

if (isRecordTableInitialLoading && tableRowIds.length === 0) {
return <RecordTableBodyLoading />;
}

return (
<>
<DraggableTableBody
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useRecoilValue } from 'recoil';

import { CheckboxCell } from '@/object-record/record-table/components/CheckboxCell';
import { GripCell } from '@/object-record/record-table/components/GripCell';
import {
StyledTd,
StyledTr,
} from '@/object-record/record-table/components/RecordTableRow';
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
import { RecordTableCellLoading } from '@/object-record/record-table/record-table-cell/components/RecordTableCellLoading';

export const RecordTableBodyLoading = () => {
const { visibleTableColumnsSelector } = useRecordTableStates();
const visibleTableColumns = useRecoilValue(visibleTableColumnsSelector());

return (
<tbody>
{Array.from({ length: 8 }).map((_, rowIndex) => (
<StyledTr
isDragging={false}
data-testid={`row-id-${rowIndex}`}
data-selectable-id={`row-id-${rowIndex}`}
>
<StyledTd data-select-disable>
<GripCell isDragging={false} />
</StyledTd>
<StyledTd>
<CheckboxCell />
</StyledTd>
{visibleTableColumns.map((column) => (
<RecordTableCellLoading key={column.fieldMetadataId} />
))}
</StyledTr>
))}
</tbody>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ type RecordTableRowProps = {
isPendingRow?: boolean;
};

const StyledTd = styled.td`
export const StyledTd = styled.td`
position: relative;
user-select: none;
`;

const StyledTr = styled.tr<{ isDragging: boolean }>`
export const StyledTr = styled.tr<{ isDragging: boolean }>`
border: 1px solid transparent;
transition: border-left-color 0.2s ease-in-out;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { StyledTd } from '@/object-record/record-table/components/RecordTableRow';
import { RecordTableCellSkeletonLoader } from '@/object-record/record-table/record-table-cell/components/RecordTableCellSkeletonLoader';

export const RecordTableCellLoading = () => {
return (
<StyledTd>
<RecordTableCellSkeletonLoader />
</StyledTd>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';

const StyledSkeletonContainer = styled.div`
padding-left: ${({ theme }) => theme.spacing(2)};
padding-right: ${({ theme }) => theme.spacing(1)};
`;

const StyledRecordTableCellLoader = ({ width }: { width?: number }) => {
const theme = useTheme();
return (
<SkeletonTheme
baseColor={theme.background.tertiary}
highlightColor={theme.background.transparent.lighter}
borderRadius={4}
>
<Skeleton width={width} height={16} />
</SkeletonTheme>
);
};

export const RecordTableCellSkeletonLoader = () => {
return (
<StyledSkeletonContainer>
<StyledRecordTableCellLoader />
</StyledSkeletonContainer>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createFamilyState } from '@/ui/utilities/state/utils/createFamilyState';

export const isRecordIndexBoardColumnLoadingFamilyState = createFamilyState<
boolean,
string | undefined
>({
key: 'isRecordIndexBoardColumnLoadingFamilyState',
defaultValue: false,
});

0 comments on commit 7010590

Please sign in to comment.