feat: implement new hero compoent

This commit is contained in:
Hossein Mehrabi 2023-08-22 17:16:05 +03:30
parent 9c1600c36d
commit 0e18e1822c
No known key found for this signature in database
GPG Key ID: 45C04964191AFAA1
14 changed files with 208 additions and 81 deletions

View File

@ -52,6 +52,7 @@
"react-dom": "18.2.0",
"react-imgix": "^9.7.0",
"react-player": "^2.12.0",
"react-use": "^17.4.0",
"typescript": "5.0.4"
},
"devDependencies": {

View File

@ -1,22 +1,23 @@
import { Tag } from '@acid-info/lsd-react'
import styled from '@emotion/styled'
import { nope } from '@/utils/general.utils'
import { Tag, TagProps } from '@acid-info/lsd-react'
import styled from '@emotion/styled'
type FilterTagsProps = {
tags: string[]
selectedTags: string[]
size?: TagProps['size']
onTagClick?: (tag: string) => void
}
export default function FilterTags(props: FilterTagsProps) {
const { tags = [], onTagClick = nope, selectedTags } = props
const { size = 'small', tags = [], onTagClick = nope, selectedTags } = props
return (
<Container>
<Tags>
{tags.map((tag, index) => (
<Tag
size="small"
size={size}
disabled={false}
key={index}
onClick={() => onTagClick(tag)}
@ -39,6 +40,7 @@ const Tags = styled.div`
gap: 8px;
overflow-x: auto;
padding-right: 14px;
flex-wrap: wrap;
min-height: 24px;
@ -55,13 +57,17 @@ const Tags = styled.div`
> * {
white-space: nowrap;
overflow: hidden;
max-width: 200px;
span {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
}
@media (max-width: 768px) {
> *:first-child {
margin-left: 16px;
}
padding-right: 16px;
@media (max-width: ${(props) => props.theme.breakpoints.md.width}px) {
justify-content: center;
}
`

View File

@ -1,67 +1,83 @@
import { NavbarFiller } from '@/components/NavBar/NavbarFiller'
import { Typography } from '@acid-info/lsd-react'
import styled from '@emotion/styled'
import { useEffect, useRef } from 'react'
import { useWindowScroll } from 'react-use'
import { uiConfigs } from '../../configs/ui.configs'
import { useNavbarState } from '../../states/navbarState'
export type HeroProps = Partial<React.ComponentProps<typeof Container>> & {
tags?: string[]
}
export const Hero: React.FC<HeroProps> = ({ tags = [], ...props }) => {
const ref = useRef<HTMLElement>(null)
const scroll = useWindowScroll()
const navbarState = useNavbarState()
useEffect(() => {
const el = ref.current
if (!el) return
const rect = el.getBoundingClientRect()
if (rect.bottom - uiConfigs.navbarRenderedHeight > 0) {
navbarState.showTitle.get() && navbarState.setShowTitle(false)
} else {
!navbarState.showTitle.get() && navbarState.setShowTitle(true)
}
}, [scroll.y, navbarState])
export default function Hero() {
return (
<Container>
<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 />
<Container {...props}>
<Title genericFontFamily="serif" component="h1" variant="display2">
<span>LOGOS</span>
<span> </span>
<span ref={ref}>PRESS ENGINE</span>
</Title>
<Description component="div" variant="subtitle1">
Your Guide to Network States and the technology driving Sovereign
Communities
</Description>
<NavbarFiller tags={tags} className="navbar__filter" />
</Container>
)
}
const Container = styled.div`
border-bottom: 1px solid rgb(var(--lsd-theme-primary));
@media (max-width: 768px) {
.desktop {
display: none;
}
h1 {
min-height: 32px;
}
}
@media (min-width: 768px) {
h1 {
min-height: 98px;
}
}
`
const HeroText = styled.div`
display: flex;
flex-direction: column;
gap: 8px 0;
align-items: center;
padding: 16px 16px 8px 16px;
@media (max-width: 768px) {
gap: 6px;
padding: 24px 16px 40px 16px;
border-bottom: 1px solid rgb(var(--lsd-border-primary));
.navbar__filter {
margin-top: 24px;
}
@media (max-width: ${(props) => props.theme.breakpoints.md.width}px) {
padding: 8px 16px 16px;
}
`
const Title = styled(Typography)`
@media (min-width: 1024px) {
font-size: var(--lsd-display1-fontSize);
line-height: var(--lsd-display1-lineHeight);
}
@media (max-width: 768px) {
font-size: var(--lsd-h4-fontSize);
line-height: var(--lsd-h4-lineHeight);
text-align: center;
@media (max-width: ${(props) => props.theme.breakpoints.md.width}px) {
font-size: var(--lsd-h4-fontSize) !important;
font-weight: var(--lsd-h4-fontWeight) !important;
line-height: var(--lsd-h4-lineHeight) !important;
}
`
const Description = styled(Typography)`
@media (max-width: 768px) {
font-size: 12px;
line-height: 16px;
text-align: center;
max-width: 407px;
@media (max-width: ${(props) => props.theme.breakpoints.md.width}px) {
font-size: 12px !important;
font-weight: 400 !important;
line-height: 16px !important;
}
`

View File

@ -1 +1 @@
export { default as Hero } from './Hero'
export * from './Hero'

View File

@ -1,13 +1,25 @@
import styled from '@emotion/styled'
import { uiConfigs } from '@/configs/ui.configs'
import { PropsWithChildren } from 'react'
import styled from '@emotion/styled'
const Main = ({ children, ...props }: PropsWithChildren<any>) => {
return <Container {...props}>{children}</Container>
export type MainProps = Partial<React.ComponentProps<typeof Container>> & {}
export const Main = ({
spacing = 'default',
children,
...props
}: MainProps) => {
return (
<Container spacing={spacing} {...props}>
{children}
</Container>
)
}
const Container = styled.main`
margin-top: ${uiConfigs.postSectionMargin}px;
const Container = styled.main<{
spacing: 'default' | false
}>`
margin-top: ${({ spacing }) =>
spacing ? uiConfigs.postSectionMargin : uiConfigs.navbarRenderedHeight}px;
margin-left: auto;
margin-right: auto;
@ -16,7 +28,10 @@ const Container = styled.main`
}
@media (max-width: 768px) {
margin-top: ${uiConfigs.postSectionMobileMargin}px;
margin-top: ${({ spacing }) =>
spacing
? uiConfigs.postSectionMobileMargin
: uiConfigs.navbarRenderedHeight}px;
}
`

View File

@ -9,14 +9,18 @@ import { IconButton, MenuIcon, Typography } from '@acid-info/lsd-react'
import styled from '@emotion/styled'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useState } from 'react'
import { useEffect, useState } from 'react'
import { NavbarState, useNavbarState } from '../../states/navbarState'
import { LogosIcon } from '../Icons/LogosIcon'
export interface NavBarProps {
showLogoType?: boolean
defaultState?: Partial<NavbarState>
}
export default function NavBar({ showLogoType = true }: NavBarProps) {
export default function NavBar({
defaultState = { showTitle: true },
}: NavBarProps) {
const state = useNavbarState(defaultState)
const themeState = useThemeState()
const { pathname } = useRouter()
const isSearchPage = pathname === '/search'
@ -32,14 +36,18 @@ export default function NavBar({ showLogoType = true }: NavBarProps) {
setShowMobileMenu(!showMobileMenu)
}
useEffect(() => {
defaultState && state.state.set((value) => ({ ...value, ...defaultState }))
}, [defaultState])
return (
<Container className={`${hide ? 'hide' : ''} ${className}`}>
<NavBarContainer>
<LeftContainer href={'/'}>
<LogosIcon color="primary" />
{showLogoType && (
{state.showTitle.get() && (
<PressLogoType variant={'h6'} genericFontFamily={'serif'}>
Press Engine
{state.title.get()}
</PressLogoType>
)}
</LeftContainer>

View File

@ -1,19 +1,32 @@
import styled from '@emotion/styled'
import { FilterTags } from '@/components/FilterTags'
import { useSearchBarContext } from '@/context/searchbar.context'
import styled from '@emotion/styled'
import { useRouter } from 'next/router'
import React from 'react'
export const NavbarFiller = () => {
export type NavbarFilter = Partial<
React.ComponentProps<typeof NavbarFillerContainer>
> & {
tags?: string[]
}
export const NavbarFiller: React.FC<NavbarFilter> = ({
tags = [],
...props
}) => {
const router = useRouter()
const { tags } = useSearchBarContext()
const onTagClick = (tag: string) => {
router.push(`/search?topics=${tag}`)
}
return (
<NavbarFillerContainer>
<FilterTags onTagClick={onTagClick} tags={tags} selectedTags={[]} />
<NavbarFillerContainer {...props}>
<FilterTags
size="large"
onTagClick={onTagClick}
tags={tags}
selectedTags={[]}
/>
</NavbarFillerContainer>
)
}
@ -24,7 +37,6 @@ export const NavbarFillerContainer = styled.div`
text-align: center;
display: flex;
justify-content: center;
margin-block: 24px;
@media (max-width: 768px) {
margin-block: 16px;

View File

@ -1,4 +1,7 @@
export const copyConfigs = {
navbar: {
title: 'Press Engine',
},
search: {
searchbarPlaceholders: {
global: () => 'Search through the LPE posts...',

View File

@ -34,7 +34,7 @@ export const HomePage: React.FC<HomePageProps> = ({
return (
<Root {...props}>
<Hero />
<Hero tags={tags} />
<PostsGrid posts={group1[0]} cols={5} bordered size="xxsmall" />
<PostsGrid
posts={highlighted.slice(0, 1)}

8
src/index.d.ts vendored
View File

@ -1,5 +1,13 @@
import { Theme as LSDTheme } from '@acid-info/lsd-react'
import { NextPage } from 'next'
import React from 'react'
declare module '@emotion/react' {
export interface Theme extends LSDTheme {}
}
declare module 'next' {
export type CustomNextPage<T = any, I = any> = NextPage<T, I> & {
getLayout?: (page: React.ReactNode) => any
}
}

View File

@ -1,19 +1,25 @@
import { Footer } from '@/components/Footer'
import { Main } from '@/components/Main'
import { PropsWithChildren } from 'react'
import { AppBar } from '../../components/NavBar'
import { NavBarProps } from '@/components/NavBar/NavBar'
import { PropsWithChildren } from 'react'
import { MainProps } from '../../components/Main/Main'
import { AppBar } from '../../components/NavBar'
interface Props {
navbarProps?: NavBarProps
mainProps?: Partial<MainProps>
}
export default function DefaultLayout(props: PropsWithChildren<Props>) {
const { navbarProps = {} } = props
const { mainProps = {}, navbarProps = {} } = props
return (
<>
<AppBar {...navbarProps} />
<Main>{props.children}</Main>
<AppBar
{...navbarProps}
defaultState={navbarProps.defaultState ?? { showTitle: true }}
/>
<Main {...mainProps}>{props.children}</Main>
<Footer />
</>
)

View File

@ -1,11 +1,12 @@
import { GetStaticProps, NextPage } from 'next'
import { CustomNextPage, GetStaticProps } from 'next'
import SEO from '../components/SEO/SEO'
import { HomePage, HomePageProps } from '../containers/HomePage'
import { DefaultLayout } from '../layouts/DefaultLayout'
import unbodyApi from '../services/unbody/unbody.service'
type PageProps = Pick<HomePageProps, 'data'>
const Page: NextPage<PageProps> = (props) => {
const Page: CustomNextPage<PageProps> = (props) => {
return (
<>
<SEO
@ -19,6 +20,17 @@ const Page: NextPage<PageProps> = (props) => {
)
}
Page.getLayout = function getLayout(page: React.ReactNode) {
return (
<DefaultLayout
mainProps={{ spacing: false }}
navbarProps={{ defaultState: { showTitle: false } }}
>
{page}
</DefaultLayout>
)
}
export const getStaticProps: GetStaticProps<PageProps> = async () => {
const { data: tags = [] } = await unbodyApi.getTopics(true)
const { data: highlighted } = await unbodyApi.getHighlightedPosts()

View File

@ -0,0 +1 @@
export * from './navbar.state'

View File

@ -0,0 +1,39 @@
import { hookstate, State, useHookstate } from '@hookstate/core'
import { useRef } from 'react'
import { copyConfigs } from '../../configs/copy.configs'
export type NavbarState = {
title: string
showTitle: boolean
}
export const defaultNavbarState: NavbarState = {
showTitle: true,
title: copyConfigs.navbar.title,
}
const navbarState = hookstate<NavbarState>(defaultNavbarState)
const wrapThemeState = (state: State<NavbarState>) => ({
state,
title: state.title,
showTitle: state.showTitle,
setShowTitle: (value: boolean) => state.showTitle.set(value),
})
export const useNavbarState = (defaultState?: Partial<NavbarState>) => {
const initialized = useRef<boolean>(false)
const state = useHookstate(navbarState)
if (defaultState && !initialized.current) {
state.set((value) => ({
...value,
...(defaultState ?? {}),
}))
initialized.current = true
}
return wrapThemeState(state)
}
export default useNavbarState