Add common loader (#18556)

To avoid white screens on reload, building a shared skeleton.

Before

https://github.com/user-attachments/assets/42bd0667-141d-4df4-9072-4077192cc71d

After

https://github.com/user-attachments/assets/e6031a72-2e25-47e3-a873-b89aaddfbd3a
This commit is contained in:
Thomas Trompette
2026-03-11 14:56:49 +01:00
committed by GitHub
parent 2c5af2654d
commit b346f4fb59
6 changed files with 86 additions and 94 deletions
@@ -0,0 +1,56 @@
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
import { PageBody } from '@/ui/layout/page/components/PageBody';
import { PAGE_BAR_MIN_HEIGHT } from '@/ui/layout/page/constants/PageBarMinHeight';
import { styled } from '@linaria/react';
import { useContext } from 'react';
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledHeaderSkeleton = styled.div`
align-items: center;
background: ${themeCssVariables.background.noisy};
display: flex;
flex-direction: row;
gap: ${themeCssVariables.spacing[2]};
justify-content: space-between;
min-height: ${PAGE_BAR_MIN_HEIGHT}px;
padding: ${themeCssVariables.spacing[3]};
`;
const StyledHeaderLeft = styled.div`
flex: 1;
`;
export const PageContentSkeletonLoader = () => {
const { theme } = useContext(ThemeContext);
return (
<>
<StyledHeaderSkeleton>
<StyledHeaderLeft>
<SkeletonTheme
baseColor={theme.background.tertiary}
highlightColor={theme.background.transparent.lighter}
borderRadius={4}
>
<Skeleton
height={SKELETON_LOADER_HEIGHT_SIZES.standard.s}
width={104}
/>
</SkeletonTheme>
</StyledHeaderLeft>
<SkeletonTheme
baseColor={theme.background.tertiary}
highlightColor={theme.background.transparent.lighter}
borderRadius={4}
>
<Skeleton
width={132}
height={SKELETON_LOADER_HEIGHT_SIZES.standard.s}
/>
</SkeletonTheme>
</StyledHeaderSkeleton>
<PageBody>{null}</PageBody>
</>
);
};
@@ -1,100 +1,14 @@
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
import { PageContentSkeletonLoader } from '~/loading/components/PageContentSkeletonLoader';
import { styled } from '@linaria/react';
import { useContext } from 'react';
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
import {
MOBILE_VIEWPORT,
ThemeContext,
themeCssVariables,
} from 'twenty-ui/theme-constants';
const StyledMainContainer = styled.div`
background: ${themeCssVariables.background.noisy};
box-sizing: border-box;
display: flex;
flex: 1 1 auto;
flex-direction: row;
gap: 8px;
min-height: 0;
padding-left: 0;
width: 100%;
@media (max-width: ${MOBILE_VIEWPORT}px) {
padding-left: 12px;
padding-bottom: 0;
}
`;
const StyledPanel = styled.div`
background: ${themeCssVariables.background.primary};
border: 1px solid ${themeCssVariables.border.color.medium};
border-radius: ${themeCssVariables.border.radius.md};
height: 100%;
overflow: auto;
width: 100%;
`;
const StyledHeaderContainer = styled.div`
flex: 1;
`;
const StyledRightPanelContainer = styled.div`
display: flex;
flex-direction: column;
width: 100%;
`;
const StyledRightPanelFlexContainer = styled.div`
align-items: center;
display: flex;
flex-direction: row;
height: 32px;
justify-content: flex-end;
margin-bottom: 12px;
`;
const StyledSkeletonHeaderLoader = () => {
const { theme } = useContext(ThemeContext);
return (
<StyledHeaderContainer>
<SkeletonTheme
baseColor={theme.background.tertiary}
highlightColor={theme.background.transparent.lighter}
borderRadius={4}
>
<Skeleton
height={SKELETON_LOADER_HEIGHT_SIZES.standard.s}
width={104}
/>
</SkeletonTheme>
</StyledHeaderContainer>
);
};
const StyledSkeletonAddLoader = () => {
const { theme } = useContext(ThemeContext);
return (
<SkeletonTheme
baseColor={theme.background.tertiary}
highlightColor={theme.background.transparent.lighter}
borderRadius={4}
>
<Skeleton width={132} height={SKELETON_LOADER_HEIGHT_SIZES.standard.s} />
</SkeletonTheme>
);
};
const RightPanelSkeleton = () => (
<StyledMainContainer>
<StyledPanel></StyledPanel>
</StyledMainContainer>
);
export const RightPanelSkeletonLoader = () => (
<StyledRightPanelContainer>
<StyledRightPanelFlexContainer>
<StyledSkeletonHeaderLoader />
<StyledSkeletonAddLoader />
</StyledRightPanelFlexContainer>
<RightPanelSkeleton />
<PageContentSkeletonLoader />
</StyledRightPanelContainer>
);
@@ -13,11 +13,9 @@ const StyledContainer = styled.div`
box-sizing: border-box;
display: flex;
flex-direction: row;
gap: 12px;
height: 100dvh;
min-width: ${NAVIGATION_DRAWER_CONSTRAINTS.default}px;
overflow: hidden;
padding: 12px 8px 12px 8px;
width: 100%;
@media (max-width: ${MOBILE_VIEWPORT}px) {
@@ -25,6 +23,11 @@ const StyledContainer = styled.div`
}
`;
const StyledLeftPanelWrapper = styled.div`
flex-shrink: 0;
padding: 12px 0 12px 8px;
`;
export const UserOrMetadataLoader = () => {
const showAuthModal = useShowAuthModal();
@@ -36,7 +39,9 @@ export const UserOrMetadataLoader = () => {
backdropZIndex={RootStackingContextZIndices.RootModalBackDrop}
/>
)}
<LeftPanelSkeletonLoader />
<StyledLeftPanelWrapper>
<LeftPanelSkeletonLoader />
</StyledLeftPanelWrapper>
<RightPanelSkeletonLoader />
</StyledContainer>
);