feat: make project modal full page on mobile

- created a new store slice (theme) to hold properties needed to style the app
- made nav fixed
- refactored resizing throttled funcs to a custom hook
- changed the modal to full size page on mobile for certain modal types ( currently just Project )
This commit is contained in:
=Mtg_Dev
2021-12-19 16:26:28 +02:00
parent 089e3af154
commit 5ae1da6369
15 changed files with 107 additions and 33 deletions

View File

@@ -1,20 +1,21 @@
import { useEffect } from "react";
import { useCallback, useEffect } from "react";
import Navbar from "./Components/Shared/Navbar/Navbar";
import ExplorePage from "./Components/ExplorePage/ExplorePage";
import ModalsContainer from "./Components/Shared/ModalsContainer/ModalsContainer";
import { useAppDispatch, useAppSelector } from './utils/hooks';
import { useAppDispatch, useAppSelector, useResizeListener } from './utils/hooks';
import { connectWallet } from './redux/features/wallet.slice';
import { setIsMobileScreen } from "./redux/features/theme.slice";
function App() {
const { isWalletConnected, webln } = useAppSelector(state => ({
isWalletConnected: state.wallet.isConnected,
webln: state.wallet.provider,
isWalletConnected: state.wallet.isConnected,
webln: state.wallet.provider,
}));
const dispatch = useAppDispatch();
useEffect(() => {
if(typeof window.webln != "undefined") {
if (typeof window.webln != "undefined") {
window.webln.enable().then((res: any) => {
dispatch(connectWallet(window.webln));
console.log("called:webln.enable()", res);
@@ -24,6 +25,11 @@ function App() {
}
}, []);
useResizeListener(() => {
dispatch(setIsMobileScreen(document.body.clientWidth < 768));
}, [dispatch])
return <div id="app" className='w-screen overflow-hidden'>
<Navbar />
<ExplorePage />

View File

@@ -1,11 +1,11 @@
import { ReactElement, useEffect, useRef, useState } from "react";
import { ReactElement, useCallback, useRef, useState } from "react";
import { ProjectCard } from "../../../utils/interfaces";
import Carousel from 'react-multi-carousel';
import { MdDoubleArrow, } from 'react-icons/md';
import { useAppDispatch } from "../../../utils/hooks";
import { ModalId, openModal } from "../../../redux/features/modals.slice";
import _throttle from 'lodash.throttle'
import ProjectCardMini from "../ProjectCardMini/ProjectCardMini";
import { useResizeListener } from 'src/utils/hooks'
const responsive = {
all: {
@@ -38,16 +38,10 @@ export default function ProjectsRow({ title, categoryId, projects }: Props) {
dispatch(openModal({ modalId: ModalId.Project, propsToPass: { projectId } }))
}
useEffect(() => {
const listener = _throttle(() => {
setCarouselItmsCnt(calcNumItems());
}, 250);
useResizeListener(() => {
setCarouselItmsCnt(calcNumItems());
}, [setCarouselItmsCnt])
window.addEventListener('resize', listener)
return () => {
window.removeEventListener('resize', listener)
}
}, [])
return (
<div className='mb-48'>

View File

@@ -27,10 +27,11 @@ export default function ProjectCard({ onClose, direction, ...props }: ModalCard)
}
);
const { isWalletConnected, webln, project } = useAppSelector(state => ({
const { isWalletConnected, webln, project, isMobileScreen } = useAppSelector(state => ({
isWalletConnected: state.wallet.isConnected,
webln: state.wallet.provider,
project: state.project.project,
isMobileScreen: state.theme.isMobileScreen
}));
@@ -87,7 +88,7 @@ export default function ProjectCard({ onClose, direction, ...props }: ModalCard)
initial='initial'
animate="animate"
exit='exit'
className="modal-card max-w-[710px]"
className={`modal-card max-w-[768px] ${props.isPageModal && isMobileScreen && 'rounded-0 w-full min-h-screen'}`}
>
<div className="relative h-[80px] lg:h-[152px]">

View File

@@ -16,15 +16,15 @@ export default function Modal({ onClose, children, ...props }: Props) {
initial={false}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className='fixed w-screen h-screen items-center overflow-x-hidden no-scrollbar'
className='fixed w-full h-full items-center overflow-x-hidden no-scrollbar'
{...props}
>
<div
className='w-screen min-h-screen relative flex flex-col justify-center items-center py-32 px-16 md:py-64 overflow-x-hidden no-scrollbar'
className='w-screen min-h-screen relative flex flex-col justify-center items-center md:py-64 md:px-16 overflow-x-hidden no-scrollbar'
>
<div
className="absolute w-full h-full top-0 left-0 bg-gray-300 bg-opacity-50 "
className={`absolute w-full h-full top-0 left-0 bg-gray-300 bg-opacity-50 ${props.isPageModal && "hidden md:block"}`}
onClick={onClose}
></div>
{children}

View File

@@ -101,7 +101,7 @@ export default function ModalsContainer() {
<AnimatePresence exitBeforeEnter>
{isOpen &&
<motion.div
className="w-screen fixed inset-0 overflow-x-hidden"
className="w-screen fixed inset-0 overflow-x-hidden z-[2020]"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{
@@ -113,8 +113,8 @@ export default function ModalsContainer() {
{openModals.map(modal => {
const Child = ModalsMap(modal.modalId);
return (
<Modal key={modal.modalId} onClose={onClose} direction={direction}>
<Child onClose={onClose} direction={direction} {...modal.propsToPass} />
<Modal key={modal.modalId} onClose={onClose} direction={direction} isPageModal={modal.isPageModal}>
<Child onClose={onClose} direction={direction} isPageModal={modal.isPageModal} {...modal.propsToPass} />
</Modal>)
})}
</AnimatePresence>

View File

@@ -45,7 +45,7 @@ export default function NavMobile({ onSearch }: Props) {
return (
<nav className='block lg:hidden overflow-hidden z-[2010]'>
<nav className='block bg-white fixed top-0 left-0 w-full lg:hidden overflow-hidden z-[2010]'>
<div className="p-16 px-32 w-screen flex justify-center items-center">
<div className="w-40 h-40 bg-gray-100 rounded-8 mr-auto overflow-hidden">
<img className="w-full h-full object-cover" src="https://www.figma.com/file/OFowr5RJk9YZCW35KT7D5K/image/07b85d84145942255afd215b3da26dbbf1dd03bd?fuid=772401335362859303" alt="" />

View File

@@ -4,13 +4,16 @@ import { MdLocalFireDepartment } from 'react-icons/md';
import { IoExtensionPuzzle } from 'react-icons/io5';
import { AiFillThunderbolt } from 'react-icons/ai';
import { BsSearch } from "react-icons/bs";
import { FormEvent, useRef, useState } from "react";
import { FormEvent, useCallback, useEffect, useRef, useState } from "react";
import { motion } from "framer-motion";
import { GrClose } from 'react-icons/gr';
import { useAppDispatch, useAppSelector } from "../../../utils/hooks";
import { ModalId, openModal } from "../../../redux/features/modals.slice";
import { Link } from "react-router-dom";
import Button from "../Button/Button";
import { setNavHeight } from "src/redux/features/theme.slice";
import _throttle from 'lodash.throttle'
import { useResizeListener } from 'src/utils/hooks'
export const navLinks = [
{ text: "Explore", url: "/", icon: FaHome, color: 'text-primary-600' },
@@ -63,12 +66,26 @@ export default function Navbar() {
onSearch(searchInput)
}
useResizeListener(function calcNavHeight() {
const navs = document.querySelectorAll('nav');
navs.forEach(nav => {
const navStyles = getComputedStyle(nav);
if (navStyles.display !== 'none') {
dispatch(setNavHeight(nav.clientHeight))
document.body.style.paddingTop = `${nav.clientHeight}px`
}
});
}, [])
return (
<>
{/* Mobile Nav */}
<NavMobile onSearch={onSearch} />
{/* Desktop Nav */}
<nav className="hidden lg:flex py-36 px-32 items-center">
{/* Desktop Nav */}
<nav className="hidden bg-white w-full lg:flex fixed top-0 left-0 py-36 px-32 items-center z-[2010]">
<Link to='/'><h2 className="text-h5 font-bold mr-40 lg:mr-64">makers.bolt.fun</h2></Link>
<ul className="flex gap-32 xl:gap-64">
{navLinks.map((link, idx) => <li key={idx} className="text-body4 hover:text-primary-600">

View File

@@ -36,7 +36,7 @@
}
.modal-card {
@apply rounded-[40px] bg-gray-50 overflow-hidden w-full max-w-[600px] shadow-2xl z-10;
@apply rounded-[40px] bg-gray-50 overflow-hidden w-full shadow-2xl z-10;
}
}
@@ -62,7 +62,7 @@ svg {
/* Track */
::-webkit-scrollbar-track {
background: transparent;
background: #ddd;
}
/* Handle */

View File

@@ -23,6 +23,7 @@ export enum ModalId {
interface OpenModal {
modalId: ModalId;
isPageModal?: boolean;
propsToPass?: any;
}
@@ -33,6 +34,7 @@ interface StoreState {
flows: ModalId[];
toOpenLater: OpenModal | null;
openModals: OpenModal[];
isMobileScreen?: boolean;
}
const initialState = {
@@ -75,9 +77,11 @@ export const modalSlice = createSlice({
) {
state.direction = Direction.START;
state.isOpen = true;
const isPageModal = action.payload.modalId === ModalId.Project;
state.openModals.push({
modalId: action.payload.modalId,
propsToPass: action.payload.propsToPass,
isPageModal,
});
},
@@ -91,9 +95,11 @@ export const modalSlice = createSlice({
) {
state.direction = action.payload.direction;
state.openModals.pop();
const isPageModal = action.payload.modalId === ModalId.Project;
state.openModals.push({
modalId: action.payload.modalId,
propsToPass: action.payload.propsToPass || {},
isPageModal,
});
},

View File

@@ -1,8 +1,6 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { Project } from "../../utils/interfaces";
import mockData from "../../api/mockData.json";
interface StoreState {
project: Project | null;
projectSet: boolean;

View File

@@ -0,0 +1,28 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface StoreState {
navHeight: number;
isMobileScreen: boolean;
}
const initialState = {
navHeight: 88,
isMobileScreen: false,
} as StoreState;
export const themeSlice = createSlice({
name: "theme",
initialState,
reducers: {
setNavHeight(state, action: PayloadAction<number>) {
state.navHeight = action.payload;
},
setIsMobileScreen(state, action: PayloadAction<boolean>) {
state.isMobileScreen = action.payload;
},
},
});
export const { setNavHeight, setIsMobileScreen } = themeSlice.actions;
export default themeSlice.reducer;

View File

@@ -2,12 +2,14 @@ import { configureStore } from "@reduxjs/toolkit";
import modalsSlice from "./features/modals.slice";
import projectSlice from "./features/project.slice";
import walletSlice from "./features/wallet.slice";
import themeSlice from "./features/theme.slice";
export const store = configureStore({
reducer: {
modals: modalsSlice,
project: projectSlice,
wallet: walletSlice,
theme: themeSlice,
},
});

2
src/utils/hooks/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export * from "./storeHooks";
export * from "./useResizeListener";

View File

@@ -1,5 +1,5 @@
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import { AppDispatch, RootState } from "../redux/store";
import { AppDispatch, RootState } from "../../redux/store";
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();

View File

@@ -0,0 +1,20 @@
import { useCallback, useEffect } from "react";
import _throttle from "lodash.throttle";
export const useResizeListener = (
listener: () => void,
depsArray: any[] = [],
options: { throttleValue?: number } = {}
) => {
options.throttleValue = options.throttleValue ?? 250;
const cb = useCallback(listener, depsArray);
useEffect(() => {
const func = _throttle(cb, 250);
func();
window.addEventListener("resize", func);
return () => {
window.removeEventListener("resize", func);
};
}, [cb]);
};