diff --git a/.eslintignore b/.eslintignore index 889fc9e51..57bc5417d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,5 +1,6 @@ lint-staged.config.js jest.config.js +jest.config.react18.js babel.config.js plopfile.mjs release.config.js diff --git a/.github/workflows/check-ci-validation.yml b/.github/workflows/check-ci-validation.yml index ccd6713b2..a9a04d637 100644 --- a/.github/workflows/check-ci-validation.yml +++ b/.github/workflows/check-ci-validation.yml @@ -88,6 +88,10 @@ jobs: run: | npm run type-check + - name: Perform type checking with TypeScript (React 18 typings) + run: | + npm run type-check:react18 + - name: Check React Compiler compatibility if: ${{ github.event_name == 'pull_request' }} run: | @@ -129,10 +133,14 @@ jobs: run: | npm ci - - name: Test codebase correctness + - name: Test codebase correctness (React 19) run: | npm run test + - name: Test codebase correctness (React 18) + run: | + npm run test:react18 + build-package: name: Build Package runs-on: ubuntu-latest diff --git a/jest.config.react18.js b/jest.config.react18.js new file mode 100644 index 000000000..46c5f7701 --- /dev/null +++ b/jest.config.react18.js @@ -0,0 +1,13 @@ +const baseConfig = require('./jest.config') + +// Runs the Jest suite against React 18 by remapping react/react-dom to the aliased 18 packages. +module.exports = { + ...baseConfig, + moduleNameMapper: { + ...baseConfig.moduleNameMapper, + '^react$': 'react-18', + '^react/(.*)$': 'react-18/$1', + '^react-dom$': 'react-dom-18', + '^react-dom/(.*)$': 'react-dom-18/$1', + }, +} diff --git a/package-lock.json b/package-lock.json index bab4a7013..7625eb089 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,8 +50,10 @@ "@types/jest": "28.1.8", "@types/jest-axe": "^3.5.3", "@types/marked": "^4.0.8", - "@types/react": "18.3.1", - "@types/react-dom": "18.3.0", + "@types/react": "19.2.17", + "@types/react-18": "npm:@types/react@^18.3", + "@types/react-dom": "19.2.3", + "@types/react-dom-18": "npm:@types/react-dom@^18.3", "@typescript-eslint/eslint-plugin": "8.46.2", "@typescript-eslint/parser": "8.46.2", "@vitejs/plugin-react": "5.2.0", @@ -80,11 +82,13 @@ "mockdate": "^3.0.2", "plop": "^3.1.1", "prettier": "3.6.2", - "react": "18.3.1", + "react": "19.2.7", + "react-18": "npm:react@18.3.1", "react-compiler-runtime": "1.0.0", "react-docgen-typescript": "2.4.0", - "react-dom": "18.3.1", - "react-is": "18.3.1", + "react-dom": "19.2.7", + "react-dom-18": "npm:react-dom@18.3.1", + "react-is": "19.2.7", "rimraf": "^3.0.2", "rollup": "2.79.2", "rollup-plugin-styles": "4.0.0", @@ -98,10 +102,9 @@ "peerDependencies": { "@ariakit/react": "~0.4.19", "classnames": "^2.2.5", - "prop-types": "^15.6.2", - "react": ">=18.0.0 <19.0.0", + "react": ">=18.0.0 <20.0.0", "react-compiler-runtime": "^1.0.0", - "react-dom": ">=18.0.0 <19.0.0" + "react-dom": ">=18.0.0 <20.0.0" } }, "node_modules/@actions/core": { @@ -7350,26 +7353,49 @@ "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.1.tgz", - "integrity": "sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==", + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-18": { + "name": "@types/react", + "version": "18.3.31", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", + "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", - "csstype": "^3.0.2" + "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.0.tgz", - "integrity": "sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", - "dependencies": { - "@types/react": "*" + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/react-dom-18": { + "name": "@types/react-dom", + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" } }, "node_modules/@types/resolve": { @@ -22621,9 +22647,20 @@ } }, "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-18": { + "name": "react", "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -22700,6 +22737,20 @@ } }, "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-dom-18": { + "name": "react-dom", "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", @@ -22713,6 +22764,16 @@ "react": "^18.3.1" } }, + "node_modules/react-dom-18/node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/react-focus-lock": { "version": "2.13.7", "resolved": "https://registry.npmjs.org/react-focus-lock/-/react-focus-lock-2.13.7.tgz", @@ -22737,9 +22798,9 @@ } }, "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", + "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", "dev": true, "license": "MIT" }, @@ -23703,14 +23764,11 @@ } }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "dev": true, - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } + "license": "MIT" }, "node_modules/semantic-release": { "version": "25.0.3", diff --git a/package.json b/package.json index 4d2715e0e..6712eecf9 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "scripts": { "postinstall": "patch-package", "setup": "npm install && npm run validate", - "validate": "npm run lint && npm run type-check && npm run test", + "validate": "npm run lint && npm run type-check && npm run type-check:react18 && npm run test && npm run test:react18", "start": "ON_SUCCESS=\"./scripts/organize-styles.sh\" rollup -c --watch --no-watch.clearScreen", "prestart:yalc": "npm run clean && yalc publish", "start:yalc": "ON_SUCCESS=\"npm run start:yalc:success\" rollup -c --watch --no-watch.clearScreen", @@ -49,8 +49,11 @@ "clean": "rimraf es lib styles dist", "test": "jest --passWithNoTests", "test:watch": "npm run test -- --watch", + "test:react18": "jest --config jest.config.react18.js", + "test:react18:watch": "npm run test:react18 -- --watch", "test:coverage": "npm run test -- --coverage", "type-check": "tsc --noEmit -p ./tsconfig.json", + "type-check:react18": "tsc --noEmit -p ./tsconfig.react18.json", "lint": "eslint --format codeframe --cache --ext js,jsx,ts,tsx ./", "storybook": "storybook dev -p 6006", "prettify": "prettier --write \"./**/*.{js,jsx,ts,tsx,json,css,scss,less,md,mdx}\"", @@ -59,10 +62,9 @@ "peerDependencies": { "@ariakit/react": "~0.4.19", "classnames": "^2.2.5", - "prop-types": "^15.6.2", - "react": ">=18.0.0 <19.0.0", + "react": ">=18.0.0 <20.0.0", "react-compiler-runtime": "^1.0.0", - "react-dom": ">=18.0.0 <19.0.0" + "react-dom": ">=18.0.0 <20.0.0" }, "devDependencies": { "@ariakit/react": "0.4.19", @@ -95,8 +97,10 @@ "@types/jest": "28.1.8", "@types/jest-axe": "^3.5.3", "@types/marked": "^4.0.8", - "@types/react": "18.3.1", - "@types/react-dom": "18.3.0", + "@types/react": "19.2.17", + "@types/react-18": "npm:@types/react@^18.3", + "@types/react-dom": "19.2.3", + "@types/react-dom-18": "npm:@types/react-dom@^18.3", "@typescript-eslint/eslint-plugin": "8.46.2", "@typescript-eslint/parser": "8.46.2", "@vitejs/plugin-react": "5.2.0", @@ -125,11 +129,13 @@ "mockdate": "^3.0.2", "plop": "^3.1.1", "prettier": "3.6.2", - "react": "18.3.1", + "react": "19.2.7", + "react-18": "npm:react@18.3.1", "react-compiler-runtime": "1.0.0", - "react-dom": "18.3.1", "react-docgen-typescript": "2.4.0", - "react-is": "18.3.1", + "react-dom": "19.2.7", + "react-dom-18": "npm:react-dom@18.3.1", + "react-is": "19.2.7", "rimraf": "^3.0.2", "rollup": "2.79.2", "rollup-plugin-styles": "4.0.0", @@ -154,6 +160,12 @@ "pretty-format": { "react-is": "$react-is" }, - "react-element-to-jsx-string": "15.0.0" + "react-element-to-jsx-string": "15.0.0", + "react-dom-18": { + "react": "$react" + }, + "@types/react-dom-18": { + "@types/react": "$@types/react" + } } } diff --git a/src/checkbox-field/checkbox-field.tsx b/src/checkbox-field/checkbox-field.tsx index 4b9dcb0cb..b4ced5b9e 100644 --- a/src/checkbox-field/checkbox-field.tsx +++ b/src/checkbox-field/checkbox-field.tsx @@ -8,6 +8,8 @@ import { useForkRef } from './use-fork-ref' import styles from './checkbox-field.module.css' +import type { JSX } from 'react' + interface CheckboxFieldProps extends Omit< JSX.IntrinsicElements['input'], diff --git a/src/components/color-picker/deprecated-button/deprecated-button.tsx b/src/components/color-picker/deprecated-button/deprecated-button.tsx index 0b81fbf1f..a7095f621 100644 --- a/src/components/color-picker/deprecated-button/deprecated-button.tsx +++ b/src/components/color-picker/deprecated-button/deprecated-button.tsx @@ -83,10 +83,4 @@ const Button = React.forwardRef(function Button( Button.displayName = 'Button' -Button.defaultProps = { - size: 'default', - loading: false, - disabled: false, -} - export { Button } diff --git a/src/components/color-picker/deprecated-dropdown/dropdown.tsx b/src/components/color-picker/deprecated-dropdown/dropdown.tsx index 403252dd5..758ca95b8 100644 --- a/src/components/color-picker/deprecated-dropdown/dropdown.tsx +++ b/src/components/color-picker/deprecated-dropdown/dropdown.tsx @@ -1,12 +1,13 @@ import './dropdown.less' import * as React from 'react' -import ReactDOM from 'react-dom' import classNames from 'classnames' import Button from '../deprecated-button' +import type { JSX } from 'react' + type BoxProps = { onShowBody?: () => void onHideBody?: () => void @@ -29,6 +30,8 @@ type BoxState = { class Box extends React.Component { public static displayName: string + private rootRef = React.createRef() + constructor(props: BoxProps, context: React.Context) { super(props, context) this.state = { @@ -48,7 +51,7 @@ class Box extends React.Component { _timeout?: ReturnType _handleClickOutside = (event: MouseEvent) => { - const dropdownDOMNode = ReactDOM.findDOMNode(this) + const dropdownDOMNode = this.rootRef.current if (dropdownDOMNode && !dropdownDOMNode.contains(event.target as Node)) this._toggleShowBody() @@ -93,13 +96,12 @@ class Box extends React.Component { ) if (scrollingParent) { - const dropdown = ReactDOM.findDOMNode(this) + const dropdown = this.rootRef.current if (!dropdown) { return } - const dropdownVerticalPosition = (ReactDOM.findDOMNode(this) as HTMLElement) - .offsetTop - const dropdownTrigger = (dropdown as Element).querySelector('.trigger') + const dropdownVerticalPosition = dropdown.offsetTop + const dropdownTrigger = dropdown.querySelector('.trigger') if (!dropdownTrigger) { return } @@ -160,6 +162,7 @@ class Box extends React.Component { return (
( - - - - - - - -) -ThreeDotsIcon.propTypes = { - /** Color of the icon. */ - color: PropTypes.string, +function ThreeDotsIcon({ color = '#A5A5A5' }: ThreeDotsIconProps) { + return ( + + + + + + + + ) } export default ThreeDotsIcon diff --git a/src/icons/alert-icon.tsx b/src/icons/alert-icon.tsx index 0fef79350..1abcbd1b9 100644 --- a/src/icons/alert-icon.tsx +++ b/src/icons/alert-icon.tsx @@ -1,5 +1,6 @@ import * as React from 'react' +import type { JSX } from 'react' import type { AlertTone } from '../utils/common-types' const alertIconForTone: Record = { diff --git a/src/icons/banner-icon.tsx b/src/icons/banner-icon.tsx index f915c1d3a..e2cbb5c8b 100644 --- a/src/icons/banner-icon.tsx +++ b/src/icons/banner-icon.tsx @@ -2,6 +2,7 @@ import * as React from 'react' import styles from './banner-icon.module.css' +import type { JSX } from 'react' import type { SystemBannerType } from '../banner/banner' const bannerIconForType: Record = { diff --git a/src/icons/close-icon.tsx b/src/icons/close-icon.tsx index bd416e116..300fd94eb 100644 --- a/src/icons/close-icon.tsx +++ b/src/icons/close-icon.tsx @@ -1,5 +1,7 @@ import * as React from 'react' +import type { JSX } from 'react' + function CloseIcon(props: JSX.IntrinsicElements['svg']) { return ( diff --git a/src/icons/password-hidden-icon.tsx b/src/icons/password-hidden-icon.tsx index 7651f3397..31482e92c 100644 --- a/src/icons/password-hidden-icon.tsx +++ b/src/icons/password-hidden-icon.tsx @@ -1,5 +1,7 @@ import * as React from 'react' +import type { JSX } from 'react' + function PasswordHiddenIcon(props: JSX.IntrinsicElements['svg']) { return ( diff --git a/src/icons/password-visible-icon.tsx b/src/icons/password-visible-icon.tsx index 93dce8294..5aac70ab4 100644 --- a/src/icons/password-visible-icon.tsx +++ b/src/icons/password-visible-icon.tsx @@ -1,5 +1,7 @@ import * as React from 'react' +import type { JSX } from 'react' + function PasswordVisibleIcon(props: JSX.IntrinsicElements['svg']) { return ( diff --git a/src/menu/menu.test.tsx b/src/menu/menu.test.tsx index 2eb8e8505..3ff2b166b 100644 --- a/src/menu/menu.test.tsx +++ b/src/menu/menu.test.tsx @@ -286,8 +286,16 @@ describe('Menu', () => { expect(screen.getByRole('menuitem', { name: 'Save' })).toBeVisible() }) + // Without providing the correct DOMRects, Ariakit can't compute the geometry needed to + // determine whether the hovercard should stay open ([ref](https://github.com/ariakit/ariakit/blob/200aa56676b8373fe10ec6c5fac61003ae82c1de/packages/ariakit-react-core/src/hovercard/hovercard.tsx#L201-L222)), + // so it schedules for it to be closed. In React 18, we used to be able to assert that + // clicks happen on the menu before the closed menu renders, but in React 19, the render + // happens before that ([ref](https://github.com/facebook/react/pull/26512)). + // Since we don't want to test Ariakit's internals, we fall back to testing keyboard + // navigation to bypass this. + await user.keyboard('{ArrowRight}') await act(async () => { - await user.click(screen.getByRole('menuitem', { name: 'Save' })) + await user.keyboard('{Enter}') await flushMicrotasks() }) diff --git a/src/modal/modal-examples.stories.tsx b/src/modal/modal-examples.stories.tsx index 0136d8474..f90ab2bb7 100644 --- a/src/modal/modal-examples.stories.tsx +++ b/src/modal/modal-examples.stories.tsx @@ -29,6 +29,8 @@ import { ScrollableContent, } from './modal-stories-components' +import type { JSX } from 'react' + export default { title: '🪟 Overlays/Modal/Examples', component: ModalComponents.Modal, diff --git a/src/modal/modal-stories-components.tsx b/src/modal/modal-stories-components.tsx index 1745edf58..9c4b3678f 100644 --- a/src/modal/modal-stories-components.tsx +++ b/src/modal/modal-stories-components.tsx @@ -8,6 +8,7 @@ import { Placeholder, times } from '../utils/storybook-helper' import * as ModalComponents from './modal' +import type { JSX } from 'react' import type { ModalFooterProps, ModalHeaderProps, ModalProps } from './modal' function Link({ children, ...props }: JSX.IntrinsicElements['a']) { diff --git a/src/select-field/select-field.tsx b/src/select-field/select-field.tsx index 23335784b..3e1228ee1 100644 --- a/src/select-field/select-field.tsx +++ b/src/select-field/select-field.tsx @@ -5,6 +5,7 @@ import { Box } from '../box' import styles from './select-field.module.css' +import type { JSX } from 'react' import type { BaseFieldVariantProps, FieldComponentProps } from '../base-field' interface SelectFieldProps diff --git a/src/stack/stack.tsx b/src/stack/stack.tsx index dd9caded6..60f956e81 100644 --- a/src/stack/stack.tsx +++ b/src/stack/stack.tsx @@ -54,15 +54,18 @@ const Stack = polymorphicComponent<'div', StackProps>(function Stack( ref={ref} > {dividers !== 'none' - ? React.Children.map(flattenChildren(children), (child, index) => - index > 0 ? ( - <> - - {child} - - ) : ( - child - ), + ? React.Children.map( + // react-keyed-flatten-children returns ReactChild[], a type removed in React 19. + flattenChildren(children) as React.ReactNode[], + (child, index) => + index > 0 ? ( + <> + + {child} + + ) : ( + child + ), ) : children} diff --git a/src/text-area/text-area.tsx b/src/text-area/text-area.tsx index e9574f380..8ca50aab7 100644 --- a/src/text-area/text-area.tsx +++ b/src/text-area/text-area.tsx @@ -137,8 +137,8 @@ function useAutoExpand({ }: { value: string | undefined autoExpand: boolean - containerRef: React.RefObject - internalRef: React.RefObject + containerRef: React.RefObject + internalRef: React.RefObject }) { const isControlled = value !== undefined diff --git a/src/toast/toast.stories.tsx b/src/toast/toast.stories.tsx index 14f4b5862..7116c91ce 100644 --- a/src/toast/toast.stories.tsx +++ b/src/toast/toast.stories.tsx @@ -18,6 +18,7 @@ import { Text } from '../text' import { StaticToast } from './static-toast' import { Toast, ToastsProvider, useToasts } from './use-toasts' +import type { JSX } from 'react' import type { ButtonVariant } from '../button' import type { StaticToastProps } from './static-toast' diff --git a/src/toast/use-toasts.tsx b/src/toast/use-toasts.tsx index 6e412df27..b4a41ce4c 100644 --- a/src/toast/use-toasts.tsx +++ b/src/toast/use-toasts.tsx @@ -70,7 +70,7 @@ const InternalToast = React.forwardRef(funct }, ref, ) { - const timeoutRef = React.useRef() + const timeoutRef = React.useRef(undefined) const removeToast = React.useCallback( function removeToast() { diff --git a/src/tooltip/tooltip.tsx b/src/tooltip/tooltip.tsx index 8a8058e22..dfd18a531 100644 --- a/src/tooltip/tooltip.tsx +++ b/src/tooltip/tooltip.tsx @@ -12,6 +12,7 @@ import { Box } from '../box' import styles from './tooltip.module.css' import type { TooltipStore, TooltipStoreState } from '@ariakit/react' +import type { JSX } from 'react' import type { ObfuscatedClassName } from '../utils/common-types' const defaultShowTimeout = 500 @@ -144,13 +145,15 @@ const Tooltip = React.forwardRef( } /* eslint-disable react-hooks/refs */ - if (typeof child.ref === 'string') { - throw new Error('Tooltip: String refs cannot be used as they cannot be forwarded') - } + const childRef = + 'ref' in child.props + ? (child.props.ref as React.Ref) + : // child.ref access kept for React 18 compatibility + (child as { ref?: React.Ref }).ref return ( <> - + {isOpen && content ? ( { ( - props: PolymorphicComponentProps, - ref: - | ((instance: ElementByTagOrAny | null) => void) - | React.MutableRefObject | null> - | null, + props: React.PropsWithoutRef< + PolymorphicComponentProps + >, + ref: React.ForwardedRef>, ): React.ReactElement | null displayName?: string } @@ -172,9 +170,6 @@ interface PolymorphicComponent< defaultProps?: Partial< PolymorphicComponentProps > - propTypes?: React.WeakValidationMap< - PolymorphicComponentProps - > displayName?: string } diff --git a/src/utils/storybook-helper.tsx b/src/utils/storybook-helper.tsx index 3ab097b7a..4d4050d2d 100644 --- a/src/utils/storybook-helper.tsx +++ b/src/utils/storybook-helper.tsx @@ -6,6 +6,7 @@ import { Box } from '../box' import { Heading } from '../heading' import { Stack } from '../stack' +import type { JSX } from 'react' import type { BoxProps } from '../box' import type { Space } from './common-types' diff --git a/tsconfig.react18.json b/tsconfig.react18.json new file mode 100644 index 000000000..9594bbc45 --- /dev/null +++ b/tsconfig.react18.json @@ -0,0 +1,17 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "paths": { + "@": ["./"], + "@storybook/react": ["node_modules/@storybook/react/dist/index.d.ts"], + "@storybook/react-vite": ["node_modules/@storybook/react-vite/dist/index.d.ts"], + "storybook/actions": ["node_modules/storybook/dist/actions/index.d.ts"], + "storybook/test": ["node_modules/storybook/dist/test/index.d.ts"], + "react": ["node_modules/@types/react-18"], + "react/*": ["node_modules/@types/react-18/*"], + "react-dom": ["node_modules/@types/react-dom-18"], + "react-dom/*": ["node_modules/@types/react-dom-18/*"], + "*": ["src/*", "node_modules/*"] + } + } +}