Skip to content

Commit

Permalink
Merge pull request #1116 from prezly/feature/dev-12659-develop-featur…
Browse files Browse the repository at this point in the history
…ed-categories-homepage-block

[DEV-12659] Develop featured categories homepage block
  • Loading branch information
kudlajz authored Mar 22, 2024
2 parents 01ccb03 + dcd39d3 commit 2e8965e
Show file tree
Hide file tree
Showing 17 changed files with 349 additions and 54 deletions.
7 changes: 7 additions & 0 deletions app/[localeCode]/(index)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { Locale } from '@prezly/theme-kit-nextjs';
import type { ReactNode } from 'react';

import { app } from '@/adapters/server';
import { Contacts } from '@/modules/Contacts';
import { FeaturedCategories } from '@/modules/FeaturedCategories';

interface Props {
children: ReactNode;
Expand All @@ -11,10 +13,15 @@ interface Props {
}

export default async function HomepageLayout({ params, children }: Props) {
const settings = await app().themeSettings();

return (
<>
{children}
<Contacts localeCode={params.localeCode} />
{settings.show_featured_categories && (
<FeaturedCategories localeCode={params.localeCode} />
)}
</>
);
}
9 changes: 4 additions & 5 deletions components/CategoryImage/CategoryImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,20 @@ import { type CardSize, getCardImageSizes } from './lib';
import styles from './CategoryImage.module.scss';

type Props = {
category: Category;
translatedCategory: TranslatedCategory;
image: Category['image'];
name: TranslatedCategory['name'];
size: CardSize;
className?: string;
};

export function CategoryImage({ category, translatedCategory, size, className }: Props) {
const { image } = category;
export function CategoryImage({ image, name, size, className }: Props) {
const fallback = useFallback();

if (image) {
return (
<Image
imageDetails={image}
alt={translatedCategory.name}
alt={name}
layout="fill"
objectFit="cover"
containerClassName={classNames(styles.imageContainer, className)}
Expand Down
4 changes: 4 additions & 0 deletions icons/generic/arrow-right.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions icons/generic/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { default as IconArrowRight } from './arrow-right.svg';
export { default as IconCaret } from './caret.svg';
export { default as IconClose } from './close.svg';
export { default as IconDownload } from './download.svg';
Expand Down
25 changes: 0 additions & 25 deletions modules/Category/Category.module.scss

This file was deleted.

23 changes: 1 addition & 22 deletions modules/Category/Category.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import type { Category as CategoryType, TranslatedCategory } from '@prezly/sdk';
import Image from '@prezly/uploadcare-image';

import { app } from '@/adapters/server';
import { PageTitle } from '@/components/PageTitle';

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

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

interface Props {
category: CategoryType;
pageSize: number;
Expand All @@ -26,25 +23,7 @@ export async function Category({ category, pageSize, translatedCategory }: Props

return (
<>
<div className={styles.header}>
<PageTitle
className={styles.title}
title={translatedCategory.name}
subtitle={translatedCategory.description}
/>
{category.image && (
<div className={styles.imageContainer}>
<Image
imageDetails={category.image}
alt={translatedCategory.name}
layout="fill"
objectFit="cover"
sizes={{ default: 180 }}
className={styles.image}
/>
</div>
)}
</div>
<PageTitle title={translatedCategory.name} subtitle={translatedCategory.description} />
<InfiniteStories
initialStories={stories}
pageSize={pageSize}
Expand Down
28 changes: 28 additions & 0 deletions modules/FeaturedCategories/FeaturedCategories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { Locale } from '@prezly/theme-kit-nextjs';

import { app } from '@/adapters/server';

import * as ui from './ui';

interface Props {
localeCode: Locale.Code;
}

export async function FeaturedCategories({ localeCode }: Props) {
const categories = await app().categories();
const translatedCategories = await app().translatedCategories(
localeCode,
categories.filter((i) => i.is_featured && i.i18n[localeCode]?.public_stories_number > 0),
);

if (translatedCategories.length === 0) {
return null;
}

return (
<ui.FeaturedCategories
categories={categories}
translatedCategories={translatedCategories}
/>
);
}
1 change: 1 addition & 0 deletions modules/FeaturedCategories/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { FeaturedCategories } from './FeaturedCategories';
32 changes: 32 additions & 0 deletions modules/FeaturedCategories/ui/CategoryImage.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.imageContainer {
display: block;
height: 100%;
}

.placeholder {
background-color: var(--prezly-accent-color);
}

.image, .placeholder {
@include border-radius-s;

height: 150px;
}

.imageContainer, .placeholder {
@include border-radius-s;

position: relative;

&::after {
@include border-radius-s;

background: black;
opacity: .5;
content: "";
position: absolute;
height: 100%;
width: 100%;
top: 0;
}
}
45 changes: 45 additions & 0 deletions modules/FeaturedCategories/ui/CategoryImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use client';

import type { Category, TranslatedCategory } from '@prezly/sdk';
import Image from '@prezly/uploadcare-image';
import classNames from 'classnames';

import { useThemeSettings } from '@/adapters/client';

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

type Props = {
image: Category['image'];
name: TranslatedCategory['name'];
className?: string;
};

export function CategoryImage({ image, name, className }: Props) {
const { accent_color } = useThemeSettings();

if (image) {
return (
<Image
imageDetails={image}
alt={name}
layout="fill"
objectFit="cover"
containerClassName={classNames(styles.imageContainer, className)}
className={styles.image}
sizes={{
mobile: 420,
tablet: 350,
desktop: 600,
default: 600,
}}
/>
);
}

return (
<div
className={classNames(className, styles.placeholder)}
style={{ backgroundColor: accent_color }}
/>
);
}
63 changes: 63 additions & 0 deletions modules/FeaturedCategories/ui/FeaturedCategories.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
.categories {
margin-top: $spacing-8;
padding-top: $spacing-8;

&::before {
content: '';
display: block;
border-top: 1px solid $color-borders;
position: absolute;
left: 0;
right: 0;
translate: 0 (-$spacing-8);
}
}

.title {
@include heading-1;

margin: 0 auto $spacing-6;
text-align: left;
color: $color-base-700;
}

.container {
display: flex;
flex-flow: row wrap;
gap: $spacing-3;
}

.item {
@include mobile-only {
flex: 1 1 calc(100% - $spacing-3);
}

@include tablet-only {
flex: 1 0 calc(33% - $spacing-3);
max-width: calc(50% - $spacing-3 / 2);
}
}

.oneColumn {
@include desktop-up {
flex: 1 0 calc(100% - $spacing-3);
}
}

.twoColumns {
@include desktop-up {
flex: 1 0 calc(50% - $spacing-3);
}
}

.threeColumns {
@include desktop-up {
flex: 1 0 calc(33% - $spacing-3);
}
}

.fourColumns {
@include desktop-up {
flex: 1 0 calc(25% - $spacing-3);
}
}
80 changes: 80 additions & 0 deletions modules/FeaturedCategories/ui/FeaturedCategories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use client';

import type { Category, TranslatedCategory } from '@prezly/sdk';
import { translations } from '@prezly/theme-kit-nextjs';
import classNames from 'classnames';

import { FormattedMessage, useLocale } from '@/adapters/client';

import { FeaturedCategory } from './FeaturedCategory';

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

interface Props {
categories: Category[];
translatedCategories: TranslatedCategory[];
}

export function FeaturedCategories({ categories, translatedCategories }: Props) {
function getCategory(translatedCategory: TranslatedCategory) {
return categories.find((i) => i.id === translatedCategory.id)!;
}

const locale = useLocale();

return (
<div className={styles.categories}>
<h2 className={styles.title}>
<FormattedMessage locale={locale} for={translations.categories.title} />
</h2>
<div className={styles.container}>
{translatedCategories.map((translatedCategory, i) => (
<FeaturedCategory
key={translatedCategory.id}
className={classNames(
styles.item,
getColumnWidth(i + 1, translatedCategories.length),
)}
image={getCategory(translatedCategory).image}
name={translatedCategory.name}
slug={translatedCategory.slug}
locale={locale}
/>
))}
</div>
</div>
);
}

function getColumnWidth(i: number, total: number) {
if (total <= 1) {
return styles.oneColumn;
}

if (total === 2) {
return styles.twoColumns;
}

if (total % 3 === 0) {
return styles.threeColumns;
}

if (total % 4 === 0) {
return styles.fourColumns;
}

// Only lay out a row of 4 if it can be followed by a row of 3.
if (total - 4 >= 3) {
if (i <= 4) {
return styles.fourColumns;
}

return getColumnWidth(i - 4, total - 4);
}

if (i <= 3) {
return styles.threeColumns;
}

return getColumnWidth(i - 3, total - 3);
}
Loading

0 comments on commit 2e8965e

Please sign in to comment.