Add modal state provider (#125)
This commit is contained in:
parent
03ea71dca4
commit
3be3a7726f
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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} />;
|
||||
}
|
Loading…
Reference in New Issue