From 3be3a7726f10283cd5665b37635d5ef6e1578b33 Mon Sep 17 00:00:00 2001 From: Szymon Szlachtowicz <38212223+Szymx95@users.noreply.github.com> Date: Fri, 12 Nov 2021 12:33:56 +0100 Subject: [PATCH] Add modal state provider (#125) --- packages/react-chat/src/components/Chat.tsx | 21 ++------- .../src/components/Chat/ChatBody.tsx | 9 +--- .../src/components/Chat/ChatInput.tsx | 17 +++---- .../src/components/Chat/ChatMessages.tsx | 22 +++++---- .../react-chat/src/components/Community.tsx | 8 ++-- .../src/components/Form/ChannelMenu.tsx | 8 ++-- .../src/components/Modals/CommunityModal.tsx | 14 +++--- .../src/components/Modals/EditModal.tsx | 13 ++++-- .../src/components/Modals/LinkModal.tsx | 16 ++++--- .../src/components/Modals/Modal.tsx | 46 ++++++++++--------- .../src/components/Modals/PictureModal.tsx | 14 +++--- .../src/components/Modals/SizeLimitModal.tsx | 19 ++++++++ .../react-chat/src/components/ReactChat.tsx | 12 +++-- .../react-chat/src/contexts/modalProvider.tsx | 44 ++++++++++++++++++ 14 files changed, 159 insertions(+), 104 deletions(-) create mode 100644 packages/react-chat/src/components/Modals/SizeLimitModal.tsx create mode 100644 packages/react-chat/src/contexts/modalProvider.tsx diff --git a/packages/react-chat/src/components/Chat.tsx b/packages/react-chat/src/components/Chat.tsx index cc3fc54e..1885fae9 100644 --- a/packages/react-chat/src/components/Chat.tsx +++ b/packages/react-chat/src/components/Chat.tsx @@ -19,17 +19,11 @@ export function Chat() { const narrow = useNarrow(); - const [isModalVisible, setIsModalVisible] = useState(false); - const showModal = () => setIsModalVisible(true); - - const [isEditVisible, setIsEditVisible] = useState(false); - const showEditModal = () => setIsEditVisible(true); - return ( {!narrow && ( - + setShowMembers(!showMembers)} showMembers={showMembers} - onCommunityClick={showModal} - onEditClick={showEditModal} membersList={membersList} groupList={groupList} setMembersList={setMembersList} @@ -61,15 +53,8 @@ export function Chat() { setCreateChat={setCreateChat} /> )} - setIsModalVisible(false)} - subtitle="Public Community" - /> - setIsEditVisible(false)} - /> + + ); } diff --git a/packages/react-chat/src/components/Chat/ChatBody.tsx b/packages/react-chat/src/components/Chat/ChatBody.tsx index 9be9ce43..60c4f990 100644 --- a/packages/react-chat/src/components/Chat/ChatBody.tsx +++ b/packages/react-chat/src/components/Chat/ChatBody.tsx @@ -27,8 +27,6 @@ enum ChatBodyState { interface ChatBodyProps { onClick: () => void; showMembers: boolean; - onCommunityClick: () => void; - onEditClick: () => void; membersList: string[]; groupList: [][]; setMembersList: any; @@ -39,8 +37,6 @@ interface ChatBodyProps { export function ChatBody({ onClick, showMembers, - onCommunityClick, - onEditClick, membersList, groupList, setMembersList, @@ -88,7 +84,7 @@ export function ChatBody({ <> {narrow && ( - + )} @@ -113,7 +109,7 @@ export function ChatBody({ {!narrow && ( @@ -129,7 +125,6 @@ export function ChatBody({ switchMemberList={() => switchShowState(ChatBodyState.Members)} setShowChannelMenu={setShowChannelMenu} setEditGroup={setEditGroup} - onEditClick={onEditClick} setGroupList={setGroupList} /> )} diff --git a/packages/react-chat/src/components/Chat/ChatInput.tsx b/packages/react-chat/src/components/Chat/ChatInput.tsx index 5267a223..d2fb81b9 100644 --- a/packages/react-chat/src/components/Chat/ChatInput.tsx +++ b/packages/react-chat/src/components/Chat/ChatInput.tsx @@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"; import styled, { useTheme } from "styled-components"; import { useMessengerContext } from "../../contexts/messengerProvider"; +import { useModal } from "../../contexts/modalProvider"; import { useLow } from "../../contexts/narrowProvider"; import { lightTheme, Theme } from "../../styles/themes"; import { uintToImgUrl } from "../../utils/uintToImgUrl"; @@ -11,7 +12,7 @@ import { GifIcon } from "../Icons/GifIcon"; import { PictureIcon } from "../Icons/PictureIcon"; import { StickerIcon } from "../Icons/StickerIcon"; import "emoji-mart/css/emoji-mart.css"; -import { Modal } from "../Modals/Modal"; +import { SizeLimitModal, SizeLimitModalName } from "../Modals/SizeLimitModal"; export function ChatInput() { const { sendMessage } = useMessengerContext(); @@ -20,7 +21,6 @@ export function ChatInput() { const [showEmoji, setShowEmoji] = useState(false); const [inputHeight, setInputHeight] = useState(40); const [imageUint, setImageUint] = useState(undefined); - const [showSizeLimit, setShowSizeLimit] = useState(false); useEffect(() => { window.addEventListener("click", () => setShowEmoji(false)); @@ -69,16 +69,11 @@ export function ChatInput() { const low = useLow(); + const { setModal } = useModal(SizeLimitModalName); + return ( - setShowSizeLimit(false)} isVisible={showSizeLimit}> - setShowSizeLimit(false)} - style={{ padding: "20px" }} - > - File size must be less than 1MB - - + {showEmoji && ( (!image ? undefined : setPictureModal(true)), [image]); + useEffect(() => (!link ? undefined : setLinkModal(true)), [link]); + return ( - setImage("")} - image={image} - /> - setLink("")} link={link} /> + + {loadingMessages && ( diff --git a/packages/react-chat/src/components/Community.tsx b/packages/react-chat/src/components/Community.tsx index 5f50300c..d25a0ec5 100644 --- a/packages/react-chat/src/components/Community.tsx +++ b/packages/react-chat/src/components/Community.tsx @@ -2,17 +2,19 @@ import React from "react"; import styled from "styled-components"; import { useMessengerContext } from "../contexts/messengerProvider"; +import { useModal } from "../contexts/modalProvider"; import { CommunityIdentity } from "./CommunityIdentity"; +import { CommunityModalName } from "./Modals/CommunityModal"; import { CommunitySkeleton } from "./Skeleton/CommunitySkeleton"; interface CommunityProps { - onClick: () => void; className?: string; } -export function Community({ onClick, className }: CommunityProps) { +export function Community({ className }: CommunityProps) { const { communityData } = useMessengerContext(); + const { setModal } = useModal(CommunityModalName); if (!communityData) { return ( @@ -24,7 +26,7 @@ export function Community({ onClick, className }: CommunityProps) { return ( <> - + setModal(true)}> > diff --git a/packages/react-chat/src/components/Form/ChannelMenu.tsx b/packages/react-chat/src/components/Form/ChannelMenu.tsx index ff9bcb13..2e60c214 100644 --- a/packages/react-chat/src/components/Form/ChannelMenu.tsx +++ b/packages/react-chat/src/components/Form/ChannelMenu.tsx @@ -2,6 +2,7 @@ import React from "react"; import styled from "styled-components"; import { useMessengerContext } from "../../contexts/messengerProvider"; +import { useModal } from "../../contexts/modalProvider"; import { useNarrow } from "../../contexts/narrowProvider"; import { ChannelData } from "../../models/ChannelData"; import { AddMemberIconSvg } from "../Icons/AddMemberIcon"; @@ -10,6 +11,7 @@ import { EgitGroupSvg } from "../Icons/EditGroupIcon"; import { LeftIconSvg } from "../Icons/LeftIcon"; import { MembersSmallSvg } from "../Icons/MembersSmallIcon"; import { MuteSvg } from "../Icons/MuteIcon"; +import { EditModalName } from "../Modals/EditModal"; import { DropdownMenu, MenuItem, MenuText } from "./DropdownMenu"; @@ -18,7 +20,6 @@ interface ChannelMenuProps { switchMemberList: () => void; setShowChannelMenu: (val: boolean) => void; setEditGroup: (val: boolean) => void; - onEditClick: () => void; setGroupList: any; } @@ -27,13 +28,12 @@ export const ChannelMenu = ({ switchMemberList, setShowChannelMenu, setEditGroup, - onEditClick, setGroupList, }: ChannelMenuProps) => { const narrow = useNarrow(); const { clearNotifications, setActiveChannel, channels } = useMessengerContext(); - + const { setModal } = useModal(EditModalName); return ( {narrow && ( @@ -58,7 +58,7 @@ export const ChannelMenu = ({ Add / remove from group - + setModal(true)}> Edit name and image diff --git a/packages/react-chat/src/components/Modals/CommunityModal.tsx b/packages/react-chat/src/components/Modals/CommunityModal.tsx index 675e4e27..c739f005 100644 --- a/packages/react-chat/src/components/Modals/CommunityModal.tsx +++ b/packages/react-chat/src/components/Modals/CommunityModal.tsx @@ -12,20 +12,18 @@ import { CopyInput } from "../Form/CopyInput"; import { StatusLogo } from "../Icons/StatusLogo"; import { textSmallStyles } from "../Text"; -import { BasicModalProps, Modal } from "./Modal"; +import { Modal } from "./Modal"; import { Section, Text } from "./ModalStyle"; -interface CommunityModalProps extends BasicModalProps, CommunityIdentityProps {} +export const CommunityModalName = "CommunityModal"; -export const CommunityModal = ({ - isVisible, - onClose, - subtitle, -}: CommunityModalProps) => { +type CommunityModalProps = CommunityIdentityProps; + +export const CommunityModal = ({ subtitle }: CommunityModalProps) => { const narrow = useNarrow(); const { communityData } = useMessengerContext(); return ( - + diff --git a/packages/react-chat/src/components/Modals/EditModal.tsx b/packages/react-chat/src/components/Modals/EditModal.tsx index cf55e74f..4adac4f3 100644 --- a/packages/react-chat/src/components/Modals/EditModal.tsx +++ b/packages/react-chat/src/components/Modals/EditModal.tsx @@ -2,15 +2,18 @@ import React, { useState } from "react"; import styled from "styled-components"; import { useMessengerContext } from "../../contexts/messengerProvider"; +import { useModal } from "../../contexts/modalProvider"; import { buttonStyles } from "../Buttons/buttonStyle"; import { ChannelLogo } from "../Channels/Channel"; import { AddIcon } from "../Icons/AddIcon"; import { textMediumStyles } from "../Text"; -import { BasicModalProps, Modal } from "./Modal"; +import { Modal } from "./Modal"; import { Heading, Section } from "./ModalStyle"; -export const EditModal = ({ isVisible, onClose }: BasicModalProps) => { +export const EditModalName = "editModal"; + +export const EditModal = () => { const { activeChannel } = useMessengerContext(); const [groupName, setGroupName] = useState(""); const [image, setImage] = useState(""); @@ -21,14 +24,16 @@ export const EditModal = ({ isVisible, onClose }: BasicModalProps) => { } }; + const { setModal } = useModal(EditModalName); + const handleUpload = () => { activeChannel.icon = image; activeChannel.name = groupName; - onClose(); + setModal(false); }; return ( - + Edit group name and image diff --git a/packages/react-chat/src/components/Modals/LinkModal.tsx b/packages/react-chat/src/components/Modals/LinkModal.tsx index 9eb2f42b..d20ecc17 100644 --- a/packages/react-chat/src/components/Modals/LinkModal.tsx +++ b/packages/react-chat/src/components/Modals/LinkModal.tsx @@ -1,19 +1,23 @@ import React from "react"; import styled from "styled-components"; +import { useModal } from "../../contexts/modalProvider"; import { buttonStyles } from "../Buttons/buttonStyle"; import { textMediumStyles } from "../Text"; -import { BasicModalProps, Modal } from "./Modal"; +import { Modal } from "./Modal"; import { Heading, Section } from "./ModalStyle"; -interface LinkModalProps extends BasicModalProps { +export const LinkModalName = "LinkModal"; + +interface LinkModalProps { link: string; } -export const LinkModal = ({ isVisible, onClose, link }: LinkModalProps) => { +export const LinkModal = ({ link }: LinkModalProps) => { + const { setModal } = useModal(LinkModalName); return ( - + Are you sure you want to visit this website? @@ -21,11 +25,11 @@ export const LinkModal = ({ isVisible, onClose, link }: LinkModalProps) => { {link} - No + setModal(false)}>No { window?.open(link, "_blank", "noopener")?.focus(); - onClose(); + setModal(false); }} > Yes, take me there diff --git a/packages/react-chat/src/components/Modals/Modal.tsx b/packages/react-chat/src/components/Modals/Modal.tsx index 0965bc35..d2a5a0b1 100644 --- a/packages/react-chat/src/components/Modals/Modal.tsx +++ b/packages/react-chat/src/components/Modals/Modal.tsx @@ -2,11 +2,11 @@ import React, { ReactNode, useCallback, useEffect } from "react"; import { createPortal } from "react-dom"; import styled from "styled-components"; +import { useModal } from "../../contexts/modalProvider"; import { CrossIcon } from "../Icons/CrossIcon"; export interface BasicModalProps { - isVisible: boolean; - onClose: () => void; + name: string; className?: string; } @@ -14,19 +14,16 @@ export interface ModalProps extends BasicModalProps { children: ReactNode; } -export const Modal = ({ - isVisible, - onClose, - children, - className, -}: ModalProps) => { +export const Modal = ({ name, children, className }: ModalProps) => { + const { isVisible, setModal } = useModal(name); + const listenKeyboard = useCallback( (event: KeyboardEvent) => { if (event.key === "Escape" || event.keyCode === 27) { - onClose(); + setModal(false); } }, - [onClose] + [setModal] ); useEffect(() => { @@ -40,18 +37,23 @@ export const Modal = ({ if (!isVisible) return null; - return createPortal( - - - - - - - {children} - - , - document.body - ); + const element = document.getElementById("modal-root"); + + if (element) { + return createPortal( + + setModal(false)} /> + + setModal(false)} className={className}> + + + {children} + + , + element + ); + } + return null; }; const ModalView = styled.div` diff --git a/packages/react-chat/src/components/Modals/PictureModal.tsx b/packages/react-chat/src/components/Modals/PictureModal.tsx index 87a641fe..3970473d 100644 --- a/packages/react-chat/src/components/Modals/PictureModal.tsx +++ b/packages/react-chat/src/components/Modals/PictureModal.tsx @@ -1,19 +1,17 @@ import React from "react"; import styled from "styled-components"; -import { BasicModalProps, Modal } from "./Modal"; +import { Modal } from "./Modal"; -interface PictureModalProps extends BasicModalProps { +export const PictureModalName = "PictureModal"; + +interface PictureModalProps { image: string; } -export const PictureModal = ({ - isVisible, - onClose, - image, -}: PictureModalProps) => { +export const PictureModal = ({ image }: PictureModalProps) => { return ( - + diff --git a/packages/react-chat/src/components/Modals/SizeLimitModal.tsx b/packages/react-chat/src/components/Modals/SizeLimitModal.tsx new file mode 100644 index 00000000..1127559c --- /dev/null +++ b/packages/react-chat/src/components/Modals/SizeLimitModal.tsx @@ -0,0 +1,19 @@ +import React from "react"; + +import { useModal } from "../../contexts/modalProvider"; + +import { Modal } from "./Modal"; + +export const SizeLimitModalName = "SizeLimitModal"; + +export function SizeLimitModal() { + const { setModal } = useModal(SizeLimitModalName); + + return ( + + setModal(false)} style={{ padding: "20px" }}> + File size must be less than 1MB + + + ); +} diff --git a/packages/react-chat/src/components/ReactChat.tsx b/packages/react-chat/src/components/ReactChat.tsx index 81e406c3..76e39f69 100644 --- a/packages/react-chat/src/components/ReactChat.tsx +++ b/packages/react-chat/src/components/ReactChat.tsx @@ -4,6 +4,7 @@ import styled from "styled-components"; import { BlockedUsersProvider } from "../contexts/blockedUsersProvider"; import { FetchMetadataProvider } from "../contexts/fetchMetadataProvider"; +import { ModalProvider } from "../contexts/modalProvider"; import { NarrowProvider } from "../contexts/narrowProvider"; import { Metadata } from "../models/Metadata"; import { GlobalStyle } from "../styles/GlobalStyle"; @@ -28,10 +29,13 @@ export function ReactChat({ - - - - + + + + + + + diff --git a/packages/react-chat/src/contexts/modalProvider.tsx b/packages/react-chat/src/contexts/modalProvider.tsx new file mode 100644 index 00000000..23f5cfd0 --- /dev/null +++ b/packages/react-chat/src/contexts/modalProvider.tsx @@ -0,0 +1,44 @@ +import React, { + createContext, + useCallback, + useContext, + useMemo, + useState, +} from "react"; + +type ModalsState = { + [name: string]: boolean; +}; + +type ModalContextType = [ + state: ModalsState, + setState: React.Dispatch> +]; + +const ModalContext = createContext([{}, () => undefined]); + +export function useModal(name: string) { + const [modals, setModals] = useContext(ModalContext); + const setModal = useCallback( + (state: boolean) => { + setModals((prev) => { + return { + ...prev, + [name]: state, + }; + }); + }, + [name, modals] + ); + const isVisible = useMemo(() => modals?.[name] ?? false, [modals, name]); + return { isVisible, setModal }; +} + +interface IdentityProviderProps { + children: React.ReactNode; +} + +export function ModalProvider({ children }: IdentityProviderProps) { + const modalState = useState({}); + return ; +}