feat: make PostGrid component responsive
This commit is contained in:
parent
debcfc103b
commit
f30f2d1b9e
|
@ -7,7 +7,7 @@ interface Props {
|
|||
episodes: LPE.Podcast.Document[]
|
||||
shows?: LPE.Podcast.Show[]
|
||||
bordered?: boolean
|
||||
size?: PostsGridProps['size']
|
||||
size?: string
|
||||
cols?: number
|
||||
displayShow?: boolean
|
||||
}
|
||||
|
@ -24,14 +24,14 @@ export default function EpisodesList({
|
|||
return (
|
||||
<EpisodeListContainer>
|
||||
{header}
|
||||
<PostsGrid
|
||||
{/* <PostsGrid
|
||||
shows={shows}
|
||||
posts={episodes}
|
||||
bordered={bordered}
|
||||
cols={cols}
|
||||
size={size}
|
||||
displayPodcastShow={displayShow}
|
||||
/>
|
||||
/> */}
|
||||
</EpisodeListContainer>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import { css } from '@emotion/react'
|
||||
/** @jsxImportSource @emotion/react */
|
||||
import { Breakpoints, Theme, useTheme } from '@acid-info/lsd-react'
|
||||
import { css, SerializedStyles } from '@emotion/react'
|
||||
import styled from '@emotion/styled'
|
||||
import React, { useMemo } from 'react'
|
||||
import { LPE } from '../../types/lpe.types'
|
||||
import { chunkArray } from '../../utils/array.utils'
|
||||
import { lsdUtils } from '../../utils/lsd.utils'
|
||||
import { lcm } from '../../utils/math.utils'
|
||||
import { PostCard, PostCardProps } from '../PostCard'
|
||||
|
||||
export type PostsGridProps = Partial<React.ComponentProps<typeof Container>> & {
|
||||
|
@ -12,41 +16,134 @@ export type PostsGridProps = Partial<React.ComponentProps<typeof Container>> & {
|
|||
}
|
||||
|
||||
export const PostsGrid: React.FC<PostsGridProps> = ({
|
||||
cols = 4,
|
||||
size = 'small',
|
||||
posts = [],
|
||||
shows = [],
|
||||
pattern = [],
|
||||
breakpoints = [],
|
||||
bordered = false,
|
||||
displayPodcastShow = true,
|
||||
...props
|
||||
}) => {
|
||||
const groups = useMemo(() => chunkArray(posts, cols), [posts, cols])
|
||||
const theme = useTheme()
|
||||
|
||||
const items = useMemo(() => {
|
||||
const cols = pattern.map((p) => p.cols)
|
||||
const chunked = chunkArray(posts, ...cols)
|
||||
|
||||
return chunked
|
||||
.map((posts, i) =>
|
||||
posts.map((post) => ({
|
||||
post,
|
||||
size: pattern[i % pattern.length]?.size,
|
||||
})),
|
||||
)
|
||||
.flat()
|
||||
}, [pattern, posts])
|
||||
|
||||
const postCardStyles = useMemo(
|
||||
() => ({
|
||||
xxsmall: PostCard.styles.xxsmall(theme),
|
||||
xsmall: PostCard.styles.xsmall(theme),
|
||||
small: PostCard.styles.small(theme),
|
||||
medium: PostCard.styles.medium(theme),
|
||||
large: PostCard.styles.large(theme),
|
||||
}),
|
||||
[theme],
|
||||
)
|
||||
|
||||
return (
|
||||
<Container {...props} cols={cols} size={size} bordered={bordered}>
|
||||
{groups.map((group, index) => (
|
||||
<div className="row" key={index}>
|
||||
{group.map((post) => (
|
||||
<div key={post.id} className="post-card-wrapper">
|
||||
<PostCard
|
||||
size={size}
|
||||
className="post-card"
|
||||
contentType={post.type}
|
||||
displayPodcastShow={displayPodcastShow}
|
||||
data={PostCard.toData(post, shows)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
<Container
|
||||
{...props}
|
||||
pattern={pattern}
|
||||
breakpoints={breakpoints}
|
||||
bordered={bordered}
|
||||
postCardStyles={postCardStyles}
|
||||
>
|
||||
<div className="row">
|
||||
{items.map(({ post, size }) => (
|
||||
<div key={post.id} className="post-card-wrapper">
|
||||
<PostCard
|
||||
size={size as any}
|
||||
applySizeStyles={false}
|
||||
className="post-card"
|
||||
contentType={post.type}
|
||||
displayPodcastShow={displayPodcastShow}
|
||||
data={PostCard.toData(post, shows)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div<{
|
||||
type Pattern = {
|
||||
cols: number
|
||||
bordered: boolean
|
||||
size: PostCardProps['size']
|
||||
}
|
||||
type Breakpoint = {
|
||||
pattern: Pattern[]
|
||||
breakpoint: Breakpoints
|
||||
}
|
||||
|
||||
const createGridStyles = ({
|
||||
theme,
|
||||
pattern = [],
|
||||
postCardStyles,
|
||||
breakpoint = false,
|
||||
}: {
|
||||
theme: Theme
|
||||
postCardStyles: {
|
||||
[name: string]: SerializedStyles
|
||||
}
|
||||
pattern: Pick<Pattern, 'cols' | 'size'>[]
|
||||
breakpoint?: boolean
|
||||
}) => {
|
||||
const cm = pattern.map((p) => p.cols).reduce(lcm, 1)
|
||||
const sum = Math.max(
|
||||
1,
|
||||
pattern.reduce((p, c) => p + c.cols, 0),
|
||||
)
|
||||
|
||||
let selectorNumber = 0
|
||||
const selectors = pattern.map((p) => {
|
||||
const start = selectorNumber + 1
|
||||
selectorNumber += p.cols
|
||||
|
||||
return new Array(p.cols)
|
||||
.fill(null)
|
||||
.map((i, index) => `${sum}n + ${start + index}`)
|
||||
})
|
||||
|
||||
return css`
|
||||
> .row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(${cm}, 1fr);
|
||||
|
||||
& > div {
|
||||
${pattern.map(
|
||||
(p, i) => `
|
||||
${selectors[i].map((s) => `&:nth-child(${s})`).join(', ')} {
|
||||
grid-column: span ${cm / p.cols};
|
||||
|
||||
.post-card {
|
||||
${postCardStyles[p.size as string].styles}
|
||||
}
|
||||
}
|
||||
`,
|
||||
)}
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
const Container = styled.div<{
|
||||
bordered: boolean
|
||||
pattern: Pattern[]
|
||||
breakpoints: Breakpoint[]
|
||||
postCardStyles: {
|
||||
[name: string]: SerializedStyles
|
||||
}
|
||||
}>`
|
||||
display: grid;
|
||||
gap: 16px 0;
|
||||
|
@ -54,7 +151,6 @@ const Container = styled.div<{
|
|||
${(props) => css`
|
||||
> .row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(${props.cols}, 1fr);
|
||||
gap: 0 16px;
|
||||
|
||||
& > div {
|
||||
|
@ -63,55 +159,115 @@ const Container = styled.div<{
|
|||
rgb(var(--lsd-border-primary));
|
||||
}
|
||||
}
|
||||
|
||||
${lsdUtils
|
||||
.breakpoints(props.breakpoints.map((b) => b.breakpoint))
|
||||
.map((breakpoint) =>
|
||||
lsdUtils.breakpoint(
|
||||
props.theme,
|
||||
breakpoint,
|
||||
'exact',
|
||||
)(css`
|
||||
${createGridStyles({
|
||||
theme: props.theme,
|
||||
pattern: props.pattern,
|
||||
postCardStyles: props.postCardStyles,
|
||||
breakpoint: true,
|
||||
})}
|
||||
`),
|
||||
)}
|
||||
`}
|
||||
|
||||
${(props) =>
|
||||
props.size === 'xxsmall' &&
|
||||
css`
|
||||
> .row {
|
||||
padding: 24px 0;
|
||||
gap: 0 32px;
|
||||
|
||||
& > div {
|
||||
border-top: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
& > div:not(:last-child)::after {
|
||||
content: ' ';
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
background: rgb(var(--lsd-border-primary));
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: -16px;
|
||||
display: ${props.bordered ? 'block' : 'none'};
|
||||
}
|
||||
}
|
||||
`}
|
||||
|
||||
${(props) =>
|
||||
props.size === 'xsmall' &&
|
||||
css`
|
||||
> .row {
|
||||
gap: 0 16px;
|
||||
|
||||
& > div {
|
||||
box-sizing: border-box;
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
& > div:last-child {
|
||||
}
|
||||
& > div:not(:last-child) {
|
||||
}
|
||||
}
|
||||
`}
|
||||
|
||||
${(props) => props.size === 'small' && css``}
|
||||
|
||||
${(props) => props.size === 'medium' && css``}
|
||||
|
||||
${(props) => props.size === 'large' && css``}
|
||||
${({ breakpoints = [], theme, postCardStyles }) => {
|
||||
return breakpoints.map((b) =>
|
||||
lsdUtils.breakpoint(
|
||||
theme,
|
||||
b.breakpoint,
|
||||
'exact',
|
||||
)(css`
|
||||
${createGridStyles({
|
||||
theme,
|
||||
pattern: b.pattern,
|
||||
postCardStyles,
|
||||
breakpoint: true,
|
||||
})}
|
||||
`),
|
||||
)
|
||||
}}
|
||||
`
|
||||
// ${b.size.map(
|
||||
// ([originalSize, targetSize]) => `
|
||||
// .post-card--${originalSize} {
|
||||
// ${postCardStyles[targetSize as string]?.styles}
|
||||
// }
|
||||
// `,
|
||||
// )}
|
||||
|
||||
// @media (max-width: ${props.theme.breakpoints.sm.width - 1}px) {
|
||||
// }
|
||||
|
||||
// @media (min-width: ${props.theme.breakpoints.sm.width}px) {
|
||||
// ${props.pattern.sm && createGridStyles({ pattern: props.pattern.sm })}
|
||||
// }
|
||||
|
||||
// @media (min-width: ${props.theme.breakpoints.md.width}px) {
|
||||
// ${props.pattern.md && createGridStyles({ pattern: props.pattern.md })}
|
||||
// }
|
||||
|
||||
// @media (min-width: ${props.theme.breakpoints.lg.width}px) {
|
||||
// ${props.pattern.lg && createGridStyles({ pattern: props.pattern.lg })}
|
||||
// }
|
||||
|
||||
// styled.div`
|
||||
// ${(props) =>
|
||||
// props.size === 'xxsmall' &&
|
||||
// css`
|
||||
// > .row {
|
||||
// padding: 24px 0;
|
||||
// gap: 0 32px;
|
||||
|
||||
// & > div {
|
||||
// border-top: 0;
|
||||
// padding: 0;
|
||||
// position: relative;
|
||||
// }
|
||||
|
||||
// & > div:not(:last-child)::after {
|
||||
// content: ' ';
|
||||
// height: 100%;
|
||||
// width: 1px;
|
||||
// background: rgb(var(--lsd-border-primary));
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// right: -16px;
|
||||
// display: ${props.bordered ? 'block' : 'none'};
|
||||
// }
|
||||
// }
|
||||
// `}
|
||||
|
||||
// ${(props) =>
|
||||
// props.size === 'xsmall' &&
|
||||
// css`
|
||||
// > .row {
|
||||
// gap: 0 16px;
|
||||
|
||||
// & > div {
|
||||
// box-sizing: border-box;
|
||||
// border-top: 0;
|
||||
// }
|
||||
|
||||
// & > div:last-child {
|
||||
// }
|
||||
// & > div:not(:last-child) {
|
||||
// }
|
||||
// }
|
||||
// `}
|
||||
|
||||
// ${(props) => props.size === 'small' && css``}
|
||||
|
||||
// ${(props) => props.size === 'medium' && css``}
|
||||
|
||||
// ${(props) => props.size === 'large' && css``} // @media (max-width: ${(
|
||||
// props,
|
||||
// ) => props.theme.breakpoints.sm.width})
|
||||
// `
|
||||
|
|
|
@ -5,7 +5,6 @@ import { Hero } from '../../components/Hero'
|
|||
import { PostsGrid } from '../../components/PostsGrid'
|
||||
import { useRecentPosts } from '../../queries/useRecentPosts.query'
|
||||
import { LPE } from '../../types/lpe.types'
|
||||
import { chunkArray } from '../../utils/array.utils'
|
||||
import { PodcastShowsPreview } from '../PodcastShowsPreview'
|
||||
|
||||
export type HomePageProps = React.DetailedHTMLProps<
|
||||
|
@ -28,29 +27,82 @@ export const HomePage: React.FC<HomePageProps> = ({
|
|||
const query = useRecentPosts({ initialData: latest, limit: 10 })
|
||||
|
||||
const [group1, group2] = useMemo(
|
||||
() => [[query.posts.slice(0, 5)], chunkArray(query.posts.slice(5), 4, 2)],
|
||||
() => [query.posts.slice(0, 5), query.posts.slice(5)],
|
||||
[query.posts],
|
||||
)
|
||||
|
||||
return (
|
||||
<Root {...props}>
|
||||
<Hero tags={tags} />
|
||||
<PostsGrid posts={group1[0]} cols={5} bordered size="xxsmall" />
|
||||
<PostsGrid posts={group1} pattern={[{ cols: 5, size: 'xxsmall' }]} />
|
||||
<PostsGrid
|
||||
posts={highlighted.slice(0, 1)}
|
||||
cols={1}
|
||||
bordered
|
||||
size="large"
|
||||
posts={highlighted.slice(0, 1)}
|
||||
pattern={[{ cols: 1, size: 'large' }]}
|
||||
breakpoints={[
|
||||
{
|
||||
breakpoint: 'xs',
|
||||
pattern: [{ cols: 1, size: 'small' }],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{group2.map((group, index) => (
|
||||
<PostsGrid
|
||||
bordered
|
||||
key={index}
|
||||
posts={group}
|
||||
cols={index % 2 !== 0 ? 2 : 4}
|
||||
size={index % 2 !== 0 ? 'medium' : 'small'}
|
||||
/>
|
||||
))}
|
||||
<PostsGrid
|
||||
pattern={[
|
||||
{ cols: 4, size: 'small' },
|
||||
{
|
||||
cols: 2,
|
||||
size: 'medium',
|
||||
},
|
||||
{
|
||||
cols: 3,
|
||||
size: 'small',
|
||||
},
|
||||
{
|
||||
cols: 5,
|
||||
size: 'medium',
|
||||
},
|
||||
]}
|
||||
breakpoints={[
|
||||
{
|
||||
breakpoint: 'xs',
|
||||
pattern: [
|
||||
{
|
||||
cols: 1,
|
||||
size: 'small',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
breakpoint: 'sm',
|
||||
pattern: [
|
||||
{
|
||||
cols: 3,
|
||||
size: 'small',
|
||||
},
|
||||
{
|
||||
cols: 2,
|
||||
size: 'medium',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
breakpoint: 'md',
|
||||
pattern: [
|
||||
{
|
||||
cols: 3,
|
||||
size: 'small',
|
||||
},
|
||||
{
|
||||
cols: 2,
|
||||
size: 'medium',
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
posts={group2}
|
||||
bordered
|
||||
/>
|
||||
|
||||
{query.hasNextPage && (
|
||||
<div className="load-more">
|
||||
<Button
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { ArrowDownIcon, Button, Typography } from '@acid-info/lsd-react'
|
||||
import { css } from '@emotion/react'
|
||||
import styled from '@emotion/styled'
|
||||
import clsx from 'clsx'
|
||||
import Image from 'next/image'
|
||||
|
@ -6,6 +7,7 @@ import Link from 'next/link'
|
|||
import React from 'react'
|
||||
import { PostsGrid } from '../../components/PostsGrid'
|
||||
import { LPE } from '../../types/lpe.types'
|
||||
import { lsdUtils } from '../../utils/lsd.utils'
|
||||
|
||||
export type PodcastShowsPreviewProps = React.DetailedHTMLProps<
|
||||
React.HTMLAttributes<HTMLDivElement>,
|
||||
|
@ -81,18 +83,21 @@ export const PodcastShowsPreview: React.FC<PodcastShowsPreviewProps> = ({
|
|||
|
||||
<div className="podcasts__show-episodes">
|
||||
<PostsGrid
|
||||
posts={(show.episodes || []).slice(0, 2)}
|
||||
posts={(show.episodes || []).slice(0, 4)}
|
||||
displayPodcastShow={false}
|
||||
shows={shows}
|
||||
size="xsmall"
|
||||
cols={2}
|
||||
/>
|
||||
<PostsGrid
|
||||
posts={(show.episodes || []).slice(2, 4)}
|
||||
displayPodcastShow={false}
|
||||
shows={shows}
|
||||
size="xsmall"
|
||||
cols={2}
|
||||
pattern={[
|
||||
{
|
||||
cols: 2,
|
||||
size: 'xsmall',
|
||||
},
|
||||
]}
|
||||
breakpoints={[
|
||||
{
|
||||
breakpoint: 'xs',
|
||||
pattern: [{ cols: 1, size: 'small' }],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -176,4 +181,34 @@ const Root = styled('div')`
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
lsdUtils.breakpoint(
|
||||
props.theme,
|
||||
'xs',
|
||||
'exact',
|
||||
)(css`
|
||||
.podcasts__shows {
|
||||
padding-top: 0;
|
||||
grid-template-columns: repeat(1, 1fr);
|
||||
}
|
||||
|
||||
.podcasts__show {
|
||||
border-right: none !important;
|
||||
padding: 0 !important;
|
||||
|
||||
&:not(:first-child) {
|
||||
border-top: 1px solid rgb(var(--lsd-border-primary));
|
||||
}
|
||||
}
|
||||
|
||||
.podcasts__show-card {
|
||||
margin-top: 0;
|
||||
padding: 24px 0px 16px 0px;
|
||||
}
|
||||
|
||||
.podcasts__show-hosts {
|
||||
margin-top: 80px;
|
||||
}
|
||||
`)}
|
||||
`
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
export const gcd = (a: number, b: number): number => (a ? gcd(b % a, a) : b)
|
||||
|
||||
export const lcm = (a: number, b: number): number => (a * b) / gcd(a, b)
|
Loading…
Reference in New Issue