From c0aca6436c70f31c1f22dd0eac43944b2bba3f8d Mon Sep 17 00:00:00 2001 From: Alex Sanders Date: Thu, 12 Dec 2024 15:26:04 +0000 Subject: [PATCH 1/3] pass grid width to `Layout` component so that consumers can layout custom pages more easily --- .../react-crossword/src/@types/Layout.ts | 1 + .../src/components/Crossword.stories.tsx | 19 ++++++++++++--- .../src/components/Crossword.tsx | 23 +++++++++++++++---- .../src/context/ContextProvider.tsx | 9 ++++---- .../react-crossword/src/context/Theme.tsx | 12 +++------- .../src/layouts/ScreenLayout.tsx | 10 ++------ 6 files changed, 45 insertions(+), 29 deletions(-) diff --git a/libs/@guardian/react-crossword/src/@types/Layout.ts b/libs/@guardian/react-crossword/src/@types/Layout.ts index 0d466cf80..25905e43f 100644 --- a/libs/@guardian/react-crossword/src/@types/Layout.ts +++ b/libs/@guardian/react-crossword/src/@types/Layout.ts @@ -10,4 +10,5 @@ export type LayoutProps = { AnagramHelper: typeof AnagramHelper; Clues: typeof Clues; SavedMessage: ComponentType; + gridWidth: number; }; diff --git a/libs/@guardian/react-crossword/src/components/Crossword.stories.tsx b/libs/@guardian/react-crossword/src/components/Crossword.stories.tsx index 268fe09b3..f94b9bb69 100644 --- a/libs/@guardian/react-crossword/src/components/Crossword.stories.tsx +++ b/libs/@guardian/react-crossword/src/components/Crossword.stories.tsx @@ -69,9 +69,16 @@ export const MultiplePlayersRow: StoryFn = () => { }; export const CustomLayoutRaw: StoryFn = () => { - const Layout = ({ Clues, Grid, Controls, SavedMessage }: LayoutProps) => { + const Layout = ({ + Clues, + Grid, + Controls, + SavedMessage, + gridWidth, + }: LayoutProps) => { return ( <> +

gridWidth: {gridWidth}

@@ -103,13 +110,19 @@ export const CustomisedLayout: StoryFn = () => { ); - const Layout = ({ Clues, Grid, Controls, SavedMessage }: LayoutProps) => { + const Layout = ({ + Clues, + Grid, + Controls, + SavedMessage, + gridWidth, + }: LayoutProps) => { return (
-
+
{ ); }; -const layoutProps: LayoutProps = { +const layoutComponents: Omit = { Grid, Controls, AnagramHelper, @@ -47,9 +48,21 @@ export const Crossword = ({ }: CrosswordProps) => { const LayoutComponent = Layout ?? ScreenLayout; + const theme = useMemo( + () => ({ ...defaultTheme, ...userTheme }), + [userTheme], + ); + + const gridWidth = useMemo( + () => + (theme.gridCellSize + theme.gridGutterSize) * data.dimensions.cols + + theme.gridGutterSize, + [theme.gridCellSize, theme.gridGutterSize, data.dimensions.cols], + ); + return ( - {children ?? } + {children ?? ( + + )}
); diff --git a/libs/@guardian/react-crossword/src/context/ContextProvider.tsx b/libs/@guardian/react-crossword/src/context/ContextProvider.tsx index f467f07dc..0ec9b553a 100644 --- a/libs/@guardian/react-crossword/src/context/ContextProvider.tsx +++ b/libs/@guardian/react-crossword/src/context/ContextProvider.tsx @@ -20,9 +20,8 @@ import type { ReactNode } from 'react'; import type { CAPICrossword } from '../@types/CAPI'; -import type { Progress } from '../@types/crossword'; +import type { Progress, Theme } from '../@types/crossword'; import type { EntryID } from '../@types/Entry'; -import type { CrosswordProps } from '../components/Crossword'; import { CurrentCellProvider } from './CurrentCell'; import { CurrentClueProvider } from './CurrentClue'; import { DataProvider } from './Data'; @@ -35,19 +34,19 @@ export const ContextProvider = ({ data, selectedEntryId, userProgress, - userTheme, + theme, children, }: { data: CAPICrossword; selectedEntryId?: EntryID; userProgress?: Progress; - userTheme?: Partial; + theme: Theme; children: ReactNode; }) => { const { entries, dimensions, solutionAvailable, id } = data; return ( - + (undefined); @@ -8,16 +7,11 @@ export const ThemeProvider = ({ theme, children, }: { - theme?: Partial; + theme: Theme; children: ReactNode; }) => { - const finalTheme = useMemo( - () => ({ ...defaultTheme, ...theme }), - [theme], - ); - return ( - {children} + {children} ); }; diff --git a/libs/@guardian/react-crossword/src/layouts/ScreenLayout.tsx b/libs/@guardian/react-crossword/src/layouts/ScreenLayout.tsx index 4264cd6f4..79ccd52dc 100644 --- a/libs/@guardian/react-crossword/src/layouts/ScreenLayout.tsx +++ b/libs/@guardian/react-crossword/src/layouts/ScreenLayout.tsx @@ -4,7 +4,6 @@ import { textSans12, textSans14 } from '@guardian/source/foundations'; import { memo } from 'react'; import type { Direction } from '../@types/Direction'; import type { LayoutProps } from '../@types/Layout'; -import { useData } from '../context/Data'; import { useTheme } from '../context/Theme'; import { useUIState } from '../context/UI'; @@ -32,19 +31,14 @@ const Layout = ({ AnagramHelper, Clues, SavedMessage, + gridWidth: actualGridWidth, }: LayoutProps) => { const { textColor, clueMinWidth, clueMaxWidth } = useTheme(); const { showAnagramHelper } = useUIState(); const theme = useTheme(); - const { gridGutterSize, gridCellSize } = useTheme(); - const { dimensions } = useData(); - - const gridWidth = Math.max( - (gridCellSize + gridGutterSize) * dimensions.cols + gridGutterSize, - 300, - ); + const gridWidth = Math.max(actualGridWidth, 300); const oneColWidth = gridWidth + clueMinWidth; const twoColWidth = gridWidth + clueMinWidth * 2; From e3a65f389b4d61b58ae2c701725a837c6025067a Mon Sep 17 00:00:00 2001 From: Alex Sanders Date: Thu, 12 Dec 2024 15:36:26 +0000 Subject: [PATCH 2/3] provide theme to stories --- .../src/components/AnagramHelper.stories.tsx | 8 +++++++- .../react-crossword/src/components/Clue.stories.tsx | 3 ++- .../react-crossword/src/components/Clues.stories.tsx | 9 +++++++-- .../react-crossword/src/components/Controls.stories.tsx | 9 +++++++-- .../react-crossword/src/components/Grid.stories.tsx | 4 +++- .../src/components/SolutionDisplay.stories.tsx | 3 ++- .../react-crossword/src/components/WordWheel.stories.tsx | 3 ++- 7 files changed, 30 insertions(+), 9 deletions(-) diff --git a/libs/@guardian/react-crossword/src/components/AnagramHelper.stories.tsx b/libs/@guardian/react-crossword/src/components/AnagramHelper.stories.tsx index 90af71e4d..c5efb63b3 100644 --- a/libs/@guardian/react-crossword/src/components/AnagramHelper.stories.tsx +++ b/libs/@guardian/react-crossword/src/components/AnagramHelper.stories.tsx @@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import { groupedClues as data } from '../../stories/formats/grouped-clues'; import { progress12Across } from '../../stories/formats/grouped-clues.progress'; import { ContextProvider } from '../context/ContextProvider'; +import { defaultTheme } from '../theme'; import { AnagramHelper } from './AnagramHelper'; const meta: Meta = { @@ -11,6 +12,7 @@ const meta: Meta = { (Story) => ( @@ -31,7 +33,11 @@ export const Default: Story = { export const LongClue: Story = { decorators: [ (Story) => ( - + ), diff --git a/libs/@guardian/react-crossword/src/components/Clue.stories.tsx b/libs/@guardian/react-crossword/src/components/Clue.stories.tsx index ec132b3b1..42189db14 100644 --- a/libs/@guardian/react-crossword/src/components/Clue.stories.tsx +++ b/libs/@guardian/react-crossword/src/components/Clue.stories.tsx @@ -1,6 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import { groupedClues as data } from '../../stories/formats/grouped-clues'; import { ContextProvider } from '../context/ContextProvider'; +import { defaultTheme } from '../theme'; import { Clue } from './Clue'; const meta: Meta = { @@ -11,7 +12,7 @@ const meta: Meta = { }, decorators: [ (Story) => ( - + ), diff --git a/libs/@guardian/react-crossword/src/components/Clues.stories.tsx b/libs/@guardian/react-crossword/src/components/Clues.stories.tsx index c236250d7..74de53b05 100644 --- a/libs/@guardian/react-crossword/src/components/Clues.stories.tsx +++ b/libs/@guardian/react-crossword/src/components/Clues.stories.tsx @@ -3,6 +3,7 @@ import { groupedClues as data } from '../../stories/formats/grouped-clues'; import { progress } from '../../stories/formats/grouped-clues.progress'; import { ContextProvider } from '../context/ContextProvider'; import { ValidAnswersProvider } from '../context/ValidAnswers'; +import { defaultTheme } from '../theme'; import { Clues } from './Clues'; const meta: Meta = { @@ -16,7 +17,11 @@ const meta: Meta = { localStorage.removeItem(data.id); return ( - + ); @@ -37,7 +42,7 @@ export const Default: Story = {}; export const WithSuccess: Story = { decorators: [ (Story) => ( - + diff --git a/libs/@guardian/react-crossword/src/components/Controls.stories.tsx b/libs/@guardian/react-crossword/src/components/Controls.stories.tsx index 299d4cc92..2607f26bd 100644 --- a/libs/@guardian/react-crossword/src/components/Controls.stories.tsx +++ b/libs/@guardian/react-crossword/src/components/Controls.stories.tsx @@ -1,6 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import { groupedClues as data } from '../../stories/formats/grouped-clues'; import { ContextProvider } from '../context/ContextProvider'; +import { defaultTheme } from '../theme'; import { Controls } from './Controls'; const meta: Meta = { @@ -12,7 +13,11 @@ const meta: Meta = { localStorage.removeItem(data.id); return ( - + ); @@ -30,7 +35,7 @@ export const NoSelectedEntry: Story = { (Story) => { localStorage.removeItem(data.id); return ( - + ); diff --git a/libs/@guardian/react-crossword/src/components/Grid.stories.tsx b/libs/@guardian/react-crossword/src/components/Grid.stories.tsx index 963eeb425..0ef12bfe5 100644 --- a/libs/@guardian/react-crossword/src/components/Grid.stories.tsx +++ b/libs/@guardian/react-crossword/src/components/Grid.stories.tsx @@ -4,6 +4,7 @@ import { progress } from '../../stories/formats/grouped-clues.progress'; import { separators as separatorData } from '../../stories/formats/separators'; import type { Progress as ProgressType } from '../@types/crossword'; import { ContextProvider } from '../context/ContextProvider'; +import { defaultTheme } from '../theme'; import { Grid } from './Grid'; const meta: Meta = { @@ -16,6 +17,7 @@ const meta: Meta = { return ( @@ -39,7 +41,7 @@ export const Progress: Story = { export const Separators: Story = { decorators: [ (Story) => ( - + ), diff --git a/libs/@guardian/react-crossword/src/components/SolutionDisplay.stories.tsx b/libs/@guardian/react-crossword/src/components/SolutionDisplay.stories.tsx index 230112792..2aa38a858 100644 --- a/libs/@guardian/react-crossword/src/components/SolutionDisplay.stories.tsx +++ b/libs/@guardian/react-crossword/src/components/SolutionDisplay.stories.tsx @@ -1,6 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import { groupedClues as data } from '../../stories/formats/grouped-clues'; import { ContextProvider } from '../context/ContextProvider'; +import { defaultTheme } from '../theme'; import { SolutionDisplay } from './SolutionDisplay'; const meta: Meta = { @@ -9,7 +10,7 @@ const meta: Meta = { args: {}, decorators: [ (Story) => ( - + ), diff --git a/libs/@guardian/react-crossword/src/components/WordWheel.stories.tsx b/libs/@guardian/react-crossword/src/components/WordWheel.stories.tsx index bfa03b1a0..c5d6c33bf 100644 --- a/libs/@guardian/react-crossword/src/components/WordWheel.stories.tsx +++ b/libs/@guardian/react-crossword/src/components/WordWheel.stories.tsx @@ -2,6 +2,7 @@ import type { Meta } from '@storybook/react'; import { type StoryObj } from '@storybook/react'; import { groupedClues as data } from '../../stories/formats/grouped-clues'; import { ContextProvider } from '../context/ContextProvider'; +import { defaultTheme } from '../theme'; import { WordWheel } from './WordWheel'; const meta: Meta = { @@ -10,7 +11,7 @@ const meta: Meta = { args: {}, decorators: [ (Story) => ( - + ), From 6a224587b738eaacc2ff830d90c5a6ef1d1df9ed Mon Sep 17 00:00:00 2001 From: Alex Sanders Date: Thu, 12 Dec 2024 16:39:54 +0000 Subject: [PATCH 3/3] wrap the clues title in the custom header, if provided --- .../react-crossword/src/components/Clues.tsx | 34 +++++++++++-------- .../src/components/Crossword.stories.tsx | 7 ++-- .../src/layouts/ScreenLayout.tsx | 7 ++-- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/libs/@guardian/react-crossword/src/components/Clues.tsx b/libs/@guardian/react-crossword/src/components/Clues.tsx index 5e39fa2f2..6eb776ef6 100644 --- a/libs/@guardian/react-crossword/src/components/Clues.tsx +++ b/libs/@guardian/react-crossword/src/components/Clues.tsx @@ -1,5 +1,5 @@ import { css } from '@emotion/react'; -import type { ComponentType } from 'react'; +import type { ComponentType, ReactNode } from 'react'; import { useCallback, useEffect, useRef } from 'react'; import type { Direction } from '../@types/Direction'; import type { EntryID } from '../@types/Entry'; @@ -14,9 +14,10 @@ type Props = { /** Use this to provide a custom header component for the list of clues. If * undefined, the word 'across' or 'down' will be displayed, unstyled. */ Header?: ComponentType<{ - /** If you use a custom clues header, this prop is required to force - * users to do something with it, for the sake of a11y... */ - direction: Direction; + /** If you use a custom clues header, it must accept children so that we + * can provide a properly marked up `label` element, for the sake of + * a11y... */ + children: ReactNode; }>; }; @@ -64,19 +65,22 @@ export const Clues = ({ direction, Header }: Props) => { } } + const label = ( + + ); + return (
-