diff --git a/dotcom-rendering/index.d.ts b/dotcom-rendering/index.d.ts index 43cc9db9544..eabc3930621 100644 --- a/dotcom-rendering/index.d.ts +++ b/dotcom-rendering/index.d.ts @@ -2,6 +2,9 @@ // 3rd party type declarations // // ------------------------------ +type GuardianCrossword = + import('@guardian/react-crossword-next').CrosswordProps['data']; + declare module 'chromatic/isChromatic'; declare module 'dynamic-import-polyfill' { diff --git a/dotcom-rendering/package.json b/dotcom-rendering/package.json index 25e40e2e627..ab9d62de7b0 100644 --- a/dotcom-rendering/package.json +++ b/dotcom-rendering/package.json @@ -50,6 +50,7 @@ "@guardian/libs": "19.2.1", "@guardian/ophan-tracker-js": "2.2.5", "@guardian/react-crossword": "2.0.2", + "@guardian/react-crossword-next": "npm:@guardian/react-crossword@0.0.0-canary-20241209150926", "@guardian/shimport": "1.0.2", "@guardian/source": "8.0.0", "@guardian/source-development-kitchen": "12.0.0", diff --git a/dotcom-rendering/scripts/env/check-deps.js b/dotcom-rendering/scripts/env/check-deps.js index c34c6ed0fec..2a9110c4195 100644 --- a/dotcom-rendering/scripts/env/check-deps.js +++ b/dotcom-rendering/scripts/env/check-deps.js @@ -8,10 +8,24 @@ if (pkg.devDependencies) { process.exit(1); } -const mismatches = Object.entries(pkg.dependencies).filter( - ([, version]) => - !semver.valid(version) && !version.startsWith('workspace:'), -); +/** + * We don't check packages that are not semver-compatible + * @type {RegExp[]} + */ +const exceptions = /** @type {const} */ ([ + /npm:@guardian\/react-crossword@0.0.0-canary/, +]); + +const mismatches = Object.entries(pkg.dependencies) + .filter( + ([, version]) => + !exceptions.some((exception) => exception.test(version)), + ) + + .filter( + ([, version]) => + !semver.valid(version) && !version.startsWith('workspace:'), + ); if (mismatches.length !== 0) { warn('dotcom-rendering dependencies should be pinned.'); diff --git a/dotcom-rendering/src/components/ArticleContainer.tsx b/dotcom-rendering/src/components/ArticleContainer.tsx index 94e02c55792..5a1e46222e4 100644 --- a/dotcom-rendering/src/components/ArticleContainer.tsx +++ b/dotcom-rendering/src/components/ArticleContainer.tsx @@ -24,6 +24,9 @@ const articleWidth = (format: ArticleFormat) => { } `; } + case ArticleDesign.Crossword: + /* The crossword player manages its own width; */ + return null; case ArticleDesign.Video: case ArticleDesign.Audio: return css` diff --git a/dotcom-rendering/src/components/BylineLink.tsx b/dotcom-rendering/src/components/BylineLink.tsx index 3c9c4beca07..e7c1eb745d0 100644 --- a/dotcom-rendering/src/components/BylineLink.tsx +++ b/dotcom-rendering/src/components/BylineLink.tsx @@ -1,6 +1,7 @@ import { isString } from '@guardian/libs'; import { Hide } from '@guardian/source/react-components'; import { DottedLines } from '@guardian/source-development-kitchen/react-components'; +import { Fragment } from 'react'; import { ArticleDesign, type ArticleFormat } from '../lib/articleFormat'; import { getBylineComponentsFromTokens, @@ -160,6 +161,10 @@ const getRenderedTokens = ( ); } + if (design === ArticleDesign.Crossword && renderedTokens.length > 0) { + return [Set by: , ...renderedTokens]; + } + return renderedTokens; }; diff --git a/dotcom-rendering/src/components/Crossword.importable.tsx b/dotcom-rendering/src/components/Crossword.importable.tsx new file mode 100644 index 00000000000..87ad0d53796 --- /dev/null +++ b/dotcom-rendering/src/components/Crossword.importable.tsx @@ -0,0 +1,6 @@ +import { Crossword as ReactCrossword } from '@guardian/react-crossword-next'; +import type { CrosswordProps } from '@guardian/react-crossword-next'; + +export const Crossword = ({ data }: CrosswordProps) => ( + +); diff --git a/dotcom-rendering/src/components/CrosswordInstructions.tsx b/dotcom-rendering/src/components/CrosswordInstructions.tsx new file mode 100644 index 00000000000..1d59bdb966b --- /dev/null +++ b/dotcom-rendering/src/components/CrosswordInstructions.tsx @@ -0,0 +1,28 @@ +import { css } from '@emotion/react'; +import { + textEgyptian17, + textEgyptianBold17, +} from '@guardian/source/foundations'; + +const instructionsStyles = css` + ${textEgyptian17}; + white-space: pre-line; +`; + +const headerStyles = css` + ${textEgyptianBold17}; + white-space: pre-line; +`; + +export const CrosswordInstructions = ({ + instructions, + className, +}: { + instructions: string; + className?: string; +}) => ( +
+ Special instructions: + {instructions} +
+); diff --git a/dotcom-rendering/src/components/CrosswordLinks.tsx b/dotcom-rendering/src/components/CrosswordLinks.tsx new file mode 100644 index 00000000000..363a70a4113 --- /dev/null +++ b/dotcom-rendering/src/components/CrosswordLinks.tsx @@ -0,0 +1,34 @@ +import { css } from '@emotion/react'; +import { isUndefined } from '@guardian/libs'; +import { textSans15 } from '@guardian/source/foundations'; +import { palette } from '../palette'; + +const crosswordLinkStyles = css` + ${textSans15}; + + a { + color: ${palette('--standfirst-link-text')}; + text-decoration: none; + :hover { + border-bottom: 1px solid ${palette('--standfirst-link-border')}; + } + } +`; + +export const CrosswordLinks = ({ + crossword, + className, +}: { + crossword: GuardianCrossword; + className?: string; +}) => { + return ( + isUndefined(crossword.pdf) || ( + + + PDF version + + + ) + ); +}; diff --git a/dotcom-rendering/src/layouts/CrosswordLayout.tsx b/dotcom-rendering/src/layouts/CrosswordLayout.tsx new file mode 100644 index 00000000000..072420a8eb3 --- /dev/null +++ b/dotcom-rendering/src/layouts/CrosswordLayout.tsx @@ -0,0 +1,790 @@ +import { css } from '@emotion/react'; +import { + from, + palette as sourcePalette, + until, +} from '@guardian/source/foundations'; +import { Hide } from '@guardian/source/react-components'; +import { StraightLines } from '@guardian/source-development-kitchen/react-components'; +import React from 'react'; +import { AdPortals } from '../components/AdPortals.importable'; +import { AdSlot, MobileStickyContainer } from '../components/AdSlot.web'; +import { AppsFooter } from '../components/AppsFooter.importable'; +import { ArticleBody } from '../components/ArticleBody'; +import { ArticleContainer } from '../components/ArticleContainer'; +import { ArticleHeadline } from '../components/ArticleHeadline'; +import { ArticleMetaApps } from '../components/ArticleMeta.apps'; +import { ArticleMeta } from '../components/ArticleMeta.web'; +import { ArticleTitle } from '../components/ArticleTitle'; +import { Carousel } from '../components/Carousel.importable'; +import { CrosswordInstructions } from '../components/CrosswordInstructions'; +import { CrosswordLinks } from '../components/CrosswordLinks'; +import { DecideLines } from '../components/DecideLines'; +import { DiscussionLayout } from '../components/DiscussionLayout'; +import { Footer } from '../components/Footer'; +import { GridItem } from '../components/GridItem'; +import { HeaderAdSlot } from '../components/HeaderAdSlot'; +import { Island } from '../components/Island'; +import { Masthead } from '../components/Masthead/Masthead'; +import { MostViewedFooterData } from '../components/MostViewedFooterData.importable'; +import { MostViewedFooterLayout } from '../components/MostViewedFooterLayout'; +import { OnwardsUpper } from '../components/OnwardsUpper.importable'; +import { RightColumn } from '../components/RightColumn'; +import { Section } from '../components/Section'; +import { SlotBodyEnd } from '../components/SlotBodyEnd.importable'; +import { Standfirst } from '../components/Standfirst'; +import { StickyBottomBanner } from '../components/StickyBottomBanner.importable'; +import { SubMeta } from '../components/SubMeta'; +import { SubNav } from '../components/SubNav.importable'; +import { type ArticleFormat, ArticleSpecial } from '../lib/articleFormat'; +import { canRenderAds } from '../lib/canRenderAds'; +import { getContributionsServiceUrl } from '../lib/contributions'; +import { decideTrail } from '../lib/decideTrail'; +import type { NavType } from '../model/extract-nav'; +import { palette as themePalette } from '../palette'; +import type { ArticleDeprecated } from '../types/article'; +import type { RenderingTarget } from '../types/renderingTarget'; +import { BannerWrapper, Stuck } from './lib/stickiness'; + +const CrosswordGrid = ({ children }: { children: React.ReactNode }) => ( +
+ {children} +
+); + +const maxWidth = css` + ${from.desktop} { + max-width: 620px; + } +`; + +const stretchLines = css` + ${until.phablet} { + margin-left: -20px; + margin-right: -20px; + } + ${until.mobileLandscape} { + margin-left: -10px; + margin-right: -10px; + } +`; + +interface CommonProps { + article: ArticleDeprecated; + format: ArticleFormat; + renderingTarget: RenderingTarget; +} + +interface WebProps extends CommonProps { + NAV: NavType; + renderingTarget: 'Web'; +} + +interface AppsProps extends CommonProps { + renderingTarget: 'Apps'; +} + +export const CrosswordLayout = (props: WebProps | AppsProps) => { + const { article, format, renderingTarget } = props; + const { + config: { isPaidContent, host, hasSurveyAd }, + } = article; + + const isApps = renderingTarget === 'Apps'; + const isWeb = renderingTarget === 'Web'; + + const showComments = article.isCommentable && !isPaidContent; + + const { branding } = article.commercialProperties[article.editionId]; + + const contributionsServiceUrl = getContributionsServiceUrl(article); + + const { absoluteServerTimes = false } = article.config.switches; + + /** + * This property currently only applies to the header and merchandising slots + */ + const renderAds = isWeb && canRenderAds(article); + return ( + <> + {isWeb && ( + <> +
+ {renderAds && ( + +
+
+ +
+
+
+ )} + + +
+ + {renderAds && hasSurveyAd && ( + + )} + + )} +
+ {isApps && ( + <> + + + + + )} +
+
+ + +
+ +
+
+ +
+ +
+
+ {article.crossword && ( + + + + )} + + + + + +
+
+ +
+
+
+ +
+ {isApps ? ( + <> + + + + + + + + ) : ( + + )} +
+
+ {!!article.crossword?.instructions && ( + + + + )} + + + + + + + + {renderAds ? ( + + ) : null} + + +
+
+
+ +
+
+ + + +
+
+ +
+ +
+ +
+ +
+ {renderAds && ( +
+ +
+ )} + + {article.storyPackage && ( +
+ + + +
+ )} + + + + + + {showComments && ( +
+ +
+ )} + + {!isPaidContent && ( +
+ + + + + +
+ )} + + {renderAds && ( +
+ +
+ )} +
+ + {isWeb && props.NAV.subNavSections && ( +
+ + + +
+ )} + + {isWeb && ( + <> +
+
+
+ + + + + + + + + )} + {isApps && ( +
+ + + +
+ )} + + ); +}; diff --git a/dotcom-rendering/src/layouts/DecideLayout.tsx b/dotcom-rendering/src/layouts/DecideLayout.tsx index b55f4973791..9920cb9d19c 100644 --- a/dotcom-rendering/src/layouts/DecideLayout.tsx +++ b/dotcom-rendering/src/layouts/DecideLayout.tsx @@ -8,6 +8,7 @@ import type { ArticleDeprecated } from '../types/article'; import type { RenderingTarget } from '../types/renderingTarget'; import { AudioLayout } from './AudioLayout'; import { CommentLayout } from './CommentLayout'; +import { CrosswordLayout } from './CrosswordLayout'; import { FullPageInteractiveLayout } from './FullPageInteractiveLayout'; import { ImmersiveLayout } from './ImmersiveLayout'; import { InteractiveLayout } from './InteractiveLayout'; @@ -273,6 +274,15 @@ const DecideLayoutWeb = ({ NAV={NAV} /> ); + case ArticleDesign.Crossword: + return ( + + ); default: return ( ): ArticleDesign => { return ArticleDesign.Timeline; case 'ProfileDesign': return ArticleDesign.Profile; + case 'CrosswordDesign': + return ArticleDesign.Crossword; default: return ArticleDesign.Standard; } @@ -270,6 +273,8 @@ const designToFEDesign = (design: ArticleDesign): FEDesign => { return 'TimelineDesign'; case ArticleDesign.Profile: return 'ProfileDesign'; + case ArticleDesign.Crossword: + return 'CrosswordDesign'; } }; diff --git a/dotcom-rendering/src/lib/renderElement.tsx b/dotcom-rendering/src/lib/renderElement.tsx index a220303bfc8..8bfe959409e 100644 --- a/dotcom-rendering/src/lib/renderElement.tsx +++ b/dotcom-rendering/src/lib/renderElement.tsx @@ -9,6 +9,7 @@ import { CartoonComponent } from '../components/CartoonComponent'; import { ChartAtom } from '../components/ChartAtom.importable'; import { CodeBlockComponent } from '../components/CodeBlockComponent'; import { CommentBlockComponent } from '../components/CommentBlockComponent'; +import { Crossword } from '../components/Crossword.importable'; import { DividerBlockComponent } from '../components/DividerBlockComponent'; import { DocumentBlockComponent } from '../components/DocumentBlockComponent.importable'; import { EmailSignUpWrapper } from '../components/EmailSignUpWrapper'; @@ -844,6 +845,14 @@ export const renderElement = ({ case 'model.dotcomrendering.pageElements.DisclaimerBlockElement': { return ; } + case 'model.dotcomrendering.pageElements.CrosswordElement': + return ( +
+ + + +
+ ); case 'model.dotcomrendering.pageElements.AudioBlockElement': case 'model.dotcomrendering.pageElements.ContentAtomBlockElement': case 'model.dotcomrendering.pageElements.GenericAtomBlockElement': diff --git a/dotcom-rendering/src/server/handler.article.web.ts b/dotcom-rendering/src/server/handler.article.web.ts index f98084f570a..25d071303f1 100644 --- a/dotcom-rendering/src/server/handler.article.web.ts +++ b/dotcom-rendering/src/server/handler.article.web.ts @@ -3,7 +3,7 @@ import { Standard as ExampleArticle } from '../../fixtures/generated/fe-articles import { decideFormat } from '../lib/articleFormat'; import { enhanceBlocks } from '../model/enhanceBlocks'; import { validateAsArticleType, validateAsBlock } from '../model/validate'; -import { enhanceArticleType } from '../types/article'; +import { enhanceArticleType, enhanceCrossword } from '../types/article'; import type { FEBlocksRequest } from '../types/frontend'; import { makePrefetchHeader } from './lib/header'; import { recordTypeAndPlatform } from './lib/logging-store'; @@ -54,6 +54,18 @@ export const handleInteractive: RequestHandler = ({ body }, res) => { res.status(200).set('Link', makePrefetchHeader(prefetchScripts)).send(html); }; +export const handleCrossword: RequestHandler = ({ body }, res) => { + recordTypeAndPlatform('crossword', 'web'); + + const article = enhanceArticleType(body, 'Web'); + const crosswordArticle = enhanceCrossword(article); + const { html, prefetchScripts } = renderHtml({ + article: crosswordArticle, + }); + + res.status(200).set('Link', makePrefetchHeader(prefetchScripts)).send(html); +}; + export const handleBlocks: RequestHandler = ({ body }, res) => { recordTypeAndPlatform('blocks'); const { diff --git a/dotcom-rendering/src/server/server.dev.ts b/dotcom-rendering/src/server/server.dev.ts index 0e9c0fb3762..1df15031170 100644 --- a/dotcom-rendering/src/server/server.dev.ts +++ b/dotcom-rendering/src/server/server.dev.ts @@ -10,6 +10,7 @@ import { handleArticle, handleArticleJson, handleBlocks, + handleCrossword, handleInteractive, } from './handler.article.web'; import { handleEditionsCrossword } from './handler.editionsCrossword'; @@ -71,6 +72,8 @@ export const devServer = (): Handler => { return handleInteractive(req, res, next); case 'AMPInteractive': return handleAMPArticle(req, res, next); + case 'Crossword': + return handleCrossword(req, res, next); case 'Blocks': return handleBlocks(req, res, next); case 'Front': diff --git a/dotcom-rendering/src/server/server.prod.ts b/dotcom-rendering/src/server/server.prod.ts index 7704baaeed3..27cc8e39fa6 100644 --- a/dotcom-rendering/src/server/server.prod.ts +++ b/dotcom-rendering/src/server/server.prod.ts @@ -18,6 +18,7 @@ import { handleArticleJson, handleArticlePerfTest, handleBlocks, + handleCrossword, handleInteractive, } from './handler.article.web'; import { handleEditionsCrossword } from './handler.editionsCrossword'; @@ -67,6 +68,7 @@ export const prodServer = (): void => { app.post('/AMPArticle', logRenderTime, handleAMPArticle); app.post('/Interactive', logRenderTime, handleInteractive); app.post('/AMPInteractive', logRenderTime, handleAMPArticle); + app.post('/Crossword', logRenderTime, handleCrossword); app.post('/Blocks', logRenderTime, handleBlocks); app.post('/Front', logRenderTime, handleFront); app.post('/FrontJSON', logRenderTime, handleFrontJson); diff --git a/dotcom-rendering/src/types/article.ts b/dotcom-rendering/src/types/article.ts index e1875c1f123..a15e90c8398 100644 --- a/dotcom-rendering/src/types/article.ts +++ b/dotcom-rendering/src/types/article.ts @@ -1,4 +1,9 @@ -import { type ArticleFormat, decideFormat } from '../lib/articleFormat'; +import { randomUUID } from 'node:crypto'; +import { + ArticleDesign, + type ArticleFormat, + decideFormat, +} from '../lib/articleFormat'; import type { ImageForAppsLightbox } from '../model/appsLightboxImages'; import { appsLightboxImages } from '../model/appsLightboxImages'; import { buildLightboxImages } from '../model/buildLightboxImages'; @@ -30,6 +35,40 @@ export type Article = { frontendData: ArticleDeprecated; }; +export const enhanceCrossword = (article: Article): Article => { + if (article.frontendData.crossword) { + const element = { + _type: 'model.dotcomrendering.pageElements.CrosswordElement' as const, + crossword: article.frontendData.crossword, + }; + return { + ...article, + format: { ...article.format, design: ArticleDesign.Crossword }, + frontendData: { + ...article.frontendData, + blocks: [ + { + id: randomUUID(), + elements: [element], + attributes: { + pinned: false, + keyEvent: false, + summary: false, + }, + primaryDateLine: + article.frontendData.webPublicationDateDisplay, + secondaryDateLine: + article.frontendData + .webPublicationSecondaryDateDisplay, + }, + ], + }, + }; + } + + throw new TypeError('article did not contain a crossword'); +}; + export const enhanceArticleType = ( data: FEArticleType, renderingTarget: RenderingTarget, diff --git a/dotcom-rendering/src/types/content.ts b/dotcom-rendering/src/types/content.ts index d481eacf9c8..c809556a4bf 100644 --- a/dotcom-rendering/src/types/content.ts +++ b/dotcom-rendering/src/types/content.ts @@ -732,6 +732,12 @@ interface WitnessTypeBlockElement extends ThirdPartyEmbeddedContent { | WitnessTypeDataVideo | WitnessTypeDataText; } + +export interface CrosswordElement { + _type: 'model.dotcomrendering.pageElements.CrosswordElement'; + crossword: GuardianCrossword & { instructions?: string }; +} + export type FEElement = | AdPlaceholderBlockElement | AudioAtomBlockElement @@ -791,7 +797,8 @@ export type FEElement = | VideoYoutubeBlockElement | VineBlockElement | YoutubeBlockElement - | WitnessTypeBlockElement; + | WitnessTypeBlockElement + | CrosswordElement; // ------------------------------------- // Misc diff --git a/dotcom-rendering/src/types/frontend.ts b/dotcom-rendering/src/types/frontend.ts index 2f91fe527d7..42b66f9b773 100644 --- a/dotcom-rendering/src/types/frontend.ts +++ b/dotcom-rendering/src/types/frontend.ts @@ -126,6 +126,7 @@ export interface FEArticleType { showTableOfContents: boolean; lang?: string; isRightToLeftLang?: boolean; + crossword?: GuardianCrossword & { instructions: string }; } type PageTypeType = { @@ -202,7 +203,8 @@ export type FEDesign = | 'FullPageInteractiveDesign' | 'NewsletterSignupDesign' | 'TimelineDesign' - | 'ProfileDesign'; // FEDisplay is the display information passed through from frontend (originating in the capi scala client) and dictates the displaystyle of the content e.g. Immersive + | 'ProfileDesign' + | 'CrosswordDesign'; // FEDisplay is the display information passed through from frontend (originating in the capi scala client) and dictates the displaystyle of the content e.g. Immersive // https://github.com/guardian/content-api-scala-client/blob/master/client/src/main/scala/com.gu.contentapi.client/utils/format/Display.scala export type FEDisplay = diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b64bb6a0eeb..c23796ef371 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -364,6 +364,9 @@ importers: '@guardian/react-crossword': specifier: 2.0.2 version: 2.0.2 + '@guardian/react-crossword-next': + specifier: npm:@guardian/react-crossword@0.0.0-canary-20241209150926 + version: /@guardian/react-crossword@0.0.0-canary-20241209150926(@emotion/react@11.11.3)(@guardian/libs@19.2.1)(@guardian/source@8.0.0)(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1)(typescript@5.5.3) '@guardian/shimport': specifier: 1.0.2 version: 1.0.2 @@ -4024,7 +4027,7 @@ packages: '@typescript-eslint/parser': 6.18.0(eslint@8.56.0)(typescript@5.5.3) eslint: 8.56.0 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.18.0)(eslint-plugin-import@2.29.1)(eslint@8.56.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.18.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.18.0)(eslint@8.56.0) tslib: 2.6.2 typescript: 5.5.3 transitivePeerDependencies: @@ -4256,6 +4259,33 @@ packages: tslib: 2.6.2 dev: false + /@guardian/react-crossword@0.0.0-canary-20241209150926(@emotion/react@11.11.3)(@guardian/libs@19.2.1)(@guardian/source@8.0.0)(@types/react@18.3.1)(react-dom@18.3.1)(react@18.3.1)(typescript@5.5.3): + resolution: {integrity: sha512-nJ9vi454SqMynQ0UDz+jBmO/l7YxgVIq6Gvfyy4p/b5cnBNsxh8n4OZtIexhJx/dOhKYsMSPw/5KU8YOnVhF9A==} + peerDependencies: + '@emotion/react': ^11.11.3 + '@guardian/libs': ^19.1.0 + '@guardian/source': ^8.0.0 + '@types/react': ^18.2.79 + react: ^18.2.0 + typescript: ~5.5.2 + peerDependenciesMeta: + '@types/react': + optional: true + typescript: + optional: true + dependencies: + '@emotion/react': 11.11.3(@types/react@18.3.1)(react@18.3.1) + '@guardian/libs': 19.2.1(tslib@2.6.2)(typescript@5.5.3) + '@guardian/source': 8.0.0(@emotion/react@11.11.3)(@types/react@18.3.1)(react@18.3.1)(tslib@2.6.2)(typescript@5.5.3) + '@types/react': 18.3.1 + react: 18.3.1 + tslib: 2.6.2 + typescript: 5.5.3 + use-local-storage-state: 19.5.0(react-dom@18.3.1)(react@18.3.1) + transitivePeerDependencies: + - react-dom + dev: false + /@guardian/react-crossword@2.0.2: resolution: {integrity: sha512-pFvCpuUH+GKz12uUzW4+Lck/ZhDWvqLodr1UwXIE7qjJCz8V4NEfuiGZkkIpVoPh+dEHTkiDQ6Ks4653KdH01g==} dependencies: @@ -6201,7 +6231,7 @@ packages: react-docgen-typescript: 2.2.2(typescript@5.5.3) tslib: 2.6.2 typescript: 5.5.3 - webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) transitivePeerDependencies: - supports-color dev: false @@ -7762,8 +7792,8 @@ packages: webpack: 5.x.x webpack-cli: 5.x.x dependencies: - webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.0.4)(webpack@5.94.0) + webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-dev-server@5.0.4)(webpack@5.94.0) dev: false /@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.94.0): @@ -7773,8 +7803,8 @@ packages: webpack: 5.x.x webpack-cli: 5.x.x dependencies: - webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.0.4)(webpack@5.94.0) + webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-dev-server@5.0.4)(webpack@5.94.0) dev: false /@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack-dev-server@5.0.4)(webpack@5.94.0): @@ -7788,8 +7818,8 @@ packages: webpack-dev-server: optional: true dependencies: - webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.0.4)(webpack@5.94.0) + webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-dev-server@5.0.4)(webpack@5.94.0) webpack-dev-server: 5.0.4(webpack-cli@5.1.4)(webpack@5.94.0) dev: false @@ -8297,7 +8327,7 @@ packages: '@babel/core': 7.26.0 find-cache-dir: 4.0.0 schema-utils: 4.2.0 - webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) dev: false /babel-plugin-istanbul@6.1.1: @@ -9460,7 +9490,7 @@ packages: postcss-modules-values: 4.0.0(postcss@8.4.47) postcss-value-parser: 4.2.0 semver: 7.5.4 - webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) dev: false /css-loader@7.1.2(webpack@5.94.0): @@ -10446,7 +10476,7 @@ packages: enhanced-resolve: 5.17.0 eslint: 8.56.0 eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.18.0)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.18.0)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.18.0)(eslint@8.56.0) fast-glob: 3.3.2 get-tsconfig: 4.7.2 is-core-module: 2.15.1 @@ -11430,7 +11460,7 @@ packages: semver: 7.5.4 tapable: 2.2.1 typescript: 5.5.3 - webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) dev: false /form-data@3.0.1: @@ -16651,7 +16681,7 @@ packages: peerDependencies: webpack: ^5.0.0 dependencies: - webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) dev: false /stylelint-config-recommended@14.0.0(stylelint@16.5.0): @@ -17130,7 +17160,7 @@ packages: semver: 7.5.4 source-map: 0.7.4 typescript: 5.5.3 - webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) dev: false /ts-node@10.9.2(@swc/core@1.9.2)(@types/node@16.18.68)(typescript@5.1.6): @@ -17603,6 +17633,17 @@ packages: qs: 6.13.0 dev: false + /use-local-storage-state@19.5.0(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-sUJAyFvsmqMpBhdwaRr7GTKkkoxb6PWeNVvpBDrLuwQF1PpbJRKIbOYeLLeqJI7B3wdfFlLLCBbmOdopiSTBOw==} + engines: {node: '>=14'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + dev: false + /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: false @@ -17892,7 +17933,7 @@ packages: mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 4.2.0 - webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) dev: false /webpack-dev-middleware@7.2.1(webpack@5.94.0): @@ -17910,7 +17951,7 @@ packages: on-finished: 2.4.1 range-parser: 1.2.1 schema-utils: 4.2.0 - webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) dev: false /webpack-dev-middleware@7.4.2(webpack@5.94.0): @@ -17972,8 +18013,8 @@ packages: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) - webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.2)(webpack-dev-server@5.0.4)(webpack@5.94.0) + webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-dev-server@5.0.4)(webpack@5.94.0) webpack-dev-middleware: 7.2.1(webpack@5.94.0) ws: 8.17.1 transitivePeerDependencies: @@ -18018,7 +18059,7 @@ packages: webpack: ^5.47.0 dependencies: tapable: 2.2.1 - webpack: 5.94.0(@swc/core@1.9.2)(esbuild@0.18.20)(webpack-cli@5.1.4) + webpack: 5.94.0(esbuild@0.18.20)(webpack-cli@5.1.4) webpack-sources: 2.3.1 dev: false patched: true