feat: refactor project structure

Refactored the project structure so that each page has its own tree of components and a global "Components" folder for the components that is used by more than one page.
- Added an "assets" directory that exports all static images/icons/fonts/...etc
This commit is contained in:
MTG2000
2021-12-30 15:12:40 +02:00
parent 5ae1da6369
commit 43bfab177e
51 changed files with 71 additions and 60 deletions

View File

@@ -0,0 +1,104 @@
import { motion } from "framer-motion";
import { FormEvent, useEffect, useState } from "react";
import { FiMenu } from 'react-icons/fi';
import { GrClose } from 'react-icons/gr';
import { BsSearch } from 'react-icons/bs'
import { navLinks } from "./Navbar";
import { AiFillThunderbolt } from 'react-icons/ai';
import { Link } from "react-router-dom";
import Button from "../Button/Button";
const navBtnVariant = {
menuHide: { rotate: 90, opacity: 0 },
menuShow: { rotate: 0, opacity: 1 },
closeHide: { rotate: -90, opacity: 0 },
closeShow: { rotate: 0, opacity: 1 },
}
const navListVariants = {
init: { x: 0 },
show: { x: "-100%" },
hide: { x: 0 }
}
interface Props {
onSearch: (search: string) => void;
}
export default function NavMobile({ onSearch }: Props) {
const [open, setOpen] = useState(false);
const [searchInput, setSearchInput] = useState("")
const handleClick = () => {
setOpen(open => !open);
}
useEffect(() => {
if (open) document.body.style.overflowY = "hidden";
else document.body.style.overflowY = "initial";
}, [open]);
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (searchInput)
onSearch(searchInput)
}
return (
<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="" />
</div>
<Link to='/'><h2 className="text-h5 font-bold mx-auto">makers.bolt.fun</h2></Link>
<button className='rounded-full ml-auto text-2xl w-[50px] h-[50px] hover:bg-gray-200' onClick={handleClick}>
{!open ? (<motion.div key={open ? 1 : 0} variants={navBtnVariant} initial='menuHide' animate='menuShow'><FiMenu /></motion.div>)
: (<motion.div key={open ? 1 : 0} variants={navBtnVariant} initial='closeHide' animate='closeShow'><GrClose /></motion.div>)}
</button>
</div>
<div className="fixed overflow-hidden left-0 pointer-events-none z-[2010] w-full min-h-[calc(100vh-76px)]">
{open && <div onClick={handleClick} className='pointer-events-auto absolute left-0 w-full min-h-full bg-gray-400 opacity-20'>
</div>}
<motion.div
className="pointer-events-auto bg-white w-full sm:max-w-[400px] min-h-full absolute left-full border shadow-2xl pt-32 sm:p-32 flex flex-col"
variants={navListVariants}
animate={open ? "show" : "hide"}
>
<div className="px-16">
<form className='relative' onSubmit={handleSubmit}>
<BsSearch className='absolute top-1/2 left-20 transform -translate-x-1/2 -translate-y-1/2 text-gray-500' />
<input
value={searchInput}
onChange={e => setSearchInput(e.target.value)}
className="bg-gray-100 text-gray-600 focus:outline-primary w-full py-12 px-20 pl-40 rounded-24 placeholder-gray-500" placeholder="Search" />
{/* <input className="btn bg-gray-100 w-full rounded-24 mt-16 placeholder-gray-500" placeholder="Search" /> */}
</form>
<Button color='primary' fullWidth className="py-12 px-40 rounded-24 mt-40">Submit App</Button>
<Button color='gray' fullWidth className="py-12 px-40 rounded-24 my-16"> <AiFillThunderbolt className='inline-block text-thunder transform scale-125' /> Connect Wallet </Button>
</div>
<ul className="py-16 gap-64 border-t">
{navLinks.map((link, idx) => <li key={idx} className="text-body3 p-16 hover:bg-gray-200">
<Link to={link.url}><link.icon className={`text-body2 inline-block mr-12 ${link.color}`} /> {link.text} </Link></li>
)}
</ul>
<ul className="px-16 py-16 pb-32 flex flex-wrap gap-y-12 border-t mt-auto">
<li className="text-body4 text-gray-500 hover:text-gray-700 w-1/2"><a href="/">About Us</a></li>
<li className="text-body4 text-gray-500 hover:text-gray-700 w-1/2"><a href="/">Support</a></li>
<li className="text-body4 text-gray-500 hover:text-gray-700 w-1/2"><a href="/">Press</a></li>
<li className="text-body4 text-gray-500 hover:text-gray-700 w-1/2"><a href="/">Contacts</a></li>
<li className="text-body4 text-gray-500 hover:text-gray-700 w-1/2"><a href="/">Careers</a></li>
<li className="text-body4 text-gray-500 hover:text-gray-700 w-1/2"><a href="/">Sitemap</a></li>
<li className="text-body4 text-gray-500 hover:text-gray-700 w-1/2"><a href="/">Legal</a></li>
<li className="text-body4 text-gray-500 hover:text-gray-700 w-1/2"><a href="/">Cookies Settings</a></li>
</ul>
</motion.div>
</div>
</nav>
)
}

