Skip to content

Commit

Permalink
Merge pull request #11763 from guardian/doml/edition-switcher-banner-…
Browse files Browse the repository at this point in the history
…with-islands

Edition switcher banner
  • Loading branch information
domlander committed Jul 10, 2024
2 parents f32c144 + fcb1a25 commit b4b8144
Show file tree
Hide file tree
Showing 13 changed files with 492 additions and 63 deletions.
104 changes: 104 additions & 0 deletions dotcom-rendering/playwright/tests/edition-switcher-banner.e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { expect, test } from '@playwright/test';
import { disableCMP } from '../lib/cmp';
import { addCookie } from '../lib/cookies';
import { loadPage } from '../lib/load-page';

const scenarios = [
{ pageId: 'uk', edition: 'AU', showBanner: true },
{ pageId: 'europe', edition: 'US', showBanner: true },
{ pageId: 'uk', edition: 'UK', showBanner: false },
{ pageId: 'us', edition: 'US', showBanner: false },
];

test.describe('Edition Switcher Banner', () => {
for (const { pageId, edition } of scenarios.filter(
({ showBanner }) => showBanner,
)) {
test.describe("the page ID doesn't match the edition", () => {
test(`It shows the banner when the edition is ${edition} and the page is /${pageId}`, async ({
context,
page,
}) => {
await addCookie(context, {
name: 'GU_EDITION',
value: `${edition}`,
});

await disableCMP(context);
await loadPage(
page,
`/Front/https://www.theguardian.com/${pageId}`,
);

await expect(
page.locator('[data-component="edition-switcher-banner"]'),
).toBeVisible();
});
});
}

for (const { pageId, edition } of scenarios.filter(
({ showBanner }) => !showBanner,
)) {
test.describe('the page ID matches the edition', () => {
test(`It does NOT show the banner when the edition is ${edition} and the page is /${pageId}`, async ({
context,
page,
}) => {
await addCookie(context, {
name: 'GU_EDITION',
value: `${edition}`,
});

await disableCMP(context);
await loadPage(
page,
`/Front/https://www.theguardian.com/${pageId}`,
);

await expect(
page.locator('[data-component="edition-switcher-banner"]'),
).not.toBeVisible();
});
});
}

test.describe('the page is not a network front', () => {
test(`It does NOT show the banner on a section front`, async ({
context,
page,
}) => {
await addCookie(context, {
name: 'GU_EDITION',
value: 'US',
});

await disableCMP(context);
await loadPage(page, `/Front/https://www.theguardian.com/uk/sport`);

await expect(
page.locator('[data-component="edition-switcher-banner"]'),
).not.toBeVisible();
});

test(`It does NOT show the banner on an article`, async ({
context,
page,
}) => {
await addCookie(context, {
name: 'GU_EDITION',
value: 'US',
});

await disableCMP(context);
await loadPage(
page,
`/Article/https://www.theguardian.com/environment/2020/oct/13/maverick-rewilders-endangered-species-extinction-conservation-uk-wildlife`,
);

await expect(
page.locator('[data-component="edition-switcher-banner"]'),
).not.toBeVisible();
});
});
});
127 changes: 127 additions & 0 deletions dotcom-rendering/src/components/EditionSwitcherBanner.importable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { css } from '@emotion/react';
import { from, palette, space, textSans14 } from '@guardian/source/foundations';
import { LinkButton } from '@guardian/source/react-components';
import { center } from '../lib/center';
import {
type Edition,
type EditionId,
getEditionFromId,
getEditionFromPageId,
} from '../lib/edition';
import { getZIndex } from '../lib/getZIndex';
import {
hideEditionSwitcherBanner,
useEditionSwitcherBanner,
} from '../lib/useUserPreferredEdition';
import XIcon from '../static/icons/x.svg';

const container = css`
position: sticky;
top: 0;
background-color: ${palette.brand[800]};
${getZIndex('editionSwitcherBanner')};
`;

const content = css`
display: flex;
justify-content: space-between;
padding: 10px;
align-items: flex-start;
${center}
${from.mobileLandscape} {
padding: 10px ${space[5]}px;
}
${from.phablet} {
align-items: center;
}
`;

const textAndLink = css`
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 10px;
${textSans14};
${from.phablet} {
flex-direction: row;
align-items: center;
gap: ${space[5]}px;
}
`;

const linkButton = css`
${from.phablet} {
padding: 18px ${space[4]}px;
}
`;

const closeButton = css`
cursor: pointer;
display: flex;
justify-content: center;
padding: 0;
background-color: unset;
border: none;
`;

type Props = {
pageId: Edition['pageId'];
edition: EditionId;
};

