move search bar to top of page (#433)

* move search bar to top of page

* move select inputs below search bar

* reduce complexity

* default search to zaprank

* votes => zaprank

---------

Co-authored-by: rleed <rleed1@pm.me>
Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
Co-authored-by: keyan <keyan.kousha+huumn@gmail.com>
This commit is contained in:
rleed 2023-09-11 19:11:47 -04:00 committed by GitHub
parent 2ba969ebab
commit 1969d82741
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 83 additions and 133 deletions

View File

@ -146,7 +146,7 @@ const orderByClause = async (by, me, models, type) => {
return 'ORDER BY "Item".ncomments DESC' return 'ORDER BY "Item".ncomments DESC'
case 'sats': case 'sats':
return 'ORDER BY "Item".msats DESC' return 'ORDER BY "Item".msats DESC'
case 'votes': case 'zaprank':
return await topOrderByWeightedSats(me, models) return await topOrderByWeightedSats(me, models)
default: default:
return `ORDER BY ${type === 'bookmarks' ? '"bookmarkCreatedAt"' : '"Item".created_at'} DESC` return `ORDER BY ${type === 'bookmarks' ? '"bookmarkCreatedAt"' : '"Item".created_at'} DESC`
@ -410,10 +410,10 @@ export default {
${typeClause(type)} ${typeClause(type)}
${whenClause(when, type)} ${whenClause(when, type)}
${await filterClause(me, models, type)} ${await filterClause(me, models, type)}
${await orderByClause(by || 'votes', me, models, type)} ${await orderByClause(by || 'zaprank', me, models, type)}
OFFSET $2 OFFSET $2
LIMIT $3`, LIMIT $3`,
orderBy: await orderByClause(by || 'votes', me, models, type) orderBy: await orderByClause(by || 'zaprank', me, models, type)
}, decodedCursor.time, decodedCursor.offset, limit, ...subArr) }, decodedCursor.time, decodedCursor.offset, limit, ...subArr)
break break
default: default:

View File

@ -131,10 +131,10 @@ export default {
case 'sats': case 'sats':
sortArr.push({ sats: 'desc' }) sortArr.push({ sats: 'desc' })
break break
case 'votes': case 'match':
sortArr.push({ wvotes: 'desc' })
break break
default: default:
sortArr.push({ wvotes: 'desc' })
break break
} }
sortArr.push('_score') sortArr.push('_score')

View File

