feat: implement new hero compoent
This commit is contained in:
parent
9c1600c36d
commit
0e18e1822c
|
@ -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": {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
`
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
`
|
||||
|
|
|
@ -1 +1 @@
|
|||
export { default as Hero } from './Hero'
|
||||
export * from './Hero'
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
`
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
export const copyConfigs = {
|
||||
navbar: {
|
||||
title: 'Press Engine',
|
||||
},
|
||||
search: {
|
||||
searchbarPlaceholders: {
|
||||
global: () => 'Search through the LPE posts...',
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 />
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from './navbar.state'
|
|
@ -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
|
Loading…
Reference in New Issue