Skip to content

Commit

Permalink
Merge pull request #1181 from prezly/feature/dev-13393-implement-full…
Browse files Browse the repository at this point in the history
…-width-hero-card

[DEV-13393] Implement Lena-style hero card
  • Loading branch information
kudlajz authored Aug 22, 2024
2 parents 98e8f25 + df6f666 commit cd3c42a
Show file tree
Hide file tree
Showing 13 changed files with 225 additions and 30 deletions.
1 change: 1 addition & 0 deletions app/[localeCode]/(index)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export default async function StoriesIndexPage({ params, searchParams }: Props)
<>
<Stories
categoryId={searchParams.category ? parseNumber(searchParams.category) : undefined}
fullWidthFeaturedStory={themeSettings.full_width_featured_story}
layout={themeSettings.layout}
localeCode={params.localeCode}
pageSize={getStoryListPageSize(themeSettings.layout)}
Expand Down
8 changes: 6 additions & 2 deletions components/CategoriesBar/CategoriesBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export function CategoriesBar({ translatedCategories }: Props) {
key={category.id}
href={{
routeName: 'category',
params: { slug: category.slug },
params: { localeCode: locale, slug: category.slug },
}}
className={classNames(styles.link, {
[styles.active]: category.slug === params.slug,
Expand All @@ -87,7 +87,11 @@ export function CategoriesBar({ translatedCategories }: Props) {
buttonClassName={styles.more}
>
{hiddenCategories.map((category) => (
<CategoryItem category={category} key={category.id} />
<CategoryItem
key={category.id}
category={category}
localeCode={locale}
/>
))}
</Dropdown>
)}
Expand Down
8 changes: 5 additions & 3 deletions components/CategoriesBar/CategoryItem/CategoryItem.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import type { TranslatedCategory } from '@prezly/sdk';
import type { Locale } from '@prezly/theme-kit-nextjs';

import { DropdownItem } from '@/components/Dropdown';

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

type Props = {
category: TranslatedCategory;
localeCode: Locale.Code;
};

export function CategoryItem({ category }: Props) {
const { name, description } = category;
export function CategoryItem({ category, localeCode }: Props) {
const { name, description, slug } = category;

return (
<DropdownItem href={{ routeName: 'category', params: { slug: category.slug } }}>
<DropdownItem href={{ routeName: 'category', params: { localeCode, slug } }}>
<span className={styles.title}>{name}</span>
{description && <span className={styles.description}>{description}</span>}
</DropdownItem>
Expand Down
116 changes: 116 additions & 0 deletions components/StoryCards/HighlightedStoryCard.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
.container {
position: relative;
display: flex;
align-items: flex-end;
min-height: 500px;
margin: (-$spacing-8) (-$spacing-5) $spacing-7;

@include tablet-up {
height: 500px;
}

@include desktop-up {
height: 700px;
margin: 0 0 $spacing-8;

&:hover .image {
transform: scale(1.05);
}

&.rounded {
@include border-radius-m;

overflow: hidden;
}
}
}

.link {
text-decoration: none;

.mask {
position: absolute;
inset: 0;
}
}

.overlay {
position: absolute;
inset: 0;
background: linear-gradient(270deg, rgb(3 7 18 / 0%) 50%, rgb(3 7 18 / 40%) 100%), linear-gradient(180deg, rgb(3 7 18 / 0%) 40%, rgb(3 7 18 / 20%) 62%, rgb(3 7 18 / 40%) 100%);
}

.image {
position: absolute;
inset: 0;
z-index: -1;

@include desktop-up {
transform: scale(1);
transition: transform 0.25s ease-in-out;
}
}

.content {
padding: $spacing-8 $spacing-4;
z-index: 2;

@include tablet-up {
padding: $spacing-8 $spacing-5;
}

@include desktop-up {
max-width: 600px;
padding: $spacing-8;
box-sizing: content-box;
}
}

.categories {
display: flex;
gap: $spacing-1;
margin-bottom: $spacing-3;

.link {
z-index: 2;
}

.badge {
color: var(--prezly-white);
background-color: color-mix(in srgb, var(--prezly-white) 20%, transparent);
backdrop-filter: blur(8px);
}
}

.title {
@include ensure-max-text-height(3, 140%);

color: var(--prezly-white);
font-size: $font-size-xl;
font-weight: $font-weight-bold;
margin: 0;

&.expanded {
@include ensure-max-text-height(4, 140%);
}

@include tablet-up {
font-size: 2.25rem;
line-height: 140%;
}
}

.subtitle {
@include ensure-max-text-height(3, 160%);

margin: 0;
margin-top: $spacing-2;
color: var(--prezly-white);
font-size: $font-size-s;
font-weight: $font-weight-regular;
line-height: 160%;

@include tablet-up {
font-size: $font-size-m;
}
}
70 changes: 62 additions & 8 deletions components/StoryCards/HighlightedStoryCard.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,81 @@
'use client';

import { Category } from '@prezly/sdk';
import { useMemo } from 'react';
import classNames from 'classnames';

import { useLocale } from '@/adapters/client';
import type { ListStory } from 'types';

import { Badge } from '../Badge';
import { Link } from '../Link';
import { StoryImage } from '../StoryImage';

import { StoryCard } from './StoryCard';

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

type Props = {
fullWidth: boolean;
rounded: boolean;
showDate: boolean;
showSubtitle: boolean;
story: ListStory;
};

export function HighlightedStoryCard({ showDate, showSubtitle, story }: Props) {
const localeCode = useLocale();
const { categories } = story;
export function HighlightedStoryCard({ fullWidth, rounded, showDate, showSubtitle, story }: Props) {
const locale = useLocale();
const { categories, slug, subtitle } = story;

const translatedCategories = useMemo(
() => Category.translations(categories, localeCode),
[categories, localeCode],
);
const translatedCategories = Category.translations(categories, locale);

if (fullWidth) {
return (
<div
className={classNames(styles.container, {
[styles.rounded]: rounded,
})}
>
<StoryImage
size="full-width"
className={styles.image}
thumbnailImage={story.thumbnail_image}
title={story.title}
/>
<div className={styles.overlay} />
<div className={styles.content}>
{translatedCategories.length > 0 && (
<div className={styles.categories}>
{translatedCategories.map((category) => (
<Link
className={styles.link}
href={{
routeName: 'category',
params: { localeCode: locale, slug: category.slug },
}}
key={category.id}
>
<Badge className={styles.badge} size="small">
{category.name}
</Badge>
</Link>
))}
</div>
)}
<Link className={styles.link} href={{ routeName: 'story', params: { slug } }}>
<h2
className={classNames(styles.title, {
[styles.expanded]: !showSubtitle || !subtitle,
})}
>
{story.title}
<span className={styles.mask} />
</h2>
</Link>
{showSubtitle && subtitle && <p className={styles.subtitle}>{subtitle}</p>}
</div>
</div>
);
}

return (
<StoryCard
Expand Down
2 changes: 0 additions & 2 deletions components/StoryCards/StoryCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
'use client';

import type { TranslatedCategory } from '@prezly/sdk';
import classNames from 'classnames';
import type { ReactNode } from 'react';
Expand Down
4 changes: 2 additions & 2 deletions components/StoryImage/StoryImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import type { ListStory } from 'types';
import { getUploadcareImage } from 'utils';

import { useFallback } from './FallbackProvider';
import { type CardSize, getCardImageSizes, getStoryThumbnail } from './lib';
import { getCardImageSizes, getStoryThumbnail, type ImageSize } from './lib';

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

type Props = {
className?: string;
isStatic?: boolean;
placeholderClassName?: string;
size: CardSize;
size: ImageSize;
thumbnailImage: ListStory['thumbnail_image'];
title: string;
};
Expand Down
28 changes: 16 additions & 12 deletions components/StoryImage/lib.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Story } from '@prezly/sdk';
import type { UploadcareImageDetails } from '@prezly/uploadcare-image/build/types';

export type CardSize = 'small' | 'medium' | 'big' | 'hero' | 'tiny';
export type ImageSize = 'small' | 'medium' | 'big' | 'hero' | 'full-width' | 'tiny';

export function getStoryThumbnail(
thumbnailImage: Story.ExtraFields['thumbnail_image'],
Expand All @@ -13,29 +13,31 @@ export function getStoryThumbnail(
return null;
}

export function getCardImageSizes(cardSize: CardSize) {
if (cardSize === 'tiny') {
export function getCardImageSizes(imageSize: ImageSize) {
if (imageSize === 'tiny') {
return '120px';
}

return [
`(max-width: 767px) ${getPhoneImageSize(cardSize)}`,
`(max-width: 1023px) ${getTabletImageSize(cardSize)}`,
getDesktopImageSize(cardSize),
`(max-width: 767px) ${getPhoneImageSize(imageSize)}`,
`(max-width: 1023px) ${getTabletImageSize(imageSize)}`,
getDesktopImageSize(imageSize),
].join(', ');
}

function getPhoneImageSize(cardSize: CardSize) {
switch (cardSize) {
function getPhoneImageSize(imageSize: ImageSize) {
switch (imageSize) {
case 'full-width':
case 'hero':
return '100w';
default:
return '95w';
}
}

function getTabletImageSize(cardSize: CardSize) {
switch (cardSize) {
function getTabletImageSize(imageSize: ImageSize) {
switch (imageSize) {
case 'full-width':
case 'hero':
return '100w';
case 'small':
Expand All @@ -45,8 +47,10 @@ function getTabletImageSize(cardSize: CardSize) {
}
}

function getDesktopImageSize(cardSize: CardSize) {
switch (cardSize) {
function getDesktopImageSize(imageSize: ImageSize) {
switch (imageSize) {
case 'full-width':
return '1200px';
case 'medium':
return '370px';
case 'small':
Expand Down
3 changes: 3 additions & 0 deletions modules/InfiniteStories/InfiniteStories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type Props = {
categories?: Category[];
category?: Pick<Category, 'id'>;
excludedStoryUuids?: Story['uuid'][];
fullWidthFeaturedStory?: boolean;
initialStories: ListStory[];
isCategoryList?: boolean;
layout: ThemeSettings['layout'];
Expand Down Expand Up @@ -49,6 +50,7 @@ export function InfiniteStories({
categories,
category,
excludedStoryUuids,
fullWidthFeaturedStory = false,
initialStories,
isCategoryList,
layout,
Expand All @@ -73,6 +75,7 @@ export function InfiniteStories({
<StoriesList
categories={categories}
category={category}
fullWidthFeaturedStory={fullWidthFeaturedStory}
isCategoryList={isCategoryList}
layout={layout}
newsroomName={newsroomName}
Expand Down
Loading

0 comments on commit cd3c42a

Please sign in to comment.