Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Data Skeleton Loading on Indexes #5828

Merged
merged 13 commits into from
Jun 19, 2024
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) {
lucasbordeau marked this conversation as resolved.
Show resolved Hide resolved
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,
});
Loading