From de578e8bd463953710b6cb8d63d6b787be7066db Mon Sep 17 00:00:00 2001 From: oliverabrahams Date: Tue, 7 Jan 2025 15:56:52 +0000 Subject: [PATCH 1/8] Add sticky clue --- .../react-crossword/src/@types/Layout.ts | 2 + .../react-crossword/src/@types/crossword.ts | 13 ++- .../src/components/Crossword.stories.tsx | 14 +++ .../src/components/Crossword.tsx | 2 + .../src/components/StickyClue.tsx | 88 +++++++++++++++++++ .../src/layouts/ScreenLayout.tsx | 6 ++ .../src/utils/parseCrosswordData.ts | 22 ++++- 7 files changed, 138 insertions(+), 9 deletions(-) create mode 100644 libs/@guardian/react-crossword/src/components/StickyClue.tsx diff --git a/libs/@guardian/react-crossword/src/@types/Layout.ts b/libs/@guardian/react-crossword/src/@types/Layout.ts index 25905e43f..e63413cea 100644 --- a/libs/@guardian/react-crossword/src/@types/Layout.ts +++ b/libs/@guardian/react-crossword/src/@types/Layout.ts @@ -3,11 +3,13 @@ import type { AnagramHelper } from '../components/AnagramHelper'; import type { Clues } from '../components/Clues'; import type { Controls } from '../components/Controls'; import type { Grid } from '../components/Grid'; +import type { StickyClue } from '../components/StickyClue'; export type LayoutProps = { Grid: typeof Grid; Controls: typeof Controls; AnagramHelper: typeof AnagramHelper; + StickyClue: typeof StickyClue; Clues: typeof Clues; SavedMessage: ComponentType; gridWidth: number; diff --git a/libs/@guardian/react-crossword/src/@types/crossword.ts b/libs/@guardian/react-crossword/src/@types/crossword.ts index e6ad06a07..b0126cd27 100644 --- a/libs/@guardian/react-crossword/src/@types/crossword.ts +++ b/libs/@guardian/react-crossword/src/@types/crossword.ts @@ -1,6 +1,6 @@ import type { CAPIEntry } from './CAPI'; import type { Direction } from './Direction'; -import type { Entry, EntryID } from './Entry'; +import type { EntryID } from './Entry'; export type Axis = 'x' | 'y'; @@ -11,7 +11,7 @@ export type Cell = Coords & { number?: number; /** Array of entries that this solution is part of */ - group?: CrosswordEntry['group']; + group?: CAPIEntry['group']; /** The cell's solution */ solution?: string; @@ -21,14 +21,13 @@ export type Cells = Map<`x${number}y${number}`, Cell> & { getByCoords: (arg0: Coords) => ReturnType; }; -export type Entries = Map; +export type Entries = Map; export type Progress = string[][]; -export type CrosswordEntry = Entry & { - direction: Direction; - clue: string; - solution?: string; +export type CrosswordEntry = CAPIEntry & { + nextEntryID: EntryID; + prevEntryID: EntryID; }; export type Crossword = { diff --git a/libs/@guardian/react-crossword/src/components/Crossword.stories.tsx b/libs/@guardian/react-crossword/src/components/Crossword.stories.tsx index 9c68f6404..005e2ef10 100644 --- a/libs/@guardian/react-crossword/src/components/Crossword.stories.tsx +++ b/libs/@guardian/react-crossword/src/components/Crossword.stories.tsx @@ -38,6 +38,20 @@ export const ShortContainer: StoryFn = () => { ); }; +export const ShortAndNarrowContainer: StoryFn = () => { + return ( + <> +
+ +
+ + ); +}; export const MultiplePlayersColumn: StoryFn = () => { return ( <> diff --git a/libs/@guardian/react-crossword/src/components/Crossword.tsx b/libs/@guardian/react-crossword/src/components/Crossword.tsx index f26f6c9ac..f207b2cad 100644 --- a/libs/@guardian/react-crossword/src/components/Crossword.tsx +++ b/libs/@guardian/react-crossword/src/components/Crossword.tsx @@ -11,6 +11,7 @@ import { AnagramHelper } from './AnagramHelper'; import { Clues } from './Clues'; import { Controls } from './Controls'; import { Grid } from './Grid'; +import { StickyClue } from './StickyClue'; export type CrosswordProps = { data: CAPICrossword; @@ -35,6 +36,7 @@ const layoutComponents: Omit = { Grid, Controls, AnagramHelper, + StickyClue, Clues, SavedMessage, }; diff --git a/libs/@guardian/react-crossword/src/components/StickyClue.tsx b/libs/@guardian/react-crossword/src/components/StickyClue.tsx new file mode 100644 index 000000000..bc3ceff91 --- /dev/null +++ b/libs/@guardian/react-crossword/src/components/StickyClue.tsx @@ -0,0 +1,88 @@ +import type { SerializedStyles } from '@emotion/react'; +import { css } from '@emotion/react'; +import { isUndefined } from '@guardian/libs'; +import { + Button, + SvgChevronLeftSingle, + SvgChevronRightSingle, +} from '@guardian/source/react-components'; +import type { ForwardedRef } from 'react'; +import { forwardRef } from 'react'; +import { useCurrentCell } from '../context/CurrentCell'; +import { useCurrentClue } from '../context/CurrentClue'; +import { useData } from '../context/Data'; +import { Clue } from './Clue'; + +type StickyClueProps = { + styles?: SerializedStyles; +}; + +export const StickyClue = forwardRef( + (props: StickyClueProps, forwardedRef: ForwardedRef) => { + const { entries, cells } = useData(); + const { currentEntryId, setCurrentEntryId } = useCurrentClue(); + const { setCurrentCell } = useCurrentCell(); + const entry = !isUndefined(currentEntryId) + ? entries.get(currentEntryId) + : undefined; + + const handleClick = (direction: 'prev' | 'next') => { + if (!entry) { + return; + } + const newEntryId = + direction === 'prev' ? entry.prevEntryID : entry.nextEntryID; + const newEntry = entries.get(newEntryId); + if (newEntry) { + setCurrentCell(cells.getByCoords(newEntry.position)); + setCurrentEntryId(newEntryId); + } + }; + + const stickyClue = css` + top: 0; + position: sticky; + display: flex; + z-index: 1; + min-height: 3.5em; + justify-content: space-between; + background: white; + `; + + return ( +
+ {entry && ( + <> + {' '} + + + + + )} +
+ ); + }, +); diff --git a/libs/@guardian/react-crossword/src/layouts/ScreenLayout.tsx b/libs/@guardian/react-crossword/src/layouts/ScreenLayout.tsx index d0821337f..007b0a902 100644 --- a/libs/@guardian/react-crossword/src/layouts/ScreenLayout.tsx +++ b/libs/@guardian/react-crossword/src/layouts/ScreenLayout.tsx @@ -4,6 +4,7 @@ import { textSans12, textSans14 } from '@guardian/source/foundations'; import type { ReactNode } from 'react'; import { memo } from 'react'; import type { LayoutProps } from '../@types/Layout'; +import { StickyClue } from '../components/StickyClue'; import { useShowAnagramHelper } from '../context/ShowAnagramHelper'; import { useTheme } from '../context/Theme'; @@ -68,6 +69,11 @@ const Layout = ({ } `} > + {showAnagramHelper ? : }
Date: Tue, 7 Jan 2025 17:22:14 +0000 Subject: [PATCH 2/8] Add sticky clue to crossword stories --- .../react-crossword/src/components/Crossword.stories.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libs/@guardian/react-crossword/src/components/Crossword.stories.tsx b/libs/@guardian/react-crossword/src/components/Crossword.stories.tsx index 005e2ef10..bfb5d75ce 100644 --- a/libs/@guardian/react-crossword/src/components/Crossword.stories.tsx +++ b/libs/@guardian/react-crossword/src/components/Crossword.stories.tsx @@ -87,12 +87,14 @@ export const CustomLayoutRaw: StoryFn = () => { Clues, Grid, Controls, + StickyClue, SavedMessage, gridWidth, }: LayoutProps) => { return ( <>

gridWidth: {gridWidth}

+ @@ -130,6 +132,7 @@ export const CustomisedLayout: StoryFn = () => { Grid, Controls, SavedMessage, + StickyClue, gridWidth, }: LayoutProps) => { return ( @@ -138,6 +141,7 @@ export const CustomisedLayout: StoryFn = () => {
+
Date: Thu, 23 Jan 2025 11:17:45 +0000 Subject: [PATCH 3/8] hide StickyClue from twocol width --- .../react-crossword/src/components/StickyClue.tsx | 7 +++++-- .../@guardian/react-crossword/src/layouts/ScreenLayout.tsx | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/libs/@guardian/react-crossword/src/components/StickyClue.tsx b/libs/@guardian/react-crossword/src/components/StickyClue.tsx index bc3ceff91..cdc4a4365 100644 --- a/libs/@guardian/react-crossword/src/components/StickyClue.tsx +++ b/libs/@guardian/react-crossword/src/components/StickyClue.tsx @@ -33,8 +33,11 @@ export const StickyClue = forwardRef( const newEntryId = direction === 'prev' ? entry.prevEntryID : entry.nextEntryID; const newEntry = entries.get(newEntryId); - if (newEntry) { - setCurrentCell(cells.getByCoords(newEntry.position)); + const firstCell = newEntry + ? cells.getByCoords(newEntry.position) + : undefined; + if (newEntry && firstCell) { + setCurrentCell(firstCell); setCurrentEntryId(newEntryId); } }; diff --git a/libs/@guardian/react-crossword/src/layouts/ScreenLayout.tsx b/libs/@guardian/react-crossword/src/layouts/ScreenLayout.tsx index 007b0a902..cbcf7bd13 100644 --- a/libs/@guardian/react-crossword/src/layouts/ScreenLayout.tsx +++ b/libs/@guardian/react-crossword/src/layouts/ScreenLayout.tsx @@ -72,6 +72,9 @@ const Layout = ({ {showAnagramHelper ? : } From 3476d185329878522a57f04164de38b76f066b2e Mon Sep 17 00:00:00 2001 From: oliverabrahams Date: Thu, 23 Jan 2025 13:14:11 +0000 Subject: [PATCH 4/8] revert some unneeded changes --- .../react-crossword/src/@types/crossword.ts | 13 ++-- .../src/components/StickyClue.tsx | 62 ++++--------------- .../src/utils/parseCrosswordData.ts | 22 +------ 3 files changed, 20 insertions(+), 77 deletions(-) diff --git a/libs/@guardian/react-crossword/src/@types/crossword.ts b/libs/@guardian/react-crossword/src/@types/crossword.ts index b0126cd27..e6ad06a07 100644 --- a/libs/@guardian/react-crossword/src/@types/crossword.ts +++ b/libs/@guardian/react-crossword/src/@types/crossword.ts @@ -1,6 +1,6 @@ import type { CAPIEntry } from './CAPI'; import type { Direction } from './Direction'; -import type { EntryID } from './Entry'; +import type { Entry, EntryID } from './Entry'; export type Axis = 'x' | 'y'; @@ -11,7 +11,7 @@ export type Cell = Coords & { number?: number; /** Array of entries that this solution is part of */ - group?: CAPIEntry['group']; + group?: CrosswordEntry['group']; /** The cell's solution */ solution?: string; @@ -21,13 +21,14 @@ export type Cells = Map<`x${number}y${number}`, Cell> & { getByCoords: (arg0: Coords) => ReturnType; }; -export type Entries = Map; +export type Entries = Map; export type Progress = string[][]; -export type CrosswordEntry = CAPIEntry & { - nextEntryID: EntryID; - prevEntryID: EntryID; +export type CrosswordEntry = Entry & { + direction: Direction; + clue: string; + solution?: string; }; export type Crossword = { diff --git a/libs/@guardian/react-crossword/src/components/StickyClue.tsx b/libs/@guardian/react-crossword/src/components/StickyClue.tsx index cdc4a4365..fe90d3b1a 100644 --- a/libs/@guardian/react-crossword/src/components/StickyClue.tsx +++ b/libs/@guardian/react-crossword/src/components/StickyClue.tsx @@ -1,14 +1,9 @@ import type { SerializedStyles } from '@emotion/react'; import { css } from '@emotion/react'; import { isUndefined } from '@guardian/libs'; -import { - Button, - SvgChevronLeftSingle, - SvgChevronRightSingle, -} from '@guardian/source/react-components'; +import { textSans15 } from '@guardian/source/foundations'; import type { ForwardedRef } from 'react'; import { forwardRef } from 'react'; -import { useCurrentCell } from '../context/CurrentCell'; import { useCurrentClue } from '../context/CurrentClue'; import { useData } from '../context/Data'; import { Clue } from './Clue'; @@ -19,36 +14,20 @@ type StickyClueProps = { export const StickyClue = forwardRef( (props: StickyClueProps, forwardedRef: ForwardedRef) => { - const { entries, cells } = useData(); - const { currentEntryId, setCurrentEntryId } = useCurrentClue(); - const { setCurrentCell } = useCurrentCell(); + const { entries } = useData(); + const { currentEntryId } = useCurrentClue(); const entry = !isUndefined(currentEntryId) ? entries.get(currentEntryId) : undefined; - const handleClick = (direction: 'prev' | 'next') => { - if (!entry) { - return; - } - const newEntryId = - direction === 'prev' ? entry.prevEntryID : entry.nextEntryID; - const newEntry = entries.get(newEntryId); - const firstCell = newEntry - ? cells.getByCoords(newEntry.position) - : undefined; - if (newEntry && firstCell) { - setCurrentCell(firstCell); - setCurrentEntryId(newEntryId); - } - }; - const stickyClue = css` top: 0; position: sticky; display: flex; z-index: 1; min-height: 3.5em; - justify-content: space-between; + justify-content: center; + ${textSans15}; background: white; `; @@ -59,31 +38,12 @@ export const StickyClue = forwardRef( ref={forwardedRef} > {entry && ( - <> - {' '} - - - - + )}
); diff --git a/libs/@guardian/react-crossword/src/utils/parseCrosswordData.ts b/libs/@guardian/react-crossword/src/utils/parseCrosswordData.ts index 94c811eea..24c26f378 100644 --- a/libs/@guardian/react-crossword/src/utils/parseCrosswordData.ts +++ b/libs/@guardian/react-crossword/src/utils/parseCrosswordData.ts @@ -1,4 +1,3 @@ -import { isUndefined } from '@guardian/libs'; import type { CAPICrossword } from '../@types/CAPI'; import type { Cell, @@ -53,26 +52,9 @@ export const parseCrosswordData = (data: { // For each entry, we'll populate `entries` and `separators` and `cells`. // // We're mutating the 'empty' targets so we can do it all in one loop. - - for (let j = 0; j < data.entries.length; j += 1) { - const nextEntryIndex = (j + 1) % data.entries.length; - const prevEntryIndex = (j - 1 + data.entries.length) % data.entries.length; - const nextEntry = data.entries[nextEntryIndex]; - const prevEntry = data.entries[prevEntryIndex]; - const entry = data.entries[j]; - if ( - isUndefined(entry) || - isUndefined(nextEntry) || - isUndefined(prevEntry) - ) { - throw new Error('Entry out of range'); - } + for (const entry of data.entries) { // populate the `entries` map - entries.set(entry.id, { - ...entry, - nextEntryID: nextEntry.id, - prevEntryID: prevEntry.id, - }); + entries.set(entry.id, entry); // populate the `separators` array for (const [separator, locations] of Object.entries( From ad2400038262a7a6a20b715e5655c91f7fccd766 Mon Sep 17 00:00:00 2001 From: oliverabrahams Date: Thu, 23 Jan 2025 13:28:59 +0000 Subject: [PATCH 5/8] revert some unneeded changes --- .../src/components/StickyClue.tsx | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/libs/@guardian/react-crossword/src/components/StickyClue.tsx b/libs/@guardian/react-crossword/src/components/StickyClue.tsx index fe90d3b1a..270cc0e18 100644 --- a/libs/@guardian/react-crossword/src/components/StickyClue.tsx +++ b/libs/@guardian/react-crossword/src/components/StickyClue.tsx @@ -2,8 +2,7 @@ import type { SerializedStyles } from '@emotion/react'; import { css } from '@emotion/react'; import { isUndefined } from '@guardian/libs'; import { textSans15 } from '@guardian/source/foundations'; -import type { ForwardedRef } from 'react'; -import { forwardRef } from 'react'; +import { memo } from 'react'; import { useCurrentClue } from '../context/CurrentClue'; import { useData } from '../context/Data'; import { Clue } from './Clue'; @@ -12,40 +11,41 @@ type StickyClueProps = { styles?: SerializedStyles; }; -export const StickyClue = forwardRef( - (props: StickyClueProps, forwardedRef: ForwardedRef) => { - const { entries } = useData(); - const { currentEntryId } = useCurrentClue(); - const entry = !isUndefined(currentEntryId) - ? entries.get(currentEntryId) - : undefined; +export const StickyClueComponent = (props: StickyClueProps) => { + const { entries } = useData(); + const { currentEntryId } = useCurrentClue(); + const entry = !isUndefined(currentEntryId) + ? entries.get(currentEntryId) + : undefined; - const stickyClue = css` - top: 0; - position: sticky; - display: flex; - z-index: 1; - min-height: 3.5em; - justify-content: center; - ${textSans15}; - background: white; - `; + const stickyClue = css` + top: 0; + position: sticky; + display: flex; + z-index: 1; + min-height: 3.5em; + justify-content: center; + align-items: start; + ${textSans15}; + background: white; + * { + padding: 0; + } + `; - return ( -
- {entry && ( - - )} -
- ); - }, -); + return ( +
+ {entry && ( + + )} +
+ ); +}; + +export const StickyClue = memo(StickyClueComponent); From 5066f054507de487e17dfe7b9267a08d887787f4 Mon Sep 17 00:00:00 2001 From: oliverabrahams Date: Thu, 23 Jan 2025 13:30:14 +0000 Subject: [PATCH 6/8] revert some unneeded changes --- libs/@guardian/react-crossword/src/components/StickyClue.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/libs/@guardian/react-crossword/src/components/StickyClue.tsx b/libs/@guardian/react-crossword/src/components/StickyClue.tsx index 270cc0e18..1941e4a0a 100644 --- a/libs/@guardian/react-crossword/src/components/StickyClue.tsx +++ b/libs/@guardian/react-crossword/src/components/StickyClue.tsx @@ -28,9 +28,6 @@ export const StickyClueComponent = (props: StickyClueProps) => { align-items: start; ${textSans15}; background: white; - * { - padding: 0; - } `; return ( From 210e3b590ff78cf79e2abbe5c0083d2540a52b86 Mon Sep 17 00:00:00 2001 From: oliverabrahams Date: Fri, 24 Jan 2025 11:11:45 +0000 Subject: [PATCH 7/8] reformat sticky clue --- .../src/components/StickyClue.tsx | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/libs/@guardian/react-crossword/src/components/StickyClue.tsx b/libs/@guardian/react-crossword/src/components/StickyClue.tsx index 1941e4a0a..e2cab6524 100644 --- a/libs/@guardian/react-crossword/src/components/StickyClue.tsx +++ b/libs/@guardian/react-crossword/src/components/StickyClue.tsx @@ -1,11 +1,10 @@ import type { SerializedStyles } from '@emotion/react'; import { css } from '@emotion/react'; import { isUndefined } from '@guardian/libs'; -import { textSans15 } from '@guardian/source/foundations'; +import { textSans12 } from '@guardian/source/foundations'; import { memo } from 'react'; import { useCurrentClue } from '../context/CurrentClue'; import { useData } from '../context/Data'; -import { Clue } from './Clue'; type StickyClueProps = { styles?: SerializedStyles; @@ -24,22 +23,34 @@ export const StickyClueComponent = (props: StickyClueProps) => { display: flex; z-index: 1; min-height: 3.5em; - justify-content: center; align-items: start; - ${textSans15}; + ${textSans12}; background: white; `; return (
{entry && ( - + <> + + + )}
); From 6411022c31ef240390d07966158afe04dbbe79fb Mon Sep 17 00:00:00 2001 From: oliverabrahams Date: Fri, 24 Jan 2025 11:12:09 +0000 Subject: [PATCH 8/8] reformat sticky clue --- libs/@guardian/react-crossword/src/components/StickyClue.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/@guardian/react-crossword/src/components/StickyClue.tsx b/libs/@guardian/react-crossword/src/components/StickyClue.tsx index e2cab6524..bc2f01f51 100644 --- a/libs/@guardian/react-crossword/src/components/StickyClue.tsx +++ b/libs/@guardian/react-crossword/src/components/StickyClue.tsx @@ -29,7 +29,7 @@ export const StickyClueComponent = (props: StickyClueProps) => { `; return ( -
+