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
98 changes: 72 additions & 26 deletions dotcom-rendering/src/components/PuzzlesBranding.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { css } from '@emotion/react';
import { css, type SerializedStyles } from '@emotion/react';
import {
from,
palette,
Expand Down Expand Up @@ -76,46 +76,92 @@ const aboutLinkStyles = css`
color: var(--puzzles-text-colour, ${palette.neutral[7]});
`;

const containerLockupStyles = css`
display: flex;
align-items: center;
gap: 7px;
white-space: nowrap;
color: var(--puzzles-text-colour, ${palette.neutral[7]});
a {
text-decoration: none;
}
${from.leftCol} {
flex-direction: column;
align-items: flex-start;
margin-top: ${space[3]}px;
}
`;

const containerLogoStyles = css`
display: block;
width: auto;
height: auto;
max-width: 100px;
max-height: 40px;
object-fit: contain;
${from.leftCol} {
max-width: 120px;
max-height: 60px;
}
`;

type Props = { branding: Branding };

export const PuzzlesBranding = ({ branding }: Props) => {
const SponsorLogoLink = ({
branding,
imageStyles,
}: Props & { imageStyles: SerializedStyles }) => {
const { ophanComponentName, ophanComponentLink } = getOphanComponents({
branding,
locationPrefix: 'front-container',
});
return (
<div
css={overlayStyles}
data-component="puzzles-branding"
data-print-layout="hide"
<a
href={branding.logo.link}
rel="nofollow"
aria-label={`Visit the ${branding.sponsorName} website`}
data-sponsor={branding.sponsorName.toLowerCase()}
data-component={ophanComponentName}
data-link-name={ophanComponentLink}
>
<div css={[alignedStyles, lockupStyles]}>
<span css={labelStyles}>{branding.logo.label}</span>
<a
href={branding.logo.link}
rel="nofollow"
aria-label={`Visit the ${branding.sponsorName} website`}
data-sponsor={branding.sponsorName.toLowerCase()}
data-component={ophanComponentName}
data-link-name={ophanComponentLink}
>
<img
css={logoStyles}
src={branding.logo.src}
alt={branding.sponsorName}
width={branding.logo.dimensions.width}
height={branding.logo.dimensions.height}
/>
</a>
</div>
</div>
<img
css={imageStyles}
src={branding.logo.src}
alt={branding.sponsorName}
width={branding.logo.dimensions.width}
height={branding.logo.dimensions.height}
/>
</a>
);
};

export const PuzzlesBranding = ({ branding }: Props) => (
<div
css={overlayStyles}
data-component="puzzles-branding"
data-print-layout="hide"
>
<div css={[alignedStyles, lockupStyles]}>
<span css={labelStyles}>{branding.logo.label}</span>
<SponsorLogoLink branding={branding} imageStyles={logoStyles} />
</div>
</div>
);

export const PuzzlesBrandingAboutLink = ({ branding }: Props) => (
<div css={[alignedStyles, aboutStyles]} data-print-layout="hide">
<a css={aboutLinkStyles} href={branding.aboutThisLink}>
About this content
</a>
</div>
);

export const PuzzlesContainerBranding = ({ branding }: Props) => (
<div css={containerLockupStyles} data-print-layout="hide">
<span css={labelStyles}>{branding.logo.label}</span>
<SponsorLogoLink
branding={branding}
imageStyles={containerLogoStyles}
/>
</div>
);
8 changes: 8 additions & 0 deletions dotcom-rendering/src/components/PuzzlesDirectory.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ export const AdFree = () => (
<PuzzlesDirectory layout={layout} renderAds={false} />
);

export const SponsoredContainer = () => (
<PuzzlesDirectory
layout={layout}
renderAds={false}
containerBranding={{ crosswords: puzzlesBranding }}
/>
);

export const Sponsored = () => {
const page = createPuzzlesPage({ layout, isAdFreeUser: true });
return (
Expand Down
30 changes: 30 additions & 0 deletions dotcom-rendering/src/components/PuzzlesDirectory.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import '@testing-library/jest-dom';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import type { ReactNode } from 'react';
import { puzzlesBranding } from '../../fixtures/manual/puzzlesBranding';
import type {
PuzzleContainer,
PuzzleItem,
Expand Down Expand Up @@ -34,6 +35,35 @@ const section = (
});

describe('PuzzlesDirectory', () => {
it('shows sponsorship only on the sponsored container', () => {
render(
<PuzzlesDirectory
layout={{
containers: [
section({ id: 'crosswords', title: 'Crosswords' }),
section(),
],
}}
renderAds={false}
containerBranding={{ crosswords: puzzlesBranding }}
/>,
);

const [crosswords, wordGames] = screen.getAllByRole('region');
expect(crosswords).toHaveTextContent('Supported by');
expect(
screen.getByRole('link', {
name: 'Visit the Example sponsor website',
}),
).toHaveAttribute('href', 'https://example.com');
expect(
screen.queryByRole('link', { name: 'About this content' }),
).not.toBeInTheDocument();
expect(
screen.getByRole('heading', { level: 2, name: 'Crosswords' }),
).toHaveAttribute('id', 'crosswords-title');
expect(wordGames).not.toHaveTextContent('Supported by');
});
it('renders puzzle tile titles as H3s with descriptive crossword text', () => {
render(
<PuzzlesDirectory
Expand Down
53 changes: 41 additions & 12 deletions dotcom-rendering/src/components/PuzzlesDirectory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ import {
headlineBold24,
headlineBold28,
palette,
space,
} from '@guardian/source/foundations';
import { ArticleDisplay } from '../lib/articleFormat';
import { puzzlesContainerStyles } from '../lib/puzzlesContainerStyles';
import type { Branding } from '../types/branding';
import type { PuzzleContainer, PuzzlesLayoutType } from '../types/puzzlesPage';
import { AdSlot } from './AdSlot.web';
import { Island } from './Island';
import { getPuzzleUrl, Rows } from './PuzzleCard';
import { PuzzlesArchiveMenu } from './PuzzlesArchiveMenu.island';
import { PuzzlesContainerBranding } from './PuzzlesBranding';
import { PuzzlesSupporting } from './PuzzlesSupporting';

export { getPuzzleUrl };
Expand All @@ -21,6 +24,8 @@ type Props = {
renderAds: boolean;
/** Omit the black rule above the first section, e.g. below the sponsorship banner. */
hideTopRule?: boolean;
/** Sponsor per container ID, for the reader's edition. */
containerBranding?: Partial<Record<string, Branding>>;
};

const sectionStyles = (isFeatured: boolean, hideTopRule: boolean) => css`
Expand Down Expand Up @@ -70,21 +75,12 @@ const sectionStyles = (isFeatured: boolean, hideTopRule: boolean) => css`

const titleStyles = css`
min-width: 0;
margin: 0;
padding: 6px 10px 12px;
${headlineBold24};
color: var(--puzzles-headline-colour, ${palette.neutral[7]});
line-height: 1;
overflow-wrap: break-word;
${from.mobileMedium} {
padding-right: 20px;
padding-left: 20px;
}

${from.tablet} {
${headlineBold28};
}

${from.desktop} {
/* Include the 1px side border in the 20px inset, as the cards do. */
padding-left: 19px;
Expand All @@ -95,6 +91,30 @@ const titleStyles = css`
}
`;

// Title left with the sponsor right on one row, stacked under the title from leftCol.
const brandedTitleStyles = css`
display: flex;
align-items: center;
justify-content: space-between;
gap: ${space[2]}px;
${from.leftCol} {
display: block;
}
`;

const headingStyles = css`
min-width: 0;
margin: 0;
${headlineBold24};
color: var(--puzzles-headline-colour, ${palette.neutral[7]});
line-height: 1;
overflow-wrap: break-word;

${from.tablet} {
${headlineBold28};
}
`;

const headingColumnStyles = css`
${from.leftCol} {
position: relative;
Expand Down Expand Up @@ -233,9 +253,11 @@ const hasContent = (container: PuzzleContainer): boolean =>
const DirectorySection = ({
container,
hideTopRule,
branding,
}: {
container: PuzzleContainer;
hideTopRule: boolean;
branding?: Branding;
}) => {
if (!hasContent(container)) {
return null;
Expand All @@ -247,9 +269,14 @@ const DirectorySection = ({
id={container.id}
>
<div css={headingColumnStyles}>
<h2 css={titleStyles} id={`${container.id}-title`}>
{container.title}
</h2>
<div css={[titleStyles, branding && brandedTitleStyles]}>
<h2 css={headingStyles} id={`${container.id}-title`}>
{container.title}
</h2>
{branding && (
<PuzzlesContainerBranding branding={branding} />
)}
</div>
</div>
<div css={contentStyles}>
<Rows
Expand Down Expand Up @@ -281,6 +308,7 @@ export const PuzzlesDirectory = ({
layout,
renderAds,
hideTopRule = false,
containerBranding,
}: Props) => (
<>
{layout.containers.map((container) => {
Expand Down Expand Up @@ -328,6 +356,7 @@ export const PuzzlesDirectory = ({
}
return (
<DirectorySection
branding={containerBranding?.[container.id]}
container={container}
hideTopRule={hideTopRule}
key={container.id}
Expand Down
Loading