Skip to content

Commit 1e0f774

Browse files
authored
Merge pull request #14163 from guardian/add-gallery-lightbox
Add lightbox to gallery images
2 parents c2de706 + 08e1b56 commit 1e0f774

File tree

8 files changed

+149
-78
lines changed

8 files changed

+149
-78
lines changed

dotcom-rendering/.storybook/modes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,8 @@ export const allModes = {
6969
globalColourScheme: 'light',
7070
viewport: breakpoints.leftCol,
7171
},
72+
'light wide': {
73+
globalColourScheme: 'light',
74+
viewport: breakpoints.wide,
75+
},
7276
};

dotcom-rendering/src/components/GalleryImage.tsx

Lines changed: 75 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
import { css } from '@emotion/react';
2+
import { isUndefined } from '@guardian/libs';
23
import { from, space, until } from '@guardian/source/foundations';
34
import { grid } from '../grid';
45
import { type ArticleFormat } from '../lib/articleFormat';
56
import { getImage } from '../lib/image';
67
import { palette } from '../palette';
78
import { type ImageBlockElement } from '../types/content';
9+
import { type RenderingTarget } from '../types/renderingTarget';
10+
import { AppsLightboxImage } from './AppsLightboxImage.importable';
811
import { GalleryCaption } from './GalleryCaption';
12+
import { Island } from './Island';
13+
import { LightboxLink } from './LightboxLink';
914
import { Picture } from './Picture';
1015

1116
type Props = {
1217
format: ArticleFormat;
1318
image: ImageBlockElement;
1419
pageId: string;
1520
webTitle: string;
21+
renderingTarget: RenderingTarget;
1622
};
1723

1824
const styles = css`
@@ -31,7 +37,31 @@ const styles = css`
3137
}
3238
`;
3339

34-
export const GalleryImage = ({ format, image, pageId, webTitle }: Props) => {
40+
const galleryBodyImageStyles = css`
41+
display: inline;
42+
position: relative;
43+
${grid.column.all}
44+
45+
${from.tablet} {
46+
${grid.column.centre}
47+
}
48+
49+
${from.desktop} {
50+
padding-bottom: ${space[10]}px;
51+
}
52+
53+
${from.leftCol} {
54+
${grid.between('centre-column-start', 'right-column-end')}
55+
}
56+
`;
57+
58+
export const GalleryImage = ({
59+
format,
60+
image,
61+
pageId,
62+
webTitle,
63+
renderingTarget,
64+
}: Props) => {
3565
const asset = getImage(image.media.allImages);
3666

3767
if (asset === undefined) {
@@ -47,15 +77,50 @@ export const GalleryImage = ({ format, image, pageId, webTitle }: Props) => {
4777

4878
return (
4979
<figure css={styles}>
50-
<Picture
51-
alt={image.data.alt ?? ''}
52-
format={format}
53-
role={image.role}
54-
master={asset.url}
55-
width={width}
56-
height={height}
57-
loading="lazy"
58-
/>
80+
<div
81+
css={galleryBodyImageStyles}
82+
/**
83+
* This ensures that the image height never goes above 96vh.
84+
*/
85+
style={{
86+
maxWidth: `calc(${width / height} * 96vh)`,
87+
}}
88+
>
89+
{renderingTarget === 'Apps' ? (
90+
<Island priority="critical">
91+
<AppsLightboxImage
92+
elementId={image.elementId}
93+
role={image.role}
94+
format={format}
95+
master={asset.url}
96+
alt={image.data.alt ?? ''}
97+
width={width}
98+
height={height}
99+
loading={'lazy'}
100+
/>
101+
</Island>
102+
) : (
103+
<Picture
104+
alt={image.data.alt ?? ''}
105+
format={format}
106+
role={image.role}
107+
master={asset.url}
108+
width={width}
109+
height={height}
110+
loading="lazy"
111+
/>
112+
)}
113+
{renderingTarget === 'Web' && !isUndefined(image.position) && (
114+
<LightboxLink
115+
role={image.role}
116+
format={format}
117+
elementId={image.elementId}
118+
isMainMedia={false}
119+
position={image.position}
120+
/>
121+
)}
122+
</div>
123+
59124
<GalleryCaption
60125
captionHtml={image.data.caption}
61126
credit={image.data.credit}

dotcom-rendering/src/components/LightboxLink.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@ export const LightboxLink = ({
151151
`,
152152
decideSize(role, format),
153153
isMainMedia &&
154-
format.display === ArticleDisplay.Immersive &&
154+
(format.display === ArticleDisplay.Immersive ||
155+
format.design === ArticleDesign.Gallery) &&
155156
visuallyHidden,
156157
]}
157158
>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const Default = {
2222
display: ArticleDisplay.Standard,
2323
theme: Pillar.News,
2424
},
25+
renderingTarget: 'Web',
2526
},
2627
} satisfies Story;
2728

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,37 @@
11
import { css } from '@emotion/react';
2+
import { isUndefined } from '@guardian/libs';
23
import { from } from '@guardian/source/foundations';
34
import { grid } from '../grid';
45
import { type ArticleFormat } from '../lib/articleFormat';
56
import { getImage } from '../lib/image';
67
import { type ImageBlockElement } from '../types/content';
8+
import { type RenderingTarget } from '../types/renderingTarget';
9+
import { AppsLightboxImage } from './AppsLightboxImage.importable';
10+
import { Island } from './Island';
11+
import { LightboxLink } from './LightboxLink';
712
import { Picture } from './Picture';
813

