import React from 'react'
import { Link } from 'react-router-dom'
type ButtonProps = (
& React.ComponentPropsWithoutRef<'button'>
& React.AriaAttributes
& { href?: never }
)
type AnchorProps = (
& Omit<LinkProps, 'to'>
& React.AriaAttributes
& { href: string }
)
type OverloadedComponent = (
& React.FunctionComponent<ButtonProps>
& React.FunctionComponent<AnchorProps>
)
const isAnchor = (props: ButtonProps | AnchorProps): props is AnchorProps => 'href' in props
const Button: OverloadedComponent = (props) => {
if (isAnchor(props)) {
const { href, ...linkProps } = props
return (
<div className="button">
<Link className="control" to={href} {...linkProps } />
</div>
)
}
return (
<div className="button">
<button className="control" {...props} />
</div>
)
}
export default ButtonButtonProps, либо AnchorProps. type - он подсказывает значения button, reset и submit;onClick без передачи href - то событие происходит именно для HTMLButtonElement, а в противном случае для HTMLAnchorElement;FunctionComponent, но сколько я ни пытался - не получается это переписать на ForwardRefRenderFunction, чтобы передаваемые ref зависели от Props. Я примерно понимаю что это должно как-то реализовываться с помощью дженериков, но решение мне в голову так и не пришло.
type QQQ = {
a?: never;
b: string;
c: string;
};
type WWW = {
a: string;
b: string;
d: string;
};
type ForwardedRefWithOverride<T extends [object, HTMLElement]> = UnionToIntersection<
T extends T ? ForwardRefExoticComponent<PropsWithoutRef<T[0]> & RefAttributes<T[1]>> : never
>;
const forwardRefWithOverrides = <T extends [object, HTMLElement][]>(
render: ForwardRefRenderFunction<T[number][1], T[number][0]>,
) => {
return forwardRef(render) as ForwardedRefWithOverride<T[number]>;
};
export const Ccc = forwardRefWithOverrides<[[QQQ, HTMLButtonElement], [WWW, HTMLLinkElement]]>((props, ref) => {
if (typeof props.a === 'string') {
const { a, b, d } = props;
return (
<>
<link ref={ref as ForwardedRef<HTMLLinkElement>} />
{a}
{b}
{d}
</>
);
}
{
const { a, b, c } = props;
return (
<button ref={ref as ForwardedRef<HTMLButtonElement>} type="button">
{a}
{b}
{c}
</button>
);
}
});
Ccc.displayName = 'Ccc';
export const Vvv = () => {
const ref1 = useRef<HTMLButtonElement>(null);
const ref2 = useRef<HTMLLinkElement>(null);
return (
<>
<Ccc ref={ref1} b="asd" c="asd" />
<Ccc ref={ref2} a="asd" b="asd" d="asd" />
{/* @ts-expect-error - c missed */}
<Ccc b="asd" d="asd" />
{/* @ts-expect-error - d missed, extra c */}
<Ccc a="asd" b="asd" c="asd" />
{/* @ts-expect-error - extra c */}
<Ccc a="asd" b="asd" c="asd" d="asd" />
{/* @ts-expect-error - wrong ref */}
<Ccc ref={ref2} b="asd" c="asd" />
</>
);
};