Skip to content

Commit 8388404

Browse files
authored
feat(home): add hero shelf type and deprecate content.featured (#638)
1 parent b73ae54 commit 8388404

File tree

13 files changed

+138
-77
lines changed

13 files changed

+138
-77
lines changed

packages/common/src/constants.ts

+9
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,15 @@ export const PLAYLIST_CONTENT_TYPE = {
5252
live: 'live',
5353
} as const;
5454

55+
// Some predefined shelf types of JW
56+
export const SHELF_LAYOUT_TYPE = {
57+
// Fullwidth hero, only available as the first shelf (index === 0)
58+
hero: 'hero',
59+
// Larger cards
60+
featured: 'featured',
61+
// By default: standard size cards (default)
62+
} as const;
63+
5564
// OTT shared player
5665
export const OTT_GLOBAL_PLAYER_ID = 'M4qoGvUk';
5766

packages/common/types/config.ts

+3
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ export type Content = {
5151
contentId?: string;
5252
title?: string;
5353
type: AppShelfType;
54+
/**
55+
* @deprecated Use the custom shelf property `layoutType = 'hero' | 'featured' | undefined` instead
56+
*/
5457
featured?: boolean;
5558
backgroundColor?: string | null;
5659
custom?: Record<string, string>;

packages/ui-react/src/components/FeaturedShelf/FeaturedShelf.module.scss packages/ui-react/src/components/HeroShelf/HeroShelf.module.scss

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ $mobile-landscape-height: 100vh;
3131
font-size: 18px;
3232
line-height: 1.5em;
3333
letter-spacing: 0.5px;
34-
background-color: var(--featured-shelf-background-color);
34+
background-color: var(--hero-shelf-background-color);
3535

3636
&:hover .chevron:not(:disabled) {
3737
opacity: 0.8;
@@ -96,7 +96,7 @@ $mobile-landscape-height: 100vh;
9696
width: 100%;
9797
height: 56.25vw;
9898
max-height: 700px;
99-
background-color: var(--featured-shelf-background-color);
99+
background-color: var(--hero-shelf-background-color);
100100

101101
@include responsive.tablet-only() {
102102
height: $tablet-height;

packages/ui-react/src/components/FeaturedShelf/FeaturedShelf.tsx packages/ui-react/src/components/HeroShelf/HeroShelf.tsx

+15-28
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import React, { useCallback, useEffect, useState, type CSSProperties, type TransitionEventHandler } from 'react';
2-
import type { PosterAspectRatio } from '@jwp/ott-common/src/utils/collection';
3-
import type { AccessModel } from '@jwp/ott-common/types/config';
4-
import type { Playlist, PlaylistItem } from '@jwp/ott-common/types/playlist';
2+
import type { Playlist } from '@jwp/ott-common/types/playlist';
53
import classNames from 'classnames';
64
import { useTranslation } from 'react-i18next';
75
import ChevronLeft from '@jwp/ott-theme/assets/icons/chevron_left.svg?react';
@@ -11,30 +9,19 @@ import { useScrolledDown } from '../../hooks/useScrolledDown';
119
import Icon from '../Icon/Icon';
1210
import useBreakpoint, { Breakpoint } from '../../hooks/useBreakpoint';
1311

14-
import styles from './FeaturedShelf.module.scss';
15-
import FeaturedMetadata from './FeaturedMetadata';
16-
import FeaturedBackground from './FeaturedBackground';
17-
import FeaturedPagination from './FeaturedPagination';
18-
import FeaturedMetadataMobile from './FeaturedMetadataMobile';
12+
import styles from './HeroShelf.module.scss';
13+
import HeroShelfMetadata from './HeroShelfMetadata';
14+
import HeroShelfBackground from './HeroShelfBackground';
15+
import HeroShelfPagination from './HeroShelfPagination';
16+
import HeroShelfMetadataMobile from './HeroShelfMetadataMobile';
1917

2018
type Props = {
2119
playlist: Playlist;
22-
onCardHover?: (playlistItem: PlaylistItem) => void;
23-
watchHistory?: { [key: string]: number };
24-
enableTitle?: boolean;
25-
enableCardTitles?: boolean;
26-
featured?: boolean;
2720
loading?: boolean;
2821
error?: unknown;
29-
title?: string;
30-
accessModel: AccessModel;
31-
isLoggedIn: boolean;
32-
hasSubscription: boolean;
33-
posterAspect?: PosterAspectRatio;
34-
visibleTilesDelta?: number;
3522
};
3623

37-
const FeaturedShelf = ({ playlist, loading = false, error = null }: Props) => {
24+
const HeroShelf = ({ playlist, loading = false, error = null }: Props) => {
3825
const [index, setIndex] = useState(0);
3926
const [nextIndex, setNextIndex] = useState(0);
4027
const { t } = useTranslation('common');
@@ -123,14 +110,14 @@ const FeaturedShelf = ({ playlist, loading = false, error = null }: Props) => {
123110
<div className={classNames(styles.shelf)}>
124111
<div className={classNames(styles.poster, styles.undimmed, { [styles.dimmed]: scrolledDown })}>
125112
<div className={styles.background} id="background">
126-
<FeaturedBackground
113+
<HeroShelfBackground
127114
item={leftItem}
128115
style={backgroundAltStyle}
129116
key={renderedItem?.mediaid === leftItem?.mediaid ? 'left-item' : leftItem?.mediaid}
130117
hidden={direction !== 'left'}
131118
/>
132-
<FeaturedBackground item={renderedItem} style={backgroundCurrentStyle} key={renderedItem?.mediaid} onTransitionEnd={handleBackgroundAnimationEnd} />
133-
<FeaturedBackground
119+
<HeroShelfBackground item={renderedItem} style={backgroundCurrentStyle} key={renderedItem?.mediaid} onTransitionEnd={handleBackgroundAnimationEnd} />
120+
<HeroShelfBackground
134121
item={rightItem}
135122
style={backgroundAltStyle}
136123
key={renderedItem?.mediaid === rightItem?.mediaid ? 'right-item' : rightItem?.mediaid}
@@ -149,7 +136,7 @@ const FeaturedShelf = ({ playlist, loading = false, error = null }: Props) => {
149136
<Icon icon={ChevronLeft} />
150137
</button>
151138
{isMobile ? (
152-
<FeaturedMetadataMobile
139+
<HeroShelfMetadataMobile
153140
loading={loading}
154141
item={item}
155142
rightItem={rightItem}
@@ -161,8 +148,8 @@ const FeaturedShelf = ({ playlist, loading = false, error = null }: Props) => {
161148
/>
162149
) : (
163150
<>
164-
<FeaturedMetadata item={renderedItem} loading={loading} playlistId={playlist.feedid} style={metadataCurrentStyle} />
165-
<FeaturedMetadata item={altItem} loading={loading} playlistId={playlist.feedid} style={metadataAltStyle} hidden={!direction} />
151+
<HeroShelfMetadata item={renderedItem} loading={loading} playlistId={playlist.feedid} style={metadataCurrentStyle} />
152+
<HeroShelfMetadata item={altItem} loading={loading} playlistId={playlist.feedid} style={metadataAltStyle} hidden={!direction} />
166153
</>
167154
)}
168155
<button
@@ -173,7 +160,7 @@ const FeaturedShelf = ({ playlist, loading = false, error = null }: Props) => {
173160
>
174161
<Icon icon={ChevronRight} />
175162
</button>
176-
<FeaturedPagination
163+
<HeroShelfPagination
177164
className={scrolledDown ? styles.dimmed : undefined}
178165
playlist={playlist}
179166
index={index}
@@ -185,4 +172,4 @@ const FeaturedShelf = ({ playlist, loading = false, error = null }: Props) => {
185172
);
186173
};
187174

188-
export default FeaturedShelf;
175+
export default HeroShelf;

packages/ui-react/src/components/FeaturedShelf/FeaturedBackground.tsx packages/ui-react/src/components/HeroShelf/HeroShelfBackground.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import React from 'react';
44

55
import Image from '../Image/Image';
66

7-
import styles from './FeaturedShelf.module.scss';
7+
import styles from './HeroShelf.module.scss';
88

9-
const FeaturedBackground = ({
9+
const HeroShelfBackground = ({
1010
item,
1111
style,
1212
hidden,
@@ -33,4 +33,4 @@ const FeaturedBackground = ({
3333
);
3434
};
3535

36-
export default React.memo(FeaturedBackground);
36+
export default React.memo(HeroShelfBackground);

packages/ui-react/src/components/FeaturedShelf/FeaturedMetadata.tsx packages/ui-react/src/components/HeroShelf/HeroShelfMetadata.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ import StartWatchingButton from '../../containers/StartWatchingButton/StartWatch
1313
import Button from '../Button/Button';
1414
import Icon from '../Icon/Icon';
1515

16-
import styles from './FeaturedShelf.module.scss';
16+
import styles from './HeroShelf.module.scss';
1717

18-
const FeaturedMetadata = ({
18+
const HeroShelfMetadata = ({
1919
item,
2020
loading,
2121
playlistId,
@@ -44,7 +44,7 @@ const FeaturedMetadata = ({
4444
return (
4545
<div
4646
className={styles.metadata}
47-
data-testid={testId(`featured-metadata--${hidden ? 'hidden' : 'visible'}`)}
47+
data-testid={testId(`shelf-hero-metadata--${hidden ? 'hidden' : 'visible'}`)}
4848
style={{ ...style, visibility: hidden ? 'hidden' : undefined }}
4949
aria-hidden={hidden ? 'true' : undefined}
5050
>
@@ -65,4 +65,4 @@ const FeaturedMetadata = ({
6565
);
6666
};
6767

68-
export default FeaturedMetadata;
68+
export default HeroShelfMetadata;

packages/ui-react/src/components/FeaturedShelf/FeaturedMetadataMobile.tsx packages/ui-react/src/components/HeroShelf/HeroShelfMetadataMobile.tsx

+7-7
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import type { PlaylistItem } from '@jwp/ott-common/types/playlist';
22
import useEventCallback from '@jwp/ott-hooks-react/src/useEventCallback';
33
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
44

5-
import FeaturedMetadata from './FeaturedMetadata';
6-
import styles from './FeaturedShelf.module.scss';
5+
import HeroShelfMetadata from './HeroShelfMetadata';
6+
import styles from './HeroShelf.module.scss';
77

88
type Props = {
99
item: PlaylistItem;
@@ -16,7 +16,7 @@ type Props = {
1616
onSlideRight: () => void;
1717
};
1818

19-
const FeaturedMetadataMobile = ({ item, leftItem, rightItem, playlistId, loading, direction, onSlideLeft, onSlideRight }: Props) => {
19+
const HeroShelfMetadataMobile = ({ item, leftItem, rightItem, playlistId, loading, direction, onSlideLeft, onSlideRight }: Props) => {
2020
const movementRef = useRef({ x: 0, y: 0, start: Date.now() });
2121
const containerRef = useRef<HTMLDivElement | null>(null);
2222
const [swipeAction, setSwipeAction] = useState<'slide' | 'scroll' | null>(null);
@@ -103,16 +103,16 @@ const FeaturedMetadataMobile = ({ item, leftItem, rightItem, playlistId, loading
103103

104104
return (
105105
<div ref={containerRef} className={styles.metadataMobile}>
106-
<FeaturedMetadata
106+
<HeroShelfMetadata
107107
loading={loading}
108108
item={leftItem}
109109
playlistId={playlistId}
110110
style={{ left: '-100%' }}
111111
hidden={direction !== 'left' && swipeAction !== 'slide'}
112112
isMobile
113113
/>
114-
<FeaturedMetadata loading={loading} item={item} playlistId={playlistId} isMobile />
115-
<FeaturedMetadata
114+
<HeroShelfMetadata loading={loading} item={item} playlistId={playlistId} isMobile />
115+
<HeroShelfMetadata
116116
loading={loading}
117117
item={rightItem}
118118
playlistId={playlistId}
@@ -124,4 +124,4 @@ const FeaturedMetadataMobile = ({ item, leftItem, rightItem, playlistId, loading
124124
);
125125
};
126126

127-
export default FeaturedMetadataMobile;
127+
export default HeroShelfMetadataMobile;

packages/ui-react/src/components/FeaturedShelf/FeaturedPagination.tsx packages/ui-react/src/components/HeroShelf/HeroShelfPagination.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { Playlist } from '@jwp/ott-common/types/playlist';
33
import { useTranslation } from 'react-i18next';
44
import { useMemo } from 'react';
55

6-
import styles from './FeaturedShelf.module.scss';
6+
import styles from './HeroShelf.module.scss';
77

88
const calculateDotSize = (direction: 'left' | 'right' | false, itemIndex: number, index: number, range: number, sizeSmall: number) => {
99
const isAnimatingLeft = direction === 'left';
@@ -38,7 +38,7 @@ type Props = {
3838
className?: string;
3939
};
4040

41-
const FeaturedPagination = ({
41+
const HeroShelfPagination = ({
4242
playlist,
4343
index: indexIn,
4444
direction,
@@ -110,4 +110,4 @@ const FeaturedPagination = ({
110110
);
111111
};
112112

113-
export default FeaturedPagination;
113+
export default HeroShelfPagination;

packages/ui-react/src/containers/ShelfList/ShelfList.module.scss

+13-1
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,27 @@
1111
position: relative;
1212
padding: calc(variables.$base-spacing * 2) calc(variables.$base-spacing * 3);
1313

14-
&.featured {
14+
&.hero {
1515
padding: 0;
1616
}
1717

18+
&.featured {
19+
padding: 12px 20%;
20+
}
21+
1822
@include responsive.mobile-only() {
1923
padding: 8px 60px 8px variables.$base-spacing;
24+
25+
&.featured {
26+
padding: 24px variables.$base-spacing;
27+
}
2028
}
2129

2230
@include responsive.tablet-only() {
2331
padding: 24px calc(#{variables.$base-spacing} * 2);
32+
33+
&.featured {
34+
padding: 24px 10%;
35+
}
2436
}
2537
}

packages/ui-react/src/containers/ShelfList/ShelfList.tsx

+25-22
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ import { useConfigStore } from '@jwp/ott-common/src/stores/ConfigStore';
99
import { useWatchHistoryStore } from '@jwp/ott-common/src/stores/WatchHistoryStore';
1010
import { slugify } from '@jwp/ott-common/src/utils/urlFormatting';
1111
import { parseAspectRatio, parseTilesDelta } from '@jwp/ott-common/src/utils/collection';
12-
import { isTruthyCustomParamValue, testId } from '@jwp/ott-common/src/utils/common';
13-
import { PersonalShelf } from '@jwp/ott-common/src/constants';
12+
import { testId } from '@jwp/ott-common/src/utils/common';
13+
import { PersonalShelf, SHELF_LAYOUT_TYPE } from '@jwp/ott-common/src/constants';
1414
import usePlaylists from '@jwp/ott-hooks-react/src/usePlaylists';
1515

1616
import Shelf from '../../components/Shelf/Shelf';
1717
import InfiniteScrollLoader from '../../components/InfiniteScrollLoader/InfiniteScrollLoader';
1818
import ErrorPage from '../../components/ErrorPage/ErrorPage';
1919
import Fade from '../../components/Animation/Fade/Fade';
20-
import FeaturedShelf from '../../components/FeaturedShelf/FeaturedShelf';
20+
import HeroShelf from '../../components/HeroShelf/HeroShelf';
2121

2222
import styles from './ShelfList.module.scss';
2323

@@ -78,32 +78,35 @@ const ShelfList = ({ rows }: Props) => {
7878
const translatedKey = custom?.[`title-${language}`];
7979
const translatedTitle = translatedKey || title || playlist?.title;
8080

81-
const isFeatured = isTruthyCustomParamValue(custom?.featured) || featured;
82-
83-
const ShelfComponent = isFeatured ? FeaturedShelf : Shelf;
81+
const isHero = custom?.layoutType === SHELF_LAYOUT_TYPE.hero && index === 0;
82+
const isFeatured = !isHero && (custom?.layoutType === SHELF_LAYOUT_TYPE.featured || featured);
8483

8584
return (
8685
<section
8786
key={`${index}_${playlist.id}`}
88-
className={classNames(styles.shelfContainer, { [styles.featured]: isFeatured })}
89-
data-testid={testId(`shelf-${isFeatured ? 'featured' : type === 'playlist' ? slugify(translatedTitle) : type}`)}
87+
className={classNames(styles.shelfContainer, { [styles.hero]: isHero, [styles.featured]: isFeatured })}
88+
data-testid={testId(`shelf-${isHero ? 'hero' : isFeatured ? 'featured' : type === 'playlist' ? slugify(translatedTitle) : type}`)}
9089
aria-label={translatedTitle}
9190
>
9291
<Fade duration={250} delay={index * 33} open>
93-
<ShelfComponent
94-
loading={isPlaceholderData}
95-
error={error}
96-
type={type}
97-
playlist={playlist}
98-
watchHistory={type === PersonalShelf.ContinueWatching ? watchHistoryDictionary : undefined}
99-
title={translatedTitle}
100-
featured={isFeatured}
101-
accessModel={accessModel}
102-
isLoggedIn={!!user}
103-
hasSubscription={!!subscription}
104-
posterAspect={posterAspect}
105-
visibleTilesDelta={visibleTilesDelta}
106-
/>
92+
{isHero ? (
93+
<HeroShelf loading={isPlaceholderData} error={error} playlist={playlist} />
94+
) : (
95+
<Shelf
96+
loading={isPlaceholderData}
97+
error={error}
98+
type={type}
99+
playlist={playlist}
100+
watchHistory={type === PersonalShelf.ContinueWatching ? watchHistoryDictionary : undefined}
101+
title={translatedTitle}
102+
featured={isFeatured}
103+
accessModel={accessModel}
104+
isLoggedIn={!!user}
105+
hasSubscription={!!subscription}
106+
posterAspect={posterAspect}
107+
visibleTilesDelta={visibleTilesDelta}
108+
/>
109+
)}
107110
</Fade>
108111
</section>
109112
);

packages/ui-react/src/utils/theming.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ export const setThemingVariables = (config: Config) => {
2626
if (bodyColor === '#000000') {
2727
// disable text shadows when using a light background
2828
root.style.setProperty('--body-text-shadow', 'none');
29-
// featured shelf should always be dark, so on a light background we fall back to gray
30-
root.style.setProperty('--featured-shelf-background-color', '#1f1f1f');
29+
// hero shelf should always be dark, so on a light background we fall back to gray
30+
root.style.setProperty('--hero-shelf-background-color', '#1f1f1f');
3131
// currently, the EPG only supports a dark background
3232
root.style.setProperty('--epg-background-color', '#262626');
3333
}

0 commit comments

Comments
 (0)