Skip to content
Draft
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
13 changes: 11 additions & 2 deletions dotcom-rendering/fixtures/manual/productBlockElement.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { extractHeadingText } from '../../src/model/enhanceProductElement';
import type { ProductBlockElement } from '../../src/types/content';
import type {
ProductBlockElement,
SummaryProduct,
} from '../../src/types/content';
import { productImage } from './productImage';

export const exampleProduct: ProductBlockElement = {
Expand Down Expand Up @@ -247,7 +250,7 @@ export const exampleProduct: ProductBlockElement = {
],
};

export const exampleAtAGlanceProductArray: ProductBlockElement[] = [
const exampleAtAGlanceProductArray: ProductBlockElement[] = [
{
_type: 'model.dotcomrendering.pageElements.ProductBlockElement',
elementId: 'b85ec38b-091b-40c2-8902-a9114df3cfe3',
Expand Down Expand Up @@ -531,3 +534,9 @@ export const exampleAtAGlanceProductArray: ProductBlockElement[] = [
id: '098',
},
];

export const exampleSummaryProducts: SummaryProduct[] =
exampleAtAGlanceProductArray.map((p) => ({
productBlock: p,
ctaIndex: 0,
}));
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ const meta = preview.meta({
title: 'Components/Horizontal Summary Product Card',
component: HorizontalSummaryProductCard,
args: {
product: { ...exampleProduct, h2Id: 'example-1' },
product: {
productBlock: { ...exampleProduct, h2Id: 'example-1' },
ctaIndex: 0,
},
format: {
design: ArticleDesign.Standard,
display: ArticleDisplay.Standard,
Expand Down
22 changes: 14 additions & 8 deletions dotcom-rendering/src/components/HorizontalSummaryProductCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import {
textSansBold17,
} from '@guardian/source/foundations';
import { Link } from '@guardian/source/react-components';
import { getProductLinkLabel } from '../lib/affiliateLinksUtils';
import type { ArticleFormat } from '../lib/articleFormat';
import { palette } from '../palette';
import type { ProductBlockElement } from '../types/content';
import type { SummaryProduct } from '../types/content';
import { ProductLinkButton } from './Button/ProductLinkButton';
import { ProductCardImage } from './ProductCardImage';

Expand Down Expand Up @@ -81,10 +82,11 @@ export const HorizontalSummaryProductCard = ({
product,
format,
}: {
product: ProductBlockElement;
product: SummaryProduct;
format: ArticleFormat;
}) => {
const cardCta = product.productCtas[0];
const { productBlock, ctaIndex } = product;
const cardCta = productBlock.productCtas[ctaIndex];
if (!cardCta) {
return null;
}
Expand All @@ -95,15 +97,19 @@ export const HorizontalSummaryProductCard = ({
<ProductCardImage
xCustComponentId={'horizontal-summary-card'}
format={format}
image={product.image}
image={productBlock.image}
url={cardCta.url}
/>
</div>
<div css={informationContainer}>
<div css={productCardHeading}>{product.primaryHeadingText}</div>
<div css={secondaryHeading}>{product.secondaryHeadingText}</div>
<div css={productCardHeading}>
{productBlock.primaryHeadingText}
</div>
<div css={secondaryHeading}>
{productBlock.secondaryHeadingText}
</div>
<Link
href={`#${product.h2Id}`}
href={`#${productBlock.h2Id}`}
onFocus={(event) => event.stopPropagation()}
cssOverrides={readMore}
data-component="at-a-glance-stacked-card-read-more"
Expand All @@ -120,7 +126,7 @@ export const HorizontalSummaryProductCard = ({
xCustComponentId="horizontal-summary-card"
fullwidth={true}
minimisePadding={true}
label={'Buy at ' + cardCta.retailer}
label={getProductLinkLabel(cardCta)}
url={cardCta.url}
/>
</div>
Expand Down
40 changes: 23 additions & 17 deletions dotcom-rendering/src/components/ProductCarouselCard.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ const meta = preview.meta({
component: ProductCarouselCard,
title: 'Components/ProductCarouselCard',
args: {
product: { ...exampleProduct, h2Id: 'h2-id' },
product: {
productBlock: { ...exampleProduct, h2Id: 'h2-id' },
ctaIndex: 0,
},
format: {
design: ArticleDesign.Standard,
display: ArticleDisplay.Standard,
Expand All @@ -32,22 +35,25 @@ export const Default = meta.story();
export const WithLongHeadingProductNameAndCTA = meta.story({
args: {
product: {
...exampleProduct,
h2Id: 'h2-id',
primaryHeadingHtml: 'Super long product category review name',
primaryHeadingText: extractHeadingText(
'<em>Super long product: category review name:</em>',
),
productName:
'Sky Kettle with a super duper long name that goes on and on',
productCtas: [
{
url: 'https://www.johnlewis.com/bosch-twk7203gb-sky-variable-temperature-kettle-1-7l-black/p3228625',
text: '',
retailer: 'John Lewis with a very long name',
price: '£45.99',
},
],
productBlock: {
...exampleProduct,
h2Id: 'h2-id',
primaryHeadingHtml: 'Super long product category review name',
primaryHeadingText: extractHeadingText(
'<em>Super long product: category review name:</em>',
),
productName:
'Sky Kettle with a super duper long name that goes on and on',
productCtas: [
{
url: 'https://www.johnlewis.com/bosch-twk7203gb-sky-variable-temperature-kettle-1-7l-black/p3228625',
text: '',
retailer: 'John Lewis with a very long name',
price: '£45.99',
},
],
},
ctaIndex: 0,
},
},
});
38 changes: 23 additions & 15 deletions dotcom-rendering/src/components/ProductCarouselCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ import {
textSansBold17,
} from '@guardian/source/foundations';
import { Link } from '@guardian/source/react-components';
import { getProductLinkLabel } from '../lib/affiliateLinksUtils';
import type { ArticleFormat } from '../lib/articleFormat';
import { palette } from '../palette';
import type { ProductBlockElement } from '../types/content';
import type { SummaryProduct } from '../types/content';
import { ProductLinkButton } from './Button/ProductLinkButton';
import { ProductCardImage } from './ProductCardImage';

export type ProductCarouselCardProps = {
product: ProductBlockElement;
product: SummaryProduct;
format: ArticleFormat;
};

Expand Down Expand Up @@ -83,10 +84,15 @@ export const ProductCarouselCard = ({
product,
format,
}: ProductCarouselCardProps) => {
const hasHeading = !!product.primaryHeadingHtml;
const firstCta = product.productCtas[0];
const headingId = product.h2Id;
const productAndBrandName = [product.brandName, product.productName]
const { productBlock, ctaIndex } = product;

const hasHeading = !!productBlock.primaryHeadingHtml;
const cardCta = productBlock.productCtas[ctaIndex];
const headingId = productBlock.h2Id;
const productAndBrandName = [
productBlock.brandName,
productBlock.productName,
]
.filter(Boolean)
.join(' ');
return (
Expand All @@ -95,7 +101,7 @@ export const ProductCarouselCard = ({
{hasHeading && (
<>
<div css={headingFont}>
{product.primaryHeadingText}
{productBlock.primaryHeadingText}
</div>
<div css={brandAndProductNameFont}>
{productAndBrandName}
Expand All @@ -106,7 +112,7 @@ export const ProductCarouselCard = ({
<div css={readMoreArea}>
{!isUndefined(headingId) &&
hasHeading &&
product.displayType !== 'ProductCardOnly' && (
productBlock.displayType !== 'ProductCardOnly' && (
<Link
href={`#${headingId}`}
cssOverrides={readMoreCta}
Expand All @@ -120,25 +126,27 @@ export const ProductCarouselCard = ({
<div css={imageArea}>
<ProductCardImage
format={format}
image={product.image}
image={productBlock.image}
xCustComponentId={'carousel-card-image'}
/>
</div>
<div css={belowImageArea}>
{!hasHeading && (
<div>
<div css={brandNameFont}>{product.brandName}</div>
<div css={productNameFont}>{product.productName}</div>
<div css={brandNameFont}>{productBlock.brandName}</div>
<div css={productNameFont}>
{productBlock.productName}
</div>
</div>
)}
{firstCta && (
{cardCta && (
<>
<div css={priceStyle}>{firstCta.price}</div>
<div css={priceStyle}>{cardCta.price}</div>
<div css={buttonWrapper}>
<ProductLinkButton
xCustComponentId={'carousel-card'}
label={`Buy at ${firstCta.retailer}`}
url={firstCta.url}
label={getProductLinkLabel(cardCta)}
url={cardCta.url}
fullwidth={true}
minimisePadding={true}
/>
Expand Down
27 changes: 27 additions & 0 deletions dotcom-rendering/src/components/ProductCtaList.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { centreColumnDecorator } from '../../.storybook/decorators/gridDecorators';
import preview from '../../.storybook/preview';
import { exampleProduct } from '../../fixtures/manual/productBlockElement';
import { ArticleDesign, ArticleDisplay, Pillar } from '../lib/articleFormat';
import { ProductCtaList } from './ProductCtaList';

const meta = preview.meta({
component: ProductCtaList,
title: 'Components/ProductCtaList',
args: {
products: [
{
productBlock: { ...exampleProduct, h2Id: 'h2-id' },
ctaIndex: 0,
},
],
format: {
design: ArticleDesign.Review,
display: ArticleDisplay.Standard,
theme: Pillar.Lifestyle,
},
title: 'At a glance',
},
decorators: [centreColumnDecorator],
});

export const Default = meta.story();
88 changes: 88 additions & 0 deletions dotcom-rendering/src/components/ProductCtaList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { css } from '@emotion/react';
import { article17, palette, remSpace } from '@guardian/source/foundations';
import { getProductLinkLabel } from '../lib/affiliateLinksUtils';
import type { ArticleFormat } from '../lib/articleFormat';
import type { SummaryProduct } from '../types/content';
import { ProductLinkButton } from './Button/ProductLinkButton';
import { Subheading } from './Subheading';

const listStyles = css`
li {
${article17}
margin-bottom: ${remSpace[1]};
padding-left: ${remSpace[5]};

p {
margin: -1.5rem 0 0 0;
}
}

li::before {
display: inline-block;
content: '';
border-radius: 50%;
height: ${remSpace[3]};
width: ${remSpace[3]};
background-color: ${palette.neutral[86]};
margin-left: -${remSpace[5]};
margin-right: ${remSpace[2]};
}

strong {
font-weight: bold;
}

a {
margin-top: ${remSpace[3]};
margin-bottom: ${remSpace[3]};
margin-left: -${remSpace[5]};
}
`;

const ListItem = ({ product }: { product: SummaryProduct }) => {
const { productBlock, ctaIndex } = product;
const cta = productBlock.productCtas[ctaIndex];
return (
<li key={product.productBlock.brandName}>
<p>
<strong>{productBlock.primaryHeadingText}</strong>
<br />
{productBlock.brandName}
</p>
{cta && (
<ProductLinkButton
xCustComponentId={'cta-list'}
label={getProductLinkLabel(cta)}
url={cta.url}
minimisePadding={true}
/>
)}
</li>
);
};

export const ProductCtaList = ({
products,
format,
title,
}: {
products: SummaryProduct[];
format: ArticleFormat;
title: string;
}) => {
return (
<>
<Subheading topPadding={true} format={format}>
{title}
</Subheading>
<ul css={listStyles}>
{products.map((product) => (
<ListItem
key={product.productBlock.elementId}
product={product}
/>
))}
</ul>
</>
);
};
Loading
Loading