Skip to content

Commit

Permalink
Merge pull request #1164 from prezly/feature/care-5671-implement-maso…
Browse files Browse the repository at this point in the history
…nry-layout-in-bea

[CARE-5671] Feature - Add support for masonry layout
  • Loading branch information
kudlajz authored Jul 4, 2024
2 parents e251fd2 + fce46f1 commit db84be4
Show file tree
Hide file tree
Showing 13 changed files with 185 additions and 40 deletions.
6 changes: 3 additions & 3 deletions app/[localeCode]/(index)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import type { Locale } from '@prezly/theme-kit-nextjs';
import { DEFAULT_PAGE_SIZE } from '@prezly/theme-kit-nextjs';
import type { Metadata } from 'next';

import { app, generatePageMetadata, routing } from '@/adapters/server';
import { Contacts } from '@/modules/Contacts';
import { FeaturedCategories } from '@/modules/FeaturedCategories';
import { Stories } from '@/modules/Stories';
import { parseNumber, parsePreviewSearchParams } from 'utils';
import { getStoryListPageSize, parseNumber, parsePreviewSearchParams } from 'utils';

interface Props {
params: {
Expand Down Expand Up @@ -43,8 +42,9 @@ export default async function StoriesIndexPage({ params, searchParams }: Props)
<>
<Stories
categoryId={searchParams.category ? parseNumber(searchParams.category) : undefined}
layout={themeSettings.layout}
localeCode={params.localeCode}
pageSize={DEFAULT_PAGE_SIZE}
pageSize={getStoryListPageSize(themeSettings.layout)}
showDate={themeSettings.show_date}
showSubtitle={themeSettings.show_subtitle}
/>
Expand Down
12 changes: 9 additions & 3 deletions app/[localeCode]/category/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { Category } from '@prezly/sdk';
import type { Locale } from '@prezly/theme-kit-nextjs';
import { DEFAULT_PAGE_SIZE } from '@prezly/theme-kit-nextjs';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';

import { app, generateCategoryPageMetadata, routing } from '@/adapters/server';
import { BroadcastTranslations } from '@/modules/Broadcast';
import { Category as CategoryIndex } from '@/modules/Category';
import { getStoryListPageSize, parsePreviewSearchParams } from 'utils';

interface Props {
params: {
localeCode: Locale.Code;
slug: NonNullable<Category.Translation['slug']>;
};
searchParams: Record<string, string>;
}

async function resolve({ localeCode, slug }: Props['params']) {
Expand All @@ -31,16 +32,21 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
return generateCategoryPageMetadata({ locale: localeCode, category });
}

export default async function CategoryPage({ params }: Props) {
export default async function CategoryPage({ params, searchParams }: Props) {
const { category, translatedCategory } = await resolve(params);
const themeSettings = await app().themeSettings();
const settings = parsePreviewSearchParams(searchParams, themeSettings);

return (
<>
<BroadcastCategoryTranslations category={category} />
<CategoryIndex
category={category}
layout={settings.layout}
pageSize={getStoryListPageSize(settings.layout)}
showDate={settings.show_date}
showSubtitle={settings.show_subtitle}
translatedCategory={translatedCategory}
pageSize={DEFAULT_PAGE_SIZE}
/>
</>
);
Expand Down
10 changes: 10 additions & 0 deletions components/StaggeredLayout/StaggeredLayout.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.container {
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: $grid-gutter-tablet;

&.desktop {
grid-template-columns: 1fr 1fr 1fr;
column-gap: $grid-gutter-desktop;
}
}
63 changes: 63 additions & 0 deletions components/StaggeredLayout/StaggeredLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
'use client';

import classNames from 'classnames';
import { Children, useMemo, useRef } from 'react';
import type { PropsWithChildren } from 'react';

import { useDevice } from '@/hooks';

import styles from './StaggeredLayout.module.scss';

export function StaggeredLayout({ children }: PropsWithChildren<{}>) {
const isLayoutInitializedRef = useRef(false);
const { isMobile, isTablet } = useDevice();

const columnCount = useMemo(() => {
if (isMobile) {
isLayoutInitializedRef.current = false;
return 0;
}

// Only use the calculated layout if we're not on mobile screen
isLayoutInitializedRef.current = true;

if (isTablet) {
return 2;
}

return 3;
}, [isMobile, isTablet]);

const childrenInColumns = useMemo(() => {
const itemsCols = new Array(columnCount);
const items = Children.toArray(children);

items.forEach((item, i) => {
const columnIndex = i % columnCount;

if (!itemsCols[columnIndex]) {
itemsCols[columnIndex] = [];
}

itemsCols[columnIndex].push(item);
});

return itemsCols;
}, [children, columnCount]);

if (!isLayoutInitializedRef.current) {
return <div>{children}</div>;
}

return (
<div
className={classNames(styles.container, {
[styles.desktop]: columnCount === 3,
})}
>
{childrenInColumns.map((columnItems, i) => (
<div key={`column-${i}`}>{columnItems}</div>
))}
</div>
);
}
1 change: 1 addition & 0 deletions components/StaggeredLayout/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { StaggeredLayout } from './StaggeredLayout';
25 changes: 18 additions & 7 deletions modules/Category/Category.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,27 @@ import type { Category as CategoryType, TranslatedCategory } from '@prezly/sdk';

import { app } from '@/adapters/server';
import { PageTitle } from '@/components/PageTitle';
import type { ThemeSettings } from 'theme-settings';

import { InfiniteStories } from '../InfiniteStories';

interface Props {
category: CategoryType;
layout: ThemeSettings['layout'];
pageSize: number;
showDate: boolean;
showSubtitle: boolean;
translatedCategory: TranslatedCategory;
}

export async function Category({ category, pageSize, translatedCategory }: Props) {
export async function Category({
category,
layout,
pageSize,
showDate,
showSubtitle,
translatedCategory,
}: Props) {
const { stories, pagination } = await app().stories({
limit: pageSize,
category,
Expand All @@ -20,20 +31,20 @@ export async function Category({ category, pageSize, translatedCategory }: Props

const newsroom = await app().newsroom();
const languageSettings = await app().languageOrDefault(translatedCategory.locale);
const settings = await app().themeSettings();

return (
<>
<PageTitle title={translatedCategory.name} subtitle={translatedCategory.description} />
<InfiniteStories
category={category}
initialStories={stories}
isCategoryList
layout={layout}
newsroomName={languageSettings.company_information.name || newsroom.name}
pageSize={pageSize}
category={category}
showDate={showDate}
showSubtitle={showSubtitle}
total={pagination.matched_records_number}
newsroomName={languageSettings.company_information.name || newsroom.name}
isCategoryList
showDate={settings.show_date}
showSubtitle={settings.show_subtitle}
/>
</>
);
Expand Down
34 changes: 19 additions & 15 deletions modules/InfiniteStories/InfiniteStories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,25 @@ import { useCallback } from 'react';

import { FormattedMessage, http, useLocale } from '@/adapters/client';
import { Button } from '@/components/Button';
import type { ThemeSettings } from 'theme-settings';
import type { ListStory } from 'types';

import { StoriesList } from './StoriesList';

import styles from './InfiniteStories.module.scss';

type Props = {
newsroomName: string;
initialStories: ListStory[];
pageSize: number;
total: number;
category?: Pick<Category, 'id'>;
categories?: Category[];
isCategoryList?: boolean;
category?: Pick<Category, 'id'>;
excludedStoryUuids?: Story['uuid'][];
initialStories: ListStory[];
isCategoryList?: boolean;
layout: ThemeSettings['layout'];
newsroomName: string;
pageSize: number;
showDate: boolean;
showSubtitle: boolean;
total: number;
};

function fetchStories(
Expand All @@ -43,16 +45,17 @@ function fetchStories(
}

export function InfiniteStories({
newsroomName,
initialStories,
pageSize,
total,
category,
categories,
isCategoryList,
category,
excludedStoryUuids,
initialStories,
isCategoryList,
layout,
newsroomName,
pageSize,
showDate,
showSubtitle,
total,
}: Props) {
const locale = useLocale();
const { load, loading, data, done } = useInfiniteLoading(
Expand All @@ -66,13 +69,14 @@ export function InfiniteStories({
return (
<div>
<StoriesList
newsroomName={newsroomName}
stories={data}
category={category}
categories={categories}
category={category}
isCategoryList={isCategoryList}
layout={layout}
newsroomName={newsroomName}
showDate={showDate}
showSubtitle={showSubtitle}
stories={data}
/>

{!done && (
Expand Down
30 changes: 23 additions & 7 deletions modules/InfiniteStories/StoriesList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { translations } from '@prezly/theme-kit-nextjs';
import { useMemo } from 'react';

import { FormattedMessage, useLocale } from '@/adapters/client';
import { StaggeredLayout } from '@/components/StaggeredLayout';
import { HighlightedStoryCard, StoryCard } from '@/components/StoryCards';
import type { ListStory } from 'types';

Expand All @@ -16,23 +17,25 @@ import Illustration from '@/public/images/no-stories-illustration.svg';
import styles from './StoriesList.module.scss';

type Props = {
newsroomName: string;
stories: ListStory[];
category?: Pick<Category, 'id'>;
categories?: Category[];
category?: Pick<Category, 'id'>;
isCategoryList?: boolean;
layout?: 'grid' | 'masonry';
newsroomName: string;
showDate: boolean;
showSubtitle: boolean;
stories: ListStory[];
};

export function StoriesList({
newsroomName,
stories,
category,
categories = [],
category,
isCategoryList = false,
layout = 'grid',
newsroomName,
showDate,
showSubtitle,
stories,
}: Props) {
const locale = useLocale();
const hasCategories = categories.length > 0;
Expand Down Expand Up @@ -87,7 +90,7 @@ export function StoriesList({
locale={locale}
/>
)}
{restStories.length > 0 && (
{restStories.length > 0 && layout === 'grid' && (
<div className={styles.storiesContainer}>
{restStories.map((story, index) => (
<StoryCard
Expand All @@ -100,6 +103,19 @@ export function StoriesList({
))}
</div>
)}
{restStories.length > 0 && layout === 'masonry' && (
<StaggeredLayout>
{restStories.map((story) => (
<StoryCard
key={story.uuid}
story={story}
size="medium"
showDate={showDate}
showSubtitle={showSubtitle}
/>
))}
</StaggeredLayout>
)}
</>
);
}
20 changes: 15 additions & 5 deletions modules/Stories/Stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,27 @@ import type { Category } from '@prezly/sdk';
import type { Locale } from '@prezly/theme-kit-nextjs';

import { app } from '@/adapters/server';
import type { ThemeSettings } from 'theme-settings';

import { InfiniteStories } from '../InfiniteStories';

interface Props {
categoryId: Category['id'] | undefined;
layout: ThemeSettings['layout'];
localeCode: Locale.Code;
pageSize: number;
showDate: boolean;
showSubtitle: boolean;
}

export async function Stories({ categoryId, localeCode, pageSize, showDate, showSubtitle }: Props) {
export async function Stories({
categoryId,
layout,
localeCode,
pageSize,
showDate,
showSubtitle,
}: Props) {
const newsroom = await app().newsroom();
const languageSettings = await app().languageOrDefault(localeCode);

Expand All @@ -26,15 +35,16 @@ export async function Stories({ categoryId, localeCode, pageSize, showDate, show
return (
<InfiniteStories
key={categoryId}
category={categoryId ? { id: categoryId } : undefined}
categories={categories}
category={categoryId ? { id: categoryId } : undefined}
excludedStoryUuids={excludedStoryUuids}
initialStories={stories}
layout={layout}
newsroomName={languageSettings.company_information.name || newsroom.name}
pageSize={pageSize}
initialStories={stories}
total={pagination.matched_records_number}
excludedStoryUuids={excludedStoryUuids}
showDate={showDate}
showSubtitle={showSubtitle}
total={pagination.matched_records_number}
/>
);
}
Expand Down
Loading

0 comments on commit db84be4

Please sign in to comment.