Skip to content

Commit 4456fee

Browse files
authored
Merge branch 'main' into revert-14689-ab/self-styled-subtitles
2 parents c85c954 + ec67385 commit 4456fee

File tree

10 files changed

+795
-11
lines changed

10 files changed

+795
-11
lines changed

dotcom-rendering/src/components/Picture.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ export type Orientation = 'portrait' | 'landscape';
2020
type PictureRoleType =
2121
| RoleType
2222
// Custom image role types that are used but do not come from CAPI / FE
23-
| 'podcastCover';
23+
| 'podcastCover'
24+
| 'productCard';
2425

2526
type Props = {
2627
role: PictureRoleType;
@@ -268,6 +269,11 @@ const decideImageWidths = ({
268269
{ breakpoint: breakpoints.mobile, width: 140 },
269270
{ breakpoint: breakpoints.wide, width: 219 },
270271
];
272+
case 'productCard':
273+
return [
274+
{ breakpoint: breakpoints.mobile, width: 328 },
275+
{ breakpoint: breakpoints.wide, width: 220 },
276+
];
271277
case 'inline':
272278
default:
273279
return [
@@ -358,6 +364,11 @@ const decideImageWidths = ({
358364
];
359365
case 'halfWidth':
360366
return [{ breakpoint: breakpoints.mobile, width: 445 }];
367+
case 'productCard':
368+
return [
369+
{ breakpoint: breakpoints.mobile, width: 328 },
370+
{ breakpoint: breakpoints.wide, width: 220 },
371+
];
361372
case 'podcastCover':
362373
return [
363374
{ breakpoint: breakpoints.mobile, width: 140 },
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { ProductCta } from '../types/content';
2+
import { ProductLinkButton } from './ProductLinkButton';
3+
4+
const getLabel = (cta: ProductCta): string => {
5+
const overrideLabel = cta.text.trim().length > 0;
6+
return overrideLabel ? cta.text : `${cta.price} at ${cta.retailer}`;
7+
};
8+
9+
export const ProductCardButtons = ({
10+
productCtas,
11+
dataComponent,
12+
}: {
13+
productCtas: ProductCta[];
14+
dataComponent?: string;
15+
}) => (
16+
<>
17+
{productCtas.map((productCta, index) => {
18+
const label = getLabel(productCta);
19+
return (
20+
<ProductLinkButton
21+
key={label}
22+
label={label}
23+
url={productCta.url}
24+
priority={index === 0 ? 'primary' : 'tertiary'}
25+
fullwidth={true}
26+
data-component={`${
27+
dataComponent ?? 'product-card-button'
28+
}-${index}`}
29+
/>
30+
);
31+
})}
32+
</>
33+
);
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { css } from '@emotion/react';
2+
import type { HTMLAttributes } from 'react';
3+
import type { ArticleFormat } from '../lib/articleFormat';
4+
import type { ProductImage } from '../types/content';
5+
import { Caption } from './Caption';
6+
import { Picture } from './Picture';
7+
8+
interface ProductCardImageProps extends HTMLAttributes<HTMLDivElement> {
9+
format: ArticleFormat;
10+
image?: ProductImage;
11+
url?: string;
12+
}
13+
14+
export const ProductCardImage = ({
15+
format,
16+
image,
17+
url,
18+
}: ProductCardImageProps) => {
19+
if (!image) {
20+
return null;
21+
}
22+
23+
const ProductPicture = () => (
24+
<Picture
25+
role={'productCard'}
26+
format={format}
27+
master={image.url}
28+
alt={image.alt}
29+
height={image.height}
30+
width={image.width}
31+
loading={'eager'}
32+
/>
33+
);
34+
35+
return (
36+
<figure>
37+
{url ? (
38+
<a
39+
href={url}
40+
target="_blank"
41+
rel="noopener noreferrer"
42+
// this is needed to override global style
43+
// html:not(.src-focus-disabled) *:focus
44+
// it has specificity(0, 2, 1) so we need (0, 3, 0)
45+
css={css`
46+
&&:focus {
47+
box-shadow: none;
48+
}
49+
`}
50+
>
51+
<ProductPicture />
52+
</a>
53+
) : (
54+
<ProductPicture />
55+
)}
56+
<Caption
57+
format={format}
58+
displayCredit={image.displayCredit}
59+
credit={image.credit}
60+
isOverlaid={false}
61+
/>
62+
</figure>
63+
);
64+
};
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import type { Meta, StoryObj } from '@storybook/react-webpack5';
2+
import { centreColumnDecorator } from '../../.storybook/decorators/gridDecorators';
3+
import { allModes } from '../../.storybook/modes';
4+
import { ArticleDesign, ArticleDisplay, Pillar } from '../lib/articleFormat';
5+
import type { ProductImage } from '../types/content';
6+
import { ProductCardInline } from './ProductCardInline';
7+
8+
const productImage: ProductImage = {
9+
url: 'https://media.guimcode.co.uk/cb193848ed75d40103eceaf12b448de2330770dc/0_0_725_725/725.jpg',
10+
caption: 'Filter-2 test image for live demo',
11+
height: 1,
12+
width: 1,
13+
alt: 'Bosch Sky kettle',
14+
credit: 'Photograph: Rachel Ogden/The Guardian',
15+
displayCredit: false,
16+
};
17+
18+
const meta = {
19+
component: ProductCardInline,
20+
title: 'Components/ProductCardInline',
21+
parameters: {
22+
chromatic: {
23+
modes: {
24+
'light mobile': allModes['light mobile'],
25+
'light desktop': allModes['light desktop'],
26+
'light wide': allModes['light wide'],
27+
'horizontal split': allModes['splitHorizontal'],
28+
},
29+
},
30+
},
31+
args: {
32+
format: {
33+
design: ArticleDesign.Standard,
34+
display: ArticleDisplay.Standard,
35+
theme: Pillar.Lifestyle,
36+
},
37+
image: productImage,
38+
productCtas: [
39+
{
40+
url: 'https://www.theguardian.com',
41+
price: '£89.99',
42+
retailer: 'Amazon',
43+
text: '',
44+
},
45+
{
46+
url: 'https://www.theguardian.com',
47+
price: '£95.99',
48+
retailer: 'John Lewis',
49+
text: '',
50+
},
51+
],
52+
brandName: 'AirCraft',
53+
productName: 'Lume',
54+
customAttributes: [
55+
{ name: 'What we love', value: 'It packs away pretty small' },
56+
{
57+
name: "What we don't love",
58+
value: 'There’s nowhere to stow the remote control',
59+
},
60+
],
61+
lowestPrice: '£89.99',
62+
isCardOnly: false,
63+
},
64+
decorators: [centreColumnDecorator],
65+
} satisfies Meta<typeof ProductCardInline>;
66+
67+
export default meta;
68+
69+
type Story = StoryObj<typeof meta>;
70+
71+
export const Default = {} satisfies Story;
72+
73+
export const ProductCardOnly = {
74+
args: {
75+
...meta.args,
76+
isCardOnly: true,
77+
},
78+
};
79+
export const ProductCardOnlyDisplayCredit = {
80+
args: {
81+
...meta.args,
82+
isCardOnly: true,
83+
image: { ...productImage, displayCredit: true },
84+
},
85+
} satisfies Story;

0 commit comments

Comments
 (0)