change: change search component

- changed search component structure & api due to it being displayed differently in mobile & desktop
This commit is contained in:
MTG2000
2022-05-27 17:04:31 +03:00
parent 4194eadb6e
commit edb90296ca
11 changed files with 93 additions and 142 deletions

View File

@@ -1,48 +1,30 @@
import { BsSearch } from "react-icons/bs";
import { motion } from "framer-motion";
import { useAppDispatch, useAppSelector, useCurrentSection } from "src/utils/hooks";
import { openModal } from "src/redux/features/modals.slice";
import Button from "../Button/Button";
import { useAppSelector, useCurrentSection } from "src/utils/hooks";
import ASSETS from "src/assets";
import Search from "./Search/Search";
import IconButton from "../IconButton/IconButton";
import { toggleSearch } from "src/redux/features/ui.slice";
import { navLinks } from "./Navbar";
import { Link, useNavigate } from "react-router-dom";
import CategoriesList from "./CategoriesList/CategoriesList";
import { useEffect, useRef, useState } from "react";
import { IoExtensionPuzzle } from "react-icons/io5";
import { useClickOutside, useToggle } from "@react-hookz/web";
import { useState } from "react";
import {
Menu,
MenuItem,
MenuButton,
MenuDivider,
SubMenu
} from '@szhsin/react-menu';
import '@szhsin/react-menu/dist/index.css';
import { FiAward, FiChevronDown, FiFeather, FiMic, FiSend } from "react-icons/fi";
import { MdComment, MdOutlineExplore, MdOutlineLocalFireDepartment } from "react-icons/md";
import { IoMdTrophy } from "react-icons/io";
import { BiCoinStack } from "react-icons/bi";
import { FiAward, FiChevronDown, FiFeather, FiMic } from "react-icons/fi";
export default function NavDesktop() {
const dispatch = useAppDispatch();
const [categoriesOpen, toggleCategories] = useToggle(false)
const categoriesRef = useRef<HTMLLIElement>(null)
useClickOutside(categoriesRef, () => toggleCategories(false))
const [searchOpen, setSearchOpen] = useState(false)
const { isWalletConnected, searchOpen } = useAppSelector((state) => ({
const { isWalletConnected } = useAppSelector((state) => ({
isWalletConnected: state.wallet.isConnected,
searchOpen: state.ui.isSearchOpen
}));
const handleSearchClick = () => {
dispatch(toggleSearch())
const openSearch = () => {
setSearchOpen(true);
};
@@ -50,7 +32,7 @@ export default function NavDesktop() {
const navigate = useNavigate()
return (<nav className="bg-white w-full flex fixed h-[72px] top-0 left-0 py-36 px-32 items-center z-[2010]">
return (<nav className="bg-white w-full flex fixed top-0 left-0 py-16 px-32 items-center z-[2010]">
<a href="https://bolt.fun/">
<h2 className="text-h5 font-bold mr-40 lg:mr-64">
<img className='h-40' src={ASSETS.Logo} alt="Bolt fun logo" />
@@ -159,12 +141,39 @@ export default function NavDesktop() {
: <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>
} */}
{currentSection === 'products' && <IconButton className='ml-16 self-center' onClick={handleSearchClick}>
{currentSection === 'products' && <IconButton className='ml-16 self-center' onClick={openSearch}>
<BsSearch className='scale-125 text-gray-400' />
</IconButton>}
</motion.div>
<Search />
<div className="relative h-24">
<motion.div
initial={{
opacity: 0,
x: '100%'
}}
animate={searchOpen ? {
opacity: 1,
x: '0',
transition: { type: "spring", stiffness: 70 }
} : {
opacity: 0,
x: '100%',
transition: {
ease: "easeIn"
}
}}
className='absolute top-0 right-0 flex items-center h-full'
>
<Search
width={326}
isOpen={searchOpen}
onClose={() => setSearchOpen(false)}
onResultClick={() => setSearchOpen(false)}
/>
</motion.div>
</div>
</nav>
);
}

View File

@@ -1,22 +1,18 @@
import { motion } from "framer-motion";
import { useEffect, useRef, useState } from "react";
import { BsChevronDown, BsSearch } from "react-icons/bs";
import { useEffect } from "react";
import { BsChevronDown } from "react-icons/bs";
import { GrClose } from "react-icons/gr";
import Button from "../Button/Button";
import ASSETS from "src/assets";
import Search from "./Search/Search";
import IconButton from "../IconButton/IconButton";
import { useAppDispatch, useAppSelector } from "src/utils/hooks";
import { toggleSearch } from "src/redux/features/ui.slice";
import { FiAward, FiChevronDown, FiFeather, FiMenu, FiMic, } from "react-icons/fi";
import { Link, useNavigate } from "react-router-dom";
import { useAppSelector } from "src/utils/hooks";
import { FiAward, FiFeather, FiMenu, FiMic, } from "react-icons/fi";
import { Link } from "react-router-dom";
import { useToggle } from "@react-hookz/web";
import styles from './styles.module.css'
import { Menu, MenuButton, MenuItem, } from "@szhsin/react-menu";
import '@szhsin/react-menu/dist/index.css';
interface Props {
}
const navBtnVariant = {
@@ -55,19 +51,13 @@ const listArrowVariants = {
}
export default function NavMobile({ }: Props) {
const dispatch = useAppDispatch();
const { searchOpen } = useAppSelector((state) => ({
isWalletConnected: state.wallet.isConnected,
searchOpen: state.ui.isSearchOpen
}));
export default function NavMobile() {
const [drawerOpen, toggleDrawerOpen] = useToggle(false);
const [communityOpen, toggleCommunityOpen] = useToggle(false)
useEffect(() => {
if (drawerOpen) document.body.style.overflowY = "hidden";
else document.body.style.overflowY = "initial";
@@ -75,39 +65,19 @@ export default function NavMobile({ }: Props) {
const handleSearchClick = () => {
toggleDrawerOpen(false)
dispatch(toggleSearch())
};
return (
<div className={`${styles.navMobile} w-screen z-[2010]`}>
<nav className={`bg-white fixed top-0 left-0 h-[67px] w-full p-16 px-32 flex justify-between items-center z-[2010]`}>
{/* <div className="w-40 h-40 bg-gray-100 rounded-8 mr-auto">
<img className="w-full h-full object-cover" src="https://www.figma.com/file/OFowr5RJk9YZCW35KT7D5K/image/07b85d84145942255afd215b3da26dbbf1dd03bd?fuid=772401335362859303" alt="" />
</div> */}
<a href="https://bolt.fun/">
<img className='h-32' src={ASSETS.Logo} alt="Bolt fun logo" />
</a>
<div className="ml-auto"></div>
<motion.div
animate={searchOpen ? { opacity: 0 } : { opacity: 1 }}
className="flex"
>
{/* <IconButton className='ml-8 self-center' onClick={handleSearchClick}>
<BsSearch className="text-gray-400" />
</IconButton> */}
<IconButton className='auto text-2xl w-[50px] h-[50px] hover:bg-gray-200 self-center' onClick={() => toggleDrawerOpen()}>
{!drawerOpen ? (<motion.div key={drawerOpen ? 1 : 0} variants={navBtnVariant} initial='menuHide' animate='menuShow'><FiMenu /></motion.div>)
: (<motion.div key={drawerOpen ? 1 : 0} variants={navBtnVariant} initial='closeHide' animate='closeShow'><GrClose /></motion.div>)}
</IconButton>
</motion.div>
<Search width='calc(100vw - 64px)' />
<IconButton className='auto text-2xl w-[50px] h-[50px] hover:bg-gray-200 self-center' onClick={() => toggleDrawerOpen()}>
{!drawerOpen ? (<motion.div key={drawerOpen ? 1 : 0} variants={navBtnVariant} initial='menuHide' animate='menuShow'><FiMenu /></motion.div>)
: (<motion.div key={drawerOpen ? 1 : 0} variants={navBtnVariant} initial='closeHide' animate='closeShow'><GrClose /></motion.div>)}
</IconButton>
</nav>
<div className="fixed left-0 pointer-events-none z-[2010] w-full min-h-[calc(100vh-76px)]">
@@ -124,6 +94,7 @@ export default function NavMobile({ }: Props) {
animate={drawerOpen ? "show" : "hide"}
>
<div className="flex flex-col gap-16 py-16">
<Search onResultClick={() => toggleDrawerOpen(false)} />
<a
href="https://airtable.com/shr2VkxarNsIFilDz"
target="_blank"
@@ -132,28 +103,14 @@ export default function NavMobile({ }: Props) {
<Button
color="primary"
fullWidth
className="py-12 px-40 rounded-24 "
className="!py-16 px-40 rounded-12 "
>
Submit LApp
Get your product listed
</Button>
</a>
<Button
color='white'
fullWidth
className="py-12 px-40 rounded-24"
onClick={() => handleSearchClick()}
>
<BsSearch className='inline-block transform scale-125' />
<span className="align-middle"> Search Apps</span>
</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="px-32 flex flex-col py-16 gap-32 border-t">
{/* {navLinks.map((link, idx) => <li key={idx} className="text-body3 p-16 active:bg-gray-200">
<Link to={link.url} onClick={() => toggleDrawerOpen(false)}><link.icon className={`text-body2 inline-block mr-12 text-primary-600`} /> <span className="align-middle">{link.text}</span> </Link></li>
)} */}
<li className="relative">
<Link
to={'/products'}
@@ -198,7 +155,7 @@ export default function NavMobile({ }: Props) {
</p>
</div>
</Link>
<p
<div
className='font-medium flex gap-16 !rounded-12 opacity-60'
>
@@ -213,7 +170,7 @@ export default function NavMobile({ }: Props) {
Coming soon
</p>
</div>
</p>
</div>
<Link
to="/hackathons"
onClick={() => toggleDrawerOpen(false)}

View File

@@ -1,15 +1,16 @@
import React, { FormEvent, useEffect, useRef, useState } from 'react'
import React, { FormEvent, useRef, useState } from 'react'
import { motion } from 'framer-motion'
import { BsSearch } from 'react-icons/bs';
import { useClickOutside, useThrottledCallback } from '@react-hookz/web'
import { useClickOutside, useThrottledCallback, useUpdateEffect } from '@react-hookz/web'
import SearchResults from './SearchResults/SearchResults'
import { useAppDispatch, useAppSelector } from 'src/utils/hooks';
import { toggleSearch } from 'src/redux/features/ui.slice';
import { SearchProjectsQuery, useSearchProjectsLazyQuery } from 'src/graphql';
interface Props {
height?: number | string;
width?: number | string;
onClose?: () => void;
onResultClick?: () => void;
isOpen?: boolean;
}
export type ProjectSearchItem = SearchProjectsQuery['searchProjects'][number];
@@ -27,24 +28,26 @@ const SearchResultsListVariants = {
}
}
export default function Search({
export default function Search({
width,
height,
onClose,
onResultClick,
isOpen
}: Props) {
const inputRef = useRef<HTMLInputElement>(null);
const [searchInput, setSearchInput] = useState("");
const containerRef = useRef<HTMLDivElement>(null)
const { isOpen } = useAppSelector(state => ({
isOpen: state.ui.isSearchOpen
}))
const dispatch = useAppDispatch()
// const { isOpen } = useAppSelector(state => ({
// isOpen: state.ui.isSearchOpen
// }))
// const dispatch = useAppDispatch()
useClickOutside(containerRef, () => {
if (isOpen)
dispatch(toggleSearch(false))
onClose?.()
})
@@ -72,7 +75,7 @@ export default function Search({
// onSearch(searchInput);
};
useEffect(() => {
useUpdateEffect(() => {
if (isOpen)
inputRef.current?.focus();
else {
@@ -83,43 +86,27 @@ export default function Search({
return (
<div className="relative h-full" ref={containerRef}>
{<motion.form
initial={{
opacity: 0,
x: '100%'
}}
animate={isOpen ? {
opacity: 1,
x: '0',
transition: { type: "spring", stiffness: 70 }
} : {
opacity: 0,
x: '100%',
transition: {
ease: "easeIn"
}
}}
className='absolute top-0 right-0 flex items-center h-full'
<div className="relative z-20 h-full" ref={containerRef}>
{<form
className='flex items-center h-full'
onSubmit={handleSubmit}
style={{
width: width ?? '326px',
width: width ?? '100%',
height: height ?? '100%'
}}
>
<div className="input-wrapper bg-white !rounded-12 ring-1 ring-gray-400">
<BsSearch className={`input-icon`} />
<div className="input-wrapper border-0 !p-16 md:!py-12 !bg-gray-100 focus-within:!bg-gray-50 focus:ring-1 focus:ring-gray-300 !rounded-12">
<BsSearch className={`input-icon w-16 mr-10 !p-0`} />
<input
autoFocus
type='text'
ref={inputRef}
className="input-text placeholder-black pl-0"
className="input-text placeholder-black !p-0"
placeholder='Search'
value={searchInput}
onChange={handleChange}
onKeyDown={e => {
if (e.key === 'Escape') dispatch(toggleSearch(false))
if (e.key === 'Escape') onClose?.()
}}
/>
@@ -134,9 +121,10 @@ export default function Search({
<SearchResults
isLoading={loading}
projects={data?.searchProjects}
onResultClick={onResultClick}
/>
</motion.div>
</motion.form>}
</form>}
</div>
)
}

View File

@@ -1,7 +1,6 @@
import { openModal } from 'src/redux/features/modals.slice';
import { openProject } from 'src/redux/features/project.slice';
import { toggleSearch } from 'src/redux/features/ui.slice';
import { useAppDispatch } from 'src/utils/hooks';
import { ProjectSearchItem } from '../Search';
import SearchProjectCard from '../SearchProjectCard/SearchProjectCard';
@@ -10,14 +9,15 @@ import styles from './styles.module.css'
interface Props {
isLoading?: boolean;
projects: ProjectSearchItem[] | undefined,
onResultClick?: () => void
}
export default function SearchResults({ projects, isLoading }: Props) {
export default function SearchResults({ projects, isLoading, onResultClick }: Props) {
const dispatch = useAppDispatch();
const handleOpenProject = (projectId: number) => {
dispatch(toggleSearch(false))
onResultClick?.()
dispatch(openModal({ Modal: "ProjectDetailsCard", props: { projectId } }))
}

View File

@@ -37,9 +37,9 @@ export default function HackathonsPage() {
<SortByFilter
filterChanged={setSortByFilter}
/>
<TopicsFilter
{/* <TopicsFilter
filterChanged={setTopicsFilter}
/>
/> */}
<Button
href='https://airtable.com/some-registration-form'
newTab

View File

@@ -30,7 +30,7 @@ export default function BountyCard({ bounty }: Props) {
}
return (
<div className="bg-white rounded-12 overflow-hidden border">
<div className="bg-white rounded-12 overflow-hidden border-2">
<img src={bounty.cover_image} className='h-[200px] w-full object-cover bg-gray-100' alt="" />
<div className="p-24">
<Header author={bounty.author} date={bounty.createdAt} />

View File

@@ -30,7 +30,7 @@ interface Props {
}
export default function QuestionCard({ question }: Props) {
return (
<div className="bg-white rounded-12 overflow-hidden border">
<div className="bg-white rounded-12 overflow-hidden border-2">
{/* <img src={question.cover_image} className='h-[200px] w-full object-cover' alt="" /> */}
<div className="p-24">
<Header author={question.author} date={question.createdAt} />

View File

@@ -29,7 +29,7 @@ export default function StoryCard({ story }: Props) {
});
return (
<div className="bg-white rounded-12 overflow-hidden border">
<div className="bg-white rounded-12 overflow-hidden border-2">
<img src={story.cover_image} className='h-[200px] w-full object-cover' alt="" />
<div className="p-24">
<Header author={story.author} date={story.createdAt} />

View File

@@ -1,4 +1,5 @@
import { MdLocalFireDepartment } from "react-icons/md";
import { numberFormatter } from "src/utils/helperFunctions";
import { ProjectCard } from "src/utils/interfaces";
@@ -20,7 +21,7 @@ export default function ProjectCardMini({ project, onClick }: Props) {
<div className="justify-around items-start min-w-0">
<p className="text-body4 w-full font-bold overflow-ellipsis overflow-hidden whitespace-nowrap">{project.title}</p>
<p className="text-body5 text-gray-600 font-light my-[5px]">{project.category.title}</p>
<span className="chip-small bg-warning-50 text-yellow-700 font-light text-body5 py-[3px] px-10"> <MdLocalFireDepartment className='inline-block text-fire transform text-body4 align-middle' /> {project.votes_count} </span>
<span className="chip-small bg-warning-50 text-yellow-700 font-light text-body5 py-[3px] px-10"> <MdLocalFireDepartment className='inline-block text-fire transform text-body4 align-middle' /> {numberFormatter(project.votes_count)} </span>
</div>
</div>
);

View File

@@ -15,6 +15,7 @@ import Lightbox from 'src/Components/Lightbox/Lightbox'
import linkifyHtml from 'linkify-html';
import ErrorMessage from 'src/Components/ErrorMessage/ErrorMessage';
import { setVoteAmount } from 'src/redux/features/vote.slice';
import { numberFormatter } from 'src/utils/helperFunctions';
interface Props extends ModalCard {
@@ -114,7 +115,7 @@ export default function ProjectDetailsCard({ direction, projectId, ...props }: P
<div>
<span className="chip-small font-light text-body5 py-4 px-12 mr-8"> {project?.category.title}</span>
<span className="chip-small bg-warning-50 font-light text-body5 py-4 px-12"><MdLocalFireDepartment className='inline-block text-fire transform text-body4 align-middle' /> {project?.votes_count}</span>
<span className="chip-small bg-warning-50 font-light text-body5 py-4 px-12"><MdLocalFireDepartment className='inline-block text-fire transform text-body4 align-middle' /> {numberFormatter(project?.votes_count)}</span>
</div>
</div>

View File

@@ -3,13 +3,11 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface StoreState {
navHeight: number;
isMobileScreen: boolean;
isSearchOpen: boolean
}
const initialState = {
navHeight: 0,
isMobileScreen: /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent) || window.innerWidth < 480,
isSearchOpen: false,
} as StoreState;
@@ -24,12 +22,9 @@ export const uiSlice = createSlice({
setIsMobileScreen(state, action: PayloadAction<boolean>) {
state.isMobileScreen = action.payload;
},
toggleSearch(state, action: PayloadAction<boolean | undefined>) {
state.isSearchOpen = action.payload ?? !state.isSearchOpen;
}
},
});
export const { setNavHeight, setIsMobileScreen, toggleSearch } = uiSlice.actions;
export const { setNavHeight, setIsMobileScreen } = uiSlice.actions;
export default uiSlice.reducer;