Prefetch Skeleton Loading on Indexes and Shows (#5545)
### Description Prefetch Skeleton Loading on Indexes and Shows ### Refs #4458 ### Demo https://jam.dev/c/a1ad04e1-80b6-4b2a-b7df-373f52f4b169 https://jam.dev/c/c5038b97-2f18-4c29-8dee-18c09376e5ee Fixes: #4458 --------- Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com> Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: Matheus <matheus_benini@hotmail.com> Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -0,0 +1,31 @@
|
||||
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledSkeletonDiv = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
`;
|
||||
export const PropertyBoxSkeletonLoader = () => {
|
||||
const theme = useTheme();
|
||||
const skeletonItems = Array.from({ length: 4 }).map((_, index) => ({
|
||||
id: `skeleton-item-${index}`,
|
||||
}));
|
||||
return (
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
{skeletonItems.map(({ id }) => (
|
||||
<StyledSkeletonDiv key={id}>
|
||||
<Skeleton width={92} height={16} />
|
||||
<Skeleton width={154} height={16} />
|
||||
</StyledSkeletonDiv>
|
||||
))}
|
||||
</SkeletonTheme>
|
||||
);
|
||||
};
|
||||
@ -12,6 +12,7 @@ import {
|
||||
} from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
||||
import { PropertyBoxSkeletonLoader } from '@/object-record/record-inline-cell/property-box/components/PropertyBoxSkeletonLoader';
|
||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||
import { RecordDetailDuplicatesSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailDuplicatesSection';
|
||||
import { RecordDetailRelationSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSection';
|
||||
@ -19,6 +20,7 @@ import { recordLoadingFamilyState } from '@/object-record/record-store/states/re
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { recordStoreIdentifierFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreIdentifierSelector';
|
||||
import { isFieldCellSupported } from '@/object-record/utils/isFieldCellSupported';
|
||||
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
|
||||
import { ShowPageContainer } from '@/ui/layout/page/ShowPageContainer';
|
||||
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
|
||||
import { ShowPageRightContainer } from '@/ui/layout/show-page/components/ShowPageRightContainer';
|
||||
@ -127,6 +129,7 @@ export const RecordShowContainer = ({
|
||||
);
|
||||
|
||||
const isReadOnly = objectMetadataItem.isRemote;
|
||||
const isPrefetchLoading = useIsPrefetchLoading();
|
||||
|
||||
return (
|
||||
<RecoilScope CustomRecoilScopeContext={ShowPageRecoilScopeContext}>
|
||||
@ -139,7 +142,7 @@ export const RecordShowContainer = ({
|
||||
logoOrAvatar={recordIdentifier?.avatarUrl ?? ''}
|
||||
avatarPlaceholder={recordIdentifier?.name ?? ''}
|
||||
date={recordFromStore.createdAt ?? ''}
|
||||
loading={loading || recordLoading}
|
||||
loading={isPrefetchLoading || loading || recordLoading}
|
||||
title={
|
||||
<FieldContext.Provider
|
||||
value={{
|
||||
@ -176,32 +179,36 @@ export const RecordShowContainer = ({
|
||||
}
|
||||
/>
|
||||
<PropertyBox>
|
||||
{inlineFieldMetadataItems.map((fieldMetadataItem, index) => (
|
||||
<FieldContext.Provider
|
||||
key={objectRecordId + fieldMetadataItem.id}
|
||||
value={{
|
||||
entityId: objectRecordId,
|
||||
maxWidth: 200,
|
||||
recoilScopeId: objectRecordId + fieldMetadataItem.id,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition:
|
||||
formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
position: index,
|
||||
objectMetadataItem,
|
||||
showLabel: true,
|
||||
labelWidth: 90,
|
||||
}),
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
>
|
||||
<RecordInlineCell
|
||||
loading={loading || recordLoading}
|
||||
readonly={isReadOnly}
|
||||
/>
|
||||
</FieldContext.Provider>
|
||||
))}
|
||||
{isPrefetchLoading ? (
|
||||
<PropertyBoxSkeletonLoader />
|
||||
) : (
|
||||
inlineFieldMetadataItems.map((fieldMetadataItem, index) => (
|
||||
<FieldContext.Provider
|
||||
key={objectRecordId + fieldMetadataItem.id}
|
||||
value={{
|
||||
entityId: objectRecordId,
|
||||
maxWidth: 200,
|
||||
recoilScopeId: objectRecordId + fieldMetadataItem.id,
|
||||
isLabelIdentifier: false,
|
||||
fieldDefinition:
|
||||
formatFieldMetadataItemAsColumnDefinition({
|
||||
field: fieldMetadataItem,
|
||||
position: index,
|
||||
objectMetadataItem,
|
||||
showLabel: true,
|
||||
labelWidth: 90,
|
||||
}),
|
||||
useUpdateRecord: useUpdateOneObjectRecordMutation,
|
||||
hotkeyScope: InlineCellHotkeyScope.InlineCell,
|
||||
}}
|
||||
>
|
||||
<RecordInlineCell
|
||||
loading={loading || recordLoading}
|
||||
readonly={isReadOnly}
|
||||
/>
|
||||
</FieldContext.Provider>
|
||||
))
|
||||
)}
|
||||
</PropertyBox>
|
||||
<RecordDetailDuplicatesSection
|
||||
objectRecordId={objectRecordId}
|
||||
@ -224,7 +231,7 @@ export const RecordShowContainer = ({
|
||||
}}
|
||||
>
|
||||
<RecordDetailRelationSection
|
||||
loading={loading || recordLoading}
|
||||
loading={isPrefetchLoading || loading || recordLoading}
|
||||
/>
|
||||
</FieldContext.Provider>
|
||||
))}
|
||||
@ -241,7 +248,7 @@ export const RecordShowContainer = ({
|
||||
tasks
|
||||
notes
|
||||
emails
|
||||
loading={loading || recordLoading}
|
||||
loading={isPrefetchLoading || loading || recordLoading}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import { useCallback, useContext } from 'react';
|
||||
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import qs from 'qs';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
@ -13,6 +11,7 @@ import { usePersistField } from '@/object-record/record-field/hooks/usePersistFi
|
||||
import { FieldRelationMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { RecordDetailRelationRecordsList } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsList';
|
||||
import { RecordDetailRelationRecordsListEmptyState } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListEmptyState';
|
||||
import { RecordDetailRelationSectionSkeletonLoader } from '@/object-record/record-show/record-detail-section/components/RecordDetailRelationSectionSkeletonLoader';
|
||||
import { RecordDetailSection } from '@/object-record/record-show/record-detail-section/components/RecordDetailSection';
|
||||
import { RecordDetailSectionHeader } from '@/object-record/record-show/record-detail-section/components/RecordDetailSectionHeader';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
@ -37,25 +36,6 @@ const StyledAddDropdown = styled(Dropdown)`
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
const StyledSkeletonDiv = styled.div`
|
||||
height: 40px;
|
||||
`;
|
||||
|
||||
const StyledRecordDetailRelationSectionSkeletonLoader = () => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
<StyledSkeletonDiv>
|
||||
<Skeleton width={129} height={16} />
|
||||
</StyledSkeletonDiv>
|
||||
</SkeletonTheme>
|
||||
);
|
||||
};
|
||||
|
||||
export const RecordDetailRelationSection = ({
|
||||
loading,
|
||||
}: RecordDetailRelationSectionProps) => {
|
||||
@ -142,7 +122,11 @@ export const RecordDetailRelationSection = ({
|
||||
|
||||
const showContent = () => {
|
||||
if (loading) {
|
||||
return <StyledRecordDetailRelationSectionSkeletonLoader />;
|
||||
return (
|
||||
<RecordDetailRelationSectionSkeletonLoader
|
||||
numSkeletons={fieldName === 'people' ? 2 : 1}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return relationRecords.length ? (
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledSkeletonDiv = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(4)};
|
||||
height: 40px;
|
||||
`;
|
||||
|
||||
export const RecordDetailRelationSectionSkeletonLoader = ({
|
||||
numSkeletons = 1,
|
||||
}: {
|
||||
numSkeletons?: number;
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
<StyledSkeletonDiv>
|
||||
{Array.from({ length: numSkeletons }).map((_, index) => (
|
||||
<Skeleton key={index} width={129} height={16} />
|
||||
))}
|
||||
</StyledSkeletonDiv>
|
||||
</SkeletonTheme>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user