Skip to content

Commit

Permalink
add multi shimmer skeleton
Browse files Browse the repository at this point in the history
  • Loading branch information
pvicensSpacedev committed Apr 24, 2024
1 parent 510ec59 commit 53c958e
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 53 deletions.
35 changes: 35 additions & 0 deletions apps/web/src/components/MultiShimmer/MultiShimmer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';
import Skeleton from 'react-loading-skeleton';
import styled from 'styled-components';

const Wrapper = styled.div`
margin-bottom: 60px;
width: 100%;
`;

const ArtworksGridSkeleton = styled.div`
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 32px;
margin-top: 16px;
`;

const ArtworkGridItemSkeleton = styled(Skeleton)`
height: 300px;
`;

const SkeletoState = () => {
return (
<Wrapper>
<ArtworksGridSkeleton>
{[...Array(12)].map((_, index) => (
<ArtworkGridItemSkeleton key={index} />
))}
</ArtworksGridSkeleton>
</Wrapper>
);
};

export function MultiShimmer() {
return <SkeletoState />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const UsernameSocialsSkeleton = styled(Skeleton)`

const ArtworksGridSkeleton = styled.div`
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-columns: repeat(4, 1fr);
grid-gap: 16px;
margin-top: 16px;
`;
Expand All @@ -66,7 +66,7 @@ const UserGallerySkeleton = () => {
<Wrapper>
<TitleSkeleton />
<ArtworksGridSkeleton>
{[...Array(6)].map((_, index) => (
{[...Array(12)].map((_, index) => (
<ArtworkGridItemSkeleton key={index} />
))}
</ArtworksGridSkeleton>
Expand Down
84 changes: 79 additions & 5 deletions apps/web/src/contexts/shimmer/ShimmerContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { createContext, memo, ReactNode, useContext, useMemo, useState } from 'react';
import {
createContext,
memo,
ReactNode,
useCallback,
useContext,
useMemo,
useState,
} from 'react';
import styled from 'styled-components';

import { MultiShimmer } from '~/components/MultiShimmer/MultiShimmer';
import Shimmer from '~/components/Shimmer/Shimmer';

type AspectRatio = 'wide' | 'square' | 'tall' | 'unknown';
Expand All @@ -23,7 +32,7 @@ export const useContentState = (): ShimmerState => {

// TODO(Terence): Fix this later
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type ContentIsLoadedEvent = (event?: any) => void;
export type ContentIsLoadedEvent = (event?: any, tokenId?: string) => void;

type ShimmerAction = {
setContentIsLoaded: ContentIsLoadedEvent;
Expand All @@ -40,13 +49,76 @@ export const useSetContentIsLoaded = (): ShimmerAction['setContentIsLoaded'] =>
return context.setContentIsLoaded;
};

const MultiShimmerContext = createContext<
{ markTokenAsLoaded: (tokenId: string) => void } | undefined
>(undefined);

export const useMultiShimmerProvider = () => {
const context = useContext(MultiShimmerContext);
if (!context) {
return null;
}
return context;
};

export const MultiShimmerProvider = ({
children,
tokenIdsToLoad,
}: {
children: ReactNode;
tokenIdsToLoad: string[];
}) => {
const [waitingForTokenIds, setWaitingForTokenIds] = useState(tokenIdsToLoad);

const markTokenAsLoaded = useCallback((tokenId: string) => {
setWaitingForTokenIds((prev) => prev.filter((id) => id !== tokenId));
}, []);

const value = useMemo(
() => ({
markTokenAsLoaded,
}),
[markTokenAsLoaded]
);

type VisibleDivProps = {
isVisible: boolean;
};

const VisibleDiv = styled.div<VisibleDivProps>`
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
visibility: ${({ isVisible }) => (isVisible ? 'visible' : 'hidden')};
`;

const isLoading = Boolean(waitingForTokenIds.length);

return (
<MultiShimmerContext.Provider value={value}>
{isLoading && (
<Container overflowHidden={isLoading}>
<VisibleDiv isVisible={isLoading}>
<MultiShimmer />
</VisibleDiv>
</Container>
)}
{children}
</MultiShimmerContext.Provider>
);
};

type Props = { children: ReactNode | ReactNode[] };

const ShimmerProvider = memo(({ children }: Props) => {
const [isLoaded, setIsLoaded] = useState(false);
const [aspectRatio, setAspectRatio] = useState<null | number>(null);
const [aspectRatioType, setAspectRatioType] = useState<null | AspectRatio>(null);

const multiShimmerContext = useMultiShimmerProvider();

const state = useMemo(
() => ({
aspectRatio,
Expand All @@ -61,7 +133,7 @@ const ShimmerProvider = memo(({ children }: Props) => {
// such as images, videos, and iframes, each of which come with different properties.
// This is getting fixed in a followup PR
// eslint-disable-next-line @typescript-eslint/no-explicit-any
setContentIsLoaded: (event?: any) => {
setContentIsLoaded: (event?: any, tokenId?: string) => {
if (event) {
// default aspect ratio to 1; if we can't determine an asset's dimensions, we'll show it in a square viewport
let aspectRatio = 1;
Expand Down Expand Up @@ -89,11 +161,13 @@ const ShimmerProvider = memo(({ children }: Props) => {
setAspectRatioType('unknown');
}
}

if (tokenId) {
multiShimmerContext?.markTokenAsLoaded(tokenId);
}
setIsLoaded(true);
},
}),
[]
[multiShimmerContext]
);

return (
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/hooks/useNftRetry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function useNftRetry({ tokenId }: useNftRetryArgs): useNftRetryResult {

const handleNftLoaded = useCallback<ContentIsLoadedEvent>(
(event) => {
shimmerContext?.setContentIsLoaded(event);
shimmerContext?.setContentIsLoaded(event, tokenId);

markTokenAsLoaded(tokenId);
},
Expand Down
104 changes: 59 additions & 45 deletions apps/web/src/scenes/UserGalleryPage/UserGalleryCollections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import styled from 'styled-components';

import breakpoints from '~/components/core/breakpoints';
import { DisplayLayout } from '~/components/core/enums';
import { MultiShimmerProvider } from '~/contexts/shimmer/ShimmerContext';
import { UserGalleryCollectionsFragment$key } from '~/generated/UserGalleryCollectionsFragment.graphql';
import { UserGalleryCollectionsQueryFragment$key } from '~/generated/UserGalleryCollectionsQueryFragment.graphql';
import useWindowSize from '~/hooks/useWindowSize';
Expand Down Expand Up @@ -58,6 +59,9 @@ function UserGalleryCollections({ galleryRef, queryRef, mobileLayout }: Props) {
tokens {
__typename
id
token {
dbid
}
}
layout {
sectionLayout {
Expand All @@ -71,6 +75,14 @@ function UserGalleryCollections({ galleryRef, queryRef, mobileLayout }: Props) {
galleryRef
);

const tokenIds = collections?.reduce((acc, collection) => {
const ids =
collection?.tokens
?.map((token) => token?.token?.dbid)
.filter((id): id is string => id !== undefined) || [];
return acc.concat(ids);
}, [] as string[]);

const isAuthenticatedUsersPage = loggedInUserId === owner?.id;

const nonNullCollections = removeNullValues(collections);
Expand Down Expand Up @@ -147,58 +159,60 @@ function UserGalleryCollections({ galleryRef, queryRef, mobileLayout }: Props) {
}

return (
<StyledUserGalleryCollections>
<WindowScroller>
{({ height, registerChild, scrollTop, onChildScroll }) => (
<AutoSizer disableHeight>
{({ width }) => (
// @ts-expect-error shitty react-virtualized types
<div ref={registerChild}>
<List
ref={listRef}
autoHeight
width={width}
height={height}
onScroll={onChildScroll}
rowHeight={cache.rowHeight}
rowCount={numCollectionsToDisplay}
scrollTop={scrollTop}
deferredMeasurementCache={cache}
rowRenderer={rowRenderer}
style={{
outline: 'none',
overflowX: 'visible',
overflowY: 'visible',
}}
containerStyle={{ overflow: 'visible' }}
overscanIndicesGetter={({
cellCount,
overscanCellsCount,
startIndex,
stopIndex,
}) => ({
overscanStartIndex: Math.max(0, startIndex - overscanCellsCount),
overscanStopIndex: Math.min(cellCount - 1, stopIndex + overscanCellsCount),
})}
/>
</div>
)}
</AutoSizer>
)}
</WindowScroller>
</StyledUserGalleryCollections>
<MultiShimmerProvider tokenIdsToLoad={tokenIds ? tokenIds?.slice(0, 12) : []}>
<StyledUserGalleryCollections>
<WindowScroller>
{({ height, registerChild, scrollTop, onChildScroll }) => (
<AutoSizer disableHeight>
{({ width }) => (
// @ts-expect-error shitty react-virtualized types
<div ref={registerChild}>
<List
ref={listRef}
autoHeight
width={width}
height={height}
onScroll={onChildScroll}
rowHeight={cache.rowHeight}
rowCount={numCollectionsToDisplay}
scrollTop={scrollTop}
deferredMeasurementCache={cache}
rowRenderer={rowRenderer}
style={{
outline: 'none',
overflowX: 'visible',
overflowY: 'visible',
}}
containerStyle={{ overflow: 'visible' }}
overscanIndicesGetter={({
cellCount,
overscanCellsCount,
startIndex,
stopIndex,
}) => ({
overscanStartIndex: Math.max(0, startIndex - overscanCellsCount),
overscanStopIndex: Math.min(cellCount - 1, stopIndex + overscanCellsCount),
})}
/>
</div>
)}
</AutoSizer>
)}
</WindowScroller>
</StyledUserGalleryCollections>
</MultiShimmerProvider>
);
}

const StyledUserGalleryCollections = styled.div`
width: 100%;
width: 100%;
padding-top: 16px;
padding-top: 16px;
@media only screen and ${breakpoints.tablet} {
padding-top: 24px;
@media only screen and ${breakpoints.tablet} {
padding-top: 24px;
}
`;
`;

const StyledUserGalleryCollectionContainer = styled.div`
padding-bottom: 48px;
Expand Down

0 comments on commit 53c958e

Please sign in to comment.