Repository navigation
feat!: support react 19, storybook 10 and styled-components 6 - #576
Merged
Merged
Conversation
Mukesh-FN
force-pushed
the
react-19-upgrade
branch
3 times, most recently
from
March 24, 2026 18:47
36c4f59 to
6d99c6e
Compare
commit: |
commit: |
Mukesh-FN
force-pushed
the
react-19-upgrade
branch
2 times, most recently
from
May 6, 2026 14:31
8b14e56 to
dda17f5
Compare
Mukesh-FN
force-pushed
the
react-19-upgrade
branch
from
October 12, 2026 16:13
dda17f5 to
50504da
Compare
Phase 1 — Tooling upgrades: - TypeScript 4 → 5.8.3, typescript-plugin-styled-components 1 → 3 - ts-jest 26 → 29, Jest 26 → 29, @types/jest 25 → 29 - Add jest-environment-jsdom (Jest 29 no longer defaults to jsdom) - eslint 7 → 8, @typescript-eslint v4 → v7, eslint-plugin-unicorn/jest/airbnb-typescript - jest.config.js: add testEnvironment jsdom, ts-jest tuple transform format - .eslintrc.js: rename recommended-requiring-type-checking → recommended-type-checked - src/utils/testing.ts: update jest-dom import path (extend-expect removed in v6) - Build scripts: ttsc → tsc (ttypescript incompatible with TS5) Phase 2 — React 19.2.4: - react/react-dom 18.2.0 → 19.2.4, @types/react v18 → v19 - @testing-library/react v14 → v16, jest-dom v5 → v6 - peerDependencies broadened to ^18.2.0 || ^19.0.0 for library consumers - overrides: react-refresh 0.14.2, react-is 19.2.4, windowed-select types Phase 3 — Remove defaultProps (removed in React 19 for FC/forwardRef): - Styled-components (Button, TextButton, HelperText, Label, experimental/Text): defaultProps → .attrs<Props>({...}) - Function components (Checkbox, Modal, SelectList): defaultProps → destructuring defaults - forwardRef components (Input, InnerInput): defaultProps → destructuring defaults Phase 4 — TypeScript type fixes for @types/react@19: - JSX global namespace removed: JSX.Element → React.JSX.Element (Breadcrumbs, TabBar/Link, Tooltip, DateField, DatePicker, Calendar, InfoBannerCard, experimental/Tooltip, withDeprecatedMessage, LabelWrapper) - ReactDOM.findDOMNode removed: add nodeRef to all CSSTransition usages (Banner, Modal×2, Offcanvas×3) using react-transition-group v4 nodeRef API - useRef<T>() requires arg: → useRef<T | null>(null) (Password, PhoneInput, DatepickerRangeInput, InnerInput) - Render-prop functions no longer assignable to ReactNode: Chip: remove unnecessary Fragment wrapper RadioButton: narrow children type to ReactNode via Omit - React.cloneElement stricter typing: cast to HTMLAttributes & RefAttributes (Tooltip) Storybook fix: - @storybook/react-dom-shim preset only enables createRoot shim for React 18 (version.startsWith("18")). React 19 falls through to legacy react-16.js shim which calls ReactDOM.render() — removed in React 19. - Fix: webpackFinal alias in .storybook/main.ts forces react-18 shim (createRoot) which is fully compatible with React 19. Result: 58/58 test suites pass, build succeeds, tsc --noEmit clean, Storybook renders correctly.
- Use useDarkMode() from storybook-dark-mode in withColorScheme decorator (works in Storybook preview hook context, same as main branch) - Fix docs.container dark mode using React hooks + addons.getChannel() to subscribe to DARK_MODE_EVENT_NAME, avoiding storybook preview-api hooks which throw outside decorator/story context - Add missing ESLint plugins (import, react, react-hooks, jsx-a11y) to plugins array so ESLint 8 correctly resolves rules from extended configs - Disable newly-triggered strict rules that fire on pre-existing code - Fix no-floating-promises in useLocaleObject.ts - Upgrade pretty-quick v3 to v4 for Prettier v3 compatibility - Fix commit-msg hook for Husky v9 (use --edit $1 instead of HUSKY_GIT_PARAMS)
- Fix type-only exports across all components (export type {}) for
Vite/esbuild compatibility
- Add import type for all type-only imports (ESLint consistent-type-imports)
- Add @vite-ignore to dynamic imports in Flag.tsx and useLocaleObject.ts
- Fix CSSTransition nodeRef for React 19 (Modal, Offcanvas, Banner)
- Fix React 19 breaking changes: defaultProps, JSX namespace, useRef types,
ReactNode functions, cloneElement typing
- Migrate react-popper to @floating-ui/react for Popover and Tooltip
- Update date-fns v3 locale dynamic imports
- Add Husky v9 pre-commit hook
- Update jscodeshift v17 codemod type casts
- Add UPGRADE_PLAN.md documenting all migration phases
… warnings - Replace legacy .eslintrc.js with ESLint 10 flat config (eslint.config.mjs) - Remove unmaintained eslint-config-airbnb-typescript; replace with typescript-eslint unified package + @eslint-react/eslint-plugin - Add eslint-plugin-import-x (maintained fork), globals, eslint-import-resolver-typescript - Fix all 14 ESLint errors: conditional hooks, missing keys, leaked conditional rendering, stale disable comments, array sort, autofocus, prefer-ternary - Fix 56 of 103 warnings: lazy useState init, ref naming convention, import naming, forwardRef (turned off for React 18+19 compat), complex dep-array expressions extracted to variables - Bump tsconfig.json lib to es2023 (required for Array#toSorted) - Add UPGRADE_PLAN_2026.md with 10-phase upgrade plan
- Upgrade styled-components ^5.3.8 → ^6.3.12; remove @types/styled-components (v6 ships own types) - Add shouldForwardProp: isPropValid to all styled HTML element components to restore v5 prop-filtering behavior (Box, Text, Headline, Button, Card, Link, Table, TabBar, Skeleton, Input, Select, Banner, Tag, Breadcrumbs, Datepicker, Popover, and more) - Fix removed v6 types: StyledComponent→IStyledComponent, drop GlobalStyleComponent and ThemeProps, ReadonlyArray<string>→string[], use RuleSet for css return type - Fix react-select components: replace styled(components.X) wrappers with inner styled divs to prevent theme-prop stripping (SingleValue, Option, DynamicWidthMenu) - Update snapshots for stylis v4 CSS output (fewer vendor prefixes, gradient formatting)
- Bump typescript devDep to ^6.0.2 - Add ignoreDeprecations: "6.0" to tsconfig for node10 moduleResolution deprecation - Exclude src/codemods entirely from tsconfig type-checking (uses Node.js globals) - Add src/global.d.ts for CSS module, warning, @styled-system/theme-get, and process declarations TS6 breaking changes fixed across ~55 files: - Implicit-any now enforced (TS7053/TS7006): typed object index expressions, callback params - Optional callbacks require optional-chaining invocation (TS2722) - React.createContext default value must match generic type - useRef<T>(null) → RefObject<T | null>, requiring casts for useDay library compatibility - FocusedInput null vs undefined strictness in @datepicker-react/hooks - String indexing on typed objects requires keyof typeof cast - Boolean coercion required for mixed-type setHasValue calls - isValidDateText updated to accept Date | undefined - SelectList triggerReference typed as HTMLDivElement | null with explicit callback ref - Tooltip: fix potential leaked conditional rendering
Remove ignoreDeprecations workaround by fixing all deprecated features: - tsconfig.json: moduleResolution "node" → "bundler" (correct for a bundler-consumed library; avoids node16/nodenext requirement for explicit .js import extensions) - package.json build:cjs: --target es5 → --target es2015 (ES5 target deprecated in TS6; consumers use bundlers that handle transpilation anyway) - SelectList.tsx/styles.ts: fix size > 0 comparisons (size is 'medium'|'small', not a number; previously type errors were masked, now correctly use truthiness checks)
moduleResolution "bundler" requires module to be "esnext" or "preserve". Without an explicit module setting, TypeScript defaults to CommonJS which is incompatible. The build scripts override --module on the CLI anyway, so this only affects IDE type-checking.
No config changes required — Storybook manages Vite internally. Storybook 8 starts cleanly with Vite 8, library build unaffected. Node ≥20.19 required by Vite 7+; running Node 24.13.1.
…nd @types/node v22 Root causes: - moduleResolution: "bundler" couldn't resolve jscodeshift/dist/testUtils (no exports field) - @types/node v22 removed CJS globals (__dirname, module) from global scope Fix: - Add tsconfig.jest.json with moduleResolution: "node" + module: "commonjs" for codemods - Add src/codemods/global.d.ts declaring __dirname, __filename, jscodeshift/dist/testUtils - Split jest.config.js into two projects: components (jsdom) and codemods (node, diagnostics off)
…e dead babel/stylelint configs
- Re-add storybook-dark-mode@5.0.0 (compatible with storybook@^10.0.0) - Restore useDarkMode() hook in withColorScheme decorator - Restore channel-based dark mode tracking in docs container - Restore darkMode parameters with lightClass/darkClass/stylePreview - Fix preview background by using CSS variable directly on wrapper div instead of relying on background:inherit through EnforcedColorScheme - Remove custom manager.ts (was for native colorScheme global approach)
…o Node 24
- ESLint config: add parserOptions blocks for .storybook/, docs/, scripts/;
disableTypeChecked for root JS/MJS config files; ignore .github/, storybook-static/
- tsconfig.eslint.json: add rootDir='.', noEmit=true, fix .storybook glob pattern
- Convert import styled from 'styled-components' to named import { styled }
across 120+ files (v6 exports styled as named export)
- Breadcrumbs: replace deprecated Children.toArray/map/cloneElement with plain array map
- useOnChange: replace useState+useEffect anti-pattern with useRef for prev value
- Modal, Offcanvas: wrap handleClose in useCallback, add to effect deps
- Fix missing exhaustive-deps across Search, MonthRangePicker, useMutationObserver,
DatepickerRangeInput, DatepickerSingleInput, DatePicker (experimental)
- Add eslint-disable comments with explanations for intentional set-state-in-effect
patterns (datepicker sync, input label float, color scheme sync, popover gate)
- Delete stale generated JS files: .storybook/main.js, preview.js,
docs/components/*.js, docs/components/migration/*.js
- package.json: add npm overrides for @csstools/css-tokenizer and
@csstools/css-parser-algorithms to fix Jest jsdom module resolution (hoisting bug)
- package.json: update lint:eslint script to eslint . (full project)
- CI: upgrade docs.yml from Node 18 to 24, gh-pages v3 to v4
- CI: add node-version 24 + cache npm to preview.yml (was missing entirely)
- CI: add --legacy-peer-deps to all npm ci install steps
- CI: add timeout-minutes and explicit permissions to all jobs
- CI: enable preview template in preview.yml (package is React 19 compatible)
…on snapshot
styled-components 6.4 made attrs-supplied props optional and exposed `as` in component props,
which broke the FC-annotated `styled(BoxedInput).attrs({ as: 'textarea' })` (TS2322) and with
it the build, the tests and the preview workflow.
jest-styled-components 7.4 normalises keyframe names to `k0` and adds the @Keyframes block to
snapshots, so the Skeleton snapshot is regenerated.
…n works The tsconfig exclude had become `src/codemods`, so lib/esm/codemods was never built and `npx @freenow/wave run_v2_migration` exited with "The 'codemods' folder does not exist". Only the test folders (`src/codemods/__*`) are excluded again, as on main. Under TypeScript 6 the codemods hit the new defaults (`strict` on, `types: []`): implicit any arrays, nullable AST fields and the CommonJS `module` global. The three CommonJS codemods now use `export default` like the other four (jscodeshift and its test utils accept both), and the lookup maps are typed as string records. Also drops the non-existent `codemods/` entry from the package files.
… preview template The pull request preview app didn't declare the styled-components peer dependency, and its experimental Button rendered without colours because no global style loaded the CSS variables. The README explains what the template is for and how to run it.
…conventions - "Migration to v3": requirements, upgrade steps and every consumer-visible change since 2.66.0 - Get Started, Versioning Policy and Browser Support: supported React, styled-components, Node and browser versions, the peer dependency policy and the minimum browser versions - CONTRIBUTING: Node 24 and npm ci, the console guard, form component specs, consumer fixtures, the styled import and shouldForwardProp conventions, the toolchain, BREAKING CHANGE footers and squash merges - README, issue and pull request templates: requirements, a working build badge, the versions bug reports need, and a pull request checklist - Fixes: ModernColors is the maroon theme, the v2 guide's React prerequisite and broken sample, Popper references, and the Button `inverted` prop on the landing page, now DarkScheme
…oads release.config.js uses the conventionalcommits preset, which semantic-release loads from the root node_modules. It was never declared and came from @commitlint/config-conventional. Since the upgrade to commitlint 21 that is preset 10, which needs conventional-changelog-writer 9, while the latest @semantic-release/commit-analyzer and release-notes-generator still use writer 8. The release on main would have stopped while generating the release notes, with "Missing helper: conventional-changelog-conventionalcommits requires conventional-changelog-writer@9 or newer". Preset 9.3.1 renders the notes with writer 8. commitlint keeps its own preset 10.
styled-components 6 gives styled components a generic call signature for the `as` prop before the plain one, and react-docgen-typescript reads the first, which only has `as` and `forwardedAs`. So the documentation of Button, TextButton, Label, Text, Card, Skeleton, Link and the experimental Text lost the descriptions and types of their props, or the props themselves. A patch in .storybook/docgen.ts makes the parser read the plain signature; it only runs in Storybook. The defaults of variant and size came from defaultProps, which are gone, so they are now @default tags. Compared with the published docs of 2.66.0, every prop default is back.
… so that webpack builds Wave
useLocaleObject imported `date-fns/locale/${locale}`. date-fns 4 exports each locale under its own
path and has no pattern for them, so webpack can't resolve the template literal: a production
build of any app that imports Wave failed with "Package path ./locale is exported from package
date-fns, but no valid target file was found", and a development build crashed the legacy
Datepickers with "Cannot find module 'undefined'". Vite built it but couldn't load the locale, so
locale="de" showed an English calendar, which already happened with Wave 2.
localeLoaders.ts has one literal import per supported locale and reads the locale by its name:
the CommonJS builds of the date-fns locales have no default export. TypeScript says they do,
because date-fns 2, which @datepicker-react/hooks installs, declares these modules globally.
webpack resolves every import at build time, including dynamic ones, against the paths a dependency exports. Vite builds an import of a path that isn't exported, and it only fails in the browser, so the Vite fixtures missed the date-fns locale import that broke every webpack build of Wave 3. The new app bundles the shared sources for production and fails on any error or warning; it fails like that against the build before the previous commit.
styled-components passes every prop to a wrapped component, and react-aria's Text spreads them on its element, so Text rendered e.g. <p variant="body2">, and so did ListBox (Select, ComboBox), Checkbox and Dialog. The shouldForwardProp sweep only covered styled HTML elements. Neither React (the attribute is lowercase) nor styled-components (only when NODE_ENV is development) warns in the tests, so FormComponents.spec now also checks that no element has a variant attribute.
Checked with consumer apps on TypeScript 5.0.4, 5.4.5 and 5.9.3: no errors with skipLibCheck. Without it, some dependencies' declarations report errors on TypeScript 6 too, and moduleResolution node needs esModuleInterop for the default React imports in the declarations.
…ements again styled-components 5 read a nested `:last-child` or `::-webkit-search-cancel-button` without `&` as a pseudo-class of the element itself, styled-components 6 reads it as a descendant selector. So the last Accordion lost its bottom divider, and Chrome showed its own clear button in a Search with a value.
The TypeScript 6 upgrade passed `padding ?? 0` to PopoverContent to satisfy its required `padding` prop, so its default padding never applied and the content touched the edges of its card. The prop is optional now and Popover passes `padding` on as before.
Since the options no longer wrap react-select's Option with styled(), an inline-flex wrapper sits inside the Option's line box, which made each option 5px higher. A block flex wrapper lays the flag and the name out as before.
…ments again Wave 2 typed `placement` with react-popper's placements, which include the auto ones. Floating UI has no such placements, so moving to it removed them. They map to Floating UI's autoPlacement middleware now, which like react-popper picks the side with the most space, and the migration guide no longer lists them as removed.
…e background now The TypeScript 6 upgrade replaced the classic dark scheme's page background, which pointed to a missing shade and so was `undefined` in Wave 2, with the darkest blue of the palette. Apps that use ClassicColors with the dark scheme see the change.
…ation again
A comparison of every story and docs page with wave.free-now.com, in the light and the dark
scheme, found these differences:
- Wrap stories in LightScheme and DarkScheme and read the dark mode with useDarkMode() again,
in the decorator, the docs container, Source and the colors table. Source watched the class
of the body instead and could miss it, which left code samples light in the dark scheme.
- Read the theme of docs pages from `store.userGlobals`, as Storybook 10 has no
`store.globals`. The Colors page crashed and the Theme toolbar didn't change docs pages.
- Render the Breakpoints example from a story hidden from the sidebar, as Canvas no longer
renders children.
- Load the flags: Vite can't analyse the import of `./flags/${code}` without a file extension,
so a Vite plugin of Storybook adds it. The published code stays the same for apps.
- Don't inline SVG files, which <use href> doesn't load from data: URLs, so the Wave logo shows
on the Welcome and Get Started pages again, and hide the table of contents of Storybook 10 on
the Welcome page.
- Repeat Storybook's default docgen options next to `tsconfigPath`, which replaced them, and
leave out props declared only by dependencies, so the ArgTypes tables have no `style` row and
no `| undefined` types again.
- Don't underline links in docs pages, as Storybook 10's rule also overrode Wave's Link.
Mukesh-FN
marked this pull request as ready for review
October 14, 2026 09:11
Mukesh-FN
requested review from
AlanFnz,
Amin52J,
chrisrolfe198 and
lizzielerwill-ctrl
as code owners
October 14, 2026 09:11
github-actions Bot
pushed a commit
that referenced
this pull request
Oct 14, 2026
## [3.0.0-rc.1](v2.66.0...v3.0.0-rc.1) (2026-10-14) ### ⚠ BREAKING CHANGES * styled-components 6 is required (peer dependency `^6.0.0`). With styled-components 5, Wave crashes on import. React 18.2+ and React 19 are supported. * the experimental `Calendar` passes its props on to react-day-picker 10, which removes the props deprecated in v9. Use `startMonth` instead of `fromMonth` and `fromYear`, `endMonth` instead of `toMonth` and `toYear`, `hidden={{ before: date }}` and `hidden={{ after: date }}` instead of `fromDate` and `toDate`, and `autoFocus` instead of `initialFocus`. The `onWeekNumberClick`, `onDayKeyUp`, `onDayKeyPress`, `onDayPointerEnter`, `onDayPointerLeave` and `onDayTouch*` handlers and the deprecated `classNames` and `styles` keys (e.g. `day_selected`) are removed; use custom `DayButton` and `WeekNumber` components instead of the handlers. * Tooltip, Popover, Datepicker and the month range picker are positioned by Floating UI instead of react-popper, with the same placements. `react-popper`, `@popperjs/core` and `wave-react-windowed-select` are no longer installed with Wave, so add them to your own dependencies if your app imports them. * snapshots of Wave components change: generated class names are shorter (`Box-sc-lqdr92` becomes e.g. `sc-bdvwhi`), component names are e.g. `styled.div`, vendor prefixes are gone (pass `enableVendorPrefixes` to `StyleSheetManager` to keep them), and Wave props no longer appear as DOM attributes. Use jest-styled-components 7. * Wave's components no longer set `defaultProps`, which React 19 ignores. The default values are the same, but e.g. `Button.defaultProps` is `undefined`. * the CommonJS build is compiled to ES2015 instead of ES5, like the ES module build. ### Features * support react 19, storybook 10 and styled-components 6 ([#576](#576)) ([83ba98d](83ba98d))
Contributor
|
🎉 This PR is included in version 3.0.0-rc.1 🎉 The release is available on: Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Wave 3: Wave works with React 19 as well as React 18. Apps need styled-components 6 to use it.
Media
Nothing should look or work differently. Every component example and documentation page was compared with today's site, in light and dark mode.
Why
Teams moving their apps to React 19 can't use Wave 2.
How
3.0.0-rc.1, that app teams can try withnpm i @freenow/wave@next styled-components@^6. Wave 2 stays the default version, and wave.free-now.com doesn't change.Try it now:
npm i https://pkg.pr.new/@freenow/wave@576 styled-components@^6Checklist
npm testpasses without console errors or warningssrc/components/FormComponents.spec.tsxnpm run test:fixtureruns the consumer apps)BREAKING CHANGE:footer in the commit message that explains what consumers need to changeMerging
next, after the PR that pauses the automaticmain→nextmerge.