diff --git a/.changeset/react17-jsx-typing.md b/.changeset/react17-jsx-typing.md new file mode 100644 index 000000000..b3511dec5 --- /dev/null +++ b/.changeset/react17-jsx-typing.md @@ -0,0 +1,5 @@ +--- +'@linaria/react': patch +--- + +Fix TypeScript typings for React 17 projects using the automatic JSX runtime (`jsx: react-jsx`), so `styled.*` intrinsic components don’t incorrectly require `children`. diff --git a/packages/react/__dtslint-react17__/index.d.ts b/packages/react/__dtslint-react17__/index.d.ts new file mode 100644 index 000000000..22aafedda --- /dev/null +++ b/packages/react/__dtslint-react17__/index.d.ts @@ -0,0 +1 @@ +// dtslint expects an `index.d.ts` in the test folder. diff --git a/packages/react/__dtslint-react17__/react17.tsx b/packages/react/__dtslint-react17__/react17.tsx new file mode 100644 index 000000000..997fddd47 --- /dev/null +++ b/packages/react/__dtslint-react17__/react17.tsx @@ -0,0 +1,8 @@ +import { styled } from '..'; + +const Button = styled.button``; + +// Should not require `children` for intrinsic elements on React 17 projects. +Button({}); +; diff --git a/packages/react/__dtslint-react17__/stubs/jsx-runtime.d.ts b/packages/react/__dtslint-react17__/stubs/jsx-runtime.d.ts new file mode 100644 index 000000000..11e8b65ad --- /dev/null +++ b/packages/react/__dtslint-react17__/stubs/jsx-runtime.d.ts @@ -0,0 +1,17 @@ +declare module 'react/jsx-runtime' { + const Fragment: unknown; + function jsx(type: unknown, props: unknown, key?: unknown): unknown; + function jsxs(type: unknown, props: unknown, key?: unknown): unknown; +} + +declare module 'react/jsx-dev-runtime' { + const Fragment: unknown; + function jsxDEV( + type: unknown, + props: unknown, + key: unknown, + isStaticChildren: boolean, + source: unknown, + self: unknown + ): unknown; +} diff --git a/packages/react/__dtslint-react17__/stubs/react17.d.ts b/packages/react/__dtslint-react17__/stubs/react17.d.ts new file mode 100644 index 000000000..5088a5856 --- /dev/null +++ b/packages/react/__dtslint-react17__/stubs/react17.d.ts @@ -0,0 +1,27 @@ +// Minimal React 17-like type surface for dtslint. +// It intentionally does NOT export a module-level `JSX` namespace. + +declare namespace React { + type ElementType = unknown; + + interface CSSProperties { + [key: string]: unknown; + } + + interface FunctionComponent

> { + (props: P & { children?: unknown }): unknown; + } + + function createElement(...args: unknown[]): unknown; +} + +export = React; +export as namespace React; + +declare global { + namespace JSX { + interface IntrinsicElements { + button: Record; + } + } +} diff --git a/packages/react/__dtslint-react17__/tsconfig.eslint.json b/packages/react/__dtslint-react17__/tsconfig.eslint.json new file mode 100644 index 000000000..fc8520e73 --- /dev/null +++ b/packages/react/__dtslint-react17__/tsconfig.eslint.json @@ -0,0 +1,3 @@ +{ + "extends": "./tsconfig.json" +} diff --git a/packages/react/__dtslint-react17__/tsconfig.json b/packages/react/__dtslint-react17__/tsconfig.json new file mode 100644 index 000000000..b607c128c --- /dev/null +++ b/packages/react/__dtslint-react17__/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": ["es6"], + "target": "ES2015", + "jsx": "react-jsx", + "noImplicitAny": true, + "noImplicitThis": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "types": [], + "noEmit": true, + "baseUrl": "./", + "paths": { + "react": ["./stubs/react17.d.ts"], + "react/jsx-runtime": ["./stubs/jsx-runtime.d.ts"], + "react/jsx-dev-runtime": ["./stubs/jsx-runtime.d.ts"] + } + } +} diff --git a/packages/react/package.json b/packages/react/package.json index 4c30c6e02..13316d255 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -53,7 +53,7 @@ "build:declarations": "tsc --emitDeclarationOnly --outDir types", "build:dist": "tsup --format cjs,esm", "test": "jest --config ../../jest.config.js --rootDir .", - "test:dts": "dtslint --localTs ../../node_modules/typescript/lib __dtslint__", + "test:dts": "dtslint --localTs ../../node_modules/typescript/lib __dtslint__ && dtslint --localTs ../../node_modules/typescript/lib __dtslint-react17__", "typecheck": "tsc --noEmit --composite false", "watch": "pnpm build:dist --watch & pnpm build:declarations --watch" }, diff --git a/packages/react/src/styled.ts b/packages/react/src/styled.ts index 9e6e5781d..290a7489a 100644 --- a/packages/react/src/styled.ts +++ b/packages/react/src/styled.ts @@ -107,7 +107,6 @@ interface IProps { style?: Record; } -// React <19 declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace JSX { @@ -115,17 +114,18 @@ declare global { } } -// React >=19 -declare module 'react' { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace JSX { - interface IntrinsicElements {} - } -} - let idx = 0; -type IntrinsicElements = React.JSX.IntrinsicElements & JSX.IntrinsicElements; +// eslint-disable-next-line @typescript-eslint/consistent-type-imports +type ReactIntrinsicElements = typeof import('react') extends { + JSX: { IntrinsicElements: infer T }; +} + ? T + : never; + +type IntrinsicElements = [ReactIntrinsicElements] extends [never] + ? JSX.IntrinsicElements + : ReactIntrinsicElements; // Components with props are not allowed function styled( diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json index ce7588219..df536eb34 100644 --- a/packages/react/tsconfig.json +++ b/packages/react/tsconfig.json @@ -7,5 +7,6 @@ "node" ], }, + "include": ["src"], "references": [{ "path": "../core" }] }