From 5df9ed9aeed3e6616aae0a43521fec04098caaf8 Mon Sep 17 00:00:00 2001 From: Maria Sauer <mcs22@inf.ufpr.br> Date: Wed, 15 Jan 2025 11:57:37 -0300 Subject: [PATCH] Issue #233: CREATE mobile navigation bar --- src/app/components/ContinueNavigation.js | 2 +- src/app/components/GroupButtonsFilters.js | 2 +- src/app/components/GroupCardsCollections.js | 19 +--- src/app/components/Header.js | 64 +++++-------- src/app/components/MenuProfile.js | 4 - src/app/components/NavigationBar.js | 101 ++++++++++++++++++++ src/app/components/Overlay.js | 72 ++++++++++---- src/app/components/SideBar.js | 9 -- src/app/contato/page.js | 100 ++++++++++--------- src/app/globals.css | 8 ++ tailwind.config.js | 5 +- 11 files changed, 249 insertions(+), 137 deletions(-) create mode 100644 src/app/components/NavigationBar.js diff --git a/src/app/components/ContinueNavigation.js b/src/app/components/ContinueNavigation.js index e795c73a..0906fc5e 100644 --- a/src/app/components/ContinueNavigation.js +++ b/src/app/components/ContinueNavigation.js @@ -2,7 +2,7 @@ import { Button } from "@mui/material" export const ContinueNavigation = () => { return ( - <div className="sm:hidden fixed bottom-0 left-0 w-full bg-grey_button flex justify-center"> + <div className="sm:hidden fixed bottom-25 left-0 w-full bg-grey_button flex justify-center"> <Button href="/busca?page=LearningObject" className="bg-secondary mt-2 text-xl text-white py-4 w-full text-center rounded-none hover:secondary-hover font-bold normal-case" diff --git a/src/app/components/GroupButtonsFilters.js b/src/app/components/GroupButtonsFilters.js index c8b8d8d7..b38b2ca8 100644 --- a/src/app/components/GroupButtonsFilters.js +++ b/src/app/components/GroupButtonsFilters.js @@ -123,7 +123,7 @@ export default function GroupButtonsFilters({ pageName, filterState, setFilterSt return ( <div className="flex flex-col w-full"> - <div className="flex justify-between pb-5 px-4 overflow-x-scroll no-scrollbar w-full pr-32 max-sm:pr-0"> + <div className="flex justify-between pb-5 px-4 overflow-x-scroll no-scrollbar w-full pr-32 max-sm:pr-0 animate-scrollHint"> <div className="flex shrink max-sm:flex-row-reverse"> {atalhos.map((item, index) => { return ( diff --git a/src/app/components/GroupCardsCollections.js b/src/app/components/GroupCardsCollections.js index f8d6fd94..245c4e19 100644 --- a/src/app/components/GroupCardsCollections.js +++ b/src/app/components/GroupCardsCollections.js @@ -55,7 +55,7 @@ export default function GroupCardsCollections({ data, cardsPerRow, collectionId </div> <> {/*mostra botão de ver mais caso os recursos de uma coleção cabem em mais de uma linha */} - {showButton ? + {showButton || data?.length > 1? ( <button fullWidth @@ -70,23 +70,8 @@ export default function GroupCardsCollections({ data, cardsPerRow, collectionId {expanded ? "Ver menos" : "Ver mais..."} </p> </button> - ) : null} + ) : null} - {data?.length > 1 ? - < button - fullWidth - disableElevation - variant="outlined" - className="hover:bg-main -ml-4 py-2 border-hidden rounded-xl normal-case flex justify-center sm:hidden" - onClick={toggleContent} - > - <p className=" px-4 text-main-text-click text-lg rounded-md" - alt={expanded ? "Ver menos" : "Ver mais..."} - > - {expanded ? "Ver menos" : "Ver mais..."} - </p> - </button> - : null} </> </div > ); diff --git a/src/app/components/Header.js b/src/app/components/Header.js index 0bcb0136..206259f1 100644 --- a/src/app/components/Header.js +++ b/src/app/components/Header.js @@ -15,36 +15,16 @@ import { useRouter } from "next/navigation"; import { ImArrowLeft } from "react-icons/im"; import Notifications from "./Notifications"; -function hexToRgb(hex) { - // Remove the hash if it exists - hex = hex.replace(/^#/, ''); - // Parse the RGB values - let bigint = parseInt(hex, 16); - let r = (bigint >> 16) & 255; - let g = (bigint >> 8) & 255; - let b = bigint & 255; - - return [r, g, b]; -} - -function rgbDistance(color1, color2) { - let r1 = color1[0], g1 = color1[1], b1 = color1[2]; - let r2 = color2[0], g2 = color2[1], b2 = color2[2]; - - return Math.sqrt( - Math.pow(r2 - r1, 2) + - Math.pow(g2 - g1, 2) + - Math.pow(b2 - b1, 2) - ); -} function DefaultContent({ + mobileSearch, handleToggleMobileSearch, setNeedLoginOpen, handleOpenMenu, setFilterState, - filterState + filterState, + isMobile }) { const pathname = usePathname(); const router = useRouter(); @@ -68,18 +48,20 @@ function DefaultContent({ return ( <> <div className="flex align-middle items-center flex-shrink-0 max-sm:pl-0 pl-5"> - <IconButton - className=" h-[36px] w-[36px] mx-2 mr-6 max-sm:mr-3 max-sm:mx-0" - size="large" - edge="start" - aria-label="open drawer" - onClick={handleOpenMenu} - > - <MenuIcon className="text-4xl text-secondary" /> - </IconButton> + {!isMobile && + <IconButton + className=" h-[36px] w-[36px] mx-2 max-sm:mr-3 max-sm:mx-0" + size="large" + edge="start" + aria-label="open drawer" + onClick={handleOpenMenu} + > + <MenuIcon className="text-4xl text-secondary" /> + </IconButton> + } <Link href="/" className="flex items-center shrink-0"> <div - className="w-32 h-32 bg-no-repeat bg-center bg-contain bg-logo mr-16 max-sm:mr-3" + className="w-32 h-32 bg-no-repeat ml-6 bg-center bg-contain bg-logo mr-16 max-sm:mr-3" aria-label="logo" ></div> </Link> @@ -89,11 +71,12 @@ function DefaultContent({ </div> <div className="flex justify-end shrink-0 items-center pr-2"> <button + id = "openSearchButton" type="button" alt="Abrir busca" title="Abrir busca" className="lg:hidden bg-secondary hover:bg-text-color w-10 h-10 rounded text-white shrink-0 transition" - onClick={handleToggleMobileSearch} + onClick={() => (handleToggleMobileSearch(mobileSearch))} > <SearchIcon className="h-full text-3xl" /> </button> @@ -128,7 +111,7 @@ function DefaultContent({ ); } -function MobileSearch({ setFilterState, filterState, handleToggleMobileSearch }) { +function MobileSearch({mobileSearch, setFilterState, filterState, handleToggleMobileSearch }) { return ( <div className="flex w-full items-center"> <SearchComponent setFilterState={setFilterState} filterState={filterState} /> @@ -137,7 +120,7 @@ function MobileSearch({ setFilterState, filterState, handleToggleMobileSearch }) alt="Fechar busca" title="Fechar busca" className={"bg-text-color w-10 h-10 rounded text-white shrink-0 transition-all duration-30000 hover:bg-blue-button"} - onClick={handleToggleMobileSearch} + onClick={() => (handleToggleMobileSearch(mobileSearch))} > <CloseIcon className="h-full text-3xl " /> </button> @@ -151,14 +134,15 @@ function MobileSearch({ setFilterState, filterState, handleToggleMobileSearch }) * @param {Function} props.handleOpenMenu - abre/fecha sidebar * @returns header */ -export default function Header({ setFilterState, filterState, handleOpenMenu }) { +export default function Header({ mobileSearch, isMobile, setFilterState, filterState, handleOpenMenu }) { const [needLoginOpen, setNeedLoginOpen] = useState(false); const [mobileSearchOpen, setMobileSearchOpen] = useState(false); - const handleToggleMobileSearch = () => { + const handleToggleMobileSearch = (mobileSearch) => { setTimeout(() => { setMobileSearchOpen((curr) => !curr); + mobileSearch.setSearchIsClicked((curr) => !curr); }, 100); }; @@ -170,9 +154,11 @@ export default function Header({ setFilterState, filterState, handleOpenMenu }) bg-repeat bg-fixed"> {mobileSearchOpen ? ( - <MobileSearch handleToggleMobileSearch={handleToggleMobileSearch} setFilterState={setFilterState} filterState={filterState} /> + <MobileSearch mobileSearch={mobileSearch} handleToggleMobileSearch={handleToggleMobileSearch} setFilterState={setFilterState} filterState={filterState} /> ) : ( <DefaultContent + mobileSearch={mobileSearch} + isMobile={isMobile} handleToggleMobileSearch={handleToggleMobileSearch} setNeedLoginOpen={setNeedLoginOpen} handleOpenMenu={handleOpenMenu} diff --git a/src/app/components/MenuProfile.js b/src/app/components/MenuProfile.js index e1ce5c74..36e85a4f 100644 --- a/src/app/components/MenuProfile.js +++ b/src/app/components/MenuProfile.js @@ -3,17 +3,13 @@ import Box from "@mui/material/Box"; import Avatar from "@mui/material/Avatar"; import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; -import Divider from "@mui/material/Divider"; import IconButton from "@mui/material/IconButton"; -import MarkunreadIcon from '@mui/icons-material/Markunread'; -import Logout from "@mui/icons-material/Logout"; import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { getStoredValue, removeFromLocalStorage, - saveToLocalStorage, } from "../handlers/localStorageHandler"; import { isLoggedIn } from "../handlers/loginHandler"; import AccountCircleRoundedIcon from '@mui/icons-material/AccountCircleRounded'; diff --git a/src/app/components/NavigationBar.js b/src/app/components/NavigationBar.js new file mode 100644 index 00000000..c51f70b2 --- /dev/null +++ b/src/app/components/NavigationBar.js @@ -0,0 +1,101 @@ +import * as React from 'react'; +import { useState, useEffect } from 'react'; +import NeedLoginModal from './needLoginModal'; +import { isLoggedIn } from "../handlers/loginHandler"; +import FileUploadIcon from '@mui/icons-material/FileUpload'; +import CollectionsBookmarkIcon from "@mui/icons-material/CollectionsBookmark"; +import SubjectIcon from "@mui/icons-material/Subject"; +import EmailRoundedIcon from '@mui/icons-material/EmailRounded'; +import HelpIcon from "@mui/icons-material/Help"; +import VerifiedIcon from "@mui/icons-material/Verified"; +import { Person } from "@mui/icons-material"; +import SearchIcon from '@mui/icons-material/Search'; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { + getStoredValue, +} from "../handlers/localStorageHandler"; + + + + +export default function NavigationBar({ mobileSearch }) { + + const [needLoginOpen, setNeedLoginOpen] = useState(false); + const pathname = usePathname(); + let searchParams = useSearchParams(); + const page = searchParams.get('page'); + const [id, setId] = useState(null); + + + + + useEffect(() => { + if (isLoggedIn()) { + let data = JSON.parse(getStoredValue("user_data")); + setId(data["id"]); + } + }, []); + + const handleOpenLoggin = (e) => { + + if (!isLoggedIn()) { + e.preventDefault(); + setNeedLoginOpen(true); + } + }; + + + const navItems = [ + { label: "Pesquisar", href: "#", icon: SearchIcon }, + { label: "Recursos", href: "/busca?page=LearningObject", icon: SubjectIcon }, + { label: "Coleções", href: "/busca?page=Collection", icon: CollectionsBookmarkIcon }, + { label: "Publicar", href: "/publicar", icon: FileUploadIcon }, + { label: "Usuários", href: "/busca?page=User", icon: Person }, + { label: "MEC", href: "/busca?page=MEC", icon: VerifiedIcon }, + { label: "Perfil", href: "/perfil", icon: Person }, + { label: "Sobre", href: "/sobre", icon: HelpIcon }, + { label: "Contato", href: "/contato", icon: EmailRoundedIcon }, + ] + + return ( + <> + <NeedLoginModal open={needLoginOpen} setOpen={setNeedLoginOpen} /> + + <nav className="bg-bg-comments h-nav-bar text-text-color p-4 fixed bottom-0 left-0 w-full z-10"> + <ul className="flex justify-between overflow-x-auto no-scrollbar animate-scrollHint"> + {navItems.map((item, index) => { + const isPublishRoute = item.href === "/publicar"; + const isPerfilRoute = item.href === "/perfil"; + const isSearchButton = item.label === "Pesquisar"; + + + return ( + <li key={index} className="flex w-24 flex-col items-center justify-center px-4 pt-2 "> + + <a href={isPublishRoute ? (isLoggedIn() ? "/publicar" : "") + : isPerfilRoute ? (isLoggedIn() ? `/perfil/${id}` : "") + : item.href} + + onClick={isPublishRoute || isPerfilRoute ? (e) => { return (handleOpenLoggin(e)) } : + isSearchButton ? (e) => { return (e.preventDefault(), document.getElementById("openSearchButton")?.click()) } + : undefined} + className="text-center "> + + <item.icon className={`cursor-pointer text-4xl ${(page === item.href.split('page=')[1]) || (pathname === item.href) || (mobileSearch.searchIsClicked && item.label === "Pesquisar") + ? "text-gray-color " + : "text-color" + }`} /> + <span className={`cursor-pointer text-sm ${(page === item.href.split('page=')[1]) || (pathname === item.href) || (mobileSearch.searchIsClicked && item.label === "Pesquisar") + ? "text-gray-color " + : "text-color" + }`}>{item.label}</span> + </a> + </li> + ) + })} + </ul> + </nav> + </> + + ) +} \ No newline at end of file diff --git a/src/app/components/Overlay.js b/src/app/components/Overlay.js index b427451f..20f0f691 100644 --- a/src/app/components/Overlay.js +++ b/src/app/components/Overlay.js @@ -1,5 +1,5 @@ "use client"; - +import * as React from "react"; import Header from "./Header"; import SideBar from "./SideBar"; import { Suspense, useEffect, useLayoutEffect, useState } from "react"; @@ -7,6 +7,7 @@ import theme from "@/app/theme"; import { ThemeProvider } from "@emotion/react"; import Loading from "./Loading"; import AcessibilityBar from "./AcessibilityBar"; +import NavigationBar from "./NavigationBar"; /** * @param {Object} props @@ -18,51 +19,80 @@ import AcessibilityBar from "./AcessibilityBar"; * @param {Function} props.setNewSize * @returns componente que engloba header/sidebar/suspense e ThemeProvider */ + export default function Overlay({ children, filterState, setFilterState, setNewSize, }) { + const [isMobile, setIsMobile] = useState(false); const [openMenu, setOpenMenu] = useState(false); + const [searchIsClicked, setSearchIsClicked] = useState(false); + + const mobileSearch = { searchIsClicked, setSearchIsClicked }; + const handleOpenMenu = () => { setOpenMenu(!openMenu); }; useEffect(() => { if (setNewSize != undefined) setNewSize(true); + + const handleResize = () => { + setIsMobile(window.innerWidth <= 768); + }; + + // Atualiza o estado inicialmente e quando a janela é redimensionada + handleResize(); + window.addEventListener("resize", handleResize); + + // Limpa o listener quando o componente for desmontado + return () => { + window.removeEventListener("resize", handleResize); + }; }, [openMenu, setNewSize]); + return ( <Suspense fallback={<Loading />}> <ThemeProvider theme={theme}> - <main className="flex min-h-screen bg-main flex-col - bg-fundo - bg-repeat - bg-fixed - text-base" + <div + className={`grid min-h-screen bg-main bg-fundo bg-repeat bg-fixed text-base grid-rows-[auto_auto_1fr_auto] `} > - <AcessibilityBar - handleOpenMenu={handleOpenMenu} - openMenu={openMenu} - /> - <SideBar + + <AcessibilityBar handleOpenMenu={handleOpenMenu} openMenu={openMenu} /> + + <Header + mobileSearch={mobileSearch} + isMobile={isMobile} setFilterState={setFilterState} filterState={filterState} - openMenu={openMenu} handleOpenMenu={handleOpenMenu} /> - <Header setFilterState={setFilterState} filterState={filterState} handleOpenMenu={handleOpenMenu} /> - <div - className={`flex flex-col ml-0 ${ - openMenu ? "md:ml-64" : "md:ml-24" - } mt-[120px] text-base`} - > - {children} + + <div > + {!isMobile && ( + <SideBar + setFilterState={setFilterState} + filterState={filterState} + openMenu={openMenu} + handleOpenMenu={handleOpenMenu} + /> + )} + + <main + className={` ${isMobile && "mb-nav-bar"} ${openMenu ? "md:ml-64" : "md:ml-24" + } mt-[120px] overflow-y-auto`} + > + {children} + </main> </div> - </main> + + {isMobile && <NavigationBar mobileSearch={mobileSearch} />} + </div> </ThemeProvider> </Suspense> ); -} +} \ No newline at end of file diff --git a/src/app/components/SideBar.js b/src/app/components/SideBar.js index c7e3cea1..98f966c8 100644 --- a/src/app/components/SideBar.js +++ b/src/app/components/SideBar.js @@ -82,15 +82,6 @@ function tradutor(name) { } } -function handleStringSubject(array) { - let string = ""; - - for (let i = 0; i < array.length; i++) { - if (array[i]) string = string + (i + 1).toString() + ","; - } - - return string.substring(0, string.length - 1); -} /** * @param {Object} props diff --git a/src/app/contato/page.js b/src/app/contato/page.js index 6c69cf86..11a5ab81 100644 --- a/src/app/contato/page.js +++ b/src/app/contato/page.js @@ -1,23 +1,24 @@ "use client"; import { Card } from "@mui/material"; -import { useState, useLayoutEffect } from "react"; +import { useState } from "react"; import Overlay from "../components/Overlay"; -import { TextField, Button, InputLabel } from "@mui/material"; +import { Button, Modal } from "@mui/material"; import mecredApi from "@/axiosConfig"; -import { Modal } from "@mui/material"; -import theme from "@/app/theme"; - export default function Contact() { const [errorMessage, setErrorMessage] = useState(""); - const [successOpen, setSuccessOpen] = useState(false) + const [successOpen, setSuccessOpen] = useState(false); - function verifyForm (formData) { - if (formData.get('name') === "" || formData.get('email') === "" || formData.get('contents') === "") { + function verifyForm(formData) { + if ( + formData.get("name") === "" || + formData.get("email") === "" || + formData.get("contents") === "" + ) { return false; } return true; - } + } const sendMessage = async (event) => { event.preventDefault(); @@ -25,30 +26,31 @@ export default function Contact() { const formData = new FormData(event.target); if (!verifyForm(formData)) { - alert('Por favor, preencha todos os campos.'); + alert("Por favor, preencha todos os campos."); return; - } - + } + await mecredApi .post("/contacts", { - name: formData.get('name'), - email: formData.get('email'), - message: formData.get('contents'), + name: formData.get("name"), + email: formData.get("email"), + message: formData.get("contents"), }) .then((response) => { setSuccessOpen(true); }) .catch((error) => { - setErrorMessage(error['response']['data']['errors'][0]) - }) - } - + setErrorMessage(error?.response?.data?.errors[0]); + }); + }; const ModalSucess = ({ open, onClose }) => { return ( <Modal open={open} onClose={onClose} className="grid place-items-center"> <div className="flex flex-col justify-center bg-main p-5 rounded"> - <p className="text-xl justify-center flex text-main-text mb-2">Mensagem enviada com sucesso!</p> + <p className="text-xl justify-center flex text-main-text mb-2"> + Mensagem enviada com sucesso! + </p> <div className="flex flex-row mt-2 justify-center gap-3"> <button className="text-sm text-main-text border-main rounded-lg normal-case font-bold bg-main hover:bg-main-hover" @@ -59,72 +61,82 @@ export default function Contact() { </div> </div> </Modal> - ) - } + ); + }; return ( <Overlay> - <ModalSucess open={successOpen} onClose={() => { setSuccessOpen(false) }} /> - <div className="flex justify-center"> + <ModalSucess + open={successOpen} + onClose={() => { + setSuccessOpen(false); + }} + /> + <div className="flex justify-center px-4"> <Card - className="text-text-filter bg-white outline outline-1 outline-outlineColor max-sm:h-max" + className="flex text-text-filter bg-white outline outline-1 mb-16 outline-outlineColor" sx={{ display: "flex", flexDirection: "column", alignItems: "center", - width: 1125, - height: 750, + width: "100%", + maxWidth: "1125px", + height: "auto", backgroundColor: "#FFFFFF", boxShadow: "none", padding: "30px", }} > - <form onSubmit={sendMessage} className="mb-5"> - <div className="flex flex-col justify-center text-center items-center gap-8 mt-16 "> + <form + onSubmit={sendMessage} + className="mb-5 flex w-full max-w-2xl" + > + <div className="flex flex-col justify-center text-center items-center gap-8 mt-8 w-full"> <p className="text-2xl font-bold">Contato</p> - <p className="text-xl font-light">Entre em contato para enviar dúvidas, sugestões ou críticas.</p> - <div> + <p className="text-xl font-light text-center"> + Entre em contato para enviar dúvidas, sugestões ou críticas. + </p> + <div className="w-full"> <p className="text-main-text text-left text-sm">Nome*</p> <input - className="flex flex-row justify-center items-center w-80 h-12 border-text-main-text border rounded text-main-text - pl-2 bg-transparent focus:outline-none focus:border-input-focus border-500" + className="w-full h-12 border-text-main-text border rounded text-main-text pl-2 bg-transparent focus:outline-none focus:border-input-focus" type="text" name="name" required /> </div> - <div> + <div className="w-full"> <p className="text-main-text text-left text-sm">E-mail*</p> <input - className="flex flex-row justify-center items-center w-80 h-12 border-text-main-text border rounded text-main-text - pl-2 bg-transparent focus:outline-none focus:border-input-focus border-500" - type="text" + className="w-full h-12 border-text-main-text border rounded text-main-text pl-2 bg-transparent focus:outline-none focus:border-input-focus" + type="email" name="email" required /> </div> - <div> + <div className="w-full"> <p className="text-main-text text-left text-sm">Mensagem*</p> <textarea - className="flex flex-row justify-center items-center w-80 h-36 border-text-main-text border rounded text-main-text - p-2 bg-transparent focus:outline-none focus:border-input-focus border-500" + className="w-full h-36 border-text-main-text border rounded text-main-text p-2 bg-transparent focus:outline-none focus:border-input-focus" name="contents" required ></textarea> </div> <Button - className="bg-secondary text-white hover:bg-main-hover normal-case font-bold w-80 text-lg" + className="bg-secondary text-white hover:bg-main-hover normal-case font-bold w-full text-lg" type="submit" alt="Enviar mensagem" title="Enviar mensagem" > Enviar mensagem </Button> - <p className="text-xs font-light mt-6">Os campos marcados com * são obrigatórios.</p> + <p className="text-xs font-light mt-6"> + Os campos marcados com * são obrigatórios. + </p> </div> </form> </Card> </div> </Overlay> - ) -} \ No newline at end of file + ); +} diff --git a/src/app/globals.css b/src/app/globals.css index a7bce123..ce9d9e85 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -12,6 +12,10 @@ -ms-overflow-style: none; /* IE and Edge */ scrollbar-width: none; /* Firefox */ } + + .mb-nav-bar { + margin-bottom: var(--nav-bar); + } } @layer base { @@ -170,6 +174,10 @@ --line-height-9xl-min: 10px; --font-size-9xl-max: 131px; --line-height-9xl-max: 22px; + + /*height*/ + --nav-bar: 100px; + } @import "themes/theme_high_contrast.css"; @import "themes/tema_especial.css"; diff --git a/tailwind.config.js b/tailwind.config.js index a830b6fd..0b51927e 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -71,8 +71,11 @@ module.exports = { }, }, animation: { - scrollHint: 'scrollHint 0.5s ease-in-out 1', + scrollHint: 'scrollHint 0.5s ease-in-out 1 ', }, + height:{ + 'nav-bar': 'var(--nav-bar)' + } }, }, plugins: [ -- GitLab