Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v16.20.0
v22.18.0
30 changes: 17 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,32 +23,30 @@
},
"dependencies": {
"@babel/cli": "^7.21.5",
"@hookform/resolvers": "^3.9.0",
"@hookform/resolvers": "^3.10.0",
"@splidejs/react-splide": "^0.7.12",
"axios": "^1.7.2",
"babel-plugin-import": "^1.13.8",
"lazysizes": "^5.3.2",
"lodash": "^4.17.11",
"moment": "^2.29.4",
"prop-types": "^15.8.1",
"pure-react-carousel": "1.30.1",
"react": "^17.0.2",
"pure-react-carousel": "1.35.0",
"react": "^19.0.0",
"react-canvas-confetti": "^1.4.0",
"react-currency-format": "^1.1.0",
"react-dom": "^17.0.2",
"react-hook-form": "^7.52.1",
"react-dom": "^19.0.0",
"react-hook-form": "^7.56.0",
"react-modal": "^3.16.1",
"react-range-slider-input": "^3.0.7",
"react-responsive": "^9.0.2",
"react-scripts": "4.0.3",
"react-spinners": "^0.11.0",
"react-spinners": "^0.17.0",
"react-uid": "^2.3.3",
"styled-components": "^5.3.11",
"youtube-player": "^5.6.0",
"yup": "^1.4.0"
},
"resolutions": {
"react-scripts/workbox-webpack-plugin/workbox-build/@surma/rollup-plugin-off-main-thread/ejs": "3.1.10",
"loader-utils": "2.0.3",
"unset-value": "2.0.1",
"shell-quote": "1.7.3",
Expand All @@ -71,7 +69,7 @@
"lint": "eslint src",
"lint-fix": "yarn lint --fix",
"build-pr": "rm -rf dist && NODE_ENV=development BABEL_ENV=development yarn babel src --out-dir dist --copy-files --ignore __tests__,spec.js,test.js",
"postinstall": "yarn build-pr"
"postinstall": "node scripts/fix-pure-react-carousel-react19.js && yarn build-pr"
},
"browserslist": {
"production": [
Expand All @@ -86,23 +84,29 @@
]
},
"devDependencies": {
"@babel/core": "^7.26.0",
"babel-loader": "^8.4.1",
"css-loader": "^6.11.0",
"style-loader": "^3.3.4",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@playwright/test": "^1.38.1",
"babel-preset-react-app": "^10.0.1",
"cross-env": "^7.0.3",
"ejs": "^3.1.10",
"eslint": "^7.32.0",
"eslint-config-airbnb": "^18.2.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-hooks": "^5.2.0",
"jest": "^26.1.0",
"jest-styled-components": "^7.1.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.8",
"react-styleguidist": "^11.1.7",
"react-test-renderer": "^17.0.2",
"react-styleguidist": "^13.1.4",
"react-test-renderer": "^19.0.0",
"semantic-release": "^17.4.6",
"start-server-and-test": "^2.0.4"
"start-server-and-test": "^2.0.4",
"webpack": "^5.106.2"
}
}
39 changes: 39 additions & 0 deletions scripts/fix-pure-react-carousel-react19.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* pure-react-carousel deepmerge treats plain objects as mergeable. React 19 elements
* use $$typeof === Symbol.for('react.transitional.element'), but the library only
* excluded Symbol.for('react.element'), so it tried to deep-merge element trees and
* overflowed the stack. React.isValidElement covers all element types.
*
* Remove this script when pure-react-carousel ships an upstream fix.
*/
const fs = require('fs');
const path = require('path');

const root = path.join(__dirname, '..');
const files = [
'node_modules/pure-react-carousel/dist/index.es.js',
'node_modules/pure-react-carousel/dist/index.cjs.js'
];

const from = 'function isReactElement(e){return e.$$typeof===REACT_ELEMENT_TYPE}';
const to = 'function isReactElement(e){return React.isValidElement(e)}';

