Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates #6

Merged
merged 3 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/ui-react/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Props = {
id?: string;
as?: 'button' | 'a';
activeClassname?: string;
target?: string;
} & React.AriaAttributes;

const Button: React.FC<Props> = ({
Expand Down
23 changes: 23 additions & 0 deletions packages/ui-react/src/components/CTAButton/CTAButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';

import useBreakpoint, { Breakpoint } from '../../hooks/useBreakpoint';
import Button from '../Button/Button';

type Props = {
label: string;
url: string;
};

export type CTAItem = {
label: string;
url: string;
description?: string;
};

const CTAButton = ({ label, url }: Props) => {
const breakpoint = useBreakpoint();

return <Button label={label} to={url} fullWidth={breakpoint < Breakpoint.md} target="_blank" />;
};

export default CTAButton;
4 changes: 2 additions & 2 deletions packages/ui-react/src/components/Card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function Card({
url,
tabIndex = 0,
}: CardProps): JSX.Element {
const { title, duration, episodeNumber, seasonNumber, cardImage: image, mediaStatus, scheduledStart } = item;
const { title, duration, episodeNumber, seasonNumber, cardImage: image, mediaStatus, scheduledStart, series_label } = item;
const {
t,
i18n: { language },
Expand Down Expand Up @@ -78,7 +78,7 @@ function Card({
if (loading || disabled || !title) return null;

if (isSeriesItem) {
return <div className={styles.tag}>{t('video:series')}</div>;
return <div className={styles.tag}>{(series_label as string) || t('video:series')}</div>;
} else if (episodeNumber) {
return <div className={styles.tag}>{formatSeriesMetaString(seasonNumber, episodeNumber)}</div>;
} else if (duration) {
Expand Down
7 changes: 7 additions & 0 deletions packages/ui-react/src/components/Header/Header.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -255,3 +255,10 @@
width: 80px;
}
}

// Custom
@include responsive.mobile-only() {
.customActions {
display: none;
}
}
15 changes: 15 additions & 0 deletions packages/ui-react/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ type Props = {
selectProfile: ({ avatarUrl, id }: { avatarUrl: string; id: string }) => void;
isSelectingProfile: boolean;
};

rightSideItems?: CustomMenuItem[];
};

type CustomMenuItem = {
label: string;
url: string;
position?: 'before' | 'right' | 'after';
key: string;
};

const Header: React.FC<Props> = ({
Expand Down Expand Up @@ -97,6 +106,7 @@ const Header: React.FC<Props> = ({
siteName,
profilesData: { currentProfile, profiles, profilesEnabled, selectProfile, isSelectingProfile } = {},
navItems = [],
rightSideItems,
}) => {
const { t } = useTranslation('menu');
const [logoLoaded, setLogoLoaded] = useState(false);
Expand Down Expand Up @@ -236,6 +246,11 @@ const Header: React.FC<Props> = ({
</div>
)}
<nav className={styles.nav}>{logoLoaded || !logoSrc ? renderNav() : null}</nav>
<div className={styles.customActions}>
{rightSideItems?.map((item) => (
<Button key={item.key} label={item.label} to={item.url} variant="text" />
))}
</div>
<div className={styles.actions}>
{renderSearch()}
{renderLanguageDropdown()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,15 @@
border: 3px solid white;
border-radius: 100%;
}

// Custom

/* stylelint-disable no-duplicate-selectors */

.buttonBar {
flex-wrap: wrap;

> a {
margin: 6px;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ type Props = {
favoriteButton?: React.ReactNode;
trailerButton?: React.ReactNode;
children: React.ReactNode;
ctaButton?: React.ReactNode;
extraButtons?: React.ReactNode[];
};

const VideoDetails: React.VFC<Props> = ({
Expand All @@ -32,6 +34,7 @@ const VideoDetails: React.VFC<Props> = ({
favoriteButton,
trailerButton,
children,
extraButtons = [],
}) => {
const breakpoint: Breakpoint = useBreakpoint();
const isMobile = breakpoint === Breakpoint.xs;
Expand All @@ -56,6 +59,9 @@ const VideoDetails: React.VFC<Props> = ({
{trailerButton}
{favoriteButton}
{shareButton}
{extraButtons.map((button, index) => (
<React.Fragment key={index}>{button}</React.Fragment>
))}
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ type Props = {
favoriteButton: React.ReactNode;
trailerButton: React.ReactNode;
live?: boolean;
extraButtons?: React.ReactNode[];
};

const VideoDetailsInline: React.FC<Props> = ({ title, description, primaryMetadata, shareButton, favoriteButton, trailerButton }) => {
const VideoDetailsInline: React.FC<Props> = ({ title, description, primaryMetadata, shareButton, favoriteButton, trailerButton, extraButtons = [] }) => {
const breakpoint: Breakpoint = useBreakpoint();
const isMobile = breakpoint === Breakpoint.xs;

Expand All @@ -30,6 +31,9 @@ const VideoDetailsInline: React.FC<Props> = ({ title, description, primaryMetada
{trailerButton}
{favoriteButton}
{shareButton}
{extraButtons.map((button, index) => (
<React.Fragment key={index}>{button}</React.Fragment>
))}
</div>
<CollapsibleText text={description} className={styles.description} maxHeight={isMobile ? 60 : 'none'} />
</div>
Expand Down
17 changes: 16 additions & 1 deletion packages/ui-react/src/components/VideoLayout/VideoLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import classNames from 'classnames';
import type { Playlist, PlaylistItem } from '@jwp/ott-common/types/playlist';
import type { AccessModel } from '@jwp/ott-common/types/config';
Expand All @@ -10,6 +10,8 @@ import Filter from '../Filter/Filter';
import VideoDetails from '../VideoDetails/VideoDetails';
import VideoDetailsInline from '../VideoDetailsInline/VideoDetailsInline';
import VideoList from '../VideoList/VideoList';
import useCustomCtaButtons from '../../hooks/useCustomCTAButtons';
import CTAButton from '../CTAButton/CTAButton';

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

Expand Down Expand Up @@ -161,6 +163,17 @@ const VideoLayout: React.FC<Props> = ({
);
};

const customCTAs = useCustomCtaButtons(player);

// Check and setup CTAs
const extraButtons: React.ReactNode[] = useMemo(() => {
return customCTAs.map((cta, index) => {
if (!cta) return undefined;

return <CTAButton key={index + cta.label} label={cta.label} url={cta.url} />;
});
}, [customCTAs]);

if (inlineLayout) {
return (
<div className={styles.videoInlineLayout} data-testid={testId('inline-layout')}>
Expand All @@ -174,6 +187,7 @@ const VideoLayout: React.FC<Props> = ({
shareButton={shareButton}
favoriteButton={favoriteButton}
trailerButton={trailerButton}
extraButtons={extraButtons}
/>
</div>
{renderRelatedVideos(isTablet)}
Expand All @@ -193,6 +207,7 @@ const VideoLayout: React.FC<Props> = ({
shareButton={shareButton}
primaryMetadata={primaryMetadata}
secondaryMetadata={secondaryMetadata}
extraButtons={extraButtons}
>
{playlist && <div className={styles.relatedVideos}>{renderRelatedVideos(true)}</div>}
{children}
Expand Down
36 changes: 33 additions & 3 deletions packages/ui-react/src/containers/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,23 @@ const Layout = () => {
const isLoggedIn = !!useAccountStore(({ user }) => user);
const favoritesEnabled = !!config.features?.favoritesList;
const { menu, assets, siteName, description, features, styling, custom } = config;

const customItems = useMemo(() => {
if (!custom) return [];

return Object.keys(custom)
.filter((key) => key.startsWith('navItem'))
.map((key) => {
const item = JSON.parse(custom[key] as string);
item.key = Math.random().toString();
return item;
});
}, [custom]);

const beforeItems = customItems.filter((item) => item.position === 'before');
const rightItems = customItems.filter((item) => item.position === 'right');
const afterItems = customItems.filter((item) => !['before', 'right'].includes(item.position));

const metaDescription = description || t('default_description');
const { footerText: configFooterText } = styling || {};
const footerText = configFooterText || unicodeToChar(env.APP_FOOTER_TEXT);
Expand Down Expand Up @@ -199,8 +216,12 @@ const Layout = () => {
selectProfile: ({ avatarUrl, id }) => selectProfile.mutate({ id, avatarUrl }),
isSelectingProfile: selectProfile.isLoading,
}}
navItems={navItems}
/>
>
<Button activeClassname={styles.headerButton} label={t('home')} to="/" variant="text" />
{menu.map((item) => (
<Button activeClassname={styles.headerButton} key={item.contentId} label={item.label} to={playlistURL(item.contentId)} variant="text" />
))}
</Header>
<main id="content" className={styles.main} tabIndex={-1}>
<Outlet />
</main>
Expand All @@ -211,12 +232,21 @@ const Layout = () => {
<li>
<MenuButton label={t('home')} to="/" />
</li>
{menu.map((item) => (
{beforeItems.map((item) => (
<MenuButton key={item.key} label={item.label} to={item.url} tabIndex={sideBarOpen ? 0 : -1} />
))}
{menu.map((item) => (
<li key={item.contentId}>
<MenuButton label={item.label} to={playlistURL(item.contentId)} />
</li>
))}
{afterItems.map((item) => (
<MenuButton key={item.key} label={item.label} to={item.url} tabIndex={sideBarOpen ? 0 : -1} />
))}
</ul>
{rightItems.map((item) => (
<MenuButton key={item.key} label={item.label} to={item.url} tabIndex={sideBarOpen ? 0 : -1} />
))}
{renderUserActions(sideBarOpen)}
</Sidebar>
</div>
Expand Down
32 changes: 32 additions & 0 deletions packages/ui-react/src/hooks/useCustomCTAButtons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// React hook
import { useMemo } from 'react';

import type { CTAItem } from '../components/CTAButton/CTAButton';

const useCustomCtaButtons = (player: any) => {

Check warning on line 6 in packages/ui-react/src/hooks/useCustomCTAButtons.ts

View workflow job for this annotation

GitHub Actions / lint (18.x)

Unexpected any. Specify a different type
const itemData = player.props.seriesItem;

// Check and setup CTAs
const customItems = useMemo(() => {
const allowedKeys = ['cta_exam', 'cta_slides', 'cta_resource1', 'cta_resource2', 'cta_resource3'];
return allowedKeys.map((key) => {
if (itemData && !itemData[key]) {
return undefined;
}
if (itemData && itemData[key] && typeof itemData[key] === 'string') {
try {
const data = JSON.parse(itemData[key] as string) as CTAItem;
return data;
} catch (error: unknown) {
console.error(`Failed to parse custom item ${key}`);
console.error(error);
return undefined;
}
}
});
}, [itemData]);

return customItems;
};

export default useCustomCtaButtons;
Loading