Add skeleton loading (#46)

This commit is contained in:
Maria Rushkova 2021-10-07 12:21:47 +02:00 committed by GitHub
parent 3f5e31f794
commit 377f4e5409
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 333 additions and 98 deletions

View File

@ -49,10 +49,10 @@ export function Chat({ theme, community, fetchMetadata }: ChatProps) {
activeChannelId={activeChannel.id} activeChannelId={activeChannel.id}
/> />
)} )}
{messenger ? (
<ChatBody <ChatBody
theme={theme} theme={theme}
channel={activeChannel} channel={activeChannel}
messenger={messenger}
messages={messages} messages={messages}
sendMessage={sendMessage} sendMessage={sendMessage}
notifications={notifications} notifications={notifications}
@ -66,9 +66,6 @@ export function Chat({ theme, community, fetchMetadata }: ChatProps) {
lastMessage={lastMessage} lastMessage={lastMessage}
fetchMetadata={fetchMetadata} fetchMetadata={fetchMetadata}
/> />
) : (
<Loading>Connecting to waku</Loading>
)}
{showMembers && !narrow && ( {showMembers && !narrow && (
<Members <Members
theme={theme} theme={theme}
@ -80,16 +77,6 @@ export function Chat({ theme, community, fetchMetadata }: ChatProps) {
); );
} }
const Loading = styled.div`
display: flex;
flex-direction: column;
flex: 1;
height: 100%;
text-align: center;
justify-content: center;
align-items: center;
`;
const ChatWrapper = styled.div` const ChatWrapper = styled.div`
width: 100%; width: 100%;
height: 100vh; height: 100vh;

View File

@ -13,6 +13,8 @@ import { EmptyChannel } from "../EmptyChannel";
import { MembersIcon } from "../Icons/MembersIcon"; import { MembersIcon } from "../Icons/MembersIcon";
import { NarrowChannels } from "../NarrowMode/NarrowChannels"; import { NarrowChannels } from "../NarrowMode/NarrowChannels";
import { NarrowMembers } from "../NarrowMode/NarrowMembers"; import { NarrowMembers } from "../NarrowMode/NarrowMembers";
import { Loading } from "../Skeleton/Loading";
import { LoadingSkeleton } from "../Skeleton/LoadingSkeleton";
import { ChatInput } from "./ChatInput"; import { ChatInput } from "./ChatInput";
import { ChatMessages } from "./ChatMessages"; import { ChatMessages } from "./ChatMessages";
@ -21,6 +23,7 @@ interface ChatBodyProps {
theme: Theme; theme: Theme;
channel: ChannelData; channel: ChannelData;
community: CommunityData; community: CommunityData;
messenger: any;
messages: ChatMessage[]; messages: ChatMessage[];
sendMessage: (text: string) => void; sendMessage: (text: string) => void;
onClick: () => void; onClick: () => void;
@ -38,6 +41,7 @@ export function ChatBody({
theme, theme,
channel, channel,
community, community,
messenger,
messages, messages,
sendMessage, sendMessage,
onClick, onClick,
@ -99,18 +103,25 @@ export function ChatBody({
> >
<MembersIcon theme={theme} /> <MembersIcon theme={theme} />
</MemberBtn> </MemberBtn>
{!messenger && <Loading theme={theme} />}
</ChatTopbar> </ChatTopbar>
{messenger ? (
<>
{!showChannelsList && !showMembersList && ( {!showChannelsList && !showMembersList && (
<> <>
<button onClick={loadNextDay}> <button onClick={loadNextDay}>
Last message date {lastMessage.toDateString()} Last message date {lastMessage.toDateString()}
</button>{" "} </button>{" "}
{messages.length > 0 ? ( {messages.length > 0 ? (
messenger ? (
<ChatMessages <ChatMessages
messages={messages} messages={messages}
theme={theme} theme={theme}
fetchMetadata={fetchMetadata} fetchMetadata={fetchMetadata}
/> />
) : (
<LoadingSkeleton theme={theme} />
)
) : ( ) : (
<EmptyChannel theme={theme} channel={channel} /> <EmptyChannel theme={theme} channel={channel} />
)} )}
@ -136,6 +147,13 @@ export function ChatBody({
setShowMembersList={setShowMembersList} setShowMembersList={setShowMembersList}
/> />
)} )}
</>
) : (
<>
<LoadingSkeleton theme={theme} />
<ChatInput theme={theme} addMessage={sendMessage} />
</>
)}
</ChatBodyWrapper> </ChatBodyWrapper>
); );
} }
@ -169,6 +187,7 @@ const ChatTopbar = styled.div<ThemeProps>`
justify-content: space-between; justify-content: space-between;
padding: 5px 8px; padding: 5px 8px;
background: ${({ theme }) => theme.bodyBackgroundColor}; background: ${({ theme }) => theme.bodyBackgroundColor};
position: relative;
&.narrow { &.narrow {
position: fixed; position: fixed;

View File

@ -0,0 +1,36 @@
import React from "react";
import styled, { keyframes } from "styled-components";
import { Theme } from "../../styles/themes";
const rotation = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`;
export const LoadingIcon = ({ theme }: { theme: Theme }) => (
<Icon
width="13"
height="12"
viewBox="0 0 13 12"
xmlns="http://www.w3.org/2000/svg"
theme={theme}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10.7682 5.07742C10.5667 4.18403 10.0777 3.37742 9.37244 2.77889C8.66702 2.18025 7.78327 1.8222 6.85295 1.7598C5.9226 1.69741 4.99749 1.93417 4.21597 2.43357C3.4346 2.93289 2.83935 3.66744 2.51731 4.52621C2.19533 5.38485 2.16318 6.32294 2.4255 7.20091C2.68785 8.07899 3.23118 8.85135 3.9762 9.40157C4.72137 9.95188 5.62791 10.25 6.56041 10.25L6.56041 11.75C5.30863 11.75 4.08949 11.3499 3.0851 10.6082C2.08057 9.86633 1.34435 8.82207 0.988276 7.63032C0.632173 6.43846 0.675924 5.16459 1.11282 3.99953C1.54966 2.8346 2.35554 1.84232 3.40827 1.16961C4.46086 0.496978 5.7044 0.179402 6.95332 0.263164C8.20227 0.346928 9.39145 0.827686 10.343 1.63521C11.2947 2.44286 11.9578 3.53431 12.2314 4.74738L10.7682 5.07742Z"
/>
</Icon>
);
const Icon = styled.svg`
& > path {
fill: ${({ theme }) => theme.primary};
}
animation: ${rotation} 2s linear infinite;
`;

View File

@ -1,37 +0,0 @@
import React from "react";
import styled from "styled-components";
import { Theme } from "../../styles/themes";
interface ThemeProps {
theme: Theme;
}
export const TagIcon = ({ theme }: ThemeProps) => {
return (
<Icon
width="20"
height="20"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
theme={theme}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 18.5C14.6944 18.5 18.5 14.6944 18.5 10C18.5 5.30558 14.6944 1.5 10 1.5C5.30558 1.5 1.5 5.30558 1.5 10C1.5 14.6944 5.30558 18.5 10 18.5ZM10 20C15.5228 20 20 15.5228 20 10C20 4.47715 15.5228 0 10 0C4.47715 0 0 4.47715 0 10C0 15.5228 4.47715 20 10 20Z"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M9.48979 5.62329C9.55789 5.21472 9.28187 4.8283 8.87329 4.7602C8.46472 4.6921 8.0783 4.96812 8.0102 5.3767L7.76762 6.8322C7.72743 7.07329 7.51884 7.25 7.27442 7.25H6C5.58579 7.25 5.25 7.58579 5.25 8C5.25 8.41421 5.58579 8.75 6 8.75H6.85775C7.16672 8.75 7.40174 9.02743 7.35095 9.3322L7.10095 10.8322C7.06077 11.0733 6.85217 11.25 6.60775 11.25H5.5C5.08579 11.25 4.75 11.5858 4.75 12C4.75 12.4142 5.08579 12.75 5.5 12.75H6.19109C6.50006 12.75 6.73508 13.0274 6.68428 13.3322L6.5102 14.3767C6.4421 14.7853 6.71812 15.1717 7.1267 15.2398C7.53527 15.3079 7.92169 15.0319 7.98979 14.6233L8.23237 13.1678C8.27256 12.9267 8.48115 12.75 8.72557 12.75H10.1911C10.5001 12.75 10.7351 13.0274 10.6843 13.3322L10.5102 14.3767C10.4421 14.7853 10.7181 15.1717 11.1267 15.2398C11.5353 15.3079 11.9217 15.0319 11.9898 14.6233L12.2324 13.1678C12.2726 12.9267 12.4811 12.75 12.7256 12.75H14C14.4142 12.75 14.75 12.4142 14.75 12C14.75 11.5858 14.4142 11.25 14 11.25H13.1422C12.8333 11.25 12.5982 10.9726 12.649 10.6678L12.899 9.1678C12.9392 8.92671 13.1478 8.75 13.3922 8.75H14.5C14.9142 8.75 15.25 8.41421 15.25 8C15.25 7.58579 14.9142 7.25 14.5 7.25H13.8089C13.4999 7.25 13.2649 6.97257 13.3157 6.6678L13.4898 5.62329C13.5579 5.21472 13.2819 4.8283 12.8733 4.7602C12.4647 4.6921 12.0783 4.96812 12.0102 5.3767L11.7676 6.8322C11.7274 7.07329 11.5188 7.25 11.2744 7.25H9.8089C9.49993 7.25 9.26491 6.97257 9.31571 6.6678L9.48979 5.62329ZM10.6078 11.25C10.8522 11.25 11.0608 11.0733 11.1009 10.8322L11.3509 9.3322C11.4017 9.02743 11.1667 8.75 10.8578 8.75H9.39224C9.14782 8.75 8.93922 8.92671 8.89904 9.1678L8.64904 10.6678C8.59824 10.9726 8.83327 11.25 9.14224 11.25H10.6078Z"
/>
</Icon>
);
};
const Icon = styled.svg<ThemeProps>`
& > path {
fill: ${({ theme }) => theme.secondary};
}
`;

View File

@ -0,0 +1,43 @@
import React from "react";
import styled from "styled-components";
import { Theme } from "../../styles/themes";
import { LoadingIcon } from "../Icons/LoadingIcon";
import { textSmallStyles } from "../Text";
interface LoadingProps {
theme: Theme;
}
export const Loading = ({ theme }: LoadingProps) => {
return (
<LoadingBlock theme={theme}>
<LoadingIcon theme={theme} />
<LoadingText>Loading messages...</LoadingText>
</LoadingBlock>
);
};
const LoadingBlock = styled.div<LoadingProps>`
display: flex;
align-items: center;
position: absolute;
left: 50%;
bottom: -35px;
transform: translateX(-50%);
padding: 4px 5px 4px 7px;
background: ${({ theme }) => theme.bodyBackgroundColor};
color: ${({ theme }) => theme.primary};
box-shadow: 0px 2px 4px rgba(0, 34, 51, 0.16),
0px 4px 12px rgba(0, 34, 51, 0.08);
border-radius: 8px;
z-index: 2;
`;
const LoadingText = styled.p<LoadingProps>`
color: ${({ theme }) => theme.primary};
margin-left: 8px;
font-weight: 500;
${textSmallStyles}
`;

View File

@ -0,0 +1,66 @@
import React from "react";
import styled from "styled-components";
import { Theme } from "../../styles/themes";
import { MessageSkeleton } from "./MessageSkeleton";
import { Skeleton } from "./Skeleton";
interface LoadingSkeletonProps {
theme: Theme;
}
export const LoadingSkeleton = ({ theme }: LoadingSkeletonProps) => {
return (
<Loading>
<MessageSkeleton theme={theme}>
<Skeleton theme={theme} />
</MessageSkeleton>
<MessageSkeleton theme={theme}>
<Skeleton theme={theme} />
</MessageSkeleton>
<MessageSkeleton theme={theme}>
<Skeleton theme={theme} />
</MessageSkeleton>
<MessageSkeleton theme={theme}>
<Skeleton theme={theme} />
</MessageSkeleton>
<MessageSkeleton theme={theme}>
<Skeleton theme={theme} />
<Skeleton theme={theme} width="30%" />
</MessageSkeleton>
<MessageSkeleton theme={theme}>
<Skeleton theme={theme} width="70%" />
</MessageSkeleton>
<MessageSkeleton theme={theme}>
<Skeleton theme={theme} width="40%" />
<Skeleton theme={theme} width="25%" />
<Skeleton theme={theme} width="30%" />
</MessageSkeleton>
<MessageSkeleton theme={theme}>
<Skeleton theme={theme} width="50%" />
<Skeleton
theme={theme}
width="147px"
height="196px"
borderRadius="16px"
/>
</MessageSkeleton>
<MessageSkeleton theme={theme}>
<Skeleton theme={theme} width="50%" />
</MessageSkeleton>
<MessageSkeleton theme={theme}>
<Skeleton theme={theme} width="70%" />
</MessageSkeleton>
</Loading>
);
};
const Loading = styled.div`
display: flex;
flex-direction: column;
justify-content: flex-end;
height: calc(100% - 44px);
padding: 8px 16px 0;
overflow: auto;
`;

View File

@ -0,0 +1,66 @@
import React, { ReactNode } from "react";
import styled from "styled-components";
import { Theme } from "../../styles/themes";
import { Skeleton } from "./Skeleton";
interface MessageSkeletonProps {
theme: Theme;
children: ReactNode;
}
export const MessageSkeleton = ({ theme, children }: MessageSkeletonProps) => {
return (
<MessageWrapper>
<AvatarSkeleton
width="40px"
height="40px"
borderRadius="50%"
theme={theme}
/>
<ContentWrapper>
<MessageHeaderWrapper>
<UserNameSkeleton theme={theme} width="132px" />
<TimeSkeleton theme={theme} width="47px" height="14px" />
</MessageHeaderWrapper>
<MessageBodyWrapper>{children}</MessageBodyWrapper>
</ContentWrapper>
</MessageWrapper>
);
};
const MessageWrapper = styled.div`
display: flex;
padding: 8px 0;
margin-bottom: 8px;
`;
const ContentWrapper = styled.div`
display: flex;
flex-direction: column;
flex: 1;
`;
const MessageHeaderWrapper = styled.div`
display: flex;
align-items: center;
`;
const MessageBodyWrapper = styled.div`
display: flex;
flex-direction: column;
`;
const AvatarSkeleton = styled(Skeleton)`
border-radius: 50%;
margin-right: 8px;
`;
const UserNameSkeleton = styled(Skeleton)`
margin-right: 4px;
`;
const TimeSkeleton = styled(Skeleton)`
margin-right: 4px;
`;

View File

@ -0,0 +1,49 @@
import styled, { keyframes } from "styled-components";
import { Theme } from "../../styles/themes";
interface SkeletonProps {
theme: Theme;
width?: string;
height?: string;
borderRadius?: string;
}
const waveKeyframe = keyframes`
0% {
transform: translateX(-100%);
}
50% {
transform: translateX(100%);
}
100% {
transform: translateX(100%);
}
`;
export const Skeleton = styled.div<SkeletonProps>`
position: relative;
display: inline-block;
width: ${({ width }) => width || "100%"};
height: ${({ height }) => height || "22px"};
background: ${({ theme }) => theme.skeletonDark};
border-radius: ${({ borderRadius }) => borderRadius || "8px"};
margin-bottom: 5px;
overflow: hidden;
&::after {
animation: ${waveKeyframe} 1.6s linear 0.5s infinite;
background: linear-gradient(
90deg,
${({ theme }) => theme.skeletonLight} 0%,
${({ theme }) => theme.skeletonDark} 100%
);
content: "";
position: absolute;
transform: translateX(-100%);
bottom: 0;
left: 0;
right: 0;
top: 0;
}
`;

View File

@ -13,6 +13,8 @@ export type Theme = {
inputColor: string; inputColor: string;
border: string; border: string;
buttonBg: string; buttonBg: string;
skeletonLight: string;
skeletonDark: string;
}; };
export const lightTheme: Theme = { export const lightTheme: Theme = {
@ -30,6 +32,8 @@ export const lightTheme: Theme = {
inputColor: "#EEF2F5", inputColor: "#EEF2F5",
border: "#EEF2F5", border: "#EEF2F5",
buttonBg: "rgba(67, 96, 223, 0.2)", buttonBg: "rgba(67, 96, 223, 0.2)",
skeletonLight: "#F6F8FA",
skeletonDark: "#E9EDF1",
}; };
export const darkTheme: Theme = { export const darkTheme: Theme = {
@ -47,6 +51,8 @@ export const darkTheme: Theme = {
inputColor: "#373737", inputColor: "#373737",
border: "#373737", border: "#373737",
buttonBg: "rgba(134, 158, 255, 0.3)", buttonBg: "rgba(134, 158, 255, 0.3)",
skeletonLight: "#2E2F31",
skeletonDark: "#141414",
}; };
export default { lightTheme, darkTheme }; export default { lightTheme, darkTheme };