Support composition with any component, not just the ones created with `tw`
jahirfiquitiva opened this issue · 3 comments
I would like this library to support styling any component and not just the ones created with tw
I have tried styling 2 pre-built components from Next without success.
1. Image
As you can see, it does not recognize the Image properties as valid
2. Link with the styles of a Button
Having created a StyledButton
component with tw
(const StyledButton = tw.button
), I want a Link
component to look like that StyledButton
The whole error reads:
const otherProps: {
title: string;
openInNewTab?: boolean | undefined;
download?: any;
hrefLang?: string | undefined;
media?: string | undefined;
ping?: string | undefined;
target?: HTMLAttributeAnchorTarget | undefined;
... 273 more ...;
key?: Key | ... 1 more ... | undefined;
}
No overload matches this call.
Overload 1 of 2, '(props: { onMouseEnter?: MouseEventHandler<HTMLButtonElement> | undefined; onTouchStart?: TouchEventHandler<HTMLButtonElement> | undefined; ... 272 more ...; $outlined?: boolean | undefined; } & { ...; }): ReactElement<...>', gave the following error.
Type '(props: LinkProps) => JSX.Element' is not assignable to type 'undefined'.
Overload 2 of 2, '(props: TailwindComponentPropsWith$As<DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, { ...; }, (props: LinkProps) => Element, LinkProps>): ReactElement<...>', gave the following error.
Type '{ title: string; openInNewTab?: boolean | undefined; download?: any; hrefLang?: string | undefined; media?: string | undefined; ping?: string | undefined; target?: HTMLAttributeAnchorTarget | undefined; ... 275 more ...; $outlined: boolean | undefined; }' is not assignable to type 'ClassAttributes<HTMLButtonElement>'.
Types of property 'ref' are incompatible.
Type 'Ref<HTMLAnchorElement> | undefined' is not assignable to type 'LegacyRef<HTMLButtonElement> | undefined'.
Type '(instance: HTMLAnchorElement | null) => void' is not assignable to type 'LegacyRef<HTMLButtonElement> | undefined'.
Type '(instance: HTMLAnchorElement | null) => void' is not assignable to type '(instance: HTMLButtonElement | null) => void'.
Types of parameters 'instance' and 'instance' are incompatible.
Type 'HTMLButtonElement | null' is not assignable to type 'HTMLAnchorElement | null'.
Type 'HTMLButtonElement' is missing the following properties from type 'HTMLAnchorElement': charset, coords, download, hreflang, and 19 more.ts(2769)
tailwind.d.ts(51, 9): The expected type comes from property '$as' which is declared here on type 'IntrinsicAttributes & { onMouseEnter?: MouseEventHandler<HTMLButtonElement> | undefined; onTouchStart?: TouchEventHandler<HTMLButtonElement> | undefined; ... 272 more ...; $outlined?: boolean | undefined; } & { ...; }'
Basically it is expecting otherProps
to be of the type of props for StyledButton
, even when I have set it to render $as={Link}
and therefore should work with Link
props
Before finding this library I was using a custom implementation which was working good enough although without many strict types.
anyway, my version of your templateFunctionFactory
accepted an element of type WebTarget
which I got from the styled-components
repo, as is as follows:
import type { ExoticComponent, ComponentType } from 'react';
type AnyComponent<P = unknown> = ExoticComponent<P> | ComponentType<P>;
type KnownTarget = keyof JSX.IntrinsicElements | AnyComponent;
export type WebTarget = string | KnownTarget;
...
I hope this can help you set an initial step for better support of any component
Here's the code for the function that I created in case it helps too
const twx = (classes: TemplateStringsArray): string => {
const cleanClasses = classes
.join(' ')
.split(/\r?\n/)
.map((it) => it.trim());
return twMerge(cleanClasses.join(' ').trim());
};
function baseStyled<T>(tag: WebTarget) {
return (classes: TemplateStringsArray | string): FC => {
const Component = tag;
// eslint-disable-next-line react/display-name
return (
props?: ComponentProps<typeof Component> & { as?: ElementType } & T,
) => {
const { as: asTag, ...otherProps } = props || {};
const FinalComponentTag = asTag || Component;
return (
<FinalComponentTag
{...otherProps}
className={cx(
Array.isArray(classes)
? twx(classes as TemplateStringsArray)
: (classes as string),
otherProps.className,
)}
/>
);
};
};
}
@MathiasGilson would you mind looking into this?