Skip to content

Commit c2e1cce

Browse files
authored
Add audio duration to L2A button (#14276)
* WIP add audio duration to L2A button * Use getAudioDurationSeconds from bridget 8.6.0 * tidy up * Filter L2A button from galleries, picture, or liveblog content * fix css * Fix test * Refactor * Fix Css * Fix formatAudioDuration
1 parent d1e3d08 commit c2e1cce

File tree

7 files changed

+128
-21
lines changed

7 files changed

+128
-21
lines changed

dotcom-rendering/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"@emotion/server": "11.11.0",
2929
"@guardian/ab-core": "8.0.0",
3030
"@guardian/braze-components": "22.2.0",
31-
"@guardian/bridget": "8.5.1",
31+
"@guardian/bridget": "8.6.0",
3232
"@guardian/browserslist-config": "6.1.0",
3333
"@guardian/cdk": "61.4.0",
3434
"@guardian/commercial-core": "27.1.0",

dotcom-rendering/src/components/ArticleMeta.apps.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ export const ArticleMetaApps = ({
244244
const isAnalysis = format.design === ArticleDesign.Analysis;
245245
const isLiveBlog = format.design === ArticleDesign.LiveBlog;
246246
const isGallery = format.design === ArticleDesign.Gallery;
247+
const isVideo = format.design === ArticleDesign.Video;
247248

248249
const shouldShowFollowButtons = (layoutOrDesignType: boolean) =>
249250
layoutOrDesignType && !!byline && !isUndefined(soleContributor);
@@ -254,6 +255,9 @@ export const ArticleMetaApps = ({
254255
const isImmersiveOrAnalysisWithMultipleAuthors =
255256
(isAnalysis || isImmersive) && !!byline && isUndefined(soleContributor);
256257

258+
const shouldShowListenToArticleButton =
259+
!!pageId && !(isLiveBlog || isPicture || isGallery || isVideo);
260+
257261
return (
258262
<div
259263
className={
@@ -365,7 +369,7 @@ export const ArticleMetaApps = ({
365369
</MetaGridBranding>
366370
)}
367371
</div>
368-
{pageId !== undefined && (
372+
{shouldShowListenToArticleButton && (
369373
<Island priority="feature" defer={{ until: 'visible' }}>
370374
<ListenToArticle articleId={pageId} />
371375
</Island>

dotcom-rendering/src/components/ListenToArticle.importable.tsx

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,53 @@ import { ListenToArticleButton } from './ListenToArticleButton';
77
type Props = {
88
articleId: string;
99
};
10+
11+
export const formatAudioDuration = (
12+
durationInSeconds: number,
13+
): string | undefined => {
14+
if (durationInSeconds >= 3600 || durationInSeconds <= 0) {
15+
return undefined;
16+
}
17+
const minutes = Math.floor((durationInSeconds % 3600) / 60);
18+
const seconds = durationInSeconds % 60;
19+
20+
const formattedDuration = `${minutes.toString()}:${seconds
21+
.toString()
22+
.padStart(2, '0')}`;
23+
24+
return formattedDuration;
25+
};
26+
1027
export const ListenToArticle = ({ articleId }: Props) => {
1128
const [showButton, setShowButton] = useState<boolean>(false);
29+
const [audioDurationSeconds, setAudioDurationSeconds] = useState<
30+
number | undefined
31+
>(undefined);
1232

13-
const isBridgetCompatible = useIsBridgetCompatible('8.5.1');
14-
33+
const isBridgetCompatible = useIsBridgetCompatible('8.6.0');
1534
useEffect(() => {
1635
if (isBridgetCompatible) {
1736
Promise.all([
1837
getListenToArticleClient().isAvailable(articleId),
1938
getListenToArticleClient().isPlaying(articleId),
39+
getListenToArticleClient().getAudioDurationSeconds(articleId),
2040
])
21-
.then(() =>
22-
// setShowButton(isAvailable && !isPlaying),
23-
setShowButton(false),
24-
)
41+
.then(() => {
42+
// TODO pending design implementation and AB test set up.
43+
// .then(({ isAvailable, isPlaying, audioDurationSeconds }) => {
44+
// setAudioDuration(
45+
// audioDurationSeconds ? audioDurationSeconds : undefined,
46+
// );
47+
// setShowButton(isAvailable && !isPlaying);
48+
setAudioDurationSeconds(undefined);
49+
setShowButton(false);
50+
})
2551
.catch((error) => {
2652
console.error(
2753
'Error fetching article audio status: ',
2854
error,
2955
);
56+
3057
setShowButton(false);
3158
});
3259
}
@@ -54,7 +81,14 @@ export const ListenToArticle = ({ articleId }: Props) => {
5481
};
5582
return (
5683
showButton && (
57-
<ListenToArticleButton onClickHandler={listenToArticleHandler} />
84+
<ListenToArticleButton
85+
onClickHandler={listenToArticleHandler}
86+
audioDuration={
87+
audioDurationSeconds !== undefined
88+
? formatAudioDuration(audioDurationSeconds)
89+
: undefined
90+
}
91+
/>
5892
)
5993
);
6094
};
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { formatAudioDuration } from './ListenToArticle.importable';
2+
3+
describe('format Audio Duration correctly', () => {
4+
const testCases = [
5+
{ input: -60, expected: undefined },
6+
{ input: 0, expected: undefined },
7+
{ input: 1, expected: '0:01' },
8+
{ input: 59, expected: '0:59' },
9+
{ input: 60, expected: '1:00' },
10+
{ input: 61, expected: '1:01' },
11+
{ input: 3599, expected: '59:59' },
12+
{ input: 3600, expected: undefined },
13+
];
14+
15+
for (const { input, expected } of testCases) {
16+
it(`correctly formats ${input} as ${expected}`, () => {
17+
expect(formatAudioDuration(input)).toEqual(expected);
18+
});
19+
}
20+
});

dotcom-rendering/src/components/ListenToArticleButton.stories.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,16 @@ export default meta;
1010

1111
type Story = StoryObj<typeof meta>;
1212

13-
export const ListenToArticleButton = {
13+
export const ListenToArticleWithDurationButton = {
1414
args: {
1515
onClickHandler: () => undefined,
16+
audioDuration: '3:02',
17+
},
18+
} satisfies Story;
19+
20+
export const ListenToArticleNoDurationButton = {
21+
args: {
22+
onClickHandler: () => undefined,
23+
audioDuration: undefined,
1624
},
1725
} satisfies Story;
Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,73 @@
11
import { css } from '@emotion/react';
2-
import { space } from '@guardian/source/foundations';
2+
import { height, space } from '@guardian/source/foundations';
33
import {
44
Button,
55
SvgMediaControlsPlay,
66
} from '@guardian/source/react-components';
77
import { palette } from '../palette';
88

9-
const button = css`
9+
const buttonCss = (audioDuration: string | undefined) => css`
10+
display: flex;
11+
align-items: center;
1012
background-color: ${palette('--follow-icon-fill')};
13+
color: ${palette('--follow-icon-background')};
1114
&:active,
1215
&:focus,
1316
&:hover {
1417
background-color: ${palette('--follow-icon-fill')};
1518
}
16-
color: ${palette('--follow-icon-background')};
1719
margin-bottom: ${space[4]}px;
1820
margin-left: ${space[2]}px;
21+
padding-left: ${space[2]}px;
22+
padding-right: ${audioDuration === undefined ? space[4] : space[3]}px;
23+
padding-bottom: 0px;
24+
font-size: 15px;
25+
height: ${height.ctaSmall}px;
26+
min-height: ${height.ctaSmall}px;
27+
28+
.src-button-space {
29+
width: 0px;
30+
}
31+
`;
32+
33+
const dividerCss = css`
34+
width: 0.5px;
35+
height: 100%;
36+
opacity: 0.5;
37+
border-left: 1px solid ${palette('--follow-icon-background')};
38+
margin-left: ${space[2]}px;
39+
margin-right: ${space[2]}px;
40+
`;
41+
42+
const durationCss = css`
43+
font-weight: 300;
1944
`;
45+
46+
const baselineCss = css`
47+
padding-bottom: 2px;
48+
`;
49+
2050
type ButtonProps = {
2151
onClickHandler: () => void;
52+
audioDuration?: string;
2253
};
23-
export const ListenToArticleButton = ({ onClickHandler }: ButtonProps) => (
54+
export const ListenToArticleButton = ({
55+
onClickHandler,
56+
audioDuration,
57+
}: ButtonProps) => (
2458
<Button
2559
onClick={onClickHandler}
26-
size="small"
27-
cssOverrides={button}
60+
size="default"
61+
iconSide="left"
62+
cssOverrides={buttonCss(audioDuration)}
2863
icon={<SvgMediaControlsPlay />}
2964
>
30-
Listen to article
65+
{audioDuration === undefined || audioDuration === '' ? null : (
66+
<>
67+
<span css={[durationCss, baselineCss]}>{audioDuration}</span>
68+
<span css={dividerCss}></span>
69+
</>
70+
)}
71+
<span css={baselineCss}>Listen to this article</span>
3172
</Button>
3273
);

pnpm-lock.yaml

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)