Skip to content

Commit

Permalink
feat: implement tile-slider dependency
Browse files Browse the repository at this point in the history
  • Loading branch information
ChristiaanScheermeijer committed Apr 8, 2024
1 parent eca51d5 commit 290e280
Show file tree
Hide file tree
Showing 8 changed files with 438 additions and 803 deletions.
1 change: 1 addition & 0 deletions packages/ui-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"dependencies": {
"@adyen/adyen-web": "^5.42.1",
"@inplayer-org/inplayer.js": "^3.13.24",
"@videodock/tile-slider": "^2.0.0-rc.2",
"classnames": "^2.3.1",
"date-fns": "^2.28.0",
"dompurify": "^2.3.8",
Expand Down
46 changes: 35 additions & 11 deletions packages/ui-react/src/components/Shelf/Shelf.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,9 @@
font-family: var(--body-alt-font-family);

&:hover,
&:focus-within
{
.chevron {
&:focus-within {
.chevron:not(:disabled) {
opacity: 1;
&.disabled {
opacity: 0.3;
}
}
}
}
Expand All @@ -28,6 +24,25 @@
text-overflow: ellipsis;
}

.slider {
overflow: visible;

:global(.TileSlider-list) {
li > a {
white-space: initial;
}

li {
opacity: 1;
transition: opacity 0.1s ease;

&:global(.TileSlider--hidden) {
opacity: 0.5;
}
}
}
}

.chevron {
display: flex;
flex-wrap: wrap;
Expand All @@ -38,22 +53,31 @@
outline-color: var(--highlight-color, white);
cursor: pointer;
transition: transform 0.3s ease-out, opacity 0.3s ease-out;
appearance: none;

> svg {
width: 30px;
height: 30px;
}
&.disabled {
cursor: default;
&:hover {
transform: none;
}

&:disabled {
opacity: 0.3;
pointer-events: none;
}

&:hover {
transform: scale(1.2);
}
}

.dots {
position: relative;
display: flex;
justify-content: center;
width: 100%;
margin-top: 12px;
}

.dot {
display: inline-block;
width: 10px;
Expand Down
87 changes: 36 additions & 51 deletions packages/ui-react/src/components/Shelf/Shelf.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useCallback, useState } from 'react';
import React, { useCallback } from 'react';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import { CYCLE_MODE_RESTART, type RenderControl, type RenderPagination, TileSlider } from '@videodock/tile-slider';
import type { Playlist, PlaylistItem } from '@jwp/ott-common/types/playlist';
import type { AccessModel, ContentType } from '@jwp/ott-common/types/config';
import { isLocked } from '@jwp/ott-common/src/utils/entitlements';
Expand All @@ -10,8 +11,8 @@ import ChevronLeft from '@jwp/ott-theme/assets/icons/chevron_left.svg?react';
import ChevronRight from '@jwp/ott-theme/assets/icons/chevron_right.svg?react';
import useBreakpoint, { Breakpoint, type Breakpoints } from '@jwp/ott-ui-react/src/hooks/useBreakpoint';
import type { PosterAspectRatio } from '@jwp/ott-common/src/utils/collection';
import '@videodock/tile-slider/lib/style.css';

import TileDock from '../TileDock/TileDock';
import Card from '../Card/Card';
import Icon from '../Icon/Icon';

Expand Down Expand Up @@ -68,11 +69,10 @@ const Shelf = ({
}: ShelfProps) => {
const breakpoint: Breakpoint = useBreakpoint();
const { t } = useTranslation('common');
const [didSlideBefore, setDidSlideBefore] = useState(false);
const tilesToShow: number = (featured ? featuredTileBreakpoints[breakpoint] : tileBreakpoints[breakpoint]) + visibleTilesDelta;

const renderTile = useCallback(
(item: PlaylistItem, isInView: boolean) => {
({ item, isVisible }: { item: PlaylistItem; isVisible: boolean }) => {
const url = mediaURL({ media: item, playlistId: playlist.feedid, play: type === PersonalShelf.ContinueWatching });

return (
Expand All @@ -81,7 +81,7 @@ const Shelf = ({
progress={watchHistory ? watchHistory[item.mediaid] : undefined}
onHover={typeof onCardHover === 'function' ? () => onCardHover(item) : undefined}
featured={featured}
disabled={!isInView}
disabled={!isVisible}
loading={loading}
isLocked={isLocked(accessModel, isLoggedIn, hasSubscription, item)}
posterAspect={posterAspect}
Expand All @@ -93,73 +93,58 @@ const Shelf = ({
[watchHistory, onCardHover, featured, loading, accessModel, isLoggedIn, hasSubscription, posterAspect, playlist.feedid, type],
);

const renderRightControl = useCallback(
(doSlide: () => void) => (
<div
className={styles.chevron}
role="button"
tabIndex={0}
aria-label={t('slide_next')}
onKeyDown={(event: React.KeyboardEvent) => (event.key === 'Enter' || event.key === ' ') && handleSlide(doSlide)}
onClick={() => handleSlide(doSlide)}
>
const renderRightControl: RenderControl = useCallback(
({ onClick, disabled }) => (
<button className={styles.chevron} disabled={disabled} aria-label={t('slide_next')} onClick={onClick}>
<Icon icon={ChevronRight} />
</div>
</button>
),
[t],
);

const renderLeftControl = useCallback(
(doSlide: () => void) => (
<div
className={classNames(styles.chevron, {
[styles.disabled]: !didSlideBefore,
})}
role="button"
tabIndex={didSlideBefore ? 0 : -1}
aria-label={t('slide_previous')}
onKeyDown={(event: React.KeyboardEvent) => (event.key === 'Enter' || event.key === ' ') && handleSlide(doSlide)}
onClick={() => handleSlide(doSlide)}
>
const renderLeftControl: RenderControl = useCallback(
({ onClick, disabled }) => (
<button className={styles.chevron} aria-label={t('slide_previous')} disabled={disabled} onClick={onClick}>
<Icon icon={ChevronLeft} />
</div>
</button>
),
[didSlideBefore, t],
);

const renderPaginationDots = (index: number, pageIndex: number) => (
<span key={pageIndex} className={classNames(styles.dot, { [styles.active]: index === pageIndex })} />
);

const renderPageIndicator = (pageIndex: number, pages: number) => (
<div aria-live="polite" className="hidden">
{t('slide_indicator', { page: pageIndex + 1, pages })}
</div>
[t],
);

const handleSlide = (doSlide: () => void): void => {
setDidSlideBefore(true);
doSlide();
const renderPagination: RenderPagination = ({ page, pages }) => {
const items = Array.from({ length: pages }, (_, pageIndex) => pageIndex);

return (
<>
<div aria-live="polite" className="hidden">
{t('slide_indicator', { page: page + 1, pages })}
</div>
{featured && (
<div aria-hidden="true" className={styles.dots}>
{items.map((current) => (
<span key={current} className={classNames(styles.dot, { [styles.active]: page === current })} />
))}
</div>
)}
</>
);
};

if (error || !playlist?.playlist) return <h2 className={styles.error}>Could not load items</h2>;

return (
<div className={classNames(styles.shelf)}>
{!featured ? <h2 className={classNames(styles.title)}>{title || playlist.title}</h2> : null}
<TileDock<PlaylistItem>
items={playlist.playlist}
<TileSlider<PlaylistItem>
className={styles.slider}
items={playlist.playlist.slice(0, 5)}
tilesToShow={tilesToShow}
wrapWithEmptyTiles={featured && playlist.playlist.length === 1}
cycleMode={'restart'}
cycleMode={CYCLE_MODE_RESTART}
showControls={!loading}
showDots={featured}
transitionTime={'0.3s'}
spacing={8}
renderLeftControl={renderLeftControl}
renderRightControl={renderRightControl}
renderPaginationDots={renderPaginationDots}
renderPageIndicator={renderPageIndicator}
renderPagination={renderPagination}
renderTile={renderTile}
/>
</div>
Expand Down
Loading

0 comments on commit 290e280

Please sign in to comment.