Add modal state provider (#125)

This commit is contained in:
Szymon Szlachtowicz 2021-11-12 12:33:56 +01:00 committed by GitHub
parent 03ea71dca4
commit 3be3a7726f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 159 additions and 104 deletions

View File

@ -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 (
<ChatWrapper>
{!narrow && (
<ChannelsWrapper>
<StyledCommunity onClick={showModal} />
<StyledCommunity />
<Channels
membersList={membersList}
groupList={groupList}
@ -42,8 +36,6 @@ export function Chat() {
<ChatBody
onClick={() => setShowMembers(!showMembers)}
showMembers={showMembers}
onCommunityClick={showModal}
onEditClick={showEditModal}
membersList={membersList}
groupList={groupList}
setMembersList={setMembersList}
@ -61,15 +53,8 @@ export function Chat() {
setCreateChat={setCreateChat}
/>
)}
<CommunityModal
isVisible={isModalVisible}
onClose={() => setIsModalVisible(false)}
subtitle="Public Community"
/>
<EditModal
isVisible={isEditVisible}
onClose={() => setIsEditVisible(false)}
/>
<CommunityModal subtitle="Public Community" />
<EditModal />
</ChatWrapper>
);
}

View File

@ -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 && (
<CommunityWrap className={className}>
<Community onClick={onCommunityClick} />
<Community />
</CommunityWrap>
)}
@ -113,7 +109,7 @@ export function ChatBody({
{!narrow && (
<MemberBtn
onClick={onClick}
className={showMembers && !narrow ? "active" : ""}
className={showMembers ? "active" : ""}
>
<MembersIcon />
</MemberBtn>
@ -129,7 +125,6 @@ export function ChatBody({
switchMemberList={() => switchShowState(ChatBodyState.Members)}
setShowChannelMenu={setShowChannelMenu}
setEditGroup={setEditGroup}
onEditClick={onEditClick}
setGroupList={setGroupList}
/>
)}

View File

@ -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 | Uint8Array>(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 (
<View>
<Modal onClose={() => setShowSizeLimit(false)} isVisible={showSizeLimit}>
<div
onClick={() => setShowSizeLimit(false)}
style={{ padding: "20px" }}
>
File size must be less than 1MB
</div>
</Modal>
<SizeLimitModal />
{showEmoji && (
<div>
<Picker
@ -119,7 +114,7 @@ export function ChatInput() {
if (e.target.files[0].size < 1024 * 1024) {
fileReader.readAsArrayBuffer(e.target.files[0]);
} else {
setShowSizeLimit(true);
setModal(true);
}
}
}}

View File

@ -1,8 +1,9 @@
import React, { useMemo, useRef, useState } from "react";
import React, { useEffect, useMemo, useRef, useState } from "react";
import styled from "styled-components";
import { useBlockedUsers } from "../../contexts/blockedUsersProvider";
import { useMessengerContext } from "../../contexts/messengerProvider";
import { useModal } from "../../contexts/modalProvider";
import { useChatScrollHandle } from "../../hooks/useChatScrollHandle";
import { ChatMessage } from "../../models/ChatMessage";
import { equalDate } from "../../utils";
@ -10,8 +11,8 @@ import { EmptyChannel } from "../Channels/EmptyChannel";
import { ContactMenu } from "../Form/ContactMenu";
import { LoadingIcon } from "../Icons/LoadingIcon";
import { UserIcon } from "../Icons/UserIcon";
import { LinkModal } from "../Modals/LinkModal";
import { PictureModal } from "../Modals/PictureModal";
import { LinkModal, LinkModalName } from "../Modals/LinkModal";
import { PictureModal, PictureModalName } from "../Modals/PictureModal";
import { textSmallStyles } from "../Text";
import { ChatMessageContent } from "./ChatMessageContent";
@ -84,14 +85,17 @@ export function ChatMessages() {
const [image, setImage] = useState("");
const [link, setLink] = useState("");
const { setModal: setPictureModal } = useModal(PictureModalName);
const { setModal: setLinkModal } = useModal(LinkModalName);
useEffect(() => (!image ? undefined : setPictureModal(true)), [image]);
useEffect(() => (!link ? undefined : setLinkModal(true)), [link]);
return (
<MessagesWrapper ref={ref}>
<PictureModal
isVisible={!!image}
onClose={() => setImage("")}
image={image}
/>
<LinkModal isVisible={!!link} onClose={() => setLink("")} link={link} />
<PictureModal image={image} />
<LinkModal link={link} />
<EmptyChannel channel={activeChannel} />
{loadingMessages && (
<LoadingWrapper>

View File

@ -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 (
<>
<button className={className} onClick={onClick}>
<button className={className} onClick={() => setModal(true)}>
<CommunityIdentity subtitle={`${communityData.members} members`} />
</button>
</>

View File

@ -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 (
<DropdownMenu>
{narrow && (
@ -58,7 +58,7 @@ export const ChannelMenu = ({
<AddMemberIconSvg width={16} height={16} />
<MenuText>Add / remove from group</MenuText>
</MenuItem>
<MenuItem onClick={onEditClick}>
<MenuItem onClick={() => setModal(true)}>
<EgitGroupSvg width={16} height={16} />
<MenuText>Edit name and image</MenuText>
</MenuItem>

View File

@ -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 (
<Modal isVisible={isVisible} onClose={onClose}>
<Modal name={CommunityModalName}>
<Section>
<CommunityIdentity subtitle={subtitle} />
</Section>

View File

@ -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 (
<Modal isVisible={isVisible} onClose={onClose}>
<Modal name={EditModalName}>
<Section>
<Heading>Edit group name and image</Heading>
</Section>

View File

@ -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 (
<Modal isVisible={isVisible} onClose={onClose}>
<Modal name={LinkModalName}>
<Section>
<Heading>Are you sure you want to visit this website?</Heading>
</Section>
@ -21,11 +25,11 @@ export const LinkModal = ({ isVisible, onClose, link }: LinkModalProps) => {
<Link>{link}</Link>
</Section>
<ButtonSection>
<ButtonNo onClick={onClose}>No</ButtonNo>
<ButtonNo onClick={() => setModal(false)}>No</ButtonNo>
<ButtonYes
onClick={() => {
window?.open(link, "_blank", "noopener")?.focus();
onClose();
setModal(false);
}}
>
Yes, take me there

View File

@ -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(
<ModalView>
<ModalOverlay onClick={onClose} />
<ModalBody className={className}>
<CloseButton onClick={onClose} className={className}>
<CrossIcon />
</CloseButton>
{children}
</ModalBody>
</ModalView>,
document.body
);
const element = document.getElementById("modal-root");
if (element) {
return createPortal(
<ModalView>
<ModalOverlay onClick={() => setModal(false)} />
<ModalBody className={className}>
<CloseButton onClick={() => setModal(false)} className={className}>
<CrossIcon />
</CloseButton>
{children}
</ModalBody>
</ModalView>,
element
);
}
return null;
};
const ModalView = styled.div`

View File

@ -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 (
<Modal isVisible={isVisible} onClose={onClose} className="picture">
<Modal name={PictureModalName} className="picture">
<ModalImageWrapper>
<ModalImage src={image}></ModalImage>
</ModalImageWrapper>

View File

@ -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 (
<Modal name={SizeLimitModalName}>
<div onClick={() => setModal(false)} style={{ padding: "20px" }}>
File size must be less than 1MB
</div>
</Modal>
);
}

View File

@ -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({
<NarrowProvider myRef={ref}>
<FetchMetadataProvider fetchMetadata={fetchMetadata}>
<BlockedUsersProvider>
<Wrapper ref={ref}>
<GlobalStyle />
<ChatLoader communityKey={communityKey} />
</Wrapper>
<ModalProvider>
<Wrapper ref={ref}>
<GlobalStyle />
<ChatLoader communityKey={communityKey} />
<div id="modal-root" />
</Wrapper>
</ModalProvider>
</BlockedUsersProvider>
</FetchMetadataProvider>
</NarrowProvider>

View File

@ -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<React.SetStateAction<ModalsState>>
];
const ModalContext = createContext<ModalContextType>([{}, () => 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<ModalsState>({});
return <ModalContext.Provider value={modalState} children={children} />;
}