From 0befacdca9b65a1bb62e42c8bccabb501b668018 Mon Sep 17 00:00:00 2001 From: andresilva-guardian Date: Fri, 23 Oct 2026 16:26:18 +0100 Subject: [PATCH 1/4] Refine puzzles archive menu styling Narrow the menu and reduce item height, with updated padding, typography and default text colour. Add responsive Storybook stories for closed, open and long-title states. --- .../PuzzlesArchiveMenu.island.stories.tsx | 84 +++++++++++++++++++ .../components/PuzzlesArchiveMenu.island.tsx | 22 +++-- 2 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 dotcom-rendering/src/components/PuzzlesArchiveMenu.island.stories.tsx diff --git a/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.stories.tsx b/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.stories.tsx new file mode 100644 index 00000000000..1b393e0a7d0 --- /dev/null +++ b/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.stories.tsx @@ -0,0 +1,84 @@ +import { css } from '@emotion/react'; +import { breakpoints } from '@guardian/source/foundations'; +import type { Meta, StoryObj } from '@storybook/react-webpack5'; +import { userEvent, within } from 'storybook/test'; +import type { PuzzleItem } from '../types/puzzlesPage'; +import { PuzzlesArchiveMenu } from './PuzzlesArchiveMenu.island'; + +const archive = (title: string): PuzzleItem => ({ + id: `archive-${title.toLowerCase().replace(/\s+/g, '-')}`, + title, + type: 'crossword', + set: title.toLowerCase(), + cardVariant: 'archive', + url: `/puzzles-and-games/crosswords/archive?type=${encodeURIComponent(title.toLowerCase())}`, +}); + +const archives = [ + 'Quick', + 'Mini', + 'Cryptic', + 'Quick cryptic', + 'Quiptic', + 'Weekend', + 'Prize', + 'Sunday quick', + 'Genius', + 'Special', +].map(archive); + +const meta = { + component: PuzzlesArchiveMenu, + title: 'Components/PuzzlesArchiveMenu', + parameters: { + chromatic: { + viewports: [ + 320, + breakpoints.mobileLandscape, + breakpoints.tablet, + breakpoints.desktop, + ], + }, + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +export const Closed = { + args: { archives, label: 'Crosswords archive' }, +} satisfies Story; + +export const Open = { + args: { archives, label: 'Crosswords archive' }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await userEvent.click(await canvas.findByText('Crosswords archive')); + }, +} satisfies Story; + +export const OpenWithLongTitles = { + args: { + archives: [ + 'Quick', + 'A crossword archive with a much longer title than usual', + 'Genius', + ].map(archive), + label: 'Crosswords archive', + }, + play: Open.play, +} satisfies Story; diff --git a/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.tsx b/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.tsx index 2a726622a31..2ef9628d9b4 100644 --- a/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.tsx +++ b/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.tsx @@ -1,5 +1,11 @@ import { css } from '@emotion/react'; -import { from, palette, space, textSans14 } from '@guardian/source/foundations'; +import { + from, + palette, + space, + textSans14, + textSans15, +} from '@guardian/source/foundations'; import { useEffect, useRef } from 'react'; import type { PuzzleItem } from '../types/puzzlesPage'; @@ -92,7 +98,7 @@ const menuStyles = css` z-index: 20; top: calc(100% + ${space[1]}px); left: 0; - min-width: 256px; + min-width: 209px; margin: 0; padding: 0; border: 1px solid var(--puzzles-border-colour, ${palette.neutral[86]}); @@ -102,14 +108,16 @@ const menuStyles = css` const linkStyles = css` display: flex; - min-height: 60px; - align-items: center; - padding: 0 ${space[5]}px; + box-sizing: border-box; + min-height: 45px; + align-items: flex-start; + padding: ${space[3]}px ${space[4]}px; border-bottom: 1px solid var(--puzzles-border-colour, ${palette.neutral[86]}); - color: var(--puzzles-headline-colour, ${palette.neutral[7]}); + color: var(--puzzles-headline-colour, ${palette.neutral[0]}); text-decoration: none; - ${textSans14}; + ${textSans15}; + line-height: 1.3; :hover, :focus-visible { From 5fd91226df2494c7aeaff6aabaa2561201f41310 Mon Sep 17 00:00:00 2001 From: andresilva-guardian Date: Fri, 23 Oct 2026 16:29:32 +0100 Subject: [PATCH 2/4] Add responsive positioning for puzzles archive menu --- .../src/components/PuzzlesArchiveMenu.island.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.tsx b/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.tsx index 2ef9628d9b4..e30254a0540 100644 --- a/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.tsx +++ b/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.tsx @@ -5,6 +5,7 @@ import { space, textSans14, textSans15, + until, } from '@guardian/source/foundations'; import { useEffect, useRef } from 'react'; import type { PuzzleItem } from '../types/puzzlesPage'; @@ -104,6 +105,20 @@ const menuStyles = css` border: 1px solid var(--puzzles-border-colour, ${palette.neutral[86]}); background: var(--puzzles-card-background, ${palette.neutral[100]}); list-style: none; + + /* + * Below the tablet breakpoint the archive label sits against the right + * edge, so open the menu leftwards to keep it inside the page. The + * section inset is 10px below mobileMedium and 20px from it, so the menu + * always ends 20px from the right edge of the viewport. + */ + ${until.tablet} { + right: 0; + left: auto; + } + ${until.mobileMedium} { + right: 10px; + } `; const linkStyles = css` From e96d3159172c77f42a77bb22b5faca18e8cae22d Mon Sep 17 00:00:00 2001 From: andresilva-guardian Date: Fri, 23 Oct 2026 16:31:44 +0100 Subject: [PATCH 3/4] Enhance styling for Puzzles Archive Menu to improve layout on mobile devices --- .../src/components/PuzzlesArchiveMenu.island.stories.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.stories.tsx b/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.stories.tsx index 1b393e0a7d0..f04be563514 100644 --- a/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.stories.tsx +++ b/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.stories.tsx @@ -1,5 +1,5 @@ import { css } from '@emotion/react'; -import { breakpoints } from '@guardian/source/foundations'; +import { breakpoints, until } from '@guardian/source/foundations'; import type { Meta, StoryObj } from '@storybook/react-webpack5'; import { userEvent, within } from 'storybook/test'; import type { PuzzleItem } from '../types/puzzlesPage'; @@ -47,6 +47,10 @@ const meta = { /* The open menu is absolutely positioned, so reserve its height. */ min-height: 620px; padding: 20px; + /* Match the 10px section inset below mobileMedium. */ + ${until.mobileMedium} { + padding-right: 10px; + } `} > From 919fd0ecba4540580c251a05fe9a2931bad394a1 Mon Sep 17 00:00:00 2001 From: andresilva-guardian Date: Fri, 23 Oct 2026 16:39:11 +0100 Subject: [PATCH 4/4] Add upward-opening behavior for Puzzles Archive Menu when space is limited --- .../PuzzlesArchiveMenu.island.stories.tsx | 17 +++++++++++ .../components/PuzzlesArchiveMenu.island.tsx | 28 +++++++++++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.stories.tsx b/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.stories.tsx index f04be563514..186a3add78b 100644 --- a/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.stories.tsx +++ b/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.stories.tsx @@ -86,3 +86,20 @@ export const OpenWithLongTitles = { }, play: Open.play, } satisfies Story; + +/** The label sits near the bottom of the viewport, so the menu opens upwards. */ +export const OpenUpwards = { + args: { archives, label: 'Crosswords archive' }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], + play: Open.play, +} satisfies Story; diff --git a/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.tsx b/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.tsx index e30254a0540..b26e9dfd0dc 100644 --- a/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.tsx +++ b/dotcom-rendering/src/components/PuzzlesArchiveMenu.island.tsx @@ -119,6 +119,12 @@ const menuStyles = css` ${until.mobileMedium} { right: 10px; } + + /* Opens upwards when there is not enough room below (see placeMenu). */ + &[data-placement='top'] { + top: auto; + bottom: calc(100% + ${space[1]}px); + } `; const linkStyles = css` @@ -151,6 +157,7 @@ export const PuzzlesArchiveMenu = ({ }: Props) => { const detailsRef = useRef(null); const summaryRef = useRef(null); + const menuRef = useRef(null); useEffect(() => { const close = (event: Event) => { @@ -186,8 +193,25 @@ export const PuzzlesArchiveMenu = ({ return null; } + /** + * Opens the menu upwards when it does not fit below the label and there + * is more room above it. + */ + const placeMenu = () => { + const menu = menuRef.current; + const summary = summaryRef.current; + if (!menu || !summary || detailsRef.current?.open !== true) return; + const { top, bottom } = summary.getBoundingClientRect(); + const spaceBelow = window.innerHeight - bottom - space[1]; + const spaceAbove = top - space[1]; + menu.dataset.placement = + menu.offsetHeight > spaceBelow && spaceAbove > spaceBelow + ? 'top' + : 'bottom'; + }; + return ( -
+
{label}{' '} -
    +
      {validArchives.map(({ archive, url }) => (