Skip to content
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
8 changes: 8 additions & 0 deletions dotcom-rendering/fixtures/manual/productBlockElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const exampleProduct: ProductBlockElement = {
primaryHeadingHtml: 'Best Kettle Overall',
primaryHeadingText: extractHeadingText('Best Kettle Overall'),
secondaryHeadingHtml: 'Bosch Sky Kettle',
secondaryHeadingText: extractHeadingText('Bosch Sky Kettle'),
brandName: 'Bosch',
productName: 'Sky Kettle',
image: productImage,
Expand Down Expand Up @@ -252,6 +253,7 @@ export const exampleAtAGlanceProductArray: ProductBlockElement[] = [
'<em>Best running watch for beginners:</em>',
),
secondaryHeadingHtml: 'Garmin Forerunner 55',
secondaryHeadingText: extractHeadingText('Garmin Forerunner 55'),
brandName: 'Garmin',
productName: 'Forerunner 55',
image: {
Expand Down Expand Up @@ -291,6 +293,7 @@ export const exampleAtAGlanceProductArray: ProductBlockElement[] = [
'<em>Best budget running watch:</em>',
),
secondaryHeadingHtml: 'Suunto Run',
secondaryHeadingText: extractHeadingText('Suunto Run'),
brandName: 'Suunto',
productName: 'Run',
image: {
Expand Down Expand Up @@ -329,6 +332,7 @@ export const exampleAtAGlanceProductArray: ProductBlockElement[] = [
'<em>Best mid-range running watch:</em>',
),
secondaryHeadingHtml: 'Coros Pace Pro',
secondaryHeadingText: extractHeadingText('Coros Pace Pro'),
brandName: 'Coros',
productName: 'Pace Pro',
image: {
Expand Down Expand Up @@ -367,6 +371,7 @@ export const exampleAtAGlanceProductArray: ProductBlockElement[] = [
'<em>Best-looking mid-range running watch:</em>',
),
secondaryHeadingHtml: 'Suunto Race 2',
secondaryHeadingText: extractHeadingText('Suunto Race 2'),
brandName: 'Suunto',
productName: 'Race 2',
image: {
Expand Down Expand Up @@ -405,6 +410,7 @@ export const exampleAtAGlanceProductArray: ProductBlockElement[] = [
'<em>The best running watch money can buy:</em>',
),
secondaryHeadingHtml: 'Garmin Forerunner 970',
secondaryHeadingText: extractHeadingText('Garmin Forerunner 970'),
brandName: 'Garmin',
productName: 'Forerunner 970',
image: {
Expand Down Expand Up @@ -443,6 +449,7 @@ export const exampleAtAGlanceProductArray: ProductBlockElement[] = [
'<em>Best running watch for battery life:</em>',
),
secondaryHeadingHtml: 'Garmin Enduro 3',
secondaryHeadingText: extractHeadingText('Garmin Enduro 3'),
brandName: 'Garmin',
productName: 'Enduro 3',
image: {
Expand Down Expand Up @@ -481,6 +488,7 @@ export const exampleAtAGlanceProductArray: ProductBlockElement[] = [
'<em>Best running watch with LTE/satellite:</em>',
),
secondaryHeadingHtml: 'Garmin Fenix 8 Pro',
secondaryHeadingText: extractHeadingText('Garmin Fenix 8 Pro'),
brandName: 'Garmin',
productName: 'Fenix 8 Pro',
image: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export const HorizontalSummaryProductCard = ({
</div>
<div css={informationContainer}>
<div css={productCardHeading}>{product.primaryHeadingText}</div>
<div css={secondaryHeading}>{product.secondaryHeadingHtml}</div>
<div css={secondaryHeading}>{product.secondaryHeadingText}</div>
<Link
href={`#${product.h2Id}`}
onFocus={(event) => event.stopPropagation()}
Expand Down
9 changes: 9 additions & 0 deletions dotcom-rendering/src/components/ProductElement.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { allModes } from '../../.storybook/modes';
import { productImage } from '../../fixtures/manual/productImage';
import { ArticleDesign, ArticleDisplay, Pillar } from '../lib/articleFormat';
import { getNestedArticleElement } from '../lib/renderElement';
import { extractHeadingText } from '../model/enhanceProductElement';
import type { ProductBlockElement } from '../types/content';
import { ProductElement } from './ProductElement';

Expand All @@ -23,7 +24,9 @@ const product = {
_type: 'model.dotcomrendering.pageElements.ProductBlockElement',
elementId: 'b1f6e8e2-3f3a-4f0c-8d1e-5f3e3e3e3e3e',
primaryHeadingHtml: '<em>Best Kettle overall</em>',
primaryHeadingText: extractHeadingText('<em>Best Kettle overall</em>'),
secondaryHeadingHtml: 'Bosch Sky Kettle',
secondaryHeadingText: extractHeadingText('Bosch Sky Kettle'),
brandName: 'Bosch',
productName: 'Sky Kettle',
image: productImage,
Expand Down Expand Up @@ -298,7 +301,9 @@ export const WithoutHeading = {
product: {
...product,
primaryHeadingHtml: '',
primaryHeadingText: '',
secondaryHeadingHtml: '',
secondaryHeadingText: '',
},
},
} satisfies Story;
Expand All @@ -318,6 +323,7 @@ export const NoSecondaryHeading = {
...product,
primaryHeadingHtml: '<em>Primary heading only</em>',
secondaryHeadingHtml: '',
secondaryHeadingText: '',
},
},
} satisfies Story;
Expand All @@ -327,6 +333,7 @@ export const NoPrimaryHeading = {
product: {
...product,
primaryHeadingHtml: '',
primaryHeadingText: '',
secondaryHeadingHtml: 'Secondary heading only',
},
},
Expand Down Expand Up @@ -380,7 +387,9 @@ export const EmptyFields = {
...product,
image: undefined,
primaryHeadingHtml: '',
primaryHeadingText: extractHeadingText(''),
secondaryHeadingHtml: '',
secondaryHeadingText: extractHeadingText(''),
brandName: '',
productName: '',
productCtas: [],
Expand Down
47 changes: 31 additions & 16 deletions dotcom-rendering/src/components/ProductElement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import { css } from '@emotion/react';
import { from } from '@guardian/source/foundations';
import type { ReactNode } from 'react';
import type { ArticleFormat } from '../lib/articleFormat';
import { parseHtml } from '../lib/domUtils';
import type { NestedArticleElement } from '../lib/renderElement';
import type { ProductBlockElement } from '../types/content';
import { ProductCardInline } from './ProductCardInline';
import { ProductCardLeftCol } from './ProductCardLeftCol';
import { buildElementTree } from './SubheadingBlockComponent';
import { Subheading } from './Subheading';

const contentContainer = css`
position: relative;
Expand Down Expand Up @@ -86,23 +85,10 @@ const Content = ({
const showLeftCol =
product.displayType === 'InlineWithProductCard' &&
shouldShowLeftColCard;
const subheadingHtml = parseHtml(
`<h2 id="${product.h2Id ?? product.elementId}">${
product.primaryHeadingHtml
? `${product.primaryHeadingHtml}<br />`
: ''
} ${product.secondaryHeadingHtml || ''}</h2>`,
);

const isSubheading = subheadingHtml.textContent
? subheadingHtml.textContent.trim().length > 0
: false;
return (
<div>
{isSubheading &&
Array.from(subheadingHtml.childNodes).map(
buildElementTree(format),
)}
<ProductSubheading product={product} format={format} />
<div css={contentContainer} data-spacefinder-role="nested">
{showLeftCol && (
<LeftColProductCardContainer>
Expand Down Expand Up @@ -131,3 +117,32 @@ const Content = ({
</div>
);
};

const ProductSubheading = ({
product,
format,
}: {
product: ProductBlockElement;
format: ArticleFormat;
}) => {
const subheadingHtml = `${
product.primaryHeadingHtml ? `${product.primaryHeadingHtml}<br />` : ''
} ${product.secondaryHeadingHtml || ''}`;

const isSubheading =
!!product.primaryHeadingText || !!product.secondaryHeadingText;

if (!isSubheading) {
return null;
}

return (
<Subheading
id={`${product.h2Id ?? product.elementId}`}
format={format}
topPadding={true}
>
<span dangerouslySetInnerHTML={{ __html: subheadingHtml }}></span>
</Subheading>
);
};
3 changes: 3 additions & 0 deletions dotcom-rendering/src/frontend/schemas/feArticle.json
Original file line number Diff line number Diff line change
Expand Up @@ -4559,6 +4559,9 @@
"secondaryHeadingHtml": {
"type": "string"
},
"secondaryHeadingText": {
"type": "string"
},
"primaryHeadingHtml": {
"type": "string"
},
Expand Down
3 changes: 3 additions & 0 deletions dotcom-rendering/src/model/block-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -4047,6 +4047,9 @@
"secondaryHeadingHtml": {
"type": "string"
},
"secondaryHeadingText": {
"type": "string"
},
"primaryHeadingHtml": {
"type": "string"
},
Expand Down
1 change: 1 addition & 0 deletions dotcom-rendering/src/model/enhanceProductElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const enhanceProductBlockElement = (
content: elementsEnhancer(element.content),
lowestPrice: getLowestPrice(element.productCtas),
primaryHeadingText: extractHeadingText(element.primaryHeadingHtml),
secondaryHeadingText: extractHeadingText(element.secondaryHeadingHtml),
});

/**
Expand Down
1 change: 1 addition & 0 deletions dotcom-rendering/src/types/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,7 @@ export interface ProductBlockElement {
productName: string;
image?: ProductImage;
secondaryHeadingHtml: string;
secondaryHeadingText?: string;
primaryHeadingHtml: string;
primaryHeadingText?: string;
customAttributes: ProductCustomAttribute[];
Expand Down
Loading