diff --git a/src/components/CollapsibleText/CollapsibleText.module.scss b/src/components/CollapsibleText/CollapsibleText.module.scss new file mode 100644 index 000000000..f234066e2 --- /dev/null +++ b/src/components/CollapsibleText/CollapsibleText.module.scss @@ -0,0 +1,36 @@ +@use '../../styles/variables'; +@use '../../styles/theme'; + +.collapsibleText { + position: relative; + padding-bottom: 20px; +} +.dummyDiv { + position: absolute; + visibility: hidden; +} +.textContainer { + overflow: hidden; + &.collapsed { + mask-image: linear-gradient(-180deg, rgba(0, 0, 0, 1) 50%, rgba(0, 0, 0, 0) 100%); + -webkit-mask-image: linear-gradient(-180deg, rgba(0, 0, 0, 1) 50%, rgba(0, 0, 0, 0) 100%); + } +} +.chevron { + position: absolute; + width: 24px; + height: 24px; + bottom: 25px; + left: calc(50% - 24px / 2); + z-index: 1; + cursor: pointer; + + > svg { + transform: rotate(90deg); + } + &.expanded { + > svg { + transform: rotate(270deg); + } + } +} diff --git a/src/components/CollapsibleText/CollapsibleText.test.tsx b/src/components/CollapsibleText/CollapsibleText.test.tsx new file mode 100644 index 000000000..6681491d3 --- /dev/null +++ b/src/components/CollapsibleText/CollapsibleText.test.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { render } from '@testing-library/react'; + +import CollapsibleText from './CollapsibleText'; + +describe('', () => { + test('renders and matches snapshot', () => { + const { container } = render(); + + expect(container).toMatchSnapshot(); + }); +}); diff --git a/src/components/CollapsibleText/CollapsibleText.tsx b/src/components/CollapsibleText/CollapsibleText.tsx new file mode 100644 index 000000000..50da6a8e2 --- /dev/null +++ b/src/components/CollapsibleText/CollapsibleText.tsx @@ -0,0 +1,50 @@ +import classNames from 'classnames'; +import React, { useEffect, useRef, useState } from 'react'; + +import IconButton from '../IconButton/IconButton'; +import ChevronRight from '../../icons/ChevronRight'; + +import styles from './CollapsibleText.module.scss'; + +type Props = { + text: string; + className?: string; + maxHeight?: 'none' | number; +}; + +const CollapsibleText: React.FC = ({ text, className, maxHeight = 'none' }: Props) => { + const dummyDiv = useRef() as React.MutableRefObject; + const [doesFlowOver, setDoesFlowOver] = useState(false); + const [collapsed, setCollapsed] = useState(true); + + const ariaLabel = collapsed ? 'Expand' : 'Collapse'; + + useEffect(() => { + dummyDiv.current && setDoesFlowOver(dummyDiv.current.scrollHeight > dummyDiv.current?.offsetHeight); + }, [maxHeight]); + + return ( +
+
+ {text} +
+
+ {text} +
+ {doesFlowOver && ( + setCollapsed(!collapsed)} + > + + + )} +
+ ); +}; + +export default CollapsibleText; diff --git a/src/components/CollapsibleText/__snapshots__/CollapsibleText.test.tsx.snap b/src/components/CollapsibleText/__snapshots__/CollapsibleText.test.tsx.snap new file mode 100644 index 000000000..20e8b007e --- /dev/null +++ b/src/components/CollapsibleText/__snapshots__/CollapsibleText.test.tsx.snap @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders and matches snapshot 1`] = ` +
+
+
+ Test... +
+
+ Test... +
+
+
+`; diff --git a/src/components/IconButton/IconButton.tsx b/src/components/IconButton/IconButton.tsx index 20f93ef70..8063615c9 100644 --- a/src/components/IconButton/IconButton.tsx +++ b/src/components/IconButton/IconButton.tsx @@ -1,3 +1,4 @@ +import classNames from 'classnames'; import React from 'react'; import styles from './IconButton.module.scss'; @@ -7,12 +8,19 @@ type Props = { children: JSX.Element; 'aria-label': string; tabIndex?: number; + className?: string; }; -const IconButton: React.FC = ({ children, onClick, 'aria-label': ariaLabel, tabIndex = 0 }: Props) => { +const IconButton: React.FC = ({ + children, + onClick, + 'aria-label': ariaLabel, + tabIndex = 0, + className, +}: Props) => { return (
({ const ulStyle = { transform: `translate3d(${transformWithOffset}%, 0, 0)`, // prettier-ignore - webkitTransform: `translate3d(${transformWithOffset}%, 0, 0)`, + WebkitTransform: `translate3d(${transformWithOffset}%, 0, 0)`, transition: transitionBasis, marginLeft: -spacing / 2, marginRight: -spacing / 2, diff --git a/src/components/Video/Video.tsx b/src/components/Video/Video.tsx index d37509597..44d179ddf 100644 --- a/src/components/Video/Video.tsx +++ b/src/components/Video/Video.tsx @@ -2,6 +2,7 @@ import React from 'react'; import type { PlaylistItem } from 'types/playlist'; import classNames from 'classnames'; +import CollapsibleText from '../CollapsibleText/CollapsibleText'; import Cinema from '../../containers/Cinema/Cinema'; import useBreakpoint, { Breakpoint } from '../../hooks/useBreakpoint'; import Favorite from '../../icons/Favorite'; @@ -24,9 +25,11 @@ type Props = { }; const Video: React.FC = ({ item, play, startPlay, goBack, posterFading, relatedShelf }: Props) => { - const posterImage = item.image.replace('720', '1280'); // Todo: 1280 should be sent from API - const breakpoint = useBreakpoint(); + const breakpoint: Breakpoint = useBreakpoint(); + const isLargeScreen = breakpoint >= Breakpoint.md; const isMobile = breakpoint === Breakpoint.xs; + const imageSourceWidth = 640 * (window.devicePixelRatio > 1 || isLargeScreen ? 2 : 1); + const posterImage = item.image.replace('720', imageSourceWidth.toString()); // Todo: should be taken from images (1280 should be sent from API) const metaData = []; if (item.pubdate) metaData.push(new Date(item.pubdate).getFullYear()); @@ -35,9 +38,7 @@ const Video: React.FC = ({ item, play, startPlay, goBack, posterFading, r if (item.rating) metaData.push(item.rating); const metaString = metaData.join(' • '); - //todo: image based on screen res, etc (like Home) //todo: breakpoints not same as css (so info padding-top acts too soon) - //todo: description enlarger return (
@@ -45,7 +46,7 @@ const Video: React.FC = ({ item, play, startPlay, goBack, posterFading, r

{item.title}

{metaString}
- {!isMobile &&
{item.description}
} +
- Test item description +
+ Test item description +
+
+ Test item description +
{ const { isLoading, error, data: playlist }: UsePlaylistResult = usePlaylist(playlistId, relatedMediaId); + if (!playlistId) return

No playlist

; if (error) return

Error here {error}

; return ( diff --git a/src/containers/Video/Video.tsx b/src/containers/Video/Video.tsx index 16bbfb1ca..b9ac810d5 100644 --- a/src/containers/Video/Video.tsx +++ b/src/containers/Video/Video.tsx @@ -33,24 +33,25 @@ const Video = ({ playlistId, videoType, episodeId, mediaId }: VideoProps): JSX.E const getSeriesItem = () => playlist?.playlist[0]; const item = videoType === 'movie' ? getMovieItem() : getSeriesItem(); - const startPlay = () => (item ? history.push(videoUrl(item, playlistId, true)) : null); - const goBack = () => (item ? history.push(videoUrl(item, playlistId, false)) : null); + const startPlay = () => item && history.push(videoUrl(item, playlistId, true)); + const goBack = () => item && history.push(videoUrl(item, playlistId, false)); - const onCardClick = (playlistItem: PlaylistItem) => - history.push(cardUrl(playlistItem, config.recommendationsPlaylist)); - const onCardHover = (playlistItem: PlaylistItem) => updateBlurImage(playlistItem.image); + const onCardClick = (item: PlaylistItem) => history.push(cardUrl(item)); + const onCardHover = (item: PlaylistItem) => updateBlurImage(item.image); - useEffect(() => { - if (item) updateBlurImage(item.image); - }, [item, updateBlurImage]); + useEffect(() => item && updateBlurImage(item.image), [item, updateBlurImage]); + + //todo: series andere playlist + //todo: currently playing in recommended + + //temp: + console.info({ episodeId }); if (isLoading) return

Loading...

; if (error) return

Error loading list

; if (!playlist) return

List not found

; if (!item) return

Can not find medium

; - console.info({ episodeId }); - return ( { export type UsePlaylistResult = UseBaseQueryResult; export default function usePlaylist(playlistId: string, relatedMediaId?: string): UsePlaylistResult { - return useQuery(['playlist', playlistId], () => getPlaylistById(playlistId, relatedMediaId), { + return useQuery(['playlist', playlistId, relatedMediaId], () => getPlaylistById(playlistId, relatedMediaId), { enabled: !!playlistId, }); } diff --git a/src/utils/formatting.ts b/src/utils/formatting.ts index 546974043..f959d998f 100644 --- a/src/utils/formatting.ts +++ b/src/utils/formatting.ts @@ -20,7 +20,7 @@ const slugify = (text: string, whitespaceChar: string = '-') => .replace(/-/g, whitespaceChar); const movieURL = (item: PlaylistItem, playlistId: string = '') => - `/m/${item.mediaid}/${slugify(item.title)}?list=${playlistId}`; + `/m/${item.mediaid}/${slugify(item.title)}${playlistId ? `?list=${playlistId}` : ''}`; const seriesURL = (item: PlaylistItem, playlistId: string = '') => `/s/${playlistId}/${slugify(item.title)}`;