improved Modals Container store structure

This commit is contained in:
=Mtg_Dev
2021-11-17 19:10:09 +02:00
parent eaad4f2ffe
commit 7d14a50cef
10 changed files with 204 additions and 195 deletions

View File

@@ -1,13 +1,13 @@
import Navbar from "./Components/Shared/Navbar/Navbar";
import ExplorePage from "./Components/ExplorePage/ExplorePage";
import Modal from "./Components/Shared/Modal/Modal";
import ModalsContainer from "./Components/Shared/ModalsContainer/ModalsContainer";
function App() {
return <div id="app" className='w-screen overflow-hidden'>
<Navbar />
<ExplorePage />
<Modal />
<ModalsContainer />
</div>;
}

View File

@@ -1,5 +1,5 @@
import { motion } from "framer-motion";
import { ModalCard, modalCardVariants } from "../Shared/Modal/Modal";
import { ModalCard, modalCardVariants } from "../Shared/ModalsContainer/ModalsContainer";
interface Props extends ModalCard {

View File

@@ -1,5 +1,5 @@
import { motion } from 'framer-motion'
import { ModalCard, modalCardVariants } from '../Shared/Modal/Modal'
import { ModalCard, modalCardVariants } from '../Shared/ModalsContainer/ModalsContainer'
export default function LoginCard_1({ onClose, direction, ...props }: ModalCard) {
return (

View File

@@ -2,11 +2,11 @@ import { motion } from 'framer-motion'
import { BiArrowBack } from 'react-icons/bi'
import { BsJoystick } from 'react-icons/bs'
import { MdLocalFireDepartment } from 'react-icons/md';
import { ModalCard, modalCardVariants } from '../Shared/Modal/Modal';
import { ModalCard, modalCardVariants } from '../Shared/ModalsContainer/ModalsContainer';
import { useQuery } from 'react-query';
import { getProjectById } from '../../api';
import { useAppDispatch } from '../../utils/hooks';
import { Direction, ModalId, openModal, setDirection } from '../../redux/features/modals.slice';
import { ModalId, openModal } from '../../redux/features/modals.slice';
export default function ProjectCard({ onClose, direction, ...props }: ModalCard) {
@@ -19,9 +19,16 @@ export default function ProjectCard({ onClose, direction, ...props }: ModalCard)
if (isLoading || !project) return <></>;
const onVote = () => {
dispatch(openModal({ modalId: ModalId.Vote, initialModalProps: { projectId: props.projectId } }))
}
const onClaim = () => {
dispatch(setDirection(Direction.NEXT));
dispatch(openModal({ modalId: ModalId.Login1, initialModalProps: { projectId: props.projectId } }))
dispatch(openModal({
modalId: ModalId.Login1,
initialModalProps: { projectId: props.projectId },
}))
}
return (
@@ -31,7 +38,7 @@ export default function ProjectCard({ onClose, direction, ...props }: ModalCard)
initial='initial'
animate="animate"
exit='exit'
className="modal-card"
className="modal-card max-w-[710px]"
>
<div className="relative h-[152px]">
@@ -47,9 +54,9 @@ export default function ProjectCard({ onClose, direction, ...props }: ModalCard)
<h3 className="text-h3 font-regular">{project.title}</h3>
<a className="text-blue-400 font-regular text-body4" target='_blank' rel="noreferrer" href={project.website}>{project.website}</a>
</div>
<div className="flex ml-auto gap-16">
<div className="flex-shrink-0 hidden md:flex ml-auto gap-16">
<button className="btn btn-primary py-12 px-24 rounded-lg my-16">Play <BsJoystick /></button>
<button className="btn bg-yellow-100 hover:bg-yellow-200 py-12 px-24 rounded-lg my-16">Vote <MdLocalFireDepartment className='text-fire' /></button>
<button onClick={onVote} className="btn bg-yellow-100 hover:bg-yellow-50 py-12 px-24 rounded-lg my-16">Vote <MdLocalFireDepartment className='text-fire' /></button>
</div>
</div>
<p className="mt-40 text-body4 leading-normal">{project.description}</p>
@@ -57,20 +64,23 @@ export default function ProjectCard({ onClose, direction, ...props }: ModalCard)
<span className="chip-small bg-red-100 text-red-800 font-regular"> payments </span>
<span className="chip-small bg-primary-100 text-primary-800 font-regular"> lightining </span>
</div>
<div className="md:hidden">
<button className="btn btn-primary w-full py-12 px-24 rounded-lg mt-24 mb-16">Play <BsJoystick /></button>
<button onClick={onVote} className="btn w-full bg-yellow-100 hover:bg-yellow-50 py-12 px-24 rounded-lg mb-24">Vote <MdLocalFireDepartment className='text-fire' /></button>
</div>
<div className="mt-40">
<h3 className="text-h5 font-bold">Screen Shots</h3>
<h3 className="text-h5 font-bold">Screenshots</h3>
<div className="grid grid-cols-1 justify-items-center md:grid-cols-2 gap-x-24 gap-y-20">
<div className="w-full max-w-[260px] self-center h-[130px] bg-gray-300 rounded-xl"></div>
<div className="w-full max-w-[260px] self-center h-[130px] bg-gray-300 rounded-xl"></div>
<div className="w-full max-w-[260px] self-center h-[130px] bg-gray-300 rounded-xl"></div>
<div className="w-full max-w-[260px] self-center h-[130px] bg-gray-300 rounded-xl"></div>
<div className="w-full max-w-[310px] self-center h-[130px] bg-gray-300 rounded-xl"></div>
<div className="w-full max-w-[310px] self-center h-[130px] bg-gray-300 rounded-xl"></div>
<div className="w-full max-w-[310px] self-center h-[130px] bg-gray-300 rounded-xl"></div>
<div className="w-full max-w-[310px] self-center h-[130px] bg-gray-300 rounded-xl"></div>
</div>
</div>
<hr className="my-40" />
<div className="text-center">
<h3 className="text-body4 font-regular">Are you the creator of this project?</h3>
<button className="btn py-12 px-24 rounded-lg my-16" onClick={onClaim}>Claim 🖐</button>
<button className="btn btn-gray py-12 px-24 rounded-lg my-16" onClick={onClaim}>Claim 🖐</button>
</div>
</div>
</motion.div>

View File

@@ -1,75 +0,0 @@
import { AnimatePresence, motion } from "framer-motion";
import { ReactElement, useEffect, useRef } from "react";
import { Direction } from "../../../redux/features/modals.slice";
import { Portal } from "../Portal/Portal";
export interface ModalCard {
onClose?: () => void;
direction: number;
modalCard: ReactElement
}
export const modalCardVariants = {
initial: (direction: any) => {
if (direction === Direction.START) return { opacity: 0, y: 300 };
else if (direction === Direction.NEXT) return { opacity: 0, x: 300 };
else if (direction === Direction.PREVIOUS) return { opacity: 0, x: -300 };
return {}
},
animate: {
x: 0, y: 0, opacity: 1, transition: { type: "spring" }
},
exit: (direction: Direction) => {
const transition = { ease: "easeIn" }
if (direction === Direction.EXIT) return { transition, opacity: 0, y: 300 };
else if (direction === Direction.NEXT) return { transition, opacity: 0, x: -300 };
else if (direction === Direction.PREVIOUS) return { transition, opacity: 0, x: 300 };
return {}
},
}
interface Props {
isOpen?: boolean;
onClose?: () => void,
maxWidth?: string;
children: ReactElement
}
export default function Modal({ children, isOpen = true, onClose = () => { }, maxWidth = "max-w-[600px]" }: Props) {
const ref = useRef<HTMLDivElement>(null);
const handleOutsideClick = (e: any) => {
if (e.target === ref.current) onClose();
}
useEffect(() => {
if (isOpen) document.body.style.overflowY = "hidden";
else document.body.style.overflowY = "initial";
}, [isOpen])
return (
<Portal>
<AnimatePresence exitBeforeEnter>
{isOpen && <motion.div
ref={ref}
onClick={handleOutsideClick}
className={`fixed overscroll-y-none inset-0 bg-gray-300 bg-opacity-70 overflow-y-auto h-full w-full`}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{
opacity: 0,
transition: { ease: "easeInOut" },
}}
>
<div
className={`w-full ${maxWidth} mx-auto py-64`}
>
{children}
</div>
</motion.div>}
</AnimatePresence>
</Portal>
)
}

View File

@@ -1,100 +1,20 @@
import { AnimatePresence, motion } from "framer-motion";
import { useEffect, useRef } from "react";
import { closeModal, Direction, ModalId } from "../../../redux/features/modals.slice";
import { useAppDispatch, useAppSelector } from "../../../utils/hooks";
import LoginCard_1 from "../../Login/LoginCard-1";
import ProjectCard from "../../Project/ProjectCard";
import { Portal } from "../Portal/Portal";
import { ReactElement } from "react";
export interface ModalCard {
onClose?: () => void;
direction: number;
[key: string]: any;
}
export const modalCardVariants = {
initial: (direction: any) => {
if (direction === Direction.START) return { opacity: 0, y: 300 };
else if (direction === Direction.NEXT) return { opacity: 0, x: 300 };
else if (direction === Direction.PREVIOUS) return { opacity: 0, x: -300 };
return {}
},
animate: {
x: 0, y: 0, opacity: 1, transition: { type: "spring" }
},
exit: (direction: Direction) => {
const transition = { ease: "easeIn" }
if (direction === Direction.EXIT) return { transition, opacity: 0, y: 300 };
else if (direction === Direction.NEXT) return { transition, opacity: 0, x: -300 };
else if (direction === Direction.PREVIOUS) return { transition, opacity: 0, x: 300 };
return {}
},
interface Props {
onClose: () => void;
children: ReactElement
}
const ModalsMap = (modalId: ModalId) => {
switch (modalId) {
case ModalId.Project:
return ProjectCard;
case ModalId.Login1:
return LoginCard_1;
default:
return () => <></>
}
}
export default function Modal() {
const { isOpen, modalId, direction, initialModalProps } = useAppSelector(state => ({ isOpen: state.modals.isOpen, modalId: state.modals.openModalId, direction: state.modals.direction, initialModalProps: state.modals.initialModalProps }))
const ref = useRef<HTMLDivElement>(null);
const dispatch = useAppDispatch();
const onClose = () => dispatch(closeModal());
const handleOutsideClick = (e: any) => {
console.log(e.target);
if (e.target === ref.current) onClose();
}
useEffect(() => {
if (isOpen) document.body.style.overflowY = "hidden";
else document.body.style.overflowY = "initial";
}, [isOpen]);
const Child = ModalsMap(modalId);
export default function Modal(props: Props) {
return (
<Portal>
<AnimatePresence exitBeforeEnter>
{isOpen && <motion.div
onClick={handleOutsideClick}
className={`fixed overscroll-y-none overflow-x-hidden inset-0 bg-gray-300 bg-opacity-70 overflow-y-auto h-full w-full`}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{
opacity: 0,
transition: { ease: "easeInOut" },
}}
>
<div
ref={ref}
className={`w-screen min-h-screen flex justify-center items-center py-64`}
>
<AnimatePresence exitBeforeEnter>
<Child onClose={onClose} direction={direction} {...initialModalProps} />
</AnimatePresence>
</div>
</motion.div>}
</AnimatePresence>
</Portal>
<div className='w-full h-full fixed inset-0 py-32 md:py-64 flex justify-center overflow-x-hidden overflow-y-scroll no-scrollbar'>
<div
className="w-full h-full bg-gray-300 bg-opacity-50 absolute inset-0"
onClick={props.onClose}
></div>
{props.children}
</div>
)
}

View File

@@ -0,0 +1,92 @@
import { AnimatePresence, motion } from "framer-motion";
import { useEffect, useRef } from "react";
import { closeModal, Direction, ModalId } from "../../../redux/features/modals.slice";
import { useAppDispatch, useAppSelector } from "../../../utils/hooks";
import LoginCard_1 from "../../Login/LoginCard-1";
import ProjectCard from "../../Project/ProjectCard";
import VoteCard from "../../Vote/VoteCard";
import Modal from "../Modal/Modal";
import { Portal } from "../Portal/Portal";
export interface ModalCard {
onClose?: () => void;
direction: number;
[key: string]: any;
}
export const modalCardVariants = {
initial: (direction: any) => {
if (direction === Direction.START) return { opacity: 0, y: 300 };
else if (direction === Direction.NEXT) return { opacity: 0, x: 300 };
else if (direction === Direction.PREVIOUS) return { opacity: 0, x: -300 };
return {}
},
animate: {
x: 0, y: 0, opacity: 1, transition: { type: "spring" }
},
exit: (direction: Direction) => {
const transition = { ease: "easeIn" }
if (direction === Direction.EXIT) return { transition, opacity: 0, y: 300 };
else if (direction === Direction.NEXT) return { transition, opacity: 0, x: -300 };
else if (direction === Direction.PREVIOUS) return { transition, opacity: 0, x: 300 };
return {}
},
}
const ModalsMap = (modalId: ModalId) => {
switch (modalId) {
case ModalId.Project:
return ProjectCard;
case ModalId.Login1:
return LoginCard_1;
case ModalId.Vote:
return VoteCard;
default:
return () => <></>
}
}
export default function ModalsContainer() {
const { isOpen, openModals, direction } = useAppSelector(state => ({ isOpen: state.modals.isOpen, openModals: state.modals.openModals, direction: state.modals.direction }))
const dispatch = useAppDispatch();
const onClose = () => dispatch(closeModal());
useEffect(() => {
if (isOpen) document.body.style.overflowY = "hidden";
else document.body.style.overflowY = "initial";
}, [isOpen]);
return (
<Portal>
<AnimatePresence exitBeforeEnter>
{isOpen &&
<motion.div
className="w-screen h-scree fixed inset-0"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{
opacity: 0,
transition: { ease: "easeInOut" },
}}
>
{openModals.map(modal => {
const Child = ModalsMap(modal.modalId);
return (
<Modal onClose={onClose}>
<Child onClose={onClose} direction={direction} {...modal.propsToPass} />
</Modal>)
})}
</motion.div>}
</AnimatePresence>
</Portal>
)
}

View File

@@ -0,0 +1,21 @@
import { motion } from 'framer-motion'
import { ModalCard, modalCardVariants } from '../Shared/ModalsContainer/ModalsContainer'
export default function VoteCard({ onClose, direction, ...props }: ModalCard) {
return (
<motion.div
custom={direction}
variants={modalCardVariants}
initial='initial'
animate="animate"
exit='exit'
className="modal-card max-w-[400px] h-[400px]"
>
<div className="p-24 h-full flex flex-col items-center justify-center">
<h3 className="text-h2 font-bold">Vote Modal</h3>
<p className="text-body2 mt-40">WIP</p>
</div>
</motion.div>
)
}

View File

@@ -12,6 +12,10 @@
@apply bg-primary-600 hover:bg-primary-500 text-white;
}
.btn-gray {
@apply bg-gray-200 hover:bg-gray-100 text-gray-900;
}
.chip {
@apply bg-gray-100 text-body4 px-16 py-8 rounded-24 font-regular;
}
@@ -20,7 +24,7 @@
}
.modal-card {
@apply rounded-[40px] bg-gray-50 overflow-hidden w-full max-w-[600px] shadow-xl;
@apply rounded-[40px] bg-gray-50 overflow-y-scroll w-full max-w-[600px] shadow-xl z-10;
}
}
@@ -52,3 +56,12 @@ svg {
::-webkit-scrollbar-thumb:hover {
background-color: #999;
}
.no-scrollbar {
-ms-overflow-style: none; /* Internet Explorer 10+ */
scrollbar-width: none; /* Firefox */
}
.no-scrollbar ::-webkit-scrollbar {
display: none; /* Safari and Chrome */
}

View File

@@ -12,21 +12,26 @@ export enum ModalId {
Project,
Login1,
Login2,
Vote,
}
interface OpenModal {
modalId: ModalId;
propsToPass: any;
}
interface StoreState {
isOpen: boolean;
isLoading: boolean;
direction: Direction;
openModalId: ModalId;
initialModalProps: any;
openModals: OpenModal[];
}
const initialState = {
isOpen: false,
isLoading: false,
direction: Direction.START,
openModalId: ModalId.None,
openModals: [] as OpenModal[],
} as StoreState;
export const modalSlice = createSlice({
@@ -41,20 +46,43 @@ export const modalSlice = createSlice({
state,
action: PayloadAction<{ modalId: ModalId; initialModalProps: any }>
) {
if (!state.isOpen) state.direction = Direction.START;
state.direction = Direction.START;
state.isOpen = true;
state.openModalId = action.payload.modalId;
state.initialModalProps = action.payload.initialModalProps;
state.openModals.push({
modalId: action.payload.modalId,
propsToPass: action.payload.initialModalProps,
});
},
replaceModal(
state,
action: PayloadAction<{
modalId: ModalId;
initialModalProps: any;
direction: Direction;
}>
) {
state.direction = action.payload.direction;
state.openModals.pop();
state.openModals.push({
modalId: action.payload.modalId,
propsToPass: action.payload.initialModalProps || {},
});
},
closeModal(state) {
state.direction = Direction.EXIT;
state.isOpen = false;
state.openModalId = ModalId.None;
state.openModals.pop();
state.isOpen = Boolean(state.openModals.length);
},
},
});
export const { closeModal, openModal, setDirection } = modalSlice.actions;
export const {
closeModal,
openModal,
replaceModal,
setDirection,
} = modalSlice.actions;
export default modalSlice.reducer;