Toast message (#155)

This commit is contained in:
Maria Rushkova 2021-12-13 18:09:26 +01:00 committed by GitHub
parent c06c0253bc
commit 7d90ad9ae4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 181 additions and 7 deletions

View File

@ -12,6 +12,7 @@ import { Members } from "./Members/Members";
import { CommunityModal } from "./Modals/CommunityModal"; import { CommunityModal } from "./Modals/CommunityModal";
import { EditModal } from "./Modals/EditModal"; import { EditModal } from "./Modals/EditModal";
import { ProfileModal } from "./Modals/ProfileModal"; import { ProfileModal } from "./Modals/ProfileModal";
import { ToastMessageList } from "./ToastMessages/ToastMessageList";
function Modals() { function Modals() {
return ( return (
@ -45,6 +46,7 @@ export function Chat() {
{showMembers && !narrow && state === ChatState.ChatBody && <Members />} {showMembers && !narrow && state === ChatState.ChatBody && <Members />}
{state === ChatState.ChatCreation && <ChatCreation />} {state === ChatState.ChatCreation && <ChatCreation />}
<Modals /> <Modals />
<ToastMessageList />
</ChatWrapper> </ChatWrapper>
); );
} }
@ -53,6 +55,7 @@ const ChatWrapper = styled.div`
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
position: relative;
`; `;
const ChannelsWrapper = styled.div` const ChannelsWrapper = styled.div`

View File

@ -5,6 +5,7 @@ import styled from "styled-components";
import { useActivities } from "../../contexts/activityProvider"; import { useActivities } from "../../contexts/activityProvider";
import { useIdentity } from "../../contexts/identityProvider"; import { useIdentity } from "../../contexts/identityProvider";
import { useModal } from "../../contexts/modalProvider"; import { useModal } from "../../contexts/modalProvider";
import { useToasts } from "../../contexts/toastProvider";
import { useManageContact } from "../../hooks/useManageContact"; import { useManageContact } from "../../hooks/useManageContact";
import { copy } from "../../utils"; import { copy } from "../../utils";
import { buttonStyles } from "../Buttons/buttonStyle"; import { buttonStyles } from "../Buttons/buttonStyle";
@ -42,6 +43,8 @@ export const ProfileModal = () => {
); );
const { setActivities } = useActivities(); const { setActivities } = useActivities();
const { setToasts } = useToasts();
const { setModal } = useModal(ProfileModalName);
const identity = useIdentity(); const identity = useIdentity();
const isUser = useMemo( const isUser = useMemo(
@ -160,6 +163,7 @@ export const ProfileModal = () => {
maxLength={280} maxLength={280}
onInput={(e) => setRequest(e.currentTarget.value)} onInput={(e) => setRequest(e.currentTarget.value)}
required required
autoFocus
/> />
</RequestSection> </RequestSection>
)} )}
@ -200,8 +204,16 @@ export const ProfileModal = () => {
requestType: "outcome", requestType: "outcome",
status: "sent", status: "sent",
}, },
]),
setToasts((prev) => [
...prev,
{
id: id + request,
type: "request",
},
]), ]),
setRequestCreation(false), setRequestCreation(false),
setModal(false),
setRequest(""); setRequest("");
}} }}
> >

View File

@ -6,6 +6,7 @@ import { ActivityProvider } from "../contexts/activityProvider";
import { FetchMetadataProvider } from "../contexts/fetchMetadataProvider"; import { FetchMetadataProvider } from "../contexts/fetchMetadataProvider";
import { ModalProvider } from "../contexts/modalProvider"; import { ModalProvider } from "../contexts/modalProvider";
import { NarrowProvider } from "../contexts/narrowProvider"; import { NarrowProvider } from "../contexts/narrowProvider";
import { ToastProvider } from "../contexts/toastProvider";
import { Metadata } from "../models/Metadata"; import { Metadata } from "../models/Metadata";
import { GlobalStyle } from "../styles/GlobalStyle"; import { GlobalStyle } from "../styles/GlobalStyle";
import { Theme } from "../styles/themes"; import { Theme } from "../styles/themes";
@ -30,11 +31,13 @@ export function ReactChat({
<FetchMetadataProvider fetchMetadata={fetchMetadata}> <FetchMetadataProvider fetchMetadata={fetchMetadata}>
<ModalProvider> <ModalProvider>
<ActivityProvider> <ActivityProvider>
<ToastProvider>
<Wrapper ref={ref}> <Wrapper ref={ref}>
<GlobalStyle /> <GlobalStyle />
<ChatLoader communityKey={communityKey} /> <ChatLoader communityKey={communityKey} />
<div id="modal-root" /> <div id="modal-root" />
</Wrapper> </Wrapper>
</ToastProvider>
</ActivityProvider> </ActivityProvider>
</ModalProvider> </ModalProvider>
</FetchMetadataProvider> </FetchMetadataProvider>

View File

@ -23,8 +23,7 @@ const LoadingBlock = styled.div`
padding: 4px 5px 4px 7px; padding: 4px 5px 4px 7px;
background: ${({ theme }) => theme.bodyBackgroundColor}; background: ${({ theme }) => theme.bodyBackgroundColor};
color: ${({ theme }) => theme.primary}; color: ${({ theme }) => theme.primary};
box-shadow: 0px 2px 4px rgba(0, 34, 51, 0.16), box-shadow: ${({ theme }) => theme.shadow};
0px 4px 12px rgba(0, 34, 51, 0.08);
border-radius: 8px; border-radius: 8px;
z-index: 2; z-index: 2;
`; `;

View File

@ -0,0 +1,91 @@
import React from "react";
import styled, { keyframes } from "styled-components";
import { useToasts } from "../../contexts/toastProvider";
import { Toast } from "../../models/Toast";
import { CheckSvg } from "../Icons/CheckIcon";
import { CrossIcon } from "../Icons/CrossIcon";
import { textSmallStyles } from "../Text";
export function AnimationToastMessage() {
return keyframes`
0% {
opacity: 0;
transform: translateY(-100%); }
100% {
opacity: 1;
transform: translateY(0); }
`;
}
type ToastMessageProps = {
toast: Toast;
};
export function ToastMessage({ toast }: ToastMessageProps) {
const { setToasts } = useToasts();
const closeToast = () => {
setToasts((prev) => prev.filter((e) => e != toast));
};
return (
<ToastWrapper>
<ToastBlock>
{toast.type !== "verification" && (
<CheckWrapper>
<CheckSvg width={20} height={20} className="accept" />
</CheckWrapper>
)}
<ToastText>
{toast.type === "request"
? "Contact Request Sent"
: "Verification Request Sent"}
</ToastText>
</ToastBlock>
<CloseButton onClick={closeToast}>
<CrossIcon />
</CloseButton>
</ToastWrapper>
);
}
const ToastWrapper = styled.div`
width: 343px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
margin-top: 8px;
background: ${({ theme }) => theme.bodyBackgroundColor};
box-shadow: ${({ theme }) => theme.shadow};
border-radius: 8px;
animation: ${AnimationToastMessage} 2s ease;
`;
const ToastBlock = styled.div`
display: flex;
align-items: center;
color: ${({ theme }) => theme.primary};
`;
const ToastText = styled.p`
font-weight: 500;
${textSmallStyles};
`;
const CheckWrapper = styled.div`
width: 32px;
height: 32px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
margin-right: 12px;
background: rgba(78, 188, 96, 0.1);
`;
const CloseButton = styled.button`
width: 32px;
height: 32px;
`;

View File

@ -0,0 +1,31 @@
import React, { useMemo } from "react";
import styled from "styled-components";
import { useToasts } from "../../contexts/toastProvider";
import { ToastMessage } from "./ToastMessage";
export function ToastMessageList() {
const { toasts } = useToasts();
const shownToasts = useMemo(() => toasts, [toasts, toasts.length]);
return (
<ToastsWrapper>
{shownToasts.map((toast) => (
<ToastMessage key={toast.id} toast={toast} />
))}
</ToastsWrapper>
);
}
const ToastsWrapper = styled.div`
position: absolute;
bottom: 56px;
right: 16px;
width: 343px;
display: flex;
flex-direction: column-reverse;
align-items: center;
z-index: 999;
`;

View File

@ -0,0 +1,26 @@
import React, { createContext, useContext, useState } from "react";
import { Toast } from "../models/Toast";
const ToastContext = createContext<{
toasts: Toast[];
setToasts: React.Dispatch<React.SetStateAction<Toast[]>>;
}>({
toasts: [],
setToasts: () => undefined,
});
export function useToasts() {
return useContext(ToastContext);
}
interface ToastProviderProps {
children: React.ReactNode;
}
export function ToastProvider({ children }: ToastProviderProps) {
const [toasts, setToasts] = useState<Toast[]>([]);
return (
<ToastContext.Provider value={{ toasts, setToasts }} children={children} />
);
}

View File

@ -0,0 +1,4 @@
export type Toast = {
id: string;
type: "request" | "incomeRequest" | "verification";
};

View File

@ -24,6 +24,7 @@ export type Theme = {
mentionHover: string; mentionHover: string;
mentionBg: string; mentionBg: string;
mentionBgHover: string; mentionBgHover: string;
shadow: string;
}; };
export const lightTheme: Theme = { export const lightTheme: Theme = {
@ -52,6 +53,8 @@ export const lightTheme: Theme = {
mentionHover: "#BDE7F2", mentionHover: "#BDE7F2",
mentionBg: "#E5F8FD", mentionBg: "#E5F8FD",
mentionBgHover: "#D4F3FA", mentionBgHover: "#D4F3FA",
shadow:
"0px 2px 4px rgba(0, 34, 51, 0.16), 0px 4px 12px rgba(0, 34, 51, 0.08)",
}; };
export const darkTheme: Theme = { export const darkTheme: Theme = {
@ -80,6 +83,8 @@ export const darkTheme: Theme = {
mentionHover: "#004E60", mentionHover: "#004E60",
mentionBg: "#004050", mentionBg: "#004050",
mentionBgHover: "#002D39", mentionBgHover: "#002D39",
shadow:
"0px 2px 4px rgba(0, 34, 51, 0.16), 0px 4px 12px rgba(0, 34, 51, 0.08)",
}; };
export default { lightTheme, darkTheme }; export default { lightTheme, darkTheme };