Skip to content

Commit 10abd7f

Browse files
gitstart-twentyv1b3mThiagomn1charlesBochet
authored
User & Metadata Loading (#5347)
### Description User & Metadata Loading ### Refs #4456 ### Demo https://github.com/twentyhq/twenty/assets/140154534/4c20fca6-feaf-45f6-ac50-6532d2ebf050 Fixes #4456 --------- Co-authored-by: gitstart-twenty <[email protected]> Co-authored-by: v1b3m <[email protected]> Co-authored-by: Thiago Nascimbeni <[email protected]> Co-authored-by: Charles Bochet <[email protected]>
1 parent 74d7479 commit 10abd7f

File tree

7 files changed

+249
-2
lines changed

7 files changed

+249
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
2+
import styled from '@emotion/styled';
3+
import { motion } from 'framer-motion';
4+
import { ANIMATION, BACKGROUND_LIGHT, GRAY_SCALE } from 'twenty-ui';
5+
6+
import { DESKTOP_NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/DesktopNavDrawerWidths';
7+
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
8+
import { MainNavigationDrawerItemsSkeletonLoader } from '~/loading/components/MainNavigationDrawerItemsSkeletonLoader';
9+
10+
const StyledAnimatedContainer = styled(motion.div)`
11+
display: flex;
12+
justify-content: end;
13+
`;
14+
15+
const StyledItemsContainer = styled.div`
16+
display: flex;
17+
flex-direction: column;
18+
gap: 32px;
19+
margin-bottom: auto;
20+
overflow-y: auto;
21+
height: calc(100vh - 32px);
22+
min-width: 216px;
23+
max-width: 216px;
24+
`;
25+
26+
const StyledSkeletonContainer = styled.div`
27+
display: flex;
28+
flex-direction: column;
29+
gap: 32px;
30+
`;
31+
32+
const StyledSkeletonTitleContainer = styled.div`
33+
display: flex;
34+
flex-direction: column;
35+
gap: 6px;
36+
margin-left: 12px;
37+
margin-top: 8px;
38+
`;
39+
40+
export const LeftPanelSkeletonLoader = () => {
41+
const isMobile = useIsMobile();
42+
const mobileWidth = isMobile ? 0 : '100%';
43+
const desktopWidth = !mobileWidth ? 12 : DESKTOP_NAV_DRAWER_WIDTHS.menu;
44+
45+
return (
46+
<StyledAnimatedContainer
47+
initial={false}
48+
animate={{
49+
width: isMobile ? mobileWidth : desktopWidth,
50+
opacity: isMobile ? 0 : 1,
51+
}}
52+
transition={{
53+
duration: ANIMATION.duration.fast,
54+
}}
55+
>
56+
<StyledItemsContainer>
57+
<StyledSkeletonContainer>
58+
<StyledSkeletonTitleContainer>
59+
<SkeletonTheme
60+
baseColor={GRAY_SCALE.gray15}
61+
highlightColor={BACKGROUND_LIGHT.transparent.lighter}
62+
borderRadius={4}
63+
>
64+
<Skeleton width={96} height={16} />
65+
</SkeletonTheme>
66+
</StyledSkeletonTitleContainer>
67+
<MainNavigationDrawerItemsSkeletonLoader length={4} />
68+
<MainNavigationDrawerItemsSkeletonLoader title length={2} />
69+
<MainNavigationDrawerItemsSkeletonLoader title length={3} />
70+
</StyledSkeletonContainer>
71+
</StyledItemsContainer>
72+
</StyledAnimatedContainer>
73+
);
74+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
2+
import styled from '@emotion/styled';
3+
import { BACKGROUND_LIGHT, GRAY_SCALE } from 'twenty-ui';
4+
5+
const StyledSkeletonContainer = styled.div`
6+
display: flex;
7+
flex-direction: column;
8+
gap: 6px;
9+
margin-left: 12px;
10+
margin-top: 8px;
11+
`;
12+
13+
export const MainNavigationDrawerItemsSkeletonLoader = ({
14+
title,
15+
length,
16+
}: {
17+
title?: boolean;
18+
length: number;
19+
}) => {
20+
return (
21+
<StyledSkeletonContainer>
22+
<SkeletonTheme
23+
baseColor={GRAY_SCALE.gray15}
24+
highlightColor={BACKGROUND_LIGHT.transparent.lighter}
25+
borderRadius={4}
26+
>
27+
{title && <Skeleton width={48} height={13} />}
28+
{Array.from({ length }).map((_, index) => (
29+
<Skeleton key={index} width={196} height={16} />
30+
))}
31+
</SkeletonTheme>
32+
</StyledSkeletonContainer>
33+
);
34+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
2+
import styled from '@emotion/styled';
3+
import {
4+
BACKGROUND_LIGHT,
5+
BORDER_COMMON,
6+
BORDER_LIGHT,
7+
GRAY_SCALE,
8+
MOBILE_VIEWPORT,
9+
} from 'twenty-ui';
10+
11+
const StyledMainContainer = styled.div`
12+
background: ${BACKGROUND_LIGHT.noisy};
13+
box-sizing: border-box;
14+
display: flex;
15+
flex: 1 1 auto;
16+
flex-direction: row;
17+
gap: 8px;
18+
min-height: 0;
19+
padding-left: 0;
20+
width: 100%;
21+
22+
@media (max-width: ${MOBILE_VIEWPORT}px) {
23+
padding-left: 12px;
24+
padding-bottom: 0;
25+
}
26+
`;
27+
28+
const StyledPanel = styled.div`
29+
background: ${BACKGROUND_LIGHT.primary};
30+
border: 1px solid ${BORDER_LIGHT.color.medium};
31+
border-radius: ${BORDER_COMMON.radius.md};
32+
height: 100%;
33+
overflow: auto;
34+
width: 100%;
35+
`;
36+
37+
const StyledHeaderContainer = styled.div`
38+
flex: 1;
39+
`;
40+
const StyledRightPanelContainer = styled.div`
41+
display: flex;
42+
flex-direction: column;
43+
width: 100%;
44+
`;
45+
46+
const StyledRightPanelFlexContainer = styled.div`
47+
display: flex;
48+
margin-top: 12px;
49+
margin-bottom: 14px;
50+
`;
51+
52+
const StyledSkeletonHeaderLoader = () => {
53+
return (
54+
<StyledHeaderContainer>
55+
<SkeletonTheme
56+
baseColor={GRAY_SCALE.gray15}
57+
highlightColor={BACKGROUND_LIGHT.transparent.lighter}
58+
borderRadius={4}
59+
>
60+
<Skeleton height={16} width={104} />
61+
</SkeletonTheme>
62+
</StyledHeaderContainer>
63+
);
64+
};
65+
66+
const StyledSkeletonAddLoader = () => {
67+
return (
68+
<SkeletonTheme
69+
baseColor={GRAY_SCALE.gray15}
70+
highlightColor={BACKGROUND_LIGHT.transparent.lighter}
71+
borderRadius={4}
72+
>
73+
<Skeleton width={132} height={16} />
74+
</SkeletonTheme>
75+
);
76+
};
77+
78+
const RightPanelSkeleton = () => (
79+
<StyledMainContainer>
80+
<StyledPanel></StyledPanel>
81+
</StyledMainContainer>
82+
);
83+
84+
export const RightPanelSkeletonLoader = () => (
85+
<StyledRightPanelContainer>
86+
<StyledRightPanelFlexContainer>
87+
<StyledSkeletonHeaderLoader />
88+
<StyledSkeletonAddLoader />
89+
</StyledRightPanelFlexContainer>
90+
<RightPanelSkeleton />
91+
</StyledRightPanelContainer>
92+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import styled from '@emotion/styled';
2+
import { BACKGROUND_LIGHT, MOBILE_VIEWPORT } from 'twenty-ui';
3+
4+
import { DESKTOP_NAV_DRAWER_WIDTHS } from '@/ui/navigation/navigation-drawer/constants/DesktopNavDrawerWidths';
5+
import { LeftPanelSkeletonLoader } from '~/loading/components/LeftPanelSkeletonLoader';
6+
import { RightPanelSkeletonLoader } from '~/loading/components/RightPanelSkeletonLoader';
7+
8+
const StyledContainer = styled.div`
9+
background: ${BACKGROUND_LIGHT.noisy};
10+
box-sizing: border-box;
11+
display: flex;
12+
flex-direction: row;
13+
gap: 12px;
14+
height: 100vh;
15+
min-width: ${DESKTOP_NAV_DRAWER_WIDTHS.menu}px;
16+
width: 100%;
17+
padding: 12px 8px 12px;
18+
overflow: hidden;
19+
20+
@media (max-width: ${MOBILE_VIEWPORT}px) {
21+
width: 100%;
22+
}
23+
`;
24+
25+
export const UserOrMetadataLoader = () => {
26+
return (
27+
<StyledContainer>
28+
<LeftPanelSkeletonLoader />
29+
<RightPanelSkeletonLoader />
30+
</StyledContainer>
31+
);
32+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { useRecoilValue } from 'recoil';
2+
3+
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadingState';
4+
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
5+
6+
export const useUserOrMetadataLoading = () => {
7+
const isCurrentUserLoaded = useRecoilValue(isCurrentUserLoadedState);
8+
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
9+
10+
return !isCurrentUserLoaded || objectMetadataItems.length === 0;
11+
};

packages/twenty-front/src/modules/object-metadata/components/ObjectMetadataItemsProvider.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useRecoilValue } from 'recoil';
44
import { ObjectMetadataItemsLoadEffect } from '@/object-metadata/components/ObjectMetadataItemsLoadEffect';
55
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
66
import { RelationPickerScope } from '@/object-record/relation-picker/scopes/RelationPickerScope';
7+
import { UserOrMetadataLoader } from '~/loading/components/UserOrMetadataLoader';
78

89
export const ObjectMetadataItemsProvider = ({
910
children,
@@ -15,10 +16,12 @@ export const ObjectMetadataItemsProvider = ({
1516
return (
1617
<>
1718
<ObjectMetadataItemsLoadEffect />
18-
{shouldDisplayChildren && (
19+
{shouldDisplayChildren ? (
1920
<RelationPickerScope relationPickerScopeId="relation-picker">
2021
{children}
2122
</RelationPickerScope>
23+
) : (
24+
<UserOrMetadataLoader />
2225
)}
2326
</>
2427
);

packages/twenty-front/src/modules/users/components/UserProvider.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import React from 'react';
22
import { useRecoilValue } from 'recoil';
33

44
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadingState';
5+
import { UserOrMetadataLoader } from '~/loading/components/UserOrMetadataLoader';
56

67
export const UserProvider = ({ children }: React.PropsWithChildren) => {
78
const isCurrentUserLoaded = useRecoilValue(isCurrentUserLoadedState);
89

9-
return !isCurrentUserLoaded ? <></> : <>{children}</>;
10+
return !isCurrentUserLoaded ? <UserOrMetadataLoader /> : <>{children}</>;
1011
};

0 commit comments

Comments
 (0)