Skip to content

Commit fbf08aa

Browse files
stipsancursoragentsquiggler-app[bot]Copilot
committed
chore: ban class components via react/prefer-function-component (#2525)
* chore: enter changesets prerelease mode on next Enter the `next` prerelease tag, point Changesets and CI/release workflows at the `next` branch, and add a sample major changeset for @sanity/ui so the Version Packages bot can open the first pre-release. Co-authored-by: Cody Olsen <[email protected]> * Version Packages (next) (#2504) Co-authored-by: squiggler-app[bot] <265501495+squiggler-app[bot]@users.noreply.github.com> * feat(ui): tombstone deprecated props, hooks, and components (major) (#2509) * chore(ui): tombstone remaining deprecated props as never Remove runtime fallbacks for deprecated space, grid, MenuButton popover, Popover boundaryElement, Badge mode, and Avatar tone props. Defaults move onto their replacements (gap, gridTemplate*, popover). Co-authored-by: Cody Olsen <[email protected]> * fix(storybook): migrate deprecated space/columns/useElementRect props Replace Stack/Inline/Menu/Tree/TabList/Button `space` with `gap`, Grid `columns`/`rows` with `gridTemplateColumns`/`gridTemplateRows`, and `useElementRect` with `useElementSize` (border size). Drop matching no-deprecated suppressions; leave theme/_compat suppressions alone. Co-authored-by: Cody Olsen <[email protected]> * feat(ui): tombstone deprecated APIs with never types (major) Finish call-site migrations, add throwing-hook tests, and a major changeset documenting removed props/hooks/components and their replacements. Co-authored-by: Cody Olsen <[email protected]> * revert(ui): keep PortalProvider boundaryElement for PortalContext PortalProvider.boundaryElement still writes PortalContextValue, so it cannot be tombstoned like other deprecated props. Co-authored-by: Cody Olsen <[email protected]> * chore: drop PortalProvider from tombstone changeset notes Co-authored-by: Cody Olsen <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Cody Olsen <[email protected]> * Version Packages (next) (#2512) Co-authored-by: squiggler-app[bot] <265501495+squiggler-app[bot]@users.noreply.github.com> * feat: React 19 compiler target and ^19.2 peer deps (#2507) * feat: target React 19 compiler and require react ^19.2 Compile @sanity/ui and @sanity/themer with reactCompiler target '19', drop the now-unnecessary react-compiler-runtime dependency, and tighten react (and react-dom for ui) peer ranges to ^19.2 across published packages. Co-authored-by: Cody Olsen <[email protected]> * chore: mark @sanity/themer bump as major in changeset Co-authored-by: Cody Olsen <[email protected]> * revert: leave @sanity/icons and @sanity/logos peer ranges unchanged Only @sanity/ui and @sanity/themer need the React 19.2 peer bump for the compiler target change. Co-authored-by: Cody Olsen <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Cody Olsen <[email protected]> * feat(ui): remove private `_visual-editing` entrypoint (#2513) * feat(ui): remove private `_visual-editing` entrypoint Drop `@sanity/ui/_visual-editing` now that tree-shaking from the main export is sufficient for visual-editing consumers. Co-authored-by: Cody Olsen <[email protected]> * chore: stop deep-linking workspace packages into transitive deps Set linkWorkspacePackages to true (not deep) so sanity and @sanity/visual-editing keep registry @sanity/ui until those packages are updated for the removed _visual-editing entrypoint. Co-authored-by: Cody Olsen <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Cody Olsen <[email protected]> * fix(ui): restore Popover max-width after boundaryElement migration (#2514) fix(ui): restore Popover max-width from floatingBoundary After tombstoning `boundaryElement`, max-width only came from BoundaryElementProvider, so migrating to floatingBoundary/referenceBoundary alone dropped constrainSize/preventOverflow width. Fall back to the floating boundary for size, and wrap the Aligned story in BoundaryElementProvider. Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Cody Olsen <[email protected]> * Version Packages (next) (#2515) Co-authored-by: squiggler-app[bot] <265501495+squiggler-app[bot]@users.noreply.github.com> * feat!: drop CommonJS from @sanity/ui, require Node.js >=22.12 (#2510) * feat!: ship ESM only and require Node.js >=22.12 Drop CommonJS build outputs and `require` export conditions from @sanity/ui, @sanity/color, and @sanity/logos. Raise published package engines to >=22.12 to match sanity. Co-authored-by: Cody Olsen <[email protected]> * revert: leave color, logos, and icons unchanged Scope the ESM-only / engines bump to @sanity/ui and @sanity/themer only. Co-authored-by: Cody Olsen <[email protected]> * refactor(ui): drop custom chunkFileNames, use tsdown defaults Hashed shared-chunk filenames already prevent the entry/chunk collision that dist/_chunks/ was guarding against (#2262). Co-authored-by: Cody Olsen <[email protected]> * refactor(ui): restore UserConfig annotation on tsdown config Match themer/logos so the inferred config type stays portable in the package TypeScript program (TS2883). Co-authored-by: Cody Olsen <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Cody Olsen <[email protected]> * Version Packages (next) (#2516) Co-authored-by: squiggler-app[bot] <265501495+squiggler-app[bot]@users.noreply.github.com> * fix: revert @sanity/themer to last published prerelease * Revert "fix: revert @sanity/themer to last published prerelease" This reverts commit e325e53. * feat: replace AnimatePresence with AnimateActivity (#2508) * feat: replace AnimatePresence with AnimateActivity Vendor Motion's AnimateActivity (Activity + exit animations) and use it in Tooltip, Popover, and Toast. Requires React 19.2 (handled separately). Co-authored-by: Cody Olsen <[email protected]> * fix: keep AnimateActivityProps module-private for knip Co-authored-by: Cody Olsen <[email protected]> * fix: keep AnimatePresence for ToastProvider Toasts are mount/unmount list items; AnimatePresence fits that better than Activity-based show/hide. AnimateActivity stays for Tooltip/Popover. Co-authored-by: Cody Olsen <[email protected]> * fix: lazy-mount AnimateActivity until first open Keep tooltips/popovers out of the DOM until first shown so hidden Activity nodes do not leak duplicate text into Testing Library queries (e.g. delay-group stories). After first open, Activity still preserves exit animations and state on subsequent hides. Co-authored-by: Cody Olsen <[email protected]> * fix: drop lazy-mount ref, assert tooltip visibility in stories Hidden Activity content is intentionally pre-rendered in the DOM, so the tooltip stories wait for visibility instead of text presence. Flipping animate off while open skips the exit animation, which is acceptable. Co-authored-by: Cody Olsen <[email protected]> * fix: await async expect in tooltip story play functions Co-authored-by: Cody Olsen <[email protected]> * feat: keep closed tooltips/popovers mounted with Activity Wrap the animate=false paths in plain <Activity> instead of unmounting, so open/close preserves state in both modes. Reset the menu's active item via effect cleanup (Activity unmounts effects on hide) so shouldFocus and selected registration behave like a fresh mount on reopen. Update tooltip/menu tests to assert visibility rather than DOM presence, and gate the Recursive popover story's content on open since hidden activities pre-render (an unconditional recursive content would render an infinitely deep hidden tree). Co-authored-by: Cody Olsen <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Cody Olsen <[email protected]> * chore: document why native useEffectEvent is blocked, pin the freshness contract (#2506) * feat!: use React's native useEffectEvent Remove the use-effect-event dependency in favor of React's built-in useEffectEvent, and raise the react/react-dom peer dependency minimum to ^19.2. Co-authored-by: Cody Olsen <[email protected]> * fix: route useEffectEvent through an internal shim React 19.2's native useEffectEvent never updates past first-render values when the calling component is wrapped in forwardRef or memo (react/react#34818) — the commit phase only applies effect-event updates to plain function-component fibers. DialogCard and Tooltip are forwardRef components, so Escape handling read a stale isTopLayer and one Escape cascade-closed every nested dialog. Keep the use-effect-event dependency removed, but route the three call sites through a small internal shim (useRef + useInsertionEffect) until react/react#34831 ships in a stable release. Co-authored-by: Cody Olsen <[email protected]> * fix: lint errors in useEffectEvent test, update changeset Co-authored-by: Cody Olsen <[email protected]> * docs: update changeset for internal effect-event shim Co-authored-by: Cody Olsen <[email protected]> * revert: keep use-effect-event, native useEffectEvent unusable in forwardRef/memo Audit of all call sites: useGlobalKeyDown and useClickOutsideEvent are public hooks that execute in the caller's fiber (all internal callers — DialogCard, Menu, Breadcrumbs — are forwardRef components, and external callers can be anything), and Tooltip is a forwardRef component. React 19.2's native useEffectEvent never updates past first-render values in forwardRef/memo fibers (react/react#34818), so no site can use the native hook until the fix ships in a stable release. Restore the use-effect-event dependency at all three sites with TODO comments, drop the internal shim, and add a regression test that pins the fresh-values contract for forwardRef/memo callers. Co-authored-by: Cody Olsen <[email protected]> * chore: restore lockfile to match next Co-authored-by: Cody Olsen <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Cody Olsen <[email protected]> * feat(ui)!: drop @juggle/resize-observer, use native ResizeObserver (#2518) * fix(ui): drop @juggle/resize-observer, use native ResizeObserver ResizeObserver is available in all browsers Sanity UI targets, so the polyfill and its custom typings are no longer needed. Co-authored-by: Cody Olsen <[email protected]> * refactor(ui): remove _ResizeObserver wrapper, use native ResizeObserver directly Co-authored-by: Cody Olsen <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Cody Olsen <[email protected]> * feat: ban forwardRef, createRef, createElement, Component, PureComponent with oxlint (#2519) feat: ban forwardRef, createRef, createElement, Component, PureComponent via oxlint Adds react entries to eslint/no-restricted-imports in .oxlintrc.json and migrates every @sanity/ui component from forwardRef to React 19 ref-as-prop function components. @sanity/icons and @sanity/logos keep forwardRef through a scoped override since their react peer ranges still include React 18, where refs only attach to forwardRef-wrapped function components. The class ErrorBoundary keeps an inline disable (error boundaries have no function equivalent), and the useGlobalKeyDown freshness-contract test keeps its deliberate forwardRef usage with an inline disable. Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Cody Olsen <[email protected]> * chore(deps): update renovatebot/github-action action to v46.2.0 (next) (#2521) * Version Packages (next) (#2517) * feat(ui): move heavy-dependency components to dedicated subpath entry points (#2522) * feat(ui): extract static CSS with vanilla-extract into @sanity/ui/styles.css (#2523) Sets up the vanilla-extract build pipeline in @sanity/ui via @sanity/tsdown-config's vanillaExtract option, with inject disabled so the stylesheet is never self-imported: consumers add `import '@sanity/ui/styles.css'` themselves, on the same export path the vanilla-extract based next major already uses. Migrates the first slice of fully static styled-components CSS (SrOnly, Spinner, SpanWithTextOverflow) to colocated .css.ts modules, and wires .css.ts compilation into every consumer of the package's TypeScript source: storybook, the icons app, the ui/themer vitest configs, the docs studio (sanity dev/build), and the docs Next app (Turbopack, via @vanilla-extract/next-plugin). Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Cody Olsen <[email protected]> * chore: ban class components via react/prefer-function-component Replace the no-restricted-imports ban on Component/PureComponent with react/prefer-function-component (allowErrorBoundary), so ErrorBoundary no longer needs an inline disable. Co-authored-by: Cody Olsen <[email protected]> * Version Packages (next) (#2524) Co-authored-by: squiggler-app[bot] <265501495+squiggler-app[bot]@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Cody Olsen <[email protected]> Co-authored-by: squiggler-app[bot] <265501495+squiggler-app[bot]@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <[email protected]>
1 parent dce815e commit fbf08aa

3 files changed

Lines changed: 2 additions & 13 deletions

File tree

.changeset/remove-forward-ref.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
"@sanity/ui": minor
33
---
44

5-
All components are now plain function components that receive `ref` as a regular prop (React 19 semantics) instead of being wrapped in `forwardRef`. Refs keep working exactly as before, but the components no longer pass `react-is` checks like `isForwardRef`. The legacy React APIs (`forwardRef`, `createRef`, `createElement`, `Component`, `PureComponent`) are now banned via oxlint across the repo.
5+
All components are now plain function components that receive `ref` as a regular prop (React 19 semantics) instead of being wrapped in `forwardRef`. Refs keep working exactly as before, but the components no longer pass `react-is` checks like `isForwardRef`. The legacy React APIs (`forwardRef`, `createRef`, `createElement`) are banned via oxlint `no-restricted-imports`, and class components are banned via `react/prefer-function-component` (with `allowErrorBoundary` so the `ErrorBoundary` class remains valid).

.oxlintrc.json

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,6 @@
5757
"name": "react",
5858
"importNames": ["createElement"],
5959
"message": "Use JSX instead of `createElement`."
60-
},
61-
{
62-
"name": "react",
63-
"importNames": ["Component", "PureComponent"],
64-
"message": "Use function components with hooks instead of class components."
6560
}
6661
]
6762
}
@@ -133,7 +128,7 @@
133128
// `^18 || ^19`), where refs only attach to function components wrapped in
134129
// `forwardRef` — so `forwardRef` stays allowed there (along with the
135130
// `createRef`/`createElement` used by the tests that pin that contract).
136-
// Class components remain banned.
131+
// Class components remain banned via the root `react/prefer-function-component`.
137132
"files": ["packages/icons/**", "packages/logos/**"],
138133
"rules": {
139134
"eslint/no-restricted-imports": [
@@ -144,11 +139,6 @@
144139
"name": "styled-components",
145140
"importNames": ["default"],
146141
"message": "Please use `import {styled} from \"styled-components\"` instead."
147-
},
148-
{
149-
"name": "react",
150-
"importNames": ["Component", "PureComponent"],
151-
"message": "Use function components with hooks instead of class components."
152142
}
153143
]
154144
}

packages/ui/src/core/utils/errorBoundary.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// oxlint-disable-next-line no-restricted-imports -- error boundaries require a class component; React has no function-component equivalent for `getDerivedStateFromError`/`componentDidCatch`
21
import {Component, PropsWithChildren} from 'react'
32

43
/**

0 commit comments

Comments
 (0)