@ -27,10 +27,12 @@ export default function Layout ({
export function SearchLayout ({ sub, children, ...props }) { export function SearchLayout ({ sub, children, ...props }) {
return ( return (
<Layout sub={sub} seo={false} footer={false} {...props}> <Layout sub={sub} seo={false} contain={false} {...props}>
<SeoSearch sub={sub} /> <SeoSearch sub={sub} />
<Search sub={sub} />
<Container as='main' className='py-4 px-sm-0'>
{children} {children}
<Search /> </Container>
</Layout> </Layout>
) )
} }

View File

@ -1,27 +1,17 @@
import Button from 'react-bootstrap/Button'
import Container from 'react-bootstrap/Container' import Container from 'react-bootstrap/Container'
import styles from './search.module.css' import styles from './search.module.css'
import SearchIcon from '../svgs/search-line.svg' import SearchIcon from '../svgs/search-line.svg'
import CloseIcon from '../svgs/close-line.svg' import { useEffect, useRef, useState } from 'react'
import { useEffect, useState } from 'react'
import { Form, Input, Select, SubmitButton } from './form' import { Form, Input, Select, SubmitButton } from './form'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
export default function Search ({ sub }) { export default function Search ({ sub }) {
const router = useRouter() const router = useRouter()
const [searching, setSearching] = useState(router.query.q)
const [q, setQ] = useState(router.query.q || '') const [q, setQ] = useState(router.query.q || '')
const [atBottom, setAtBottom] = useState() const inputRef = useRef(null)
useEffect(() => { useEffect(() => {
setAtBottom(Math.ceil(window.innerHeight + window.pageYOffset) >= document.body.offsetHeight) inputRef.current?.focus()
window.onscroll = function (ev) {
if (Math.ceil(window.innerHeight + window.pageYOffset) >= document.body.offsetHeight) {
setAtBottom(true)
} else {
setAtBottom(false)
}
}
}, []) }, [])
const search = async values => { const search = async values => {
@ -43,7 +33,7 @@ export default function Search ({ sub }) {
} }
if (values.what === '' || values.what === 'all') delete values.what if (values.what === '' || values.what === 'all') delete values.what
if (values.sort === '' || values.sort === 'match') delete values.sort if (values.sort === '' || values.sort === 'zaprank') delete values.sort
if (values.when === '' || values.when === 'forever') delete values.when if (values.when === '' || values.when === 'forever') delete values.when
await router.push({ await router.push({
pathname: prefix + '/search', pathname: prefix + '/search',
@ -52,25 +42,39 @@ export default function Search ({ sub }) {
} }
} }
const showSearch = atBottom || searching || router.query.q
const filter = sub !== 'jobs' const filter = sub !== 'jobs'
const what = router.pathname.startsWith('/stackers') ? 'stackers' : router.query.what || 'all' const what = router.pathname.startsWith('/stackers') ? 'stackers' : router.query.what || 'all'
const sort = router.query.sort || 'match' const sort = router.query.sort || 'zaprank'
const when = router.query.when || 'forever' const when = router.query.when || 'forever'
return ( return (
<> <>
<div className={`${styles.searchSection} ${showSearch ? styles.solid : styles.hidden}`}> <div className={styles.searchSection}>
<Container className={`px-md-0 ${styles.searchContainer} ${filter ? styles.leaveRoom : ''}`}> <Container className={`px-md-0 ${styles.searchContainer} ${filter ? styles.leaveRoom : ''}`}>
{showSearch
? (
<Form <Form
className={styles.formActive}
initial={{ q, what, sort, when }} initial={{ q, what, sort, when }}
onSubmit={search} onSubmit={search}
> >
<div className={`${styles.active} my-3`}>
<Input
name='q'
required
autoFocus
groupClassName='me-3 mb-0 flex-grow-1'
className='flex-grow-1'
clear
innerRef={inputRef}
overrideValue={q}
onChange={async (formik, e) => {
setQ(e.target.value?.trim())
}}
/>
<SubmitButton variant='primary' className={styles.search}>
<SearchIcon width={22} height={22} />
</SubmitButton>
</div>
{filter && {filter &&
<div className='text-muted fw-bold my-3 d-flex align-items-center'> <div className='text-muted fw-bold d-flex align-items-center'>
<Select <Select
groupClassName='me-2 mb-0' groupClassName='me-2 mb-0'
onChange={(formik, e) => search({ ...formik?.values, what: e.target.value })} onChange={(formik, e) => search({ ...formik?.values, what: e.target.value })}
@ -88,7 +92,7 @@ export default function Search ({ sub }) {
name='sort' name='sort'
size='sm' size='sm'
overrideValue={sort} overrideValue={sort}
items={['match', 'recent', 'comments', 'sats', 'votes']} items={['zaprank', 'match', 'recent', 'comments', 'sats']}
/> />
for for
<Select <Select
@ -102,46 +106,9 @@ export default function Search ({ sub }) {
</>} </>}
</div>} </div>}
<div className={styles.active}>
<Input
name='q'
required
autoFocus
groupClassName='me-3 mb-0 flex-grow-1'
className='flex-grow-1'
clear
overrideValue={q}
onChange={async (formik, e) => {
setSearching(true)
setQ(e.target.value?.trim())
}}
/>
{q || atBottom || router.query.q
? (
<SubmitButton variant='primary' className={styles.search}>
<SearchIcon width={22} height={22} />
</SubmitButton>
)
: (
<Button
className={styles.search} onClick={() => {
setSearching(false)
}}
>
<CloseIcon width={26} height={26} />
</Button>)}
</div>
</Form> </Form>
)
: (
<Button className={`${styles.search} ${styles.formActive}`} onClick={() => setSearching(true)}>
<SearchIcon width={22} height={22} />
</Button>
)}
</Container> </Container>
</div> </div>
<div className={`${styles.searchPadding} ${filter ? styles.leaveRoom : ''}`} />
</> </>
) )
} }

View File

@ -1,8 +1,9 @@
.searchSection { .searchSection {
position: fixed; position: sticky;
bottom: 0; top: 0;
left: 0; box-shadow: 0 4px 12px -4px hsl(0deg 0% 59% / 10%);
right: 0; background-color: var(--bs-body-bg);
z-index: 1;
} }
.searchContainer { .searchContainer {
@ -14,17 +15,6 @@
height: 130px !important; height: 130px !important;
} }
.searchSection.solid {
pointer-events: auto;
background: var(--bs-body-bg);
box-shadow: 0 -4px 12px hsl(0deg 0% 59% / 10%);
}
.searchSection.hidden {
pointer-events: none;
background: transparent;
}
.search { .search {
width: 50px; width: 50px;
height: 50px; height: 50px;
@ -36,21 +26,13 @@
left: auto !important; left: auto !important;
} }
.formActive {
pointer-events: auto;
bottom: 18px;
right: 18px;
left: 18px;
position: absolute;
}
form>.active { form>.active {
display: flex; display: flex;
pointer-events: auto; pointer-events: auto;
flex-flow: row wrap; flex-flow: row nowrap;
align-items: center; align-items: center;
} }
.searchPadding { form>.active :global(.input-group) {
padding-bottom: 88px; flex-flow: nowrap;
} }

View File

@ -20,7 +20,7 @@ export default function TopHeader ({ sub, cat }) {
if (typeof query.by !== 'undefined') { if (typeof query.by !== 'undefined') {
if (query.by === '' || if (query.by === '' ||
(what === 'stackers' && (query.by === 'stacked' || !USER_SORTS.includes(query.by))) || (what === 'stackers' && (query.by === 'stacked' || !USER_SORTS.includes(query.by))) ||
(what !== 'stackers' && (query.by === 'votes' || !ITEM_SORTS.includes(query.by)))) { (what !== 'stackers' && (query.by === 'zaprank' || !ITEM_SORTS.includes(query.by)))) {
delete query.by delete query.by
} }
} }
@ -32,7 +32,7 @@ export default function TopHeader ({ sub, cat }) {
} }
const what = cat const what = cat
const by = router.query.by || (what === 'stackers' ? 'stacked' : 'votes') const by = router.query.by || (what === 'stackers' ? 'stacked' : 'zaprank')
const when = router.query.when || '' const when = router.query.when || ''
return ( return (

View File

@ -33,7 +33,7 @@ module.exports = {
SUBS, SUBS,
SUBS_NO_JOBS, SUBS_NO_JOBS,
USER_SORTS: ['stacked', 'spent', 'comments', 'posts', 'referrals'], USER_SORTS: ['stacked', 'spent', 'comments', 'posts', 'referrals'],
ITEM_SORTS: ['votes', 'comments', 'sats'], ITEM_SORTS: ['zaprank', 'comments', 'sats'],
WHENS: ['day', 'week', 'month', 'year', 'forever'], WHENS: ['day', 'week', 'month', 'year', 'forever'],
ITEM_TYPES: context => { ITEM_TYPES: context => {
const items = ['all', 'posts', 'comments', 'bounties', 'links', 'discussions', 'polls'] const items = ['all', 'posts', 'comments', 'bounties', 'links', 'discussions', 'polls']

View File

@ -2,7 +2,6 @@ import { SearchLayout } from '../../components/layout'
import { getGetServerSideProps } from '../../api/ssrApollo' import { getGetServerSideProps } from '../../api/ssrApollo'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { SUB_SEARCH } from '../../fragments/subs' import { SUB_SEARCH } from '../../fragments/subs'
import Down from '../../svgs/arrow-down-line.svg'
import Items from '../../components/items' import Items from '../../components/items'
export const getServerSideProps = getGetServerSideProps({ export const getServerSideProps = getGetServerSideProps({
@ -26,7 +25,7 @@ export default function Index ({ ssrData }) {
/> />
: ( : (
<div className='text-muted text-center mt-5' style={{ fontFamily: 'lightning', fontSize: '2rem', opacity: '0.75' }}> <div className='text-muted text-center mt-5' style={{ fontFamily: 'lightning', fontSize: '2rem', opacity: '0.75' }}>
<Down width={22} height={22} className='me-2' />search for something<Down width={22} height={22} className='ms-2' /> search for something
</div>)} </div>)}
</SearchLayout> </SearchLayout>
) )