Add skeleton loading (#46)
This commit is contained in:
parent
3f5e31f794
commit
377f4e5409
|
@ -49,26 +49,23 @@ export function Chat({ theme, community, fetchMetadata }: ChatProps) {
|
|||
activeChannelId={activeChannel.id}
|
||||
/>
|
||||
)}
|
||||
{messenger ? (
|
||||
<ChatBody
|
||||
theme={theme}
|
||||
channel={activeChannel}
|
||||
messages={messages}
|
||||
sendMessage={sendMessage}
|
||||
notifications={notifications}
|
||||
setActiveChannel={setActiveChannel}
|
||||
activeChannelId={activeChannel.id}
|
||||
onClick={() => setShowMembers(!showMembers)}
|
||||
showMembers={showMembers}
|
||||
community={community}
|
||||
showCommunity={!showChannels}
|
||||
loadNextDay={() => loadNextDay(activeChannel.name)}
|
||||
lastMessage={lastMessage}
|
||||
fetchMetadata={fetchMetadata}
|
||||
/>
|
||||
) : (
|
||||
<Loading>Connecting to waku</Loading>
|
||||
)}
|
||||
<ChatBody
|
||||
theme={theme}
|
||||
channel={activeChannel}
|
||||
messenger={messenger}
|
||||
messages={messages}
|
||||
sendMessage={sendMessage}
|
||||
notifications={notifications}
|
||||
setActiveChannel={setActiveChannel}
|
||||
activeChannelId={activeChannel.id}
|
||||
onClick={() => setShowMembers(!showMembers)}
|
||||
showMembers={showMembers}
|
||||
community={community}
|
||||
showCommunity={!showChannels}
|
||||
loadNextDay={() => loadNextDay(activeChannel.name)}
|
||||
lastMessage={lastMessage}
|
||||
fetchMetadata={fetchMetadata}
|
||||
/>
|
||||
{showMembers && !narrow && (
|
||||
<Members
|
||||
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`
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
|
|
|
@ -13,6 +13,8 @@ import { EmptyChannel } from "../EmptyChannel";
|
|||
import { MembersIcon } from "../Icons/MembersIcon";
|
||||
import { NarrowChannels } from "../NarrowMode/NarrowChannels";
|
||||
import { NarrowMembers } from "../NarrowMode/NarrowMembers";
|
||||
import { Loading } from "../Skeleton/Loading";
|
||||
import { LoadingSkeleton } from "../Skeleton/LoadingSkeleton";
|
||||
|
||||
import { ChatInput } from "./ChatInput";
|
||||
import { ChatMessages } from "./ChatMessages";
|
||||
|
@ -21,6 +23,7 @@ interface ChatBodyProps {
|
|||
theme: Theme;
|
||||
channel: ChannelData;
|
||||
community: CommunityData;
|
||||
messenger: any;
|
||||
messages: ChatMessage[];
|
||||
sendMessage: (text: string) => void;
|
||||
onClick: () => void;
|
||||
|
@ -38,6 +41,7 @@ export function ChatBody({
|
|||
theme,
|
||||
channel,
|
||||
community,
|
||||
messenger,
|
||||
messages,
|
||||
sendMessage,
|
||||
onClick,
|
||||
|
@ -99,43 +103,57 @@ export function ChatBody({
|
|||
>
|
||||
<MembersIcon theme={theme} />
|
||||
</MemberBtn>
|
||||
{!messenger && <Loading theme={theme} />}
|
||||
</ChatTopbar>
|
||||
{!showChannelsList && !showMembersList && (
|
||||
{messenger ? (
|
||||
<>
|
||||
<button onClick={loadNextDay}>
|
||||
Last message date {lastMessage.toDateString()}
|
||||
</button>{" "}
|
||||
{messages.length > 0 ? (
|
||||
<ChatMessages
|
||||
messages={messages}
|
||||
theme={theme}
|
||||
fetchMetadata={fetchMetadata}
|
||||
/>
|
||||
) : (
|
||||
<EmptyChannel theme={theme} channel={channel} />
|
||||
{!showChannelsList && !showMembersList && (
|
||||
<>
|
||||
<button onClick={loadNextDay}>
|
||||
Last message date {lastMessage.toDateString()}
|
||||
</button>{" "}
|
||||
{messages.length > 0 ? (
|
||||
messenger ? (
|
||||
<ChatMessages
|
||||
messages={messages}
|
||||
theme={theme}
|
||||
fetchMetadata={fetchMetadata}
|
||||
/>
|
||||
) : (
|
||||
<LoadingSkeleton theme={theme} />
|
||||
)
|
||||
) : (
|
||||
<EmptyChannel theme={theme} channel={channel} />
|
||||
)}
|
||||
<ChatInput theme={theme} addMessage={sendMessage} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{showChannelsList && narrow && (
|
||||
<NarrowChannels
|
||||
theme={theme}
|
||||
community={community.name}
|
||||
notifications={notifications}
|
||||
setActiveChannel={setActiveChannel}
|
||||
setShowChannels={setShowChannelsList}
|
||||
activeChannelId={activeChannelId}
|
||||
/>
|
||||
)}
|
||||
{showMembersList && narrow && (
|
||||
<NarrowMembers
|
||||
theme={theme}
|
||||
community={community}
|
||||
setShowChannels={setShowChannelsList}
|
||||
setShowMembersList={setShowMembersList}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<LoadingSkeleton theme={theme} />
|
||||
<ChatInput theme={theme} addMessage={sendMessage} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{showChannelsList && narrow && (
|
||||
<NarrowChannels
|
||||
theme={theme}
|
||||
community={community.name}
|
||||
notifications={notifications}
|
||||
setActiveChannel={setActiveChannel}
|
||||
setShowChannels={setShowChannelsList}
|
||||
activeChannelId={activeChannelId}
|
||||
/>
|
||||
)}
|
||||
{showMembersList && narrow && (
|
||||
<NarrowMembers
|
||||
theme={theme}
|
||||
community={community}
|
||||
setShowChannels={setShowChannelsList}
|
||||
setShowMembersList={setShowMembersList}
|
||||
/>
|
||||
)}
|
||||
</ChatBodyWrapper>
|
||||
);
|
||||
}
|
||||
|
@ -169,6 +187,7 @@ const ChatTopbar = styled.div<ThemeProps>`
|
|||
justify-content: space-between;
|
||||
padding: 5px 8px;
|
||||
background: ${({ theme }) => theme.bodyBackgroundColor};
|
||||
position: relative;
|
||||
|
||||
&.narrow {
|
||||
position: fixed;
|
||||
|
|
|
@ -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;
|
||||
`;
|
|
@ -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};
|
||||
}
|
||||
`;
|
|
@ -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}
|
||||
`;
|
|
@ -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;
|
||||
`;
|
|
@ -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;
|
||||
`;
|
|
@ -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;
|
||||
}
|
||||
`;
|
|
@ -13,6 +13,8 @@ export type Theme = {
|
|||
inputColor: string;
|
||||
border: string;
|
||||
buttonBg: string;
|
||||
skeletonLight: string;
|
||||
skeletonDark: string;
|
||||
};
|
||||
|
||||
export const lightTheme: Theme = {
|
||||
|
@ -30,6 +32,8 @@ export const lightTheme: Theme = {
|
|||
inputColor: "#EEF2F5",
|
||||
border: "#EEF2F5",
|
||||
buttonBg: "rgba(67, 96, 223, 0.2)",
|
||||
skeletonLight: "#F6F8FA",
|
||||
skeletonDark: "#E9EDF1",
|
||||
};
|
||||
|
||||
export const darkTheme: Theme = {
|
||||
|
@ -47,6 +51,8 @@ export const darkTheme: Theme = {
|
|||
inputColor: "#373737",
|
||||
border: "#373737",
|
||||
buttonBg: "rgba(134, 158, 255, 0.3)",
|
||||
skeletonLight: "#2E2F31",
|
||||
skeletonDark: "#141414",
|
||||
};
|
||||
|
||||
export default { lightTheme, darkTheme };
|
||||
|
|
Loading…
Reference in New Issue