last detailing

This commit is contained in:
amirhouieh 2023-05-16 22:56:44 +02:00
parent 4d27bc6a96
commit 3eea66a23b
25 changed files with 195 additions and 135 deletions

View File

@ -12,19 +12,19 @@ import { useScrollDirection } from '@/utils/ui.utils'
import { useRouter } from 'next/router'
import { useSearchBarContext } from '@/context/searchbar.context'
interface NavbarProps {
interface AppBarProps {
isDark: boolean
toggle: () => void
onSearch?: (query: string, tags: string[]) => void
onReset?: () => void
}
export default function Navbar({
export default function AppBar({
isDark,
toggle,
onReset,
onSearch,
}: NavbarProps) {
}: AppBarProps) {
const { resultsNumber } = useSearchBarContext()
const { pathname } = useRouter()
const isSearchPage = pathname === '/search'
@ -52,7 +52,7 @@ export default function Navbar({
return (
<Container className={`${hide ? 'hide' : ''} ${className}`}>
<AppBar>
<NavBar>
<LogosIconContainer href={'/'}>
<LogosIcon color="primary" />
</LogosIconContainer>
@ -69,10 +69,10 @@ export default function Navbar({
size="small"
onClick={() => onSearchIconClick()}
>
<SearchIcon />
<SearchIcon color="primary" />
</IconButton>
</Icons>
</AppBar>
</NavBar>
<MobileSearchContainer
className={`searchBar ${hideSearch ? 'hide' : ''}`}
>
@ -92,8 +92,8 @@ const Container = styled.div`
transition: top 0.2s;
position: fixed;
top: 0;
left: 0;
z-index: 101;
left: calc(calc(100% - ${uiConfigs.maxContainerWidth}px) / 2);
&._page {
@media (min-width: 768px) {
@ -110,7 +110,14 @@ const Container = styled.div`
&.search_page {
}
@media (max-width: ${uiConfigs.maxContainerWidth}px) {
left: 16px;
width: calc(100% - 32px);
}
@media (max-width: 768px) {
left: 0;
width: 100%;
&.hide {
top: -44px;
}
@ -137,7 +144,7 @@ const MobileSearchContainer = styled.div`
}
`
const AppBar = styled.nav`
const NavBar = styled.nav`
display: flex;
padding: 8px 0;
align-items: center;
@ -204,7 +211,3 @@ const Icons = styled.div`
}
}
`
const Selector = styled(IconButton)`
border-left: none;
`

View File

@ -0,0 +1 @@
export { default as AppBar } from './AppBar'

View File

@ -43,13 +43,7 @@ const ArticleContainer = styled.article`
flex-direction: column;
gap: 16px;
max-width: 700px;
margin-inline: 5%;
padding-bottom: 80px;
// temporary breakpoint
@media (max-width: 1024px) {
margin-inline: 16px;
}
`
const TextContainer = styled.div`

View File

@ -18,6 +18,7 @@ export const ArticleHeading = ({
block,
headingElementsRef,
typographyProps,
...props
}: Props) => {
const id =
extractIdFromFirstTag(block.html) || `${block.tagName}-${block.order}`
@ -37,6 +38,7 @@ export const ArticleHeading = ({
className={extractClassFromFirstTag(block.html) || ''}
dangerouslySetInnerHTML={{ __html: `${extractInnerHtml(block.html)}` }}
{...(typographyProps || {})}
{...props}
/>
</>
)
@ -48,7 +50,7 @@ const Headline = styled(Typography)`
@media (max-width: 768px) {
&.title {
font-size: var(--lsd-h3-fontSize);
font-size: var(--lsd-h4-fontSize);
line-height: var(--lsd-h4-lineHeight);
}

View File

@ -6,7 +6,7 @@ import { PostImageRatio } from '../../Post/Post'
import ArticleStats from '../Article.Stats'
import { Typography } from '@acid-info/lsd-react'
import styled from '@emotion/styled'
import ArticleSummary, { MobileSummary } from './Article.Summary'
import ArticleSummary from './Article.Summary'
import { calcReadingTime } from '@/utils/string.utils'
import { Authors } from '@/components/Authors'
import { Tags } from '@/components/Tags'
@ -14,12 +14,8 @@ import { UnbodyGraphQl } from '@/lib/unbody/unbody-content.types'
import { ArticleHeading } from '@/components/Article/Article.Heading'
import { useArticleContainerContext } from '@/containers/ArticleContainer.Context'
import { useIntersectionObserver } from '@/utils/ui.utils'
import { MobileToc } from '@/components/Article/Article.MobileToc'
import { useSearchBarContext } from '@/context/searchbar.context'
const ArticleHeader = ({
title,
toc,
summary,
subtitle,
mentions,
@ -27,9 +23,8 @@ const ArticleHeader = ({
modifiedAt,
blocks,
}: GoogleDocEnhanced) => {
const { setTocId, tocId } = useArticleContainerContext()
const { setTocId } = useArticleContainerContext()
const headingElementsRef = useIntersectionObserver(setTocId)
const { resultsNumber } = useSearchBarContext()
const _thumbnail = useMemo(() => {
const coverImage = getArticleCover(blocks)
@ -58,15 +53,14 @@ const ArticleHeader = ({
}, [blocks])
return (
<header>
<ArticleHeaderContainer>
<ArticleStats dateStr={modifiedAt} readingLength={readingTime} />
<ArticleHeading
<ArticleTitle
block={blocks[0] as any}
typographyProps={{
variant: 'h1',
genericFontFamily: 'serif',
component: 'h1',
style: { marginBottom: '16px' },
}}
headingElementsRef={headingElementsRef}
/>
@ -79,22 +73,60 @@ const ArticleHeader = ({
{subtitle}
</ArticleSubtitle>
)}
<Tags tags={tags} />
<Tags tags={tags} className={'articleTags'} />
<AuthorsContainer>
<Authors mentions={mentions} email={true} gap={12} />
</AuthorsContainer>
<MobileCollapseContainer>
{resultsNumber === null && <MobileToc toc={toc} />}
{resultsNumber === null && <MobileSummary summary={summary} />}
</MobileCollapseContainer>
{/*<MobileCollapseContainer>*/}
{/* {resultsNumber === null && <MobileToc toc={toc} />}*/}
{/* {resultsNumber === null && <MobileSummary summary={summary} />}*/}
{/*</MobileCollapseContainer>*/}
<ArticleSummary
summary={summary}
className={'mobileSummary'}
showLabel={false}
/>
{_thumbnail}
<ArticleSummary summary={summary} />
</header>
<ArticleSummary
summary={summary}
className={'desktopSummary'}
showLabel={true}
/>
</ArticleHeaderContainer>
)
}
const MobileCollapseContainer = styled.div`
margin-bottom: 32px;
const ArticleHeaderContainer = styled.header`
.mobileSummary {
display: none;
}
.desktopSummary {
display: block;
}
@media (max-width: 768px) {
.mobileSummary {
color: red;
display: block;
p {
font-size: var(--lsd-body3-fontSize);
line-height: var(--lsd-body3-lineHeight);
margin-bottom: 24px;
}
hr {
display: none;
}
}
.desktopSummary {
display: none;
}
.articleTags {
display: none;
}
}
`
const CustomTypography = styled(Typography)`
@ -103,18 +135,34 @@ const CustomTypography = styled(Typography)`
white-space: pre-wrap;
`
const ArticleTitle = styled(CustomTypography)`
margin-bottom: 24px;
const ArticleTitle = styled(ArticleHeading)`
margin-bottom: 16px;
@media (max-width: 768px) {
margin-bottom: 8px;
}
`
const ArticleSubtitle = styled(CustomTypography)`
margin-bottom: 16px;
@media (max-width: 768px) {
font-size: var(--lsd-subtitle1-fontSize);
}
`
const AuthorsContainer = styled.div`
//margin-block: 24px;
margin-top: 24px;
margin-bottom: 32px;
@media (max-width: 768px) {
margin-top: 16px;
margin-bottom: 24px;
a[href^='mailto:'] {
display: none;
}
}
`
export default ArticleHeader

View File

@ -4,24 +4,14 @@ import React from 'react'
import { Collapse } from '@/components/Collapse'
import useIsDarkState from '@/states/isDarkState/isDarkState'
export const MobileSummary = ({ summary }: { summary: string }) => {
const isDark = useIsDarkState().get()
return (
<SummaryContainerMobile
label={'Summary'}
initOpen={false}
style={{ background: isDark ? 'black' : 'white' }}
>
<SummaryParagraph variant="h6" component={'p'}>
{summary}
</SummaryParagraph>
</SummaryContainerMobile>
)
type Props = {
summary: string
className?: string
showLabel?: boolean
}
const ArticleSummary = ({ summary }: { summary: string }) => (
<ArticleSummaryContainer>
<Typography variant="body3">summary</Typography>
const ArticleSummary = ({ summary, className, showLabel }: Props) => (
<ArticleSummaryContainer className={className}>
{showLabel && <Typography variant="body3">summary</Typography>}
<SummaryParagraph variant="h6" component={'p'}>
{summary}
</SummaryParagraph>
@ -35,6 +25,7 @@ const ArticleSummaryContainer = styled('div')`
@media (max-width: 770px) {
display: none;
}
> span {
margin-bottom: 16px;
display: block;
@ -46,17 +37,4 @@ const SummaryParagraph = styled(Typography)`
display: block;
`
const SummaryContainerMobile = styled(Collapse)`
display: none;
@media (max-width: 770px) {
display: block;
p {
padding: 12px 14px;
margin-bottom: 0;
font-size: var(--lsd-font-size-body2);
line-height: var(--lsd-line-height-body2);
}
}
`
export default ArticleSummary

View File

@ -1,7 +1,7 @@
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 { NavbarFiller } from '@/components/AppBar/NavbarFiller'
import { Searchbar } from '@/components/Searchbar'
export default function Hero() {
@ -18,7 +18,6 @@ export default function Hero() {
</Description>
</HeroText>
<NavbarFiller />
<Searchbar className={'desktop'} />
</Container>
)
}

View File

@ -2,8 +2,8 @@ import styled from '@emotion/styled'
import { uiConfigs } from '@/configs/ui.configs'
import { PropsWithChildren } from 'react'
const Main = ({ children }: PropsWithChildren) => {
return <Container>{children}</Container>
const Main = ({ children, ...props }: PropsWithChildren<any>) => {
return <Container {...props}>{children}</Container>
}
const Container = styled.main`
@ -11,6 +11,10 @@ const Container = styled.main`
margin-left: auto;
margin-right: auto;
@media (max-width: ${uiConfigs.maxContainerWidth}px) {
padding: 0 16px;
}
@media (max-width: 768px) {
margin-top: ${uiConfigs.postSectionMobileMargin}px;
}

View File

@ -1 +0,0 @@
export { default as Navbar } from './Navbar';

View File

@ -137,13 +137,20 @@ export default function Post({
const _thumbnail = useMemo(() => {
if (!showImage || !coverImage) return null
let allImageProps = [
...imagePropsArray,
...(imageProps ? [imageProps] : []),
]
if (postType === PostType.BODY) {
return (
<Link href={`/article/${slug}`}>
{[...imagePropsArray, ...(imageProps ? [imageProps] : [])].map(
(imageProps, index) => (
<ResponsiveImage key={index} {...imageProps} data={coverImage} />
),
{allImageProps.length > 0 ? (
allImageProps.map((_imageProps, index) => (
<ResponsiveImage key={index} {..._imageProps} data={coverImage} />
))
) : (
<ResponsiveImage {...imageProps} data={coverImage} />
)}
</Link>
)
@ -151,14 +158,16 @@ export default function Post({
return (
<>
<Link href={`/article/${slug}`}>
{[...imagePropsArray, ...(imageProps ? [imageProps] : [])].map(
(imageProps, index) => (
{allImageProps.length > 0 ? (
allImageProps.map((_imageProps, index) => (
<ResponsiveImage
key={index}
{...imageProps}
{..._imageProps}
data={coverImage}
/>
),
))
) : (
<ResponsiveImage {...imageProps} data={coverImage} />
)}
</Link>
{_title}

View File

@ -79,6 +79,7 @@ const CustomGrid = styled(Grid)`
min-height: 500px;
@media (max-width: 768px) {
gap: 8px;
min-height: auto;
}
`

View File

@ -29,7 +29,9 @@ export default function RelatedArticles({ data }: Props) {
tags: article.doc.tags,
coverImage: getArticleCover(article.doc.blocks),
}))}
pageSize={4}
pageSize={
typeof window !== 'undefined' && window.innerWidth < 768 ? 2 : 4
}
loading={data.loading}
/>
}

View File

@ -103,7 +103,6 @@ export default function Searchbar(props: SearchbarProps) {
performSearch('', [])
return
}
setQuery('')
setFilterTags([])
setActive(false)
@ -126,7 +125,8 @@ export default function Searchbar(props: SearchbarProps) {
}
}
const isCollapsed = isValidSearchInput(filterTags) && !active
const withValue = isValidSearchInput(filterTags) || resultsNumber !== null
const isCollapsed = withValue && !active
useEffect(() => {
if (active && query.length > 0) {
@ -203,11 +203,13 @@ export default function Searchbar(props: SearchbarProps) {
<div>
<IconButton
className={styles.searchButton}
onClick={() =>
isValidSearchInput() ? performClear() : performSearch()
}
onClick={() => (withValue ? performClear() : performSearch())}
>
{isValidSearchInput() ? <CloseIcon /> : <SearchIcon />}
{withValue ? (
<CloseIcon color="primary" />
) : (
<SearchIcon color="primary" />
)}
</IconButton>
</div>
</SearchBox>
@ -231,8 +233,10 @@ export default function Searchbar(props: SearchbarProps) {
variant={'subtitle2'}
dangerouslySetInnerHTML={{
__html: [
`${resultsNumber} matches`,
`<span class="helper">${resultsHelperText}<span>`,
...(resultsNumber !== null ? [`${resultsNumber} matches`] : []),
...(resultsHelperText !== null
? [`<span class="helper">${resultsHelperText}<span>`]
: []),
].join('<span class="dot">.</span>'),
}}
/>
@ -249,6 +253,7 @@ const TagsWrapper = styled.div`
margin-top: 19px;
height: 24px;
}
@media (max-width: 768px) {
&.active {
margin-top: 10px;

View File

@ -20,10 +20,6 @@ export function SearchbarContainer({
className,
beSticky = false,
}: Props) {
const { pathname } = useRouter()
const isSearchPage = pathname === '/search'
const isArticlePage = pathname === '/article/[slug]'
const { sticky, stickyRef, height } = useSticky<HTMLDivElement>(
uiConfigs.navbarRenderedHeight,
)
@ -74,9 +70,25 @@ const SearchBarWrapper = styled.div<Props>`
&.sticky {
position: fixed;
top: ${uiConfigs.navbarRenderedHeight - 1}px;
z-index: 100;
max-width: ${uiConfigs.maxContainerWidth}px;
border-top: none;
}
@media (max-width: ${uiConfigs.maxContainerWidth}px) {
&.sticky {
width: calc(100% - 32px);
left: 16px;
}
}
@media (max-width: 768px) {
&.sticky {
width: 100%;
left: 0;
top: 0;
}
}
`

View File

@ -35,10 +35,6 @@ export const Section = ({ title, subtitle, children, ...props }: Props) => {
const SectionContainer = styled.section`
width: 100%;
box-sizing: border-box;
@media (max-width: 1440px) {
padding-inline: 16px;
}
`
const Container = styled.div`

View File

@ -4,13 +4,13 @@ import styled from '@emotion/styled'
import Link from 'next/link'
import { useRouter } from 'next/router'
const Tags = ({ tags }: { tags: string[] }) => {
const Tags = ({ tags, className }: { tags: string[]; className?: string }) => {
const router = useRouter()
const { query } = router
const { topics } = query
return tags.length > 0 ? (
<TagsContainer>
<TagsContainer className={className}>
{tags.map((tag, idx) => (
<Link key={`tag-${idx}`} href={`/search?topics=${tag}`}>
<Tag

View File

@ -1,6 +1,6 @@
export const uiConfigs = {
navbarRenderedHeight: 45,
postSectionMargin: 108,
postSectionMargin: 101,
postSectionMobileMargin: 48,
articleSectionMargin: 40,
maxContainerWidth: 1440,

View File

@ -1,13 +1,10 @@
import { Navbar } from '@/components/Navbar'
import useIsDarkState from '@/states/isDarkState/isDarkState'
import { PropsWithChildren } from 'react'
import { NavbarFiller } from '@/components/Navbar/NavbarFiller'
import { Searchbar } from '@/components/Searchbar'
import { ESearchScope } from '@/types/ui.types'
import styles from './Article.layout.module.css'
import { Footer } from '@/components/Footer'
import { Main } from '@/components/Main'
import { useArticleContext } from '@/context/article.context'
import { AppBar } from '@/components/AppBar'
type Props = PropsWithChildren<{
// onSearch: (query: string, filters: string[]) => void
@ -19,7 +16,7 @@ export default function ArticleLayout({ children }: Props) {
return (
<>
<header className={styles.header}>
<Navbar
<AppBar
isDark={isDarkState.get()}
toggle={isDarkState.toggle}
onSearch={onSearch}

View File

@ -1,42 +1,41 @@
import { Navbar } from '@/components/Navbar'
import { AppBar } from '../../components/AppBar'
import useIsDarkState from '@/states/isDarkState/isDarkState'
import { PropsWithChildren } from 'react'
import { Hero } from '@/components/Hero'
import { NavbarFiller } from '@/components/Navbar/NavbarFiller'
import { NavbarFiller } from '@/components/AppBar/NavbarFiller'
import { Searchbar } from '@/components/Searchbar'
import { Footer } from '@/components/Footer'
import { Main } from '@/components/Main'
import { uiConfigs } from '@/configs/ui.configs'
import { useRouter } from 'next/router'
import { defaultThemes } from '@acid-info/lsd-react'
import styled from '@emotion/styled'
export default function DefaultLayout(props: PropsWithChildren<any>) {
const isDarkState = useIsDarkState()
return (
<>
<header
<HeaderContainer
style={{
textAlign: 'center',
marginBlock: `${uiConfigs.navbarRenderedHeight}px`,
}}
>
<div
// style={{
// borderBottom: `1px solid rgb(${
// isDarkState.get()
// ? defaultThemes.dark.palette.border.primary
// : defaultThemes.light.palette.border.primary
// })`,
// }}
>
<Navbar isDark={isDarkState.get()} toggle={isDarkState.toggle} />
<div>
<AppBar isDark={isDarkState.get()} toggle={isDarkState.toggle} />
<Hero />
</div>
<Searchbar withFilterTags={false} />
</header>
<Searchbar withFilterTags={false} beSticky={true} />
</HeaderContainer>
<Main>{props.children}</Main>
<Footer />
</>
)
}
const HeaderContainer = styled.header`
@media (min-width: 776px) and (max-width: ${uiConfigs.maxContainerWidth}px) {
padding: 0 16px;
}
`

View File

@ -1,12 +1,10 @@
import { Navbar } from '@/components/Navbar'
import useIsDarkState from '@/states/isDarkState/isDarkState'
import { PropsWithChildren } from 'react'
import { NavbarFiller } from '@/components/Navbar/NavbarFiller'
import { Searchbar } from '@/components/Searchbar'
import { ESearchScope } from '@/types/ui.types'
import styles from './Search.layout.module.css'
import { Footer } from '@/components/Footer'
import { Main } from '@/components/Main'
import { AppBar } from '@/components/AppBar'
import styled from '@emotion/styled'
import { uiConfigs } from '@/configs/ui.configs'
export default function SearchLayout(props: PropsWithChildren<any>) {
@ -14,10 +12,22 @@ export default function SearchLayout(props: PropsWithChildren<any>) {
return (
<>
<header className={styles.header}>
<Navbar isDark={isDarkState.get()} toggle={isDarkState.toggle} />
<AppBar isDark={isDarkState.get()} toggle={isDarkState.toggle} />
</header>
<Main>{props.children}</Main>
<MainContainer className={'search_page'}>{props.children}</MainContainer>
<Footer />
</>
)
}
const MainContainer = styled(Main)`
&.search_page {
margin-top: ${uiConfigs.postSectionMargin * 1.7}px;
}
@media (max-width: 768px) {
&.search_page {
margin-top: ${uiConfigs.postSectionMobileMargin * 3}px;
}
}
`

View File

@ -42,7 +42,6 @@ export async function getStaticPaths() {
export const getStaticProps = async ({ params }: GetStaticPropsContext) => {
const { slug } = params!
console.log('rendering', slug)
if (!slug) {
return {

View File

@ -84,7 +84,12 @@ export default function SearchPage({
}, [mounted, router.query])
useEffect(() => {
setResultsNumber(articles.data.length + blocks.data.length)
if (
articles.data.length + blocks.data.length <
initialArticles.length + initialBlocks.length
) {
setResultsNumber(articles.data.length + blocks.data.length)
}
const tags = extractTopicsFromQuery(router.query)
setResultsHelperText(
[

View File

@ -5,10 +5,6 @@ class SearchService {
constructor() {}
search = (query: string, tags: string[], postType: PostTypes) => {
console.log(
`/api/search/general/${postType}?q=${query}&tags=${tags.join(',')}`,
)
return fetch(
`/api/search/general/${postType}?q=${query}&tags=${tags.join(',')}`,
)

View File

@ -83,6 +83,7 @@ export function useIntersectionObserver(
headings.forEach((heading) => {
if (heading.isIntersecting && heading.target instanceof HTMLElement) {
const targetId = heading.target.getAttribute('id')
console.log(targetId)
if (targetId) setActiveId(targetId)
}
})