for (const rel of files) {
const abs = path.join(root, rel);
if (!fs.existsSync(abs)) {
// Package not installed (e.g. CI pruning) — skip quietly
continue;
}
const src = fs.readFileSync(abs, 'utf8');
if (src.includes(to)) {
continue;
}
if (!src.includes(from)) {
console.warn(
`[fix-pure-react-carousel-react19] Expected snippet missing in ${rel}; skip (version changed?)`
);
continue;
}
fs.writeFileSync(abs, src.split(from).join(to), 'utf8');
console.log(`[fix-pure-react-carousel-react19] Patched ${rel}`);
}
35 changes: 19 additions & 16 deletions src/components/Molecules/CTA/CTAMultiCard/CTAMultiCard.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import { snakeCase } from 'lodash';
import { useMediaQuery } from 'react-responsive';
Expand Down Expand Up @@ -40,6 +40,23 @@ const CTAMultiCard = ({ data }) => {
const isBelowL = useMediaQuery({ maxWidth: breakpointValues.L - 1 });
const useSplideCarousel = Boolean(carouselOfCards && isBelowL);

const splideOptions = useMemo(
() => ({
arrows: false,
pagination: false,
// Reduce swipe "throw" as Matt felt the defaults are too much
// See https://splidejs.com/guides/options/
drag: 'free',
flickPower: 50,
perMove: 1,
dragMinThreshold: { mouse: 50, touch: 50 },
gap: '1rem',
fixedWidth: '309px',
padding: { left: '0px', right: '0px' }
}),
[]
);

if (!cards || !Array.isArray(cards) || cards.length === 0) {
return null;
}
Expand All @@ -64,21 +81,7 @@ const CTAMultiCard = ({ data }) => {
useSplideCarousel={useSplideCarousel}
>
{useSplideCarousel ? (
<Splide
options={{
arrows: false,
pagination: false,
// Reduce swipe "throw" as Matt felt the defaults are too much
// See https://splidejs.com/guides/options/
drag: 'free',
flickPower: 50,
perMove: 1,
dragMinThreshold: { mouse: 50, touch: 50 },
gap: '1rem',
fixedWidth: '309px',
padding: { left: '0px', right: '0px' }
}}
>
<Splide options={splideOptions}>
{cards.map((card, index) => (
<SplideSlide key={card?.id ? `${card.id}-${index}` : `cta-card-${index}`}>
<CTACard
Expand Down
44 changes: 27 additions & 17 deletions src/components/Molecules/Countdown/Countdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import moment from 'moment';
import Text from '../../Atoms/Text/Text';
import { Wrapper, Digits } from './Countdown.style';

const pad2 = n => String(n).padStart(2, '0');

const Countdown = ({
endDate, color = 'black', endMessage = null, introMessage = null
}) => {
Expand All @@ -17,32 +19,40 @@ const Countdown = ({
});

useEffect(() => {
const isoEndDate = new Date(endDate).toISOString();
setThisEndDate(moment(isoEndDate));
const parsed = moment(endDate);
if (!parsed.isValid()) {
setThisEndDate(null);
setCountdownHasEnded(true);
return;
}
setThisEndDate(parsed);
}, [endDate]);

useEffect(() => {
const interval = setInterval(() => {
if (!thisEndDate || !thisEndDate.isValid()) {
return undefined;
}

const tick = () => {
const now = moment();
const timeRemaining = moment(thisEndDate - now);
const diffSeconds = thisEndDate.diff(now, 'seconds');

const days = timeRemaining.format('DDD');
const hours = timeRemaining.format('HH');
const minutes = timeRemaining.format('mm');
const seconds = timeRemaining.format('ss');
if (diffSeconds < 1) {
setCountdownHasEnded(true);
return;
}

const dur = moment.duration(thisEndDate.diff(now));
setCountdownTime({
days: days - 1,
hours: hours - 1,
minutes,
seconds
days: pad2(Math.max(0, Math.floor(dur.asDays()))),
hours: pad2(dur.hours()),
minutes: pad2(dur.minutes()),
seconds: pad2(dur.seconds())
});
};

if (thisEndDate.diff(now, 'seconds') < 1) {
clearInterval(interval);
setCountdownHasEnded(true);
}
}, 1000);
tick();
const interval = setInterval(tick, 1000);
return () => clearInterval(interval);
}, [thisEndDate]);

Expand Down
2 changes: 1 addition & 1 deletion src/components/Molecules/Countdown/Countdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ import Text from '../../Atoms/Text/Text';
const intro = <Text tag="h2" size="xl">Hello</Text>;
const end = <Text tag="h2" size="xl">Bye</Text>;

<Countdown endDate="Jan 16, 2026 10:30:00" endMessage={end} introMessage={intro}/>
<Countdown endDate="Dec 31, 2030 23:59:59" endMessage={end} introMessage={intro}/>
```
29 changes: 24 additions & 5 deletions src/components/Molecules/VideoBanner/VideoBanner.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,20 @@ const VideoBanner = ({
const videoEl = useRef(null);

const triggerPlay = () => {
videoEl.current.play();
const el = videoEl.current;
if (!el) return;
const p = el.play();
if (p && typeof p.catch === 'function') {
p.catch(() => {
// Autoplay may be blocked until user gesture; muted autoplay usually succeeds.
});
}
};

useEffect(() => {
const el = videoEl.current;
if (!el) return undefined;

// Trigger onload autoplay based on prop:
if (autoPlay) {
// As it's a Chrome requirement to mute any autoplay videos,
Expand All @@ -37,12 +47,21 @@ const VideoBanner = ({
}

// And attach event listener based on prop:
let onEnded;
if (!loop && showPosterAfterPlaying) {
videoEl.current.addEventListener('ended', () => {
// Reloads video, which re-shows poster
videoEl.current.load();
});
onEnded = () => {
if (videoEl.current) {
videoEl.current.load();
}
};
el.addEventListener('ended', onEnded);
}

return () => {
if (onEnded) {
el.removeEventListener('ended', onEnded);
}
};
}, [autoPlay, loop, showPosterAfterPlaying]);

return (
Expand Down
27 changes: 27 additions & 0 deletions styleguide.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,33 @@
const path = require('path');

// Without react-scripts, Styleguidist has no default Babel pipeline. CRA used to supply
// react-scripts/config/webpack.config.js via react-styleguidist's auto-discovery.
module.exports = {
webpackConfig: {
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
configFile: path.resolve(__dirname, 'babel.config.js')
}
}
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.(svg|png|jpe?g|gif|webp|ico|woff2?|eot|ttf|otf|mp4|webm|ogg)$/i,
type: 'asset/resource'
}
]
}
},
getComponentPathLine(componentPath) {
const name = path.basename(componentPath, '.js');
return `import { ${name} } from '@comicrelief/component-library';`;
Expand Down
Loading
Loading