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
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,18 @@ import { HostedContentOnwards } from './HostedContentOnwards';
type Props = {
url: string;
branding?: Branding;
isGalleryPage?: boolean;
};

type OnwardsResponse = {
trails: TrailType[];
};

export const FetchHostedOnwards = ({ branding, url }: Props) => {
export const FetchHostedOnwards = ({
branding,
url,
isGalleryPage = false,
}: Props) => {
const { data, error } = useApi<OnwardsResponse>(url);

if (error) {
Expand All @@ -31,6 +36,7 @@ export const FetchHostedOnwards = ({ branding, url }: Props) => {
<HostedContentOnwards
trails={trails}
brandName={branding?.sponsorName ?? ''}
isGalleryPage={isGalleryPage}
/>
);
};
8 changes: 4 additions & 4 deletions dotcom-rendering/src/components/GalleryCaption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,14 @@ const hostedGalleryStyles = css`
${textSansBold17}
align-self: end;

${from.tablet} {
padding-bottom: ${space[10]}px;
}

Comment on lines +58 to +61

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lift and shift to make the CSS easier to read:
small screen -> large screen CSS ordering

${between.tablet.and.desktop} {
padding-left: 0;
padding-right: 0;
}

${from.tablet} {
padding-bottom: ${space[10]}px;
}
`;

const positionIndicatorStyles = css`
Expand Down
16 changes: 14 additions & 2 deletions dotcom-rendering/src/components/GalleryImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { css } from '@emotion/react';
import { isUndefined } from '@guardian/libs';
import { between, from, space, until } from '@guardian/source/foundations';
import { grid } from '../grid';
import { type ArticleFormat } from '../lib/articleFormat';
import { ArticleDesign, type ArticleFormat } from '../lib/articleFormat';
import { getImage } from '../lib/image';
import { palette } from '../palette';
import type { ImageBlockElement } from '../types/content';
Expand Down Expand Up @@ -47,6 +47,12 @@ const styles = css`
}
`;

const hostedGalleryOverrides = css`
${between.desktop.and.leftCol} {
${grid.centreRule(2, 'transparent')}
}
`;
Comment on lines +50 to +54

@cemms1 cemms1 Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This removes the vertical rule to the left of the caption (see screenshot of the problem) which appears from desktop until leftCol breakpoint and comes from the new grid logic

Image


const galleryBodyImageStyles = css`
display: inline;
position: relative;
Expand Down Expand Up @@ -87,7 +93,13 @@ export const GalleryImage = ({
}

return (
<figure css={styles}>
<figure
css={[
styles,
format.design === ArticleDesign.HostedGallery &&
hostedGalleryOverrides,
]}
>
<div
css={galleryBodyImageStyles}
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const meta = preview.meta({
args: {
trails: hostedOnwardsTrails,
brandName: 'TrendAI',
isGalleryPage: false,
},
render: (args) => <HostedContentOnwards {...args} />,
});
Expand All @@ -18,3 +19,10 @@ export const Default = meta.story({});
export const WithAccentColour = meta.story({
decorators: hostedPaletteDecorator('#d90c1f'),
});

export const HostedGallery = meta.story({
args: {
isGalleryPage: true,
},
decorators: hostedPaletteDecorator('#d90c1f'),
});
75 changes: 57 additions & 18 deletions dotcom-rendering/src/components/HostedContentOnwards.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { css } from '@emotion/react';
import {
palette as sourcePalette,
from,
space,
textSans17,
textSansBold20,
Expand All @@ -12,25 +12,30 @@ import { HostedContentOnwardsCard } from './HostedContentOnwardsCard';
type HostedContentOnwardsProps = {
trails: TrailType[];
brandName: string;
isGalleryPage: boolean;
};

/**
* Override --accent-colour variable at a higher CSS specificity
* for hosted gallery articles only, because this only has a dark design
*/
const galleryOverrides = css`
--accent-colour: ${palette('--onward-text')};
`;
Comment on lines +18 to +24

@cemms1 cemms1 Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to colour contrast issues, we suppress the accent colour from appearing in most dark mode hosted pages. Galleries only ever have "dark mode" so we need to do this in a more permanent way. A simple way to do this is to override the previous one set at the page level by adding a higher specificity colour variable which remains the same between dark and light modes


const headerStyles = css`
margin-bottom: ${space[1]}px;
border-top: ${space[2]}px solid
var(--accent-colour, ${sourcePalette.neutral[86]});
border-top: ${space[2]}px solid var(--accent-colour);
`;

const headingStyles = css`
${textSans17}
padding-top: ${space[2]}px;
color: ${palette('--hosted-content-onwards-heading')};
padding-bottom: ${space[2]}px;
color: ${palette('--onward-text')};

@media (prefers-color-scheme: dark) {
color: ${palette('--hosted-content-onwards-heading')};
}

[data-color-scheme='dark'] & {
color: ${palette('--hosted-content-onwards-heading')};
color: ${palette('--onward-text')};
}

span {
Expand All @@ -47,37 +52,71 @@ const stackedCardsStyles = css`

const stackedCardWrapper = css`
width: 100%;
border-top: 2px solid ${palette('--onward-content-border')};
padding-top: ${space[2]}px;
padding-bottom: ${space[2]}px;
border-top: 1px solid ${palette('--article-border')};
padding: ${space[2]}px 0;

&:last-of-type {
padding-bottom: 0;
padding: ${space[2]}px 0 0 0;
}
`;

const rowCardWrapper = css`
${stackedCardWrapper}
${from.desktop} {
width: 100%;
border-top: none;
border-right: 1px solid ${palette('--article-border')};
padding: 0 ${space[2]}px;

&:first-of-type {
padding: 0 ${space[2]}px 0 0;
}
&:last-of-type {
border-right: none;
padding: 0 0 0 ${space[2]}px;
}
}
`;

const galleryStyles = css`
${from.desktop} {
flex-direction: row;
}
`;

export const HostedContentOnwards = ({
trails,
brandName,
isGalleryPage = false,
}: HostedContentOnwardsProps) => {
return (
<>
<div css={isGalleryPage && galleryOverrides}>
<header css={headerStyles}>
<h2 css={headingStyles}>
More from
<span>{brandName}</span>
</h2>
</header>

<ul css={stackedCardsStyles}>
<ul css={[stackedCardsStyles, isGalleryPage && galleryStyles]}>
{trails.map((trail) => {
return (
<li key={trail.url} css={stackedCardWrapper}>
<HostedContentOnwardsCard trail={trail} />
<li
key={trail.url}
css={
isGalleryPage
? rowCardWrapper
: stackedCardWrapper
}
>
<HostedContentOnwardsCard
trail={trail}
isGalleryPage={isGalleryPage}
/>
</li>
);
})}
</ul>
</>
</div>
);
};
49 changes: 22 additions & 27 deletions dotcom-rendering/src/components/HostedContentOnwardsCard.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
import { css } from '@emotion/react';
import { space, textSansBold15 } from '@guardian/source/foundations';
import { getZIndex } from '../lib/getZIndex';
import { generateImageURL } from '../lib/image';
import { palette } from '../palette';
import type { TrailType } from '../types/trails';

type Props = {
trail: TrailType;
isGalleryPage?: boolean;
};

type CardPictureProps = {
image: string;
alt: string;
};

const imageStyles = css`
width: 120px;
`;

const mediaOverlayContainerStyles = css`
position: absolute;
top: 0;
Expand Down Expand Up @@ -61,28 +54,30 @@ const headingStyles = css`
color: ${palette('--card-headline')};
`;

const CardPicture = ({ image, alt }: CardPictureProps) => {
return (
<>
<picture>
<img alt={alt} src={image} css={imageStyles} />
</picture>
<div css={mediaOverlayContainerStyles}>
<div className="media-overlay" />
</div>
</>
);
};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this to be inline instead of a separate component as I feel it's slightly easier to follow this way


export const HostedContentOnwardsCard = ({ trail }: Props) => {
export const HostedContentOnwardsCard = ({
trail,
isGalleryPage = false,
}: Props) => {
return (
<a href={trail.url} css={linkStyles}>
<h3 css={headingStyles}>{trail.headline}</h3>
{!!trail.image && (
<CardPicture
image={trail.image.src}
alt={trail.image.altText || ''}
/>
<>
<picture>
<img
alt={trail.image.altText}
src={generateImageURL({
mainImage: trail.image.src,
imageWidth: isGalleryPage ? 180 : 120,
resolution: 'low',
aspectRatio: '5:4',
})}
Comment on lines +69 to +74

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requests the correct image size from Fastly rather than using the trail image URL directly. This allows us to control the aspect ratio and desired resolution of the images

/>
</picture>
<div css={mediaOverlayContainerStyles}>
<div className="media-overlay" />
</div>
</>
)}
</a>
);
Expand Down
15 changes: 15 additions & 0 deletions dotcom-rendering/src/layouts/HostedGalleryLayout.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,25 @@ import { hostedPaletteDecorator } from '../../.storybook/decorators/themeDecorat
import { allModes } from '../../.storybook/modes';
import preview from '../../.storybook/preview';
import { hostedGallery } from '../../fixtures/manual/hostedGallery';
import { hostedOnwardsTrails } from '../../fixtures/manual/onwardsTrails';
import {
ArticleDesign,
ArticleDisplay,
ArticleSpecial,
} from '../lib/articleFormat';
import { customMockFetch } from '../lib/mockRESTCalls';
import { enhanceArticleType } from '../types/article';
import { HostedGalleryLayout } from './HostedGalleryLayout';

const mockOnwardsContentFetch = customMockFetch([
{
mockedMethod: 'GET',
mockedUrl: `${hostedGallery.config.ajaxUrl}/${hostedGallery.config.pageId}/onward.json`,
mockedStatus: 200,
mockedBody: { trails: hostedOnwardsTrails },
},
]);
Comment on lines +15 to +22

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mocked response of hosted onward content for Storybook stories


const meta = preview.meta({
title: 'Layouts/HostedGallery',
component: HostedGalleryLayout,
Expand All @@ -21,6 +32,10 @@ const meta = preview.meta({
},
},
},
render: (args) => {
global.fetch = mockOnwardsContentFetch;
return <HostedGalleryLayout {...args} />;
},
});

const { hostedCampaignColour = '' } =
Expand Down
Loading
Loading