major refactor in navbar and searchbar wip
This commit is contained in:
parent
e704b36038
commit
4d27bc6a96
|
@ -1,46 +1,57 @@
|
|||
import { Typography } from '@acid-info/lsd-react'
|
||||
import styled from '@emotion/styled'
|
||||
import { uiConfigs } from '@/configs/ui.configs'
|
||||
import { NavbarFiller } from '@/components/Navbar/NavbarFiller'
|
||||
import { Searchbar } from '@/components/Searchbar'
|
||||
|
||||
export default function Hero() {
|
||||
return (
|
||||
<Container>
|
||||
<Title genericFontFamily="serif" component="h1" variant="h1">
|
||||
<span>LOGOS</span>
|
||||
<span> → </span>
|
||||
<span>PRESS ENGINE</span>
|
||||
</Title>
|
||||
<Description component="div" variant="body1">
|
||||
Blog with media written by Logos members
|
||||
</Description>
|
||||
<HeroText>
|
||||
<Title genericFontFamily="serif" component="h1" variant="h1">
|
||||
<span>LOGOS</span>
|
||||
<span> → </span>
|
||||
<span>PRESS ENGINE</span>
|
||||
</Title>
|
||||
<Description component="div" variant="body1">
|
||||
Blog with media written by Logos members
|
||||
</Description>
|
||||
</HeroText>
|
||||
<NavbarFiller />
|
||||
<Searchbar className={'desktop'} />
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
border-bottom: 1px solid rgb(var(--lsd-theme-primary));
|
||||
@media (max-width: 768px) {
|
||||
.desktop {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const HeroText = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16px 16px 8px 16px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
align-items: flex-start;
|
||||
text-align: left;
|
||||
gap: 6px;
|
||||
}
|
||||
`
|
||||
|
||||
const Title = styled(Typography)`
|
||||
@media (min-width: 1440px) {
|
||||
font-size: 90px;
|
||||
line-height: 98px;
|
||||
font-size: var(--lsd-display1-fontSize);
|
||||
line-height: var(--lsd-display1-lineHeight);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
font-size: 36px;
|
||||
line-height: 44px;
|
||||
> span:last-of-type {
|
||||
display: block;
|
||||
}
|
||||
font-size: var(--lsd-h4-fontSize);
|
||||
line-height: var(--lsd-h4-lineHeight);
|
||||
}
|
||||
`
|
||||
|
||||
|
|
|
@ -1,38 +1,149 @@
|
|||
import styled from '@emotion/styled'
|
||||
import { IconButton } from '@acid-info/lsd-react'
|
||||
import { CloseIcon, IconButton, SearchIcon } from '@acid-info/lsd-react'
|
||||
import { LogosIcon } from '../Icons/LogosIcon'
|
||||
import { SunIcon } from '../Icons/SunIcon'
|
||||
import { MoonIcon } from '../Icons/MoonIcon'
|
||||
import { uiConfigs } from '@/configs/ui.configs'
|
||||
import Link from 'next/link'
|
||||
import styles from '@/components/Searchbar/Search.module.css'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Searchbar } from '@/components/Searchbar'
|
||||
import { useScrollDirection } from '@/utils/ui.utils'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useSearchBarContext } from '@/context/searchbar.context'
|
||||
|
||||
interface NavbarProps {
|
||||
isDark: boolean
|
||||
toggle: () => void
|
||||
onSearch?: (query: string, tags: string[]) => void
|
||||
onReset?: () => void
|
||||
}
|
||||
|
||||
export default function Navbar({ isDark, toggle }: NavbarProps) {
|
||||
export default function Navbar({
|
||||
isDark,
|
||||
toggle,
|
||||
onReset,
|
||||
onSearch,
|
||||
}: NavbarProps) {
|
||||
const { resultsNumber } = useSearchBarContext()
|
||||
const { pathname } = useRouter()
|
||||
const isSearchPage = pathname === '/search'
|
||||
|
||||
const [hideSearch, setHideSearch] = useState(
|
||||
resultsNumber === null && !isSearchPage,
|
||||
)
|
||||
const [hide, setHide] = useState(false)
|
||||
const scrollDirection = useScrollDirection()
|
||||
const onSearchIconClick = () => {
|
||||
setHideSearch(!hideSearch)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (scrollDirection) {
|
||||
setHide(scrollDirection === 'down')
|
||||
if (!hideSearch && resultsNumber === null) {
|
||||
setHideSearch(scrollDirection === 'down')
|
||||
}
|
||||
}
|
||||
setHideSearch(resultsNumber === null)
|
||||
}, [scrollDirection, resultsNumber])
|
||||
|
||||
const className = pathname.split('/')[1] + '_page'
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<LogosIconContainer href={'/'}>
|
||||
<LogosIcon color="primary" />
|
||||
</LogosIconContainer>
|
||||
<Icons>
|
||||
<IconButton size="small" onClick={() => toggle()}>
|
||||
{isDark ? <SunIcon color="primary" /> : <MoonIcon color="primary" />}
|
||||
</IconButton>
|
||||
</Icons>
|
||||
<Container className={`${hide ? 'hide' : ''} ${className}`}>
|
||||
<AppBar>
|
||||
<LogosIconContainer href={'/'}>
|
||||
<LogosIcon color="primary" />
|
||||
</LogosIconContainer>
|
||||
<Icons>
|
||||
<IconButton size="small" onClick={() => toggle()}>
|
||||
{isDark ? (
|
||||
<SunIcon color="primary" />
|
||||
) : (
|
||||
<MoonIcon color="primary" />
|
||||
)}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
className={'searchIcon searchIconHome'}
|
||||
size="small"
|
||||
onClick={() => onSearchIconClick()}
|
||||
>
|
||||
<SearchIcon />
|
||||
</IconButton>
|
||||
</Icons>
|
||||
</AppBar>
|
||||
<MobileSearchContainer
|
||||
className={`searchBar ${hideSearch ? 'hide' : ''}`}
|
||||
>
|
||||
<Searchbar
|
||||
className={'mobile'}
|
||||
onSearch={onSearch}
|
||||
onReset={onReset}
|
||||
withFilterTags={isSearchPage}
|
||||
/>
|
||||
</MobileSearchContainer>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.nav`
|
||||
const Container = styled.div`
|
||||
width: 100%;
|
||||
transition: top 0.2s;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 101;
|
||||
|
||||
&._page {
|
||||
@media (min-width: 768px) {
|
||||
.searchBar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.searchIconHome {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.article_page,
|
||||
&.search_page {
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
&.hide {
|
||||
top: -44px;
|
||||
}
|
||||
}
|
||||
|
||||
> * {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
`
|
||||
|
||||
const MobileSearchContainer = styled.div`
|
||||
width: 100%;
|
||||
transition: transform 0.3s ease-in-out;
|
||||
z-index: 98;
|
||||
transform: translateY(44px);
|
||||
|
||||
@media (max-width: 768px) {
|
||||
display: block;
|
||||
&.hide {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const AppBar = styled.nav`
|
||||
display: flex;
|
||||
padding: 8px 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-bottom: 1px solid rgb(var(--lsd-theme-primary));
|
||||
position: fixed;
|
||||
//position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
|
@ -78,6 +189,20 @@ const Icons = styled.div`
|
|||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
|
||||
> *:last-of-type {
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
.searchIcon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.searchIcon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const Selector = styled(IconButton)`
|
||||
|
|
|
@ -2,6 +2,19 @@
|
|||
width: 100% !important;
|
||||
}
|
||||
|
||||
.searchInput{
|
||||
font-size: 14px;
|
||||
transition: font-size 150ms ease-in-out;
|
||||
}
|
||||
.searchInput.active{
|
||||
font-size: 28px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.searchInput.active{
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.searchButton{
|
||||
border: none !important;
|
||||
}
|
||||
|
|
|
@ -29,10 +29,18 @@ export type SearchbarProps = {
|
|||
className?: string
|
||||
onSearch?: (query: string, filterTags: string[]) => void
|
||||
onReset?: () => void
|
||||
withFilterTags?: boolean
|
||||
beSticky?: boolean
|
||||
}
|
||||
|
||||
export default function Searchbar(props: SearchbarProps) {
|
||||
const { onSearch, onReset } = props
|
||||
const {
|
||||
onSearch,
|
||||
beSticky,
|
||||
onReset,
|
||||
className,
|
||||
withFilterTags = true,
|
||||
} = props
|
||||
const { resultsNumber, resultsHelperText, tags } = useSearchBarContext()
|
||||
|
||||
const [placeholder, setPlaceholder] = useState<string>()
|
||||
|
@ -135,12 +143,12 @@ export default function Searchbar(props: SearchbarProps) {
|
|||
}, [active, searchScope, query.length])
|
||||
|
||||
const showResultsNumber = resultsNumber !== null && active
|
||||
const renderTagFilters = tags.length > 0 && !isArticlePage
|
||||
const showTagFilters = renderTagFilters && active
|
||||
const showTagFilters = withFilterTags && active
|
||||
|
||||
let height = 45
|
||||
if (active) {
|
||||
height += 10
|
||||
height +=
|
||||
typeof window !== 'undefined' ? (window.innerWidth > 768 ? 10 : 0) : 0
|
||||
}
|
||||
if (showResultsNumber) {
|
||||
height += isArticlePage ? 22 : 26
|
||||
|
@ -151,6 +159,7 @@ export default function Searchbar(props: SearchbarProps) {
|
|||
|
||||
return (
|
||||
<SearchbarContainer
|
||||
className={className}
|
||||
onUnfocus={() => {
|
||||
setActive(false)
|
||||
if (router.query.query && router.query.query.length > 0) {
|
||||
|
@ -161,6 +170,7 @@ export default function Searchbar(props: SearchbarProps) {
|
|||
transition: 'height 150ms ease-in-out',
|
||||
height: active ? `${height}px` : 'auto',
|
||||
}}
|
||||
beSticky={beSticky}
|
||||
>
|
||||
<SearchBox>
|
||||
<TextField
|
||||
|
@ -177,10 +187,7 @@ export default function Searchbar(props: SearchbarProps) {
|
|||
// height: active ? '56px' : 'auto',
|
||||
}}
|
||||
inputProps={{
|
||||
style: {
|
||||
fontSize: active ? '28px' : '14px',
|
||||
transition: 'font-size 150ms ease-in-out',
|
||||
},
|
||||
className: `${styles.searchInput} ${active ? styles.active : ''}`,
|
||||
}}
|
||||
/>
|
||||
{searchScope === ESearchScope.ARTICLE && (
|
||||
|
@ -204,7 +211,7 @@ export default function Searchbar(props: SearchbarProps) {
|
|||
</IconButton>
|
||||
</div>
|
||||
</SearchBox>
|
||||
{renderTagFilters && (
|
||||
{withFilterTags && (
|
||||
<TagsWrapper className={showTagFilters ? 'active' : ''}>
|
||||
<FilterTags
|
||||
tags={tags}
|
||||
|
@ -223,9 +230,10 @@ export default function Searchbar(props: SearchbarProps) {
|
|||
onClick={() => setActive(true)}
|
||||
variant={'subtitle2'}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: [`${resultsNumber} matches`, resultsHelperText].join(
|
||||
'<span class="dot">.</span>',
|
||||
),
|
||||
__html: [
|
||||
`${resultsNumber} matches`,
|
||||
`<span class="helper">${resultsHelperText}<span>`,
|
||||
].join('<span class="dot">.</span>'),
|
||||
}}
|
||||
/>
|
||||
</SearchbarContainer>
|
||||
|
@ -241,6 +249,11 @@ const TagsWrapper = styled.div`
|
|||
margin-top: 19px;
|
||||
height: 24px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
&.active {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const ResultsStatus = styled.div`
|
||||
|
@ -298,6 +311,21 @@ const Collapsed = styled(Typography)`
|
|||
b {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.helper,
|
||||
.tags {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.helper {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
width: 200px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
`
|
||||
const SearchBox = styled.div`
|
||||
display: flex;
|
||||
|
|
|
@ -9,12 +9,16 @@ import { useRouter } from 'next/router'
|
|||
type Props = PropsWithChildren<{
|
||||
onUnfocus?: () => void
|
||||
style?: any
|
||||
className?: string
|
||||
beSticky?: boolean
|
||||
}>
|
||||
|
||||
export function SearchbarContainer({
|
||||
children,
|
||||
onUnfocus = nope,
|
||||
style = {},
|
||||
className,
|
||||
beSticky = false,
|
||||
}: Props) {
|
||||
const { pathname } = useRouter()
|
||||
const isSearchPage = pathname === '/search'
|
||||
|
@ -40,7 +44,7 @@ export function SearchbarContainer({
|
|||
<SearchBarWrapper
|
||||
style={style}
|
||||
ref={stickyRef}
|
||||
className={sticky || isSearchPage || isArticlePage ? 'sticky' : ''}
|
||||
className={`${className} ${beSticky && sticky ? 'sticky' : ''}`}
|
||||
>
|
||||
{children}
|
||||
</SearchBarWrapper>
|
||||
|
@ -59,10 +63,7 @@ const SearchBarWrapper = styled.div<Props>`
|
|||
width: 100%;
|
||||
|
||||
background: rgb(var(--lsd-surface-primary));
|
||||
//height: 44px;
|
||||
|
||||
border-bottom: 1px solid rgb(var(--lsd-border-primary));
|
||||
//border-top: 1px solid rgb(var(--lsd-border-primary));
|
||||
transition: top 0.2s ease-in-out;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
export const uiConfigs = {
|
||||
navbarRenderedHeight: 45,
|
||||
postSectionMargin: 108,
|
||||
postSectionMobileMargin: 78,
|
||||
postSectionMobileMargin: 48,
|
||||
articleSectionMargin: 40,
|
||||
maxContainerWidth: 1440,
|
||||
articleRenderedMT: 45 * 2,
|
||||
|
|
|
@ -37,7 +37,6 @@ export const SearchBarProvider = ({ children }: any) => {
|
|||
}, [router])
|
||||
|
||||
const resetResults = () => {
|
||||
console.log('resetting results')
|
||||
setResultsNumber(null)
|
||||
setResultsHelperText(null)
|
||||
}
|
||||
|
|
|
@ -19,9 +19,9 @@ export default function ArticleLayout({ children }: Props) {
|
|||
return (
|
||||
<>
|
||||
<header className={styles.header}>
|
||||
<Navbar isDark={isDarkState.get()} toggle={isDarkState.toggle} />
|
||||
<Searchbar
|
||||
searchScope={ESearchScope.ARTICLE}
|
||||
<Navbar
|
||||
isDark={isDarkState.get()}
|
||||
toggle={isDarkState.toggle}
|
||||
onSearch={onSearch}
|
||||
onReset={onReset}
|
||||
/>
|
||||
|
|
|
@ -22,19 +22,18 @@ export default function DefaultLayout(props: PropsWithChildren<any>) {
|
|||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
borderBottom: `1px solid rgb(${
|
||||
isDarkState.get()
|
||||
? defaultThemes.dark.palette.border.primary
|
||||
: defaultThemes.light.palette.border.primary
|
||||
})`,
|
||||
}}
|
||||
// style={{
|
||||
// borderBottom: `1px solid rgb(${
|
||||
// isDarkState.get()
|
||||
// ? defaultThemes.dark.palette.border.primary
|
||||
// : defaultThemes.light.palette.border.primary
|
||||
// })`,
|
||||
// }}
|
||||
>
|
||||
<Navbar isDark={isDarkState.get()} toggle={isDarkState.toggle} />
|
||||
<Hero />
|
||||
<NavbarFiller />
|
||||
</div>
|
||||
<Searchbar />
|
||||
<Searchbar withFilterTags={false} />
|
||||
</header>
|
||||
<Main>{props.children}</Main>
|
||||
<Footer />
|
||||
|
|
|
@ -15,9 +15,6 @@ export default function SearchLayout(props: PropsWithChildren<any>) {
|
|||
<>
|
||||
<header className={styles.header}>
|
||||
<Navbar isDark={isDarkState.get()} toggle={isDarkState.toggle} />
|
||||
{/*<NavbarFiller />*/}
|
||||
<div style={{ height: `${uiConfigs.navbarRenderedHeight - 2}px` }} />
|
||||
<Searchbar searchScope={ESearchScope.ARTICLE} />
|
||||
</header>
|
||||
<Main>{props.children}</Main>
|
||||
<Footer />
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { MutableRefObject, RefObject, useEffect, useRef, useState } from 'react'
|
||||
import React, {
|
||||
MutableRefObject,
|
||||
RefObject,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
|
||||
export const useSticky = <T extends HTMLElement>(dy: number = 0) => {
|
||||
const stickyRef = useRef<T>(null)
|
||||
|
@ -124,4 +130,25 @@ export function useWindowSize() {
|
|||
}
|
||||
}
|
||||
|
||||
export const useScrollDirection = () => {
|
||||
const [scrollDirection, setScrollDirection] = useState<string | null>(null)
|
||||
const [lastScrollTop, setLastScrollTop] = useState(0)
|
||||
|
||||
const handleScroll = () => {
|
||||
const scrollTop = window.pageYOffset || document.documentElement.scrollTop
|
||||
const scrollDirection = scrollTop > lastScrollTop ? 'down' : 'up'
|
||||
|
||||
setLastScrollTop(scrollTop)
|
||||
setScrollDirection(scrollDirection)
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
window.addEventListener('scroll', handleScroll, { passive: true })
|
||||
|
||||
return () => window.removeEventListener('scroll', handleScroll)
|
||||
}, [lastScrollTop])
|
||||
|
||||
return scrollDirection
|
||||
}
|
||||
|
||||
export default useWindowSize
|
||||
|
|
Loading…
Reference in New Issue