914
type Props = {
1015
mainMedia: ImageBlockElement;
1116
format: ArticleFormat;
17+
renderingTarget: RenderingTarget;
1218
};
1319

1420
const styles = css`
1521
${grid.column.all}
22+
position: relative;
1623
height: calc(80vh - 48px);
1724
grid-row: 1/8;
1825
${from.desktop} {
1926
height: calc(100vh - 48px);
2027
}
2128
`;
2229

23-
export const MainMediaGallery = ({ mainMedia, format }: Props) => {
30+
export const MainMediaGallery = ({
31+
mainMedia,
32+
format,
33+
renderingTarget,
34+
}: Props) => {
2435
const asset = getImage(mainMedia.media.allImages);
2536

2637
if (asset === undefined) {
@@ -36,16 +47,40 @@ export const MainMediaGallery = ({ mainMedia, format }: Props) => {
3647

3748
return (
3849
<figure css={styles}>
39-
<Picture
40-
alt={mainMedia.data.alt ?? ''}
41-
format={format}
42-
role={mainMedia.role}
43-
master={asset.url}
44-
width={width}
45-
height={height}
46-
loading="eager"
47-
isMainMedia={true}
48-
/>
50+
{renderingTarget === 'Apps' ? (
51+
<Island priority="critical">
52+
<AppsLightboxImage
53+
elementId={mainMedia.elementId}
54+
role={mainMedia.role}
55+
format={format}
56+
master={asset.url}
57+
alt={mainMedia.data.alt ?? ''}
58+
width={width}
59+
height={height}
60+
loading="eager"
61+
/>
62+
</Island>
63+
) : (
64+
<Picture
65+
alt={mainMedia.data.alt ?? ''}
66+
format={format}
67+
role={mainMedia.role}
68+
master={asset.url}
69+
width={width}
70+
height={height}
71+
loading="eager"
72+
isMainMedia={true}
73+
/>
74+
)}
75+
{renderingTarget === 'Web' && !isUndefined(mainMedia.position) && (
76+
<LightboxLink
77+
role={mainMedia.role}
78+
format={format}
79+
elementId={mainMedia.elementId}
80+
isMainMedia={true}
81+
position={mainMedia.position}
82+
/>
83+
)}
4984
</figure>
5085
);
5186
};

dotcom-rendering/src/components/Picture.tsx

Lines changed: 11 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
import { css } from '@emotion/react';
2-
import { breakpoints, from, space } from '@guardian/source/foundations';
3-
import {
4-
type CSSProperties,
5-
Fragment,
6-
useCallback,
7-
useEffect,
8-
useState,
9-
} from 'react';
10-
import { grid } from '../grid';
2+
import { breakpoints } from '@guardian/source/foundations';
3+
import { Fragment, useCallback, useEffect, useState } from 'react';
114
import {
125
ArticleDesign,
136
ArticleDisplay,
@@ -472,50 +465,15 @@ export const Sources = ({ sources }: { sources: ImageSource[] }) => {
472465
);
473466
};
474467

475-
const galleryBodyImageStyles = css`
476-
${grid.column.all}
477-
478-
${from.tablet} {
479-
${grid.column.centre}
480-
}
481-
482-
${from.desktop} {
483-
padding-bottom: ${space[10]}px;
484-
}
485-
486-
${from.leftCol} {
487-
${grid.between('centre-column-start', 'right-column-end')}
488-
}
489-
`;
490-
491-
/**
492-
* This ensures that the image height never goes above 96vh.
493-
* The ratio parameter should be width:height.
494-
*/
495-
const imageMaxWidth = (
496-
design: ArticleDesign,
497-
ratio: number,
498-
): CSSProperties | undefined =>
499-
design === ArticleDesign.Gallery
500-
? { maxWidth: `calc(${ratio} * 96vh)` }
501-
: undefined;
502-
503-
const styles = (
504-
{ design }: ArticleFormat,
505-
isLightbox: boolean,
506-
isMainMedia: boolean,
507-
) => {
468+
const styles = ({ design }: ArticleFormat, isLightbox: boolean) => {
508469
if (design === ArticleDesign.Gallery) {
509-
return css(
510-
css`
511-
img {
512-
width: 100%;
513-
height: 100%;
514-
object-fit: cover;
515-
}
516-
`,
517-
isMainMedia ? undefined : galleryBodyImageStyles,
518-
);
470+
return css`
471+
img {
472+
width: 100%;
473+
height: 100%;
474+
object-fit: cover;
475+
}
476+
`;
519477
}
520478
return isLightbox ? flex : block;
521479
};
@@ -581,10 +539,7 @@ export const Picture = ({
581539
const fallbackSource = getFallbackSource(sources);
582540

583541
return (
584-
<picture
585-
css={styles(format, isLightbox, isMainMedia)}
586-
style={imageMaxWidth(format.design, 1 / ratio)}
587-
>
542+
<picture css={styles(format, isLightbox)}>
588543
{/* Immersive Main Media images get additional sources specifically for when in portrait orientation */}
589544
{format.display === ArticleDisplay.Immersive && isMainMedia && (
590545
<>

dotcom-rendering/src/layouts/GalleryLayout.stories.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Meta, StoryObj } from '@storybook/react';
2+
import { allModes } from '../../.storybook/modes';
23
import { Gallery as GalleryFixture } from '../../fixtures/generated/fe-articles/Gallery';
34
import { WithBranding } from '../components/ArticleMeta.web.stories';
45
import { ArticleDesign } from '../lib/articleFormat';
@@ -10,6 +11,13 @@ import { GalleryLayout } from './GalleryLayout';
1011
const meta = {
1112
title: 'Layouts/Gallery',
1213
component: GalleryLayout,
14+
parameters: {
15+
chromatic: {
16+
modes: {
17+
'light wide': allModes['light wide'],
18+
},
19+
},
20+
},
1321
} satisfies Meta<typeof GalleryLayout>;
1422

1523
export default meta;

dotcom-rendering/src/layouts/GalleryLayout.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ export const GalleryLayout = (props: WebProps | AppProps) => {
210210
<MainMediaGallery
211211
mainMedia={gallery.mainMedia}
212212
format={format}
213+
renderingTarget={props.renderingTarget}
213214
/>
214215
<ArticleTitle
215216
format={format}
@@ -299,6 +300,7 @@ export const GalleryLayout = (props: WebProps | AppProps) => {
299300
format={format}
300301
pageId={frontendData.pageId}
301302
webTitle={frontendData.webTitle}
303+
renderingTarget={props.renderingTarget}
302304
/>
303305
{isWeb && shouldShowAds && (
304306
<div css={galleryItemAdvertStyles}>

0 commit comments

Comments
 (0)