You were close.
ElementProps expects a string - keyof JSX.IntrinsicElements whether typeof tagName might be a ComponentType<{}.
Al you need to do is to move type restriction inside a type util.
import React, { useRef, createElement } from "react";
type ElementProps<Tag> =
Tag extends keyof JSX.IntrinsicElements
? JSX.IntrinsicElements[Tag]
: never
type Props = {
tagName?: React.ComponentType | keyof JSX.IntrinsicElements;
};
const Editable: React.FC<Props> = ({
tagName = 'div',
...props
}) => {
const htmlEl = useRef(null);
const elementProps: ElementProps<typeof tagName> = {
...props,
ref: htmlEl,
};
return createElement(tagName, elementProps);
};
Playground
I was wondering if I could get the only detailed props regarding the tag that passed in the tagname?
Sure, it is possible.
import React, { useRef, createElement } from "react";
type ElementProps<Tag> = Tag extends keyof JSX.IntrinsicElements
? JSX.IntrinsicElements[Tag]
: never;
type Values<T> = T[keyof T];
type ObtainHTMLProps<T extends Values<JSX.IntrinsicElements>> =
T extends React.DetailedHTMLProps<infer Props, HTMLElement> ? Props : never;
type AllowedProps = Values<{
[Tag in keyof JSX.IntrinsicElements]: {
tagName: Tag;
} & ObtainHTMLProps<JSX.IntrinsicElements[Tag]>;
}>;
const Editable: React.FC<AllowedProps> = ({ tagName = "div", ...props }) => {
const htmlEl = useRef(null);
const elementProps: ElementProps<typeof tagName> = {
...props,
ref: htmlEl,
};
return createElement(tagName, elementProps);
};
const canvas = <Editable tagName="canvas" width={20} /> // ok
const anchor = <Editable tagName="a" href={'www.example.com'} /> // ok
const invalid = <Editable tagName="canvas" href={'www.example.com'} /> // expected error
See react types node_modules\@types\react\index.d.ts. You will find there IntrinsicElements

Having tagName you can easily infer first argument of DetailedHTMLProps. See ObtainHTMLProps.
Now, you need to create a union of all allowed attributes and it is a finite number of props. See AllowedProps.