View File

@@ -0,0 +1,14 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import Navbar from './Navbar';
export default {
title: 'Shared/Navbar',
component: Navbar,
} as ComponentMeta<typeof Navbar>;
const Template: ComponentStory<typeof Navbar> = (args) => <Navbar />;
export const Default = Template.bind({});

View File

@@ -0,0 +1,123 @@
import NavMobile from "./NavMobile";
import { FaHome } from 'react-icons/fa';
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 { motion } from "framer-motion";
import { GrClose } from 'react-icons/gr';
import { useAppDispatch, useAppSelector } from "src/utils/hooks";
import { ModalId, openModal } from "src/redux/features/modals.slice";
import { Link } from "react-router-dom";
import Button from "../Button/Button";
import { setNavHeight } from "src/redux/features/theme.slice";
import { useResizeListener } from 'src/utils/hooks'
export const navLinks = [
{ text: "Explore", url: "/", icon: FaHome, color: 'text-primary-600' },
{ text: "Hottest", url: "/categories/hottest", icon: MdLocalFireDepartment, color: 'text-primary-600' },
{ text: "Categories", url: "/categories", icon: IoExtensionPuzzle, color: 'text-primary-600' },
]
export default function Navbar() {
const [searchOpen, setSearchOpen] = useState(false)
const [searchInput, setSearchInput] = useState("");
const inputRef = useRef<HTMLInputElement>(null)
const dispatch = useAppDispatch()
const { isWalletConnected, webln } = useAppSelector(state => ({
isWalletConnected: state.wallet.isConnected,
webln: state.wallet.provider,
}));
const toggleSearch = () => {
if (!searchOpen) {
console.log(inputRef.current);
inputRef.current?.focus();
}
setSearchOpen(!searchOpen);
}
const onSearch = (search: string) => {
// Make Search Request
alert(`Your Searched for: ${search}`)
}
const onConnectWallet = () => {
dispatch(openModal({
modalId: ModalId.Login_ScanWallet
}));
}
const onWithdraw = () => {
dispatch(openModal({
modalId: ModalId.Claim_FundWithdraw
}))
}
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
// Make Search Request
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 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">
<Link to={link.url}><link.icon className={`text-body2 align-middle inline-block mr-8 ${link.color}`} /> {link.text}</Link></li>
)}
</ul>
<div className="ml-auto flex">
<motion.div
animate={searchOpen ? { opacity: 0 } : { opacity: 1 }}
className="flex">
<Button color='primary' size='md' className="lg:px-40">Submit App</Button>
{isWalletConnected ?
<Button className="ml-16 py-12 px-16 lg:px-20">Connected <AiFillThunderbolt className='inline-block text-thunder transform scale-125' /></Button>
: <Button className="ml-16 py-12 px-16 lg:px-20" onClick={onConnectWallet}><AiFillThunderbolt className='inline-block text-thunder transform scale-125' /> Connect Wallet </Button>
}
</motion.div>
<form onBlur={toggleSearch} className='relative flex items-center' onSubmit={handleSubmit}>
{searchOpen ? <GrClose onClick={toggleSearch} className='text-gray-500 w-24 h-24 mx-12 z-20 hover:cursor-pointer' /> : <BsSearch onClick={toggleSearch} className='text-gray-500 w-24 h-24 mx-12 z-20 hover:cursor-pointer' />}
{searchOpen && <motion.input
ref={inputRef}
value={searchInput}
onChange={e => setSearchInput(e.target.value)}
initial={{ scaleX: .3, opacity: 0, originX: 'right' }}
animate={searchOpen ? { scaleX: 1, opacity: 1, originX: 'right' } : { scaleX: .3, opacity: 0, originX: 'right' }}
onAnimationComplete={() => {
if (searchOpen) inputRef.current?.focus()
}}
className="absolute top-0 right-0 z-10 bg-gray-100 text-gray-600 focus:outline-primary w-[300px] py-12 px-20 pr-40 rounded-24 placeholder-gray-500" placeholder="Search" />
}
</form>
</div>
</nav>
</>
)
}