Skip to content

Commit 2260d3c

Browse files
authored
Crosswords: scrollable clue list (#13507)
* Update to latest crossword canary * Scrollable clue list on tablet * Make clues scrollable until leftCol * Add gradient overlay to clue list * Update gradient visibilty on load and when window resized
1 parent 6ba7650 commit 2260d3c

File tree

3 files changed

+110
-19
lines changed

3 files changed

+110
-19
lines changed

dotcom-rendering/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"@guardian/libs": "21.6.0",
5151
"@guardian/ophan-tracker-js": "2.2.5",
5252
"@guardian/react-crossword": "2.0.2",
53-
"@guardian/react-crossword-next": "npm:@guardian/[email protected]20250226111729",
53+
"@guardian/react-crossword-next": "npm:@guardian/[email protected]20250303163323",
5454
"@guardian/shimport": "1.0.2",
5555
"@guardian/source": "8.0.0",
5656
"@guardian/source-development-kitchen": "12.0.0",

dotcom-rendering/src/components/CrosswordComponent.importable.tsx

Lines changed: 105 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ import { css } from '@emotion/react';
22
import { Crossword as ReactCrossword } from '@guardian/react-crossword-next';
33
import type { CrosswordProps } from '@guardian/react-crossword-next';
44
import {
5+
between,
56
from,
67
headlineBold17,
78
space,
89
textSans14,
910
textSansItalic12,
1011
} from '@guardian/source/foundations';
1112
import { Hide } from '@guardian/source/react-components';
13+
import libDebounce from 'lodash.debounce';
1214
import type { ReactNode } from 'react';
13-
import { memo } from 'react';
15+
import { memo, useEffect, useRef, useState } from 'react';
16+
import { removeMediaRulePrefix, useMatchMedia } from '../lib/useMatchMedia';
1417
import { palette } from '../palette';
1518
import { AdSlot } from './AdSlot.web';
1619

@@ -51,13 +54,55 @@ const Layout: CrosswordProps['Layout'] = ({
5154
gridWidth,
5255
MobileBannerAd,
5356
}) => {
57+
const cluesRef = useRef<HTMLDivElement>(null);
58+
const [showGradient, setShowGradient] = useState(false);
59+
60+
const betweenTabletAndLeftCol = useMatchMedia(
61+
removeMediaRulePrefix(between.tablet.and.leftCol),
62+
);
63+
64+
const updateGradientVisibility = () => {
65+
const clueList = cluesRef.current;
66+
if (!clueList) return;
67+
const scrollPos = clueList.scrollTop;
68+
const maxScroll = clueList.scrollHeight - clueList.clientHeight;
69+
setShowGradient(scrollPos < maxScroll - 16);
70+
};
71+
72+
useEffect(() => {
73+
const clueList = cluesRef.current;
74+
if (!clueList) return;
75+
76+
updateGradientVisibility();
77+
78+
clueList.addEventListener(
79+
'scroll',
80+
libDebounce(updateGradientVisibility, 100),
81+
);
82+
window.addEventListener(
83+
'resize',
84+
libDebounce(updateGradientVisibility, 100),
85+
);
86+
87+
return () => {
88+
clueList.removeEventListener(
89+
'scroll',
90+
libDebounce(updateGradientVisibility, 100),
91+
);
92+
window.removeEventListener(
93+
'resize',
94+
libDebounce(updateGradientVisibility, 100),
95+
);
96+
};
97+
}, []);
98+
5499
return (
55100
<div
56101
css={css`
57102
display: flex;
58103
flex-direction: column;
59104
gap: ${space[4]}px;
60-
${from.phablet} {
105+
${from.tablet} {
61106
flex-direction: row;
62107
}
63108
`}
@@ -71,7 +116,7 @@ const Layout: CrosswordProps['Layout'] = ({
71116
<FocusedClue
72117
additionalCss={css`
73118
max-width: ${gridWidth}px;
74-
${from.phablet} {
119+
${from.tablet} {
75120
display: none;
76121
}
77122
`}
@@ -85,7 +130,8 @@ const Layout: CrosswordProps['Layout'] = ({
85130
>
86131
<FocusedClue
87132
additionalCss={css`
88-
${from.phablet} {
133+
max-width: ${gridWidth}px;
134+
${from.tablet} {
89135
display: none;
90136
}
91137
`}
@@ -106,22 +152,67 @@ const Layout: CrosswordProps['Layout'] = ({
106152

107153
<div
108154
css={css`
109-
${textSans14};
155+
position: relative;
110156
flex: 1;
111157
display: flex;
112-
flex-direction: column;
113-
gap: ${space[4]}px;
114-
align-items: flex-start;
115-
${from.desktop} {
116-
flex-direction: row;
158+
${from.tablet} {
159+
max-height: ${gridWidth}px;
160+
::after {
161+
display: ${showGradient ? 'block' : 'none'};
162+
position: absolute;
163+
content: '';
164+
bottom: 0;
165+
left: 0;
166+
width: 100%;
167+
height: 64px;
168+
background-image: linear-gradient(
169+
180deg,
170+
transparent,
171+
${palette('--article-background')}
172+
);
173+
}
117174
}
118-
> * {
119-
flex: 1;
175+
${from.leftCol} {
176+
max-height: none;
177+
::after {
178+
background-image: none;
179+
}
120180
}
121181
`}
122182
>
123-
<Clues direction="across" Header={CluesHeader} />
124-
<Clues direction="down" Header={CluesHeader} />
183+
<div
184+
ref={cluesRef}
185+
css={css`
186+
${textSans14};
187+
flex: 1;
188+
display: flex;
189+
flex-direction: column;
190+
gap: ${space[4]}px;
191+
${from.tablet} {
192+
overflow-y: scroll;
193+
}
194+
${from.desktop} {
195+
flex-direction: row;
196+
}
197+
${from.leftCol} {
198+
overflow: visible;
199+
}
200+
> * {
201+
flex: 1;
202+
}
203+
`}
204+
>
205+
<Clues
206+
direction="across"
207+
Header={CluesHeader}
208+
scrollToSelected={betweenTabletAndLeftCol}
209+
/>
210+
<Clues
211+
direction="down"
212+
Header={CluesHeader}
213+
scrollToSelected={betweenTabletAndLeftCol}
214+
/>
215+
</div>
125216
</div>
126217
</div>
127218
);

pnpm-lock.yaml

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)