/**
* Displays a banner between the header and main content on network fronts.
* Provides a link to switch to the homepage of the user's preferred edition.
*
* See PR for details: https://github.com/guardian/dotcom-rendering/pull/11763
*/
export const EditionSwitcherBanner = ({ pageId, edition }: Props) => {
const { showBanner, isBannerClosed } = useEditionSwitcherBanner(
pageId,
edition,
);

const suggestedPageId = getEditionFromId(edition).pageId;
const suggestedEdition = getEditionFromId(edition).title.replace(
' edition',
'',
);
const defaultEdition = getEditionFromPageId(pageId);
const defaultEditionName = defaultEdition?.title.replace(' edition', '');

if (isBannerClosed || !showBanner || !defaultEditionName) {
return null;
}

return (
<aside data-component="edition-switcher-banner" css={container}>
<div css={content}>
<div css={textAndLink}>
<p>You are viewing the {defaultEditionName} homepage</p>
<LinkButton
href={`/${suggestedPageId}`}
priority="primary"
size="xsmall"
cssOverrides={linkButton}
data-link-name="edition-switcher-banner switch-edition"
>
View the {suggestedEdition} homepage
</LinkButton>
</div>
<button
type="button"
css={closeButton}
data-link-name="edition-switcher-banner close-banner"
onClick={() => {
hideEditionSwitcherBanner();
}}
>
<XIcon />
</button>
</div>
</aside>
);
};
18 changes: 18 additions & 0 deletions dotcom-rendering/src/components/EditionSwitcherBanner.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { Meta, StoryObj } from '@storybook/react';
import { EditionSwitcherBanner as EditionSwitcherBannerComponent } from './EditionSwitcherBanner.importable';

const meta = {
title: 'Components/EditionSwitcherBanner',
component: EditionSwitcherBannerComponent,
} satisfies Meta<typeof EditionSwitcherBannerComponent>;

export default meta;

type Story = StoryObj<typeof meta>;

export const EditionSwitcherBanner = {
args: {
pageId: 'uk',
edition: 'US',
},
} satisfies Story;
33 changes: 33 additions & 0 deletions dotcom-rendering/src/components/EditionSwitcherBanner.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import '@testing-library/jest-dom';
import { userEvent } from '@storybook/test';
import { act, render, screen } from '@testing-library/react';
import { EditionSwitcherBanner } from './EditionSwitcherBanner.importable';

describe('EditionSwitcherBanner', () => {
it('should display the correct text', () => {
render(<EditionSwitcherBanner pageId="us" edition="UK" />);

screen.getByText('You are viewing the US homepage');

const link = screen.getByRole('link', { name: 'View the UK homepage' });
expect(link).toHaveAttribute('href', '/uk');
});

test('should no longer be present in DOM after clicking the close button', async () => {
const { container } = render(
<EditionSwitcherBanner pageId="us" edition="UK" />,
);
container.querySelector('[data-component="edition-switcher-banner"]');

const closeButton = screen.getByRole('button');
await act(async () => {
await userEvent.click(closeButton);
});

expect(
container.querySelector(
'[data-component="edition-switcher-banner"]',
),
).toBeNull();
});
});
65 changes: 65 additions & 0 deletions dotcom-rendering/src/components/FrontSubNav.importable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { css } from '@emotion/react';
import { StraightLines } from '@guardian/source-development-kitchen/react-components';
import type { EditionId } from '../lib/edition';
import { useEditionSwitcherBanner } from '../lib/useUserPreferredEdition';
import type { SubNavType } from '../model/extract-nav';
import { Section } from './Section';
import { SubNav } from './SubNav.importable';

type Props = {
subNavSections: SubNavType;
currentNavLink: string;
hasPageSkin: boolean;
pageId: string;
userEdition: EditionId;
currentPillarTitle?: string;
};

export const FrontSubNav = ({
subNavSections,
currentNavLink,
hasPageSkin,
pageId,
userEdition,
currentPillarTitle,
}: Props) => {
const { showBanner, isBannerClosed } = useEditionSwitcherBanner(
pageId,
userEdition,
);

if (showBanner && !isBannerClosed) {
return null;
}

return (
<>
<Section
fullWidth={true}
padSides={false}
element="aside"
hasPageSkin={hasPageSkin}
>
<SubNav
subNavSections={subNavSections}
currentNavLink={currentNavLink}
position="header"
currentPillarTitle={currentPillarTitle}
/>
</Section>
<Section
fullWidth={true}
padSides={false}
showTopBorder={false}
hasPageSkin={hasPageSkin}
>
<StraightLines
cssOverrides={css`
display: block;
`}
count={4}
/>
</Section>
</>
);
};
3 changes: 3 additions & 0 deletions dotcom-rendering/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type Props = {
idApiUrl: string;
headerTopBarSearchCapiSwitch: boolean;
hasPageSkin?: boolean;
pageId?: string;
};

export const Header = ({
Expand All @@ -52,6 +53,7 @@ export const Header = ({
idApiUrl,
headerTopBarSearchCapiSwitch,
hasPageSkin = false,
pageId,
}: Props) => (
<div css={headerStyles} data-component="nav3">
<Island priority="critical">
Expand All @@ -68,6 +70,7 @@ export const Header = ({
idApiUrl={idApiUrl}
headerTopBarSearchCapiSwitch={headerTopBarSearchCapiSwitch}
hasPageSkin={hasPageSkin}
pageId={pageId}
/>
</Island>

Expand Down
Loading

0 comments on commit b4b8144

Please sign